diff options
Diffstat (limited to 'gfx/thebes')
169 files changed, 84992 insertions, 0 deletions
diff --git a/gfx/thebes/CJKCompatSVS.cpp b/gfx/thebes/CJKCompatSVS.cpp new file mode 100644 index 0000000000..553dadcee8 --- /dev/null +++ b/gfx/thebes/CJKCompatSVS.cpp @@ -0,0 +1,2048 @@ +// Generated by gencjkcisvs.py. Do not edit. + +#include <stdint.h> + +#define U16(v) (((v) >> 8) & 0xFF), ((v)&0xFF) +#define U24(v) (((v) >> 16) & 0xFF), (((v) >> 8) & 0xFF), ((v)&0xFF) +#define U32(v) \ + (((v) >> 24) & 0xFF), (((v) >> 16) & 0xFF), (((v) >> 8) & 0xFF), ((v)&0xFF) +#define GLYPH(v) U16(v >= 0x2F800 ? (v) - (0x2F800 - 0xFB00) : (v)) + +// Fallback mappings for CJK Compatibility Ideographs Standardized Variants +// taken from StandardizedVariants-6.3.0.txt. +// Using OpenType format 14 cmap subtable structure to reuse the lookup code +// for fonts. The glyphID field is used to store the corresponding codepoints +// CJK Compatibility Ideographs. To fit codepoints into the 16-bit glyphID +// field, CJK Compatibility Ideographs Supplement (U+2F800..U+2FA1F) will be +// mapped to 0xFB00..0xFD1F. +extern const uint8_t sCJKCompatSVSTable[] = { + U16(14), // format + U32(5065), // length + U32(3), // numVarSelectorRecords + U24(0xFE00), + U32(0), + U32(43), // varSelectorRecord[0] + U24(0xFE01), + U32(0), + U32(4557), // varSelectorRecord[1] + U24(0xFE02), + U32(0), + U32(5001), // varSelectorRecord[2] + // 0xFE00 + U32(902), // numUVSMappings + U24(0x349E), + GLYPH(0x2F80C), + U24(0x34B9), + GLYPH(0x2F813), + U24(0x34BB), + GLYPH(0x2F9CA), + U24(0x34DF), + GLYPH(0x2F81F), + U24(0x3515), + GLYPH(0x2F824), + U24(0x36EE), + GLYPH(0x2F867), + U24(0x36FC), + GLYPH(0x2F868), + U24(0x3781), + GLYPH(0x2F876), + U24(0x382F), + GLYPH(0x2F883), + U24(0x3862), + GLYPH(0x2F888), + U24(0x387C), + GLYPH(0x2F88A), + U24(0x38C7), + GLYPH(0x2F896), + U24(0x38E3), + GLYPH(0x2F89B), + U24(0x391C), + GLYPH(0x2F8A2), + U24(0x393A), + GLYPH(0x2F8A1), + U24(0x3A2E), + GLYPH(0x2F8C2), + U24(0x3A6C), + GLYPH(0x2F8C7), + U24(0x3AE4), + GLYPH(0x2F8D1), + U24(0x3B08), + GLYPH(0x2F8D0), + U24(0x3B19), + GLYPH(0x2F8CE), + U24(0x3B49), + GLYPH(0x2F8DE), + U24(0x3B9D), + GLYPH(0xFAD2), + U24(0x3C18), + GLYPH(0x2F8EE), + U24(0x3C4E), + GLYPH(0x2F8F2), + U24(0x3D33), + GLYPH(0x2F90A), + U24(0x3D96), + GLYPH(0x2F916), + U24(0x3EAC), + GLYPH(0x2F92A), + U24(0x3EB8), + GLYPH(0x2F92C), + U24(0x3F1B), + GLYPH(0x2F933), + U24(0x3FFC), + GLYPH(0x2F93E), + U24(0x4008), + GLYPH(0x2F93F), + U24(0x4018), + GLYPH(0xFAD3), + U24(0x4039), + GLYPH(0xFAD4), + U24(0x4046), + GLYPH(0x2F94B), + U24(0x4096), + GLYPH(0x2F94C), + U24(0x40E3), + GLYPH(0x2F951), + U24(0x412F), + GLYPH(0x2F958), + U24(0x4202), + GLYPH(0x2F960), + U24(0x4227), + GLYPH(0x2F964), + U24(0x42A0), + GLYPH(0x2F967), + U24(0x4301), + GLYPH(0x2F96D), + U24(0x4334), + GLYPH(0x2F971), + U24(0x4359), + GLYPH(0x2F974), + U24(0x43D5), + GLYPH(0x2F981), + U24(0x43D9), + GLYPH(0x2F8D7), + U24(0x440B), + GLYPH(0x2F984), + U24(0x446B), + GLYPH(0x2F98E), + U24(0x452B), + GLYPH(0x2F9A7), + U24(0x455D), + GLYPH(0x2F9AE), + U24(0x4561), + GLYPH(0x2F9AF), + U24(0x456B), + GLYPH(0x2F9B2), + U24(0x45D7), + GLYPH(0x2F9BF), + U24(0x45F9), + GLYPH(0x2F9C2), + U24(0x4635), + GLYPH(0x2F9C8), + U24(0x46BE), + GLYPH(0x2F9CD), + U24(0x46C7), + GLYPH(0x2F9CE), + U24(0x4995), + GLYPH(0x2F9EF), + U24(0x49E6), + GLYPH(0x2F9F2), + U24(0x4A6E), + GLYPH(0x2F9F8), + U24(0x4A76), + GLYPH(0x2F9F9), + U24(0x4AB2), + GLYPH(0x2F9FC), + U24(0x4B33), + GLYPH(0x2FA03), + U24(0x4BCE), + GLYPH(0x2FA08), + U24(0x4CCE), + GLYPH(0x2FA0D), + U24(0x4CED), + GLYPH(0x2FA0E), + U24(0x4CF8), + GLYPH(0x2FA11), + U24(0x4D56), + GLYPH(0x2FA16), + U24(0x4E0D), + GLYPH(0xF967), + U24(0x4E26), + GLYPH(0xFA70), + U24(0x4E32), + GLYPH(0xF905), + U24(0x4E38), + GLYPH(0x2F801), + U24(0x4E39), + GLYPH(0xF95E), + U24(0x4E3D), + GLYPH(0x2F800), + U24(0x4E41), + GLYPH(0x2F802), + U24(0x4E82), + GLYPH(0xF91B), + U24(0x4E86), + GLYPH(0xF9BA), + U24(0x4EAE), + GLYPH(0xF977), + U24(0x4EC0), + GLYPH(0xF9FD), + U24(0x4ECC), + GLYPH(0x2F819), + U24(0x4EE4), + GLYPH(0xF9A8), + U24(0x4F60), + GLYPH(0x2F804), + U24(0x4F80), + GLYPH(0xFA73), + U24(0x4F86), + GLYPH(0xF92D), + U24(0x4F8B), + GLYPH(0xF9B5), + U24(0x4FAE), + GLYPH(0xFA30), + U24(0x4FBB), + GLYPH(0x2F806), + U24(0x4FBF), + GLYPH(0xF965), + U24(0x5002), + GLYPH(0x2F807), + U24(0x502B), + GLYPH(0xF9D4), + U24(0x507A), + GLYPH(0x2F808), + U24(0x5099), + GLYPH(0x2F809), + U24(0x50CF), + GLYPH(0x2F80B), + U24(0x50DA), + GLYPH(0xF9BB), + U24(0x50E7), + GLYPH(0xFA31), + U24(0x5140), + GLYPH(0xFA0C), + U24(0x5145), + GLYPH(0xFA74), + U24(0x514D), + GLYPH(0xFA32), + U24(0x5154), + GLYPH(0x2F80F), + U24(0x5164), + GLYPH(0x2F810), + U24(0x5167), + GLYPH(0x2F814), + U24(0x5168), + GLYPH(0xFA72), + U24(0x5169), + GLYPH(0xF978), + U24(0x516D), + GLYPH(0xF9D1), + U24(0x5177), + GLYPH(0x2F811), + U24(0x5180), + GLYPH(0xFA75), + U24(0x518D), + GLYPH(0x2F815), + U24(0x5192), + GLYPH(0x2F8D2), + U24(0x5195), + GLYPH(0x2F8D3), + U24(0x5197), + GLYPH(0x2F817), + U24(0x51A4), + GLYPH(0x2F818), + U24(0x51AC), + GLYPH(0x2F81A), + U24(0x51B5), + GLYPH(0xFA71), + U24(0x51B7), + GLYPH(0xF92E), + U24(0x51C9), + GLYPH(0xF979), + U24(0x51CC), + GLYPH(0xF955), + U24(0x51DC), + GLYPH(0xF954), + U24(0x51DE), + GLYPH(0xFA15), + U24(0x51F5), + GLYPH(0x2F81D), + U24(0x5203), + GLYPH(0x2F81E), + U24(0x5207), + GLYPH(0xFA00), + U24(0x5217), + GLYPH(0xF99C), + U24(0x5229), + GLYPH(0xF9DD), + U24(0x523A), + GLYPH(0xF9FF), + U24(0x523B), + GLYPH(0x2F820), + U24(0x5246), + GLYPH(0x2F821), + U24(0x5272), + GLYPH(0x2F822), + U24(0x5277), + GLYPH(0x2F823), + U24(0x5289), + GLYPH(0xF9C7), + U24(0x529B), + GLYPH(0xF98A), + U24(0x52A3), + GLYPH(0xF99D), + U24(0x52B3), + GLYPH(0x2F992), + U24(0x52C7), + GLYPH(0xFA76), + U24(0x52C9), + GLYPH(0xFA33), + U24(0x52D2), + GLYPH(0xF952), + U24(0x52DE), + GLYPH(0xF92F), + U24(0x52E4), + GLYPH(0xFA34), + U24(0x52F5), + GLYPH(0xF97F), + U24(0x52FA), + GLYPH(0xFA77), + U24(0x5305), + GLYPH(0x2F829), + U24(0x5306), + GLYPH(0x2F82A), + U24(0x5317), + GLYPH(0xF963), + U24(0x533F), + GLYPH(0xF9EB), + U24(0x5349), + GLYPH(0x2F82C), + U24(0x5351), + GLYPH(0xFA35), + U24(0x535A), + GLYPH(0x2F82E), + U24(0x5373), + GLYPH(0x2F82F), + U24(0x5375), + GLYPH(0xF91C), + U24(0x537D), + GLYPH(0x2F830), + U24(0x537F), + GLYPH(0x2F831), + U24(0x53C3), + GLYPH(0xF96B), + U24(0x53CA), + GLYPH(0x2F836), + U24(0x53DF), + GLYPH(0x2F837), + U24(0x53E5), + GLYPH(0xF906), + U24(0x53EB), + GLYPH(0x2F839), + U24(0x53F1), + GLYPH(0x2F83A), + U24(0x5406), + GLYPH(0x2F83B), + U24(0x540F), + GLYPH(0xF9DE), + U24(0x541D), + GLYPH(0xF9ED), + U24(0x5438), + GLYPH(0x2F83D), + U24(0x5442), + GLYPH(0xF980), + U24(0x5448), + GLYPH(0x2F83E), + U24(0x5468), + GLYPH(0x2F83F), + U24(0x549E), + GLYPH(0x2F83C), + U24(0x54A2), + GLYPH(0x2F840), + U24(0x54BD), + GLYPH(0xF99E), + U24(0x54F6), + GLYPH(0x2F841), + U24(0x5510), + GLYPH(0x2F842), + U24(0x5553), + GLYPH(0x2F843), + U24(0x5555), + GLYPH(0xFA79), + U24(0x5563), + GLYPH(0x2F844), + U24(0x5584), + GLYPH(0x2F845), + U24(0x5587), + GLYPH(0xF90B), + U24(0x5599), + GLYPH(0xFA7A), + U24(0x559D), + GLYPH(0xFA36), + U24(0x55AB), + GLYPH(0x2F848), + U24(0x55B3), + GLYPH(0x2F849), + U24(0x55C0), + GLYPH(0xFA0D), + U24(0x55C2), + GLYPH(0x2F84A), + U24(0x55E2), + GLYPH(0xFA7B), + U24(0x5606), + GLYPH(0xFA37), + U24(0x5651), + GLYPH(0x2F84E), + U24(0x5668), + GLYPH(0xFA38), + U24(0x5674), + GLYPH(0x2F84F), + U24(0x56F9), + GLYPH(0xF9A9), + U24(0x5716), + GLYPH(0x2F84B), + U24(0x5717), + GLYPH(0x2F84D), + U24(0x578B), + GLYPH(0x2F855), + U24(0x57CE), + GLYPH(0x2F852), + U24(0x57F4), + GLYPH(0x2F853), + U24(0x580D), + GLYPH(0x2F854), + U24(0x5831), + GLYPH(0x2F857), + U24(0x5832), + GLYPH(0x2F856), + U24(0x5840), + GLYPH(0xFA39), + U24(0x585A), + GLYPH(0xFA10), + U24(0x585E), + GLYPH(0xF96C), + U24(0x58A8), + GLYPH(0xFA3A), + U24(0x58AC), + GLYPH(0x2F858), + U24(0x58B3), + GLYPH(0xFA7D), + U24(0x58D8), + GLYPH(0xF94A), + U24(0x58DF), + GLYPH(0xF942), + U24(0x58EE), + GLYPH(0x2F851), + U24(0x58F2), + GLYPH(0x2F85A), + U24(0x58F7), + GLYPH(0x2F85B), + U24(0x5906), + GLYPH(0x2F85C), + U24(0x591A), + GLYPH(0x2F85D), + U24(0x5922), + GLYPH(0x2F85E), + U24(0x5944), + GLYPH(0xFA7E), + U24(0x5948), + GLYPH(0xF90C), + U24(0x5951), + GLYPH(0xF909), + U24(0x5954), + GLYPH(0xFA7F), + U24(0x5962), + GLYPH(0x2F85F), + U24(0x5973), + GLYPH(0xF981), + U24(0x59D8), + GLYPH(0x2F865), + U24(0x59EC), + GLYPH(0x2F862), + U24(0x5A1B), + GLYPH(0x2F863), + U24(0x5A27), + GLYPH(0x2F864), + U24(0x5A62), + GLYPH(0xFA80), + U24(0x5A66), + GLYPH(0x2F866), + U24(0x5AB5), + GLYPH(0x2F986), + U24(0x5B08), + GLYPH(0x2F869), + U24(0x5B28), + GLYPH(0xFA81), + U24(0x5B3E), + GLYPH(0x2F86A), + U24(0x5B85), + GLYPH(0xFA04), + U24(0x5BC3), + GLYPH(0x2F86D), + U24(0x5BD8), + GLYPH(0x2F86E), + U24(0x5BE7), + GLYPH(0xF95F), + U24(0x5BEE), + GLYPH(0xF9BC), + U24(0x5BF3), + GLYPH(0x2F870), + U24(0x5BFF), + GLYPH(0x2F872), + U24(0x5C06), + GLYPH(0x2F873), + U24(0x5C22), + GLYPH(0x2F875), + U24(0x5C3F), + GLYPH(0xF9BD), + U24(0x5C60), + GLYPH(0x2F877), + U24(0x5C62), + GLYPH(0xF94B), + U24(0x5C64), + GLYPH(0xFA3B), + U24(0x5C65), + GLYPH(0xF9DF), + U24(0x5C6E), + GLYPH(0xFA3C), + U24(0x5C8D), + GLYPH(0x2F87A), + U24(0x5CC0), + GLYPH(0x2F879), + U24(0x5D19), + GLYPH(0xF9D5), + U24(0x5D43), + GLYPH(0x2F87C), + U24(0x5D50), + GLYPH(0xF921), + U24(0x5D6B), + GLYPH(0x2F87F), + U24(0x5D6E), + GLYPH(0x2F87E), + U24(0x5D7C), + GLYPH(0x2F880), + U24(0x5DB2), + GLYPH(0x2F9F4), + U24(0x5DBA), + GLYPH(0xF9AB), + U24(0x5DE1), + GLYPH(0x2F881), + U24(0x5DE2), + GLYPH(0x2F882), + U24(0x5DFD), + GLYPH(0x2F884), + U24(0x5E28), + GLYPH(0x2F885), + U24(0x5E3D), + GLYPH(0x2F886), + U24(0x5E69), + GLYPH(0x2F887), + U24(0x5E74), + GLYPH(0xF98E), + U24(0x5EA6), + GLYPH(0xFA01), + U24(0x5EB0), + GLYPH(0x2F88B), + U24(0x5EB3), + GLYPH(0x2F88C), + U24(0x5EB6), + GLYPH(0x2F88D), + U24(0x5EC9), + GLYPH(0xF9A2), + U24(0x5ECA), + GLYPH(0xF928), + U24(0x5ED2), + GLYPH(0xFA82), + U24(0x5ED3), + GLYPH(0xFA0B), + U24(0x5ED9), + GLYPH(0xFA83), + U24(0x5EEC), + GLYPH(0xF982), + U24(0x5EFE), + GLYPH(0x2F890), + U24(0x5F04), + GLYPH(0xF943), + U24(0x5F22), + GLYPH(0x2F894), + U24(0x5F53), + GLYPH(0x2F874), + U24(0x5F62), + GLYPH(0x2F899), + U24(0x5F69), + GLYPH(0xFA84), + U24(0x5F6B), + GLYPH(0x2F89A), + U24(0x5F8B), + GLYPH(0xF9D8), + U24(0x5F9A), + GLYPH(0x2F89C), + U24(0x5FA9), + GLYPH(0xF966), + U24(0x5FAD), + GLYPH(0xFA85), + U24(0x5FCD), + GLYPH(0x2F89D), + U24(0x5FD7), + GLYPH(0x2F89E), + U24(0x5FF5), + GLYPH(0xF9A3), + U24(0x5FF9), + GLYPH(0x2F89F), + U24(0x6012), + GLYPH(0xF960), + U24(0x601C), + GLYPH(0xF9AC), + U24(0x6075), + GLYPH(0xFA6B), + U24(0x6081), + GLYPH(0x2F8A0), + U24(0x6094), + GLYPH(0xFA3D), + U24(0x60C7), + GLYPH(0x2F8A5), + U24(0x60D8), + GLYPH(0xFA86), + U24(0x60E1), + GLYPH(0xF9B9), + U24(0x6108), + GLYPH(0xFA88), + U24(0x6144), + GLYPH(0xF9D9), + U24(0x6148), + GLYPH(0x2F8A6), + U24(0x614C), + GLYPH(0x2F8A7), + U24(0x614E), + GLYPH(0xFA87), + U24(0x6160), + GLYPH(0xFA8A), + U24(0x6168), + GLYPH(0xFA3E), + U24(0x617A), + GLYPH(0x2F8AA), + U24(0x618E), + GLYPH(0xFA3F), + U24(0x6190), + GLYPH(0xF98F), + U24(0x61A4), + GLYPH(0x2F8AD), + U24(0x61AF), + GLYPH(0x2F8AE), + U24(0x61B2), + GLYPH(0x2F8AC), + U24(0x61DE), + GLYPH(0x2F8AF), + U24(0x61F2), + GLYPH(0xFA40), + U24(0x61F6), + GLYPH(0xF90D), + U24(0x6200), + GLYPH(0xF990), + U24(0x6210), + GLYPH(0x2F8B2), + U24(0x621B), + GLYPH(0x2F8B3), + U24(0x622E), + GLYPH(0xF9D2), + U24(0x6234), + GLYPH(0xFA8C), + U24(0x625D), + GLYPH(0x2F8B4), + U24(0x62B1), + GLYPH(0x2F8B5), + U24(0x62C9), + GLYPH(0xF925), + U24(0x62CF), + GLYPH(0xF95B), + U24(0x62D3), + GLYPH(0xFA02), + U24(0x62D4), + GLYPH(0x2F8B6), + U24(0x62FC), + GLYPH(0x2F8BA), + U24(0x62FE), + GLYPH(0xF973), + U24(0x633D), + GLYPH(0x2F8B9), + U24(0x6350), + GLYPH(0x2F8B7), + U24(0x6368), + GLYPH(0x2F8BB), + U24(0x637B), + GLYPH(0xF9A4), + U24(0x6383), + GLYPH(0x2F8BC), + U24(0x63A0), + GLYPH(0xF975), + U24(0x63A9), + GLYPH(0x2F8C1), + U24(0x63C4), + GLYPH(0xFA8D), + U24(0x63C5), + GLYPH(0x2F8C0), + U24(0x63E4), + GLYPH(0x2F8BD), + U24(0x641C), + GLYPH(0xFA8E), + U24(0x6422), + GLYPH(0x2F8BF), + U24(0x6452), + GLYPH(0xFA8F), + U24(0x6469), + GLYPH(0x2F8C3), + U24(0x6477), + GLYPH(0x2F8C6), + U24(0x647E), + GLYPH(0x2F8C4), + U24(0x649A), + GLYPH(0xF991), + U24(0x649D), + GLYPH(0x2F8C5), + U24(0x64C4), + GLYPH(0xF930), + U24(0x654F), + GLYPH(0xFA41), + U24(0x6556), + GLYPH(0xFA90), + U24(0x656C), + GLYPH(0x2F8C9), + U24(0x6578), + GLYPH(0xF969), + U24(0x6599), + GLYPH(0xF9BE), + U24(0x65C5), + GLYPH(0xF983), + U24(0x65E2), + GLYPH(0xFA42), + U24(0x65E3), + GLYPH(0x2F8CB), + U24(0x6613), + GLYPH(0xF9E0), + U24(0x6649), + GLYPH(0x2F8CD), + U24(0x6674), + GLYPH(0xFA12), + U24(0x6688), + GLYPH(0xF9C5), + U24(0x6691), + GLYPH(0xFA43), + U24(0x669C), + GLYPH(0x2F8D5), + U24(0x66B4), + GLYPH(0xFA06), + U24(0x66C6), + GLYPH(0xF98B), + U24(0x66F4), + GLYPH(0xF901), + U24(0x66F8), + GLYPH(0x2F8CC), + U24(0x6700), + GLYPH(0x2F8D4), + U24(0x6717), + GLYPH(0xF929), + U24(0x671B), + GLYPH(0xFA93), + U24(0x6721), + GLYPH(0x2F8DA), + U24(0x674E), + GLYPH(0xF9E1), + U24(0x6753), + GLYPH(0x2F8DC), + U24(0x6756), + GLYPH(0xFA94), + U24(0x675E), + GLYPH(0x2F8DB), + U24(0x677B), + GLYPH(0xF9C8), + U24(0x6785), + GLYPH(0x2F8E0), + U24(0x6797), + GLYPH(0xF9F4), + U24(0x67F3), + GLYPH(0xF9C9), + U24(0x67FA), + GLYPH(0x2F8DF), + U24(0x6817), + GLYPH(0xF9DA), + U24(0x681F), + GLYPH(0x2F8E5), + U24(0x6852), + GLYPH(0x2F8E1), + U24(0x6881), + GLYPH(0xF97A), + U24(0x6885), + GLYPH(0xFA44), + U24(0x688E), + GLYPH(0x2F8E4), + U24(0x68A8), + GLYPH(0xF9E2), + U24(0x6914), + GLYPH(0x2F8E6), + U24(0x6942), + GLYPH(0x2F8E8), + U24(0x69A3), + GLYPH(0x2F8E9), + U24(0x69EA), + GLYPH(0x2F8EA), + U24(0x6A02), + GLYPH(0xF914), + U24(0x6A13), + GLYPH(0xF94C), + U24(0x6AA8), + GLYPH(0x2F8EB), + U24(0x6AD3), + GLYPH(0xF931), + U24(0x6ADB), + GLYPH(0x2F8ED), + U24(0x6B04), + GLYPH(0xF91D), + U24(0x6B21), + GLYPH(0x2F8EF), + U24(0x6B54), + GLYPH(0x2F8F1), + U24(0x6B72), + GLYPH(0x2F8F3), + U24(0x6B77), + GLYPH(0xF98C), + U24(0x6B79), + GLYPH(0xFA95), + U24(0x6B9F), + GLYPH(0x2F8F4), + U24(0x6BAE), + GLYPH(0xF9A5), + U24(0x6BBA), + GLYPH(0xF970), + U24(0x6BBB), + GLYPH(0x2F8F6), + U24(0x6C4E), + GLYPH(0x2F8FA), + U24(0x6C67), + GLYPH(0x2F8FE), + U24(0x6C88), + GLYPH(0xF972), + U24(0x6CBF), + GLYPH(0x2F8FC), + U24(0x6CCC), + GLYPH(0xF968), + U24(0x6CCD), + GLYPH(0x2F8FD), + U24(0x6CE5), + GLYPH(0xF9E3), + U24(0x6D16), + GLYPH(0x2F8FF), + U24(0x6D1B), + GLYPH(0xF915), + U24(0x6D1E), + GLYPH(0xFA05), + U24(0x6D34), + GLYPH(0x2F907), + U24(0x6D3E), + GLYPH(0x2F900), + U24(0x6D41), + GLYPH(0xF9CA), + U24(0x6D69), + GLYPH(0x2F903), + U24(0x6D6A), + GLYPH(0xF92A), + U24(0x6D77), + GLYPH(0xFA45), + U24(0x6D78), + GLYPH(0x2F904), + U24(0x6D85), + GLYPH(0x2F905), + U24(0x6DCB), + GLYPH(0xF9F5), + U24(0x6DDA), + GLYPH(0xF94D), + U24(0x6DEA), + GLYPH(0xF9D6), + U24(0x6DF9), + GLYPH(0x2F90E), + U24(0x6E1A), + GLYPH(0xFA46), + U24(0x6E2F), + GLYPH(0x2F908), + U24(0x6E6E), + GLYPH(0x2F909), + U24(0x6E9C), + GLYPH(0xF9CB), + U24(0x6EBA), + GLYPH(0xF9EC), + U24(0x6EC7), + GLYPH(0x2F90C), + U24(0x6ECB), + GLYPH(0xFA99), + U24(0x6ED1), + GLYPH(0xF904), + U24(0x6EDB), + GLYPH(0xFA98), + U24(0x6F0F), + GLYPH(0xF94E), + U24(0x6F22), + GLYPH(0xFA47), + U24(0x6F23), + GLYPH(0xF992), + U24(0x6F6E), + GLYPH(0x2F90F), + U24(0x6FC6), + GLYPH(0x2F912), + U24(0x6FEB), + GLYPH(0xF922), + U24(0x6FFE), + GLYPH(0xF984), + U24(0x701B), + GLYPH(0x2F915), + U24(0x701E), + GLYPH(0xFA9B), + U24(0x7039), + GLYPH(0x2F913), + U24(0x704A), + GLYPH(0x2F917), + U24(0x7070), + GLYPH(0x2F835), + U24(0x7077), + GLYPH(0x2F919), + U24(0x707D), + GLYPH(0x2F918), + U24(0x7099), + GLYPH(0xF9FB), + U24(0x70AD), + GLYPH(0x2F91A), + U24(0x70C8), + GLYPH(0xF99F), + U24(0x70D9), + GLYPH(0xF916), + U24(0x7145), + GLYPH(0x2F91C), + U24(0x7149), + GLYPH(0xF993), + U24(0x716E), + GLYPH(0xFA48), + U24(0x719C), + GLYPH(0x2F91E), + U24(0x71CE), + GLYPH(0xF9C0), + U24(0x71D0), + GLYPH(0xF9EE), + U24(0x7210), + GLYPH(0xF932), + U24(0x721B), + GLYPH(0xF91E), + U24(0x7228), + GLYPH(0x2F920), + U24(0x722B), + GLYPH(0xFA49), + U24(0x7235), + GLYPH(0xFA9E), + U24(0x7250), + GLYPH(0x2F922), + U24(0x7262), + GLYPH(0xF946), + U24(0x7280), + GLYPH(0x2F924), + U24(0x7295), + GLYPH(0x2F925), + U24(0x72AF), + GLYPH(0xFA9F), + U24(0x72C0), + GLYPH(0xF9FA), + U24(0x72FC), + GLYPH(0xF92B), + U24(0x732A), + GLYPH(0xFA16), + U24(0x7375), + GLYPH(0xF9A7), + U24(0x737A), + GLYPH(0x2F928), + U24(0x7387), + GLYPH(0xF961), + U24(0x738B), + GLYPH(0x2F929), + U24(0x73A5), + GLYPH(0x2F92B), + U24(0x73B2), + GLYPH(0xF9AD), + U24(0x73DE), + GLYPH(0xF917), + U24(0x7406), + GLYPH(0xF9E4), + U24(0x7409), + GLYPH(0xF9CC), + U24(0x7422), + GLYPH(0xFA4A), + U24(0x7447), + GLYPH(0x2F92E), + U24(0x745C), + GLYPH(0x2F92F), + U24(0x7469), + GLYPH(0xF9AE), + U24(0x7471), + GLYPH(0xFAA1), + U24(0x7485), + GLYPH(0x2F931), + U24(0x7489), + GLYPH(0xF994), + U24(0x7498), + GLYPH(0xF9EF), + U24(0x74CA), + GLYPH(0x2F932), + U24(0x7506), + GLYPH(0xFAA2), + U24(0x7524), + GLYPH(0x2F934), + U24(0x753B), + GLYPH(0xFAA3), + U24(0x753E), + GLYPH(0x2F936), + U24(0x7559), + GLYPH(0xF9CD), + U24(0x7565), + GLYPH(0xF976), + U24(0x7570), + GLYPH(0xF962), + U24(0x75E2), + GLYPH(0xF9E5), + U24(0x7610), + GLYPH(0x2F93A), + U24(0x761D), + GLYPH(0xFAA4), + U24(0x761F), + GLYPH(0xFAA5), + U24(0x7642), + GLYPH(0xF9C1), + U24(0x7669), + GLYPH(0xF90E), + U24(0x76CA), + GLYPH(0xFA17), + U24(0x76DB), + GLYPH(0xFAA7), + U24(0x76E7), + GLYPH(0xF933), + U24(0x76F4), + GLYPH(0xFAA8), + U24(0x7701), + GLYPH(0xF96D), + U24(0x771E), + GLYPH(0x2F945), + U24(0x771F), + GLYPH(0x2F946), + U24(0x7740), + GLYPH(0xFAAA), + U24(0x774A), + GLYPH(0xFAA9), + U24(0x778B), + GLYPH(0x2F94A), + U24(0x77A7), + GLYPH(0xFA9D), + U24(0x784E), + GLYPH(0x2F94E), + U24(0x786B), + GLYPH(0xF9CE), + U24(0x788C), + GLYPH(0xF93B), + U24(0x7891), + GLYPH(0xFA4B), + U24(0x78CA), + GLYPH(0xF947), + U24(0x78CC), + GLYPH(0xFAAB), + U24(0x78FB), + GLYPH(0xF964), + U24(0x792A), + GLYPH(0xF985), + U24(0x793C), + GLYPH(0xFA18), + U24(0x793E), + GLYPH(0xFA4C), + U24(0x7948), + GLYPH(0xFA4E), + U24(0x7949), + GLYPH(0xFA4D), + U24(0x7950), + GLYPH(0xFA4F), + U24(0x7956), + GLYPH(0xFA50), + U24(0x795D), + GLYPH(0xFA51), + U24(0x795E), + GLYPH(0xFA19), + U24(0x7965), + GLYPH(0xFA1A), + U24(0x797F), + GLYPH(0xF93C), + U24(0x798D), + GLYPH(0xFA52), + U24(0x798E), + GLYPH(0xFA53), + U24(0x798F), + GLYPH(0xFA1B), + U24(0x79AE), + GLYPH(0xF9B6), + U24(0x79CA), + GLYPH(0xF995), + U24(0x79EB), + GLYPH(0x2F957), + U24(0x7A1C), + GLYPH(0xF956), + U24(0x7A40), + GLYPH(0xFA54), + U24(0x7A4A), + GLYPH(0x2F95A), + U24(0x7A4F), + GLYPH(0x2F95B), + U24(0x7A81), + GLYPH(0xFA55), + U24(0x7AB1), + GLYPH(0xFAAC), + U24(0x7ACB), + GLYPH(0xF9F7), + U24(0x7AEE), + GLYPH(0x2F95F), + U24(0x7B20), + GLYPH(0xF9F8), + U24(0x7BC0), + GLYPH(0xFA56), + U24(0x7BC6), + GLYPH(0x2F962), + U24(0x7BC9), + GLYPH(0x2F963), + U24(0x7C3E), + GLYPH(0xF9A6), + U24(0x7C60), + GLYPH(0xF944), + U24(0x7C7B), + GLYPH(0xFAAE), + U24(0x7C92), + GLYPH(0xF9F9), + U24(0x7CBE), + GLYPH(0xFA1D), + U24(0x7CD2), + GLYPH(0x2F966), + U24(0x7CD6), + GLYPH(0xFA03), + U24(0x7CE3), + GLYPH(0x2F969), + U24(0x7CE7), + GLYPH(0xF97B), + U24(0x7CE8), + GLYPH(0x2F968), + U24(0x7D00), + GLYPH(0x2F96A), + U24(0x7D10), + GLYPH(0xF9CF), + U24(0x7D22), + GLYPH(0xF96A), + U24(0x7D2F), + GLYPH(0xF94F), + U24(0x7D5B), + GLYPH(0xFAAF), + U24(0x7D63), + GLYPH(0x2F96C), + U24(0x7DA0), + GLYPH(0xF93D), + U24(0x7DBE), + GLYPH(0xF957), + U24(0x7DC7), + GLYPH(0x2F96E), + U24(0x7DF4), + GLYPH(0xF996), + U24(0x7E02), + GLYPH(0x2F96F), + U24(0x7E09), + GLYPH(0xFA58), + U24(0x7E37), + GLYPH(0xF950), + U24(0x7E41), + GLYPH(0xFA59), + U24(0x7E45), + GLYPH(0x2F970), + U24(0x7F3E), + GLYPH(0xFAB1), + U24(0x7F72), + GLYPH(0xFA5A), + U24(0x7F79), + GLYPH(0xF9E6), + U24(0x7F7A), + GLYPH(0x2F976), + U24(0x7F85), + GLYPH(0xF90F), + U24(0x7F95), + GLYPH(0x2F978), + U24(0x7F9A), + GLYPH(0xF9AF), + U24(0x7FBD), + GLYPH(0xFA1E), + U24(0x7FFA), + GLYPH(0x2F979), + U24(0x8001), + GLYPH(0xF934), + U24(0x8005), + GLYPH(0xFA5B), + U24(0x8046), + GLYPH(0xF9B0), + U24(0x8060), + GLYPH(0x2F97D), + U24(0x806F), + GLYPH(0xF997), + U24(0x8070), + GLYPH(0x2F97F), + U24(0x807E), + GLYPH(0xF945), + U24(0x808B), + GLYPH(0xF953), + U24(0x80AD), + GLYPH(0x2F8D6), + U24(0x80B2), + GLYPH(0x2F982), + U24(0x8103), + GLYPH(0x2F983), + U24(0x813E), + GLYPH(0x2F985), + U24(0x81D8), + GLYPH(0xF926), + U24(0x81E8), + GLYPH(0xF9F6), + U24(0x81ED), + GLYPH(0xFA5C), + U24(0x8201), + GLYPH(0x2F893), + U24(0x8204), + GLYPH(0x2F98C), + U24(0x8218), + GLYPH(0xFA6D), + U24(0x826F), + GLYPH(0xF97C), + U24(0x8279), + GLYPH(0xFA5D), + U24(0x828B), + GLYPH(0x2F990), + U24(0x8291), + GLYPH(0x2F98F), + U24(0x829D), + GLYPH(0x2F991), + U24(0x82B1), + GLYPH(0x2F993), + U24(0x82B3), + GLYPH(0x2F994), + U24(0x82BD), + GLYPH(0x2F995), + U24(0x82E5), + GLYPH(0xF974), + U24(0x82E6), + GLYPH(0x2F996), + U24(0x831D), + GLYPH(0x2F999), + U24(0x8323), + GLYPH(0x2F99C), + U24(0x8336), + GLYPH(0xF9FE), + U24(0x8352), + GLYPH(0xFAB3), + U24(0x8353), + GLYPH(0x2F9A0), + U24(0x8363), + GLYPH(0x2F99A), + U24(0x83AD), + GLYPH(0x2F99B), + U24(0x83BD), + GLYPH(0x2F99D), + U24(0x83C9), + GLYPH(0xF93E), + U24(0x83CA), + GLYPH(0x2F9A1), + U24(0x83CC), + GLYPH(0x2F9A2), + U24(0x83DC), + GLYPH(0x2F9A3), + U24(0x83E7), + GLYPH(0x2F99E), + U24(0x83EF), + GLYPH(0xFAB4), + U24(0x83F1), + GLYPH(0xF958), + U24(0x843D), + GLYPH(0xF918), + U24(0x8449), + GLYPH(0xF96E), + U24(0x8457), + GLYPH(0xFA5F), + U24(0x84EE), + GLYPH(0xF999), + U24(0x84F1), + GLYPH(0x2F9A8), + U24(0x84F3), + GLYPH(0x2F9A9), + U24(0x84FC), + GLYPH(0xF9C2), + U24(0x8516), + GLYPH(0x2F9AA), + U24(0x8564), + GLYPH(0x2F9AC), + U24(0x85CD), + GLYPH(0xF923), + U24(0x85FA), + GLYPH(0xF9F0), + U24(0x8606), + GLYPH(0xF935), + U24(0x8612), + GLYPH(0xFA20), + U24(0x862D), + GLYPH(0xF91F), + U24(0x863F), + GLYPH(0xF910), + U24(0x8650), + GLYPH(0x2F9B3), + U24(0x865C), + GLYPH(0xF936), + U24(0x8667), + GLYPH(0x2F9B5), + U24(0x8669), + GLYPH(0x2F9B6), + U24(0x8688), + GLYPH(0x2F9B8), + U24(0x86A9), + GLYPH(0x2F9B7), + U24(0x86E2), + GLYPH(0x2F9BA), + U24(0x870E), + GLYPH(0x2F9B9), + U24(0x8728), + GLYPH(0x2F9BC), + U24(0x876B), + GLYPH(0x2F9BD), + U24(0x8779), + GLYPH(0xFAB5), + U24(0x8786), + GLYPH(0x2F9BE), + U24(0x87BA), + GLYPH(0xF911), + U24(0x87E1), + GLYPH(0x2F9C0), + U24(0x8801), + GLYPH(0x2F9C1), + U24(0x881F), + GLYPH(0xF927), + U24(0x884C), + GLYPH(0xFA08), + U24(0x8860), + GLYPH(0x2F9C3), + U24(0x8863), + GLYPH(0x2F9C4), + U24(0x88C2), + GLYPH(0xF9A0), + U24(0x88CF), + GLYPH(0xF9E7), + U24(0x88D7), + GLYPH(0x2F9C6), + U24(0x88DE), + GLYPH(0x2F9C7), + U24(0x88E1), + GLYPH(0xF9E8), + U24(0x88F8), + GLYPH(0xF912), + U24(0x88FA), + GLYPH(0x2F9C9), + U24(0x8910), + GLYPH(0xFA60), + U24(0x8941), + GLYPH(0xFAB6), + U24(0x8964), + GLYPH(0xF924), + U24(0x8986), + GLYPH(0xFAB7), + U24(0x898B), + GLYPH(0xFA0A), + U24(0x8996), + GLYPH(0xFA61), + U24(0x8AA0), + GLYPH(0x2F9CF), + U24(0x8AAA), + GLYPH(0xF96F), + U24(0x8ABF), + GLYPH(0xFAB9), + U24(0x8ACB), + GLYPH(0xFABB), + U24(0x8AD2), + GLYPH(0xF97D), + U24(0x8AD6), + GLYPH(0xF941), + U24(0x8AED), + GLYPH(0xFABE), + U24(0x8AF8), + GLYPH(0xFA22), + U24(0x8AFE), + GLYPH(0xF95D), + U24(0x8B01), + GLYPH(0xFA62), + U24(0x8B39), + GLYPH(0xFA63), + U24(0x8B58), + GLYPH(0xF9FC), + U24(0x8B80), + GLYPH(0xF95A), + U24(0x8B8A), + GLYPH(0xFAC0), + U24(0x8C48), + GLYPH(0xF900), + U24(0x8C55), + GLYPH(0x2F9D2), + U24(0x8CAB), + GLYPH(0x2F9D4), + U24(0x8CC1), + GLYPH(0x2F9D5), + U24(0x8CC2), + GLYPH(0xF948), + U24(0x8CC8), + GLYPH(0xF903), + U24(0x8CD3), + GLYPH(0xFA64), + U24(0x8D08), + GLYPH(0xFA65), + U24(0x8D1B), + GLYPH(0x2F9D6), + U24(0x8D77), + GLYPH(0x2F9D7), + U24(0x8DBC), + GLYPH(0x2F9DB), + U24(0x8DCB), + GLYPH(0x2F9DA), + U24(0x8DEF), + GLYPH(0xF937), + U24(0x8DF0), + GLYPH(0x2F9DC), + U24(0x8ECA), + GLYPH(0xF902), + U24(0x8ED4), + GLYPH(0x2F9DE), + U24(0x8F26), + GLYPH(0xF998), + U24(0x8F2A), + GLYPH(0xF9D7), + U24(0x8F38), + GLYPH(0xFAC2), + U24(0x8F3B), + GLYPH(0xFA07), + U24(0x8F62), + GLYPH(0xF98D), + U24(0x8F9E), + GLYPH(0x2F98D), + U24(0x8FB0), + GLYPH(0xF971), + U24(0x8FB6), + GLYPH(0xFA66), + U24(0x9023), + GLYPH(0xF99A), + U24(0x9038), + GLYPH(0xFA25), + U24(0x9072), + GLYPH(0xFAC3), + U24(0x907C), + GLYPH(0xF9C3), + U24(0x908F), + GLYPH(0xF913), + U24(0x9094), + GLYPH(0x2F9E2), + U24(0x90CE), + GLYPH(0xF92C), + U24(0x90DE), + GLYPH(0xFA2E), + U24(0x90F1), + GLYPH(0x2F9E3), + U24(0x90FD), + GLYPH(0xFA26), + U24(0x9111), + GLYPH(0x2F9E4), + U24(0x911B), + GLYPH(0x2F9E6), + U24(0x916A), + GLYPH(0xF919), + U24(0x9199), + GLYPH(0xFAC4), + U24(0x91B4), + GLYPH(0xF9B7), + U24(0x91CC), + GLYPH(0xF9E9), + U24(0x91CF), + GLYPH(0xF97E), + U24(0x91D1), + GLYPH(0xF90A), + U24(0x9234), + GLYPH(0xF9B1), + U24(0x9238), + GLYPH(0x2F9E7), + U24(0x9276), + GLYPH(0xFAC5), + U24(0x927C), + GLYPH(0x2F9EA), + U24(0x92D7), + GLYPH(0x2F9E8), + U24(0x92D8), + GLYPH(0x2F9E9), + U24(0x9304), + GLYPH(0xF93F), + U24(0x934A), + GLYPH(0xF99B), + U24(0x93F9), + GLYPH(0x2F9EB), + U24(0x9415), + GLYPH(0x2F9EC), + U24(0x958B), + GLYPH(0x2F9EE), + U24(0x95AD), + GLYPH(0xF986), + U24(0x95B7), + GLYPH(0x2F9F0), + U24(0x962E), + GLYPH(0xF9C6), + U24(0x964B), + GLYPH(0xF951), + U24(0x964D), + GLYPH(0xFA09), + U24(0x9675), + GLYPH(0xF959), + U24(0x9678), + GLYPH(0xF9D3), + U24(0x967C), + GLYPH(0xFAC6), + U24(0x9686), + GLYPH(0xF9DC), + U24(0x96A3), + GLYPH(0xF9F1), + U24(0x96B7), + GLYPH(0xFA2F), + U24(0x96B8), + GLYPH(0xF9B8), + U24(0x96C3), + GLYPH(0x2F9F3), + U24(0x96E2), + GLYPH(0xF9EA), + U24(0x96E3), + GLYPH(0xFA68), + U24(0x96F6), + GLYPH(0xF9B2), + U24(0x96F7), + GLYPH(0xF949), + U24(0x9723), + GLYPH(0x2F9F5), + U24(0x9732), + GLYPH(0xF938), + U24(0x9748), + GLYPH(0xF9B3), + U24(0x9756), + GLYPH(0xFA1C), + U24(0x97DB), + GLYPH(0xFAC9), + U24(0x97E0), + GLYPH(0x2F9FA), + U24(0x97FF), + GLYPH(0xFA69), + U24(0x980B), + GLYPH(0xFACB), + U24(0x9818), + GLYPH(0xF9B4), + U24(0x9829), + GLYPH(0x2FA00), + U24(0x983B), + GLYPH(0xFA6A), + U24(0x985E), + GLYPH(0xF9D0), + U24(0x98E2), + GLYPH(0x2FA02), + U24(0x98EF), + GLYPH(0xFA2A), + U24(0x98FC), + GLYPH(0xFA2B), + U24(0x9928), + GLYPH(0xFA2C), + U24(0x9929), + GLYPH(0x2FA04), + U24(0x99A7), + GLYPH(0x2FA05), + U24(0x99C2), + GLYPH(0x2FA06), + U24(0x99F1), + GLYPH(0xF91A), + U24(0x99FE), + GLYPH(0x2FA07), + U24(0x9A6A), + GLYPH(0xF987), + U24(0x9B12), + GLYPH(0xFACD), + U24(0x9B6F), + GLYPH(0xF939), + U24(0x9C40), + GLYPH(0x2FA0B), + U24(0x9C57), + GLYPH(0xF9F2), + U24(0x9CFD), + GLYPH(0x2FA0C), + U24(0x9D67), + GLYPH(0x2FA0F), + U24(0x9DB4), + GLYPH(0xFA2D), + U24(0x9DFA), + GLYPH(0xF93A), + U24(0x9E1E), + GLYPH(0xF920), + U24(0x9E7F), + GLYPH(0xF940), + U24(0x9E97), + GLYPH(0xF988), + U24(0x9E9F), + GLYPH(0xF9F3), + U24(0x9EBB), + GLYPH(0x2FA15), + U24(0x9ECE), + GLYPH(0xF989), + U24(0x9EF9), + GLYPH(0x2FA17), + U24(0x9EFE), + GLYPH(0x2FA18), + U24(0x9F05), + GLYPH(0x2FA19), + U24(0x9F0F), + GLYPH(0x2FA1A), + U24(0x9F16), + GLYPH(0x2FA1B), + U24(0x9F3B), + GLYPH(0x2FA1C), + U24(0x9F43), + GLYPH(0xFAD8), + U24(0x9F8D), + GLYPH(0xF9C4), + U24(0x9F8E), + GLYPH(0xFAD9), + U24(0x9F9C), + GLYPH(0xF907), + U24(0x20122), + GLYPH(0x2F803), + U24(0x2051C), + GLYPH(0x2F812), + U24(0x20525), + GLYPH(0x2F91B), + U24(0x2054B), + GLYPH(0x2F816), + U24(0x2063A), + GLYPH(0x2F80D), + U24(0x20804), + GLYPH(0x2F9D9), + U24(0x208DE), + GLYPH(0x2F9DD), + U24(0x20A2C), + GLYPH(0x2F834), + U24(0x20B63), + GLYPH(0x2F838), + U24(0x214E4), + GLYPH(0x2F859), + U24(0x216A8), + GLYPH(0x2F860), + U24(0x216EA), + GLYPH(0x2F861), + U24(0x219C8), + GLYPH(0x2F86C), + U24(0x21B18), + GLYPH(0x2F871), + U24(0x21D0B), + GLYPH(0x2F8F8), + U24(0x21DE4), + GLYPH(0x2F87B), + U24(0x21DE6), + GLYPH(0x2F87D), + U24(0x22183), + GLYPH(0x2F889), + U24(0x2219F), + GLYPH(0x2F939), + U24(0x22331), + GLYPH(0x2F891), + U24(0x226D4), + GLYPH(0x2F8A4), + U24(0x22844), + GLYPH(0xFAD0), + U24(0x2284A), + GLYPH(0xFACF), + U24(0x22B0C), + GLYPH(0x2F8B8), + U24(0x22BF1), + GLYPH(0x2F8BE), + U24(0x2300A), + GLYPH(0x2F8CA), + U24(0x232B8), + GLYPH(0x2F897), + U24(0x2335F), + GLYPH(0x2F980), + U24(0x23393), + GLYPH(0x2F989), + U24(0x2339C), + GLYPH(0x2F98A), + U24(0x233C3), + GLYPH(0x2F8DD), + U24(0x233D5), + GLYPH(0xFAD1), + U24(0x2346D), + GLYPH(0x2F8E3), + U24(0x236A3), + GLYPH(0x2F8EC), + U24(0x238A7), + GLYPH(0x2F8F0), + U24(0x23A8D), + GLYPH(0x2F8F7), + U24(0x23AFA), + GLYPH(0x2F8F9), + U24(0x23CBC), + GLYPH(0x2F8FB), + U24(0x23D1E), + GLYPH(0x2F906), + U24(0x23ED1), + GLYPH(0x2F90D), + U24(0x23F5E), + GLYPH(0x2F910), + U24(0x23F8E), + GLYPH(0x2F911), + U24(0x24263), + GLYPH(0x2F91D), + U24(0x242EE), + GLYPH(0xFA6C), + U24(0x243AB), + GLYPH(0x2F91F), + U24(0x24608), + GLYPH(0x2F923), + U24(0x24735), + GLYPH(0x2F926), + U24(0x24814), + GLYPH(0x2F927), + U24(0x24C36), + GLYPH(0x2F935), + U24(0x24C92), + GLYPH(0x2F937), + U24(0x24FA1), + GLYPH(0x2F93B), + U24(0x24FB8), + GLYPH(0x2F93C), + U24(0x25044), + GLYPH(0x2F93D), + U24(0x250F2), + GLYPH(0x2F942), + U24(0x250F3), + GLYPH(0x2F941), + U24(0x25119), + GLYPH(0x2F943), + U24(0x25133), + GLYPH(0x2F944), + U24(0x25249), + GLYPH(0xFAD5), + U24(0x2541D), + GLYPH(0x2F94D), + U24(0x25626), + GLYPH(0x2F952), + U24(0x2569A), + GLYPH(0x2F954), + U24(0x256C5), + GLYPH(0x2F955), + U24(0x2597C), + GLYPH(0x2F95C), + U24(0x25AA7), + GLYPH(0x2F95D), + U24(0x25BAB), + GLYPH(0x2F961), + U24(0x25C80), + GLYPH(0x2F965), + U24(0x25CD0), + GLYPH(0xFAD6), + U24(0x25F86), + GLYPH(0x2F96B), + U24(0x261DA), + GLYPH(0x2F898), + U24(0x26228), + GLYPH(0x2F972), + U24(0x26247), + GLYPH(0x2F973), + U24(0x262D9), + GLYPH(0x2F975), + U24(0x2633E), + GLYPH(0x2F977), + U24(0x264DA), + GLYPH(0x2F97B), + U24(0x26523), + GLYPH(0x2F97C), + U24(0x265A8), + GLYPH(0x2F97E), + U24(0x267A7), + GLYPH(0x2F987), + U24(0x267B5), + GLYPH(0x2F988), + U24(0x26B3C), + GLYPH(0x2F997), + U24(0x26C36), + GLYPH(0x2F9A4), + U24(0x26CD5), + GLYPH(0x2F9A6), + U24(0x26D6B), + GLYPH(0x2F9A5), + U24(0x26F2C), + GLYPH(0x2F9AD), + U24(0x26FB1), + GLYPH(0x2F9B0), + U24(0x270D2), + GLYPH(0x2F9B1), + U24(0x273CA), + GLYPH(0x2F9AB), + U24(0x27667), + GLYPH(0x2F9C5), + U24(0x278AE), + GLYPH(0x2F9CB), + U24(0x27966), + GLYPH(0x2F9CC), + U24(0x27CA8), + GLYPH(0x2F9D3), + U24(0x27ED3), + GLYPH(0xFAD7), + U24(0x27F2F), + GLYPH(0x2F9D8), + U24(0x285D2), + GLYPH(0x2F9E0), + U24(0x285ED), + GLYPH(0x2F9E1), + U24(0x2872E), + GLYPH(0x2F9E5), + U24(0x28BFA), + GLYPH(0x2F9ED), + U24(0x28D77), + GLYPH(0x2F9F1), + U24(0x29145), + GLYPH(0x2F9F6), + U24(0x291DF), + GLYPH(0x2F81C), + U24(0x2921A), + GLYPH(0x2F9F7), + U24(0x2940A), + GLYPH(0x2F9FB), + U24(0x29496), + GLYPH(0x2F9FD), + U24(0x295B6), + GLYPH(0x2FA01), + U24(0x29B30), + GLYPH(0x2FA09), + U24(0x2A0CE), + GLYPH(0x2FA10), + U24(0x2A105), + GLYPH(0x2FA12), + U24(0x2A20E), + GLYPH(0x2FA13), + U24(0x2A291), + GLYPH(0x2FA14), + U24(0x2A392), + GLYPH(0x2F88F), + U24(0x2A600), + GLYPH(0x2FA1D), + // 0xFE01 + U32(88), // numUVSMappings + U24(0x3B9D), + GLYPH(0x2F8E7), + U24(0x3EB8), + GLYPH(0x2F92D), + U24(0x4039), + GLYPH(0x2F949), + U24(0x4FAE), + GLYPH(0x2F805), + U24(0x50E7), + GLYPH(0x2F80A), + U24(0x514D), + GLYPH(0x2F80E), + U24(0x51B5), + GLYPH(0x2F81B), + U24(0x5207), + GLYPH(0x2F850), + U24(0x52C7), + GLYPH(0x2F825), + U24(0x52C9), + GLYPH(0x2F826), + U24(0x52E4), + GLYPH(0x2F827), + U24(0x52FA), + GLYPH(0x2F828), + U24(0x5317), + GLYPH(0x2F82B), + U24(0x5351), + GLYPH(0x2F82D), + U24(0x537F), + GLYPH(0x2F832), + U24(0x5584), + GLYPH(0x2F846), + U24(0x5599), + GLYPH(0x2F847), + U24(0x559D), + GLYPH(0xFA78), + U24(0x5606), + GLYPH(0x2F84C), + U24(0x585A), + GLYPH(0xFA7C), + U24(0x5B3E), + GLYPH(0x2F86B), + U24(0x5BE7), + GLYPH(0xF9AA), + U24(0x5C6E), + GLYPH(0x2F878), + U24(0x5ECA), + GLYPH(0x2F88E), + U24(0x5F22), + GLYPH(0x2F895), + U24(0x6094), + GLYPH(0x2F8A3), + U24(0x614C), + GLYPH(0x2F8A9), + U24(0x614E), + GLYPH(0x2F8A8), + U24(0x618E), + GLYPH(0xFA89), + U24(0x61F2), + GLYPH(0xFA8B), + U24(0x61F6), + GLYPH(0x2F8B1), + U24(0x654F), + GLYPH(0x2F8C8), + U24(0x6674), + GLYPH(0xFA91), + U24(0x6691), + GLYPH(0x2F8CF), + U24(0x6717), + GLYPH(0xFA92), + U24(0x671B), + GLYPH(0x2F8D9), + U24(0x6885), + GLYPH(0x2F8E2), + U24(0x6A02), + GLYPH(0xF95C), + U24(0x6BBA), + GLYPH(0xFA96), + U24(0x6D41), + GLYPH(0xFA97), + U24(0x6D77), + GLYPH(0x2F901), + U24(0x6ECB), + GLYPH(0x2F90B), + U24(0x6F22), + GLYPH(0xFA9A), + U24(0x701E), + GLYPH(0x2F914), + U24(0x716E), + GLYPH(0xFA9C), + U24(0x7235), + GLYPH(0x2F921), + U24(0x732A), + GLYPH(0xFAA0), + U24(0x7387), + GLYPH(0xF9DB), + U24(0x7471), + GLYPH(0x2F930), + U24(0x7570), + GLYPH(0x2F938), + U24(0x76CA), + GLYPH(0xFAA6), + U24(0x76F4), + GLYPH(0x2F940), + U24(0x771F), + GLYPH(0x2F947), + U24(0x774A), + GLYPH(0x2F948), + U24(0x788C), + GLYPH(0x2F94F), + U24(0x78CC), + GLYPH(0x2F950), + U24(0x7956), + GLYPH(0x2F953), + U24(0x798F), + GLYPH(0x2F956), + U24(0x7A40), + GLYPH(0x2F959), + U24(0x7BC0), + GLYPH(0xFAAD), + U24(0x7DF4), + GLYPH(0xFA57), + U24(0x8005), + GLYPH(0xFAB2), + U24(0x8201), + GLYPH(0x2F98B), + U24(0x8279), + GLYPH(0xFA5E), + U24(0x82E5), + GLYPH(0x2F998), + U24(0x8457), + GLYPH(0x2F99F), + U24(0x865C), + GLYPH(0x2F9B4), + U24(0x8779), + GLYPH(0x2F9BB), + U24(0x8996), + GLYPH(0xFAB8), + U24(0x8AAA), + GLYPH(0xF9A1), + U24(0x8AED), + GLYPH(0x2F9D0), + U24(0x8AF8), + GLYPH(0xFABA), + U24(0x8AFE), + GLYPH(0xFABD), + U24(0x8B01), + GLYPH(0xFABC), + U24(0x8B39), + GLYPH(0xFABF), + U24(0x8B8A), + GLYPH(0x2F9D1), + U24(0x8D08), + GLYPH(0xFAC1), + U24(0x8F38), + GLYPH(0x2F9DF), + U24(0x9038), + GLYPH(0xFA67), + U24(0x96E3), + GLYPH(0xFAC7), + U24(0x9756), + GLYPH(0xFAC8), + U24(0x97FF), + GLYPH(0xFACA), + U24(0x980B), + GLYPH(0x2F9FE), + U24(0x983B), + GLYPH(0xFACC), + U24(0x9B12), + GLYPH(0x2FA0A), + U24(0x9F9C), + GLYPH(0xF908), + U24(0x22331), + GLYPH(0x2F892), + U24(0x25AA7), + GLYPH(0x2F95E), + // 0xFE02 + U32(12), // numUVSMappings + U24(0x537F), + GLYPH(0x2F833), + U24(0x5BE7), + GLYPH(0x2F86F), + U24(0x618E), + GLYPH(0x2F8AB), + U24(0x61F2), + GLYPH(0x2F8B0), + U24(0x6717), + GLYPH(0x2F8D8), + U24(0x6A02), + GLYPH(0xF9BF), + U24(0x6BBA), + GLYPH(0x2F8F5), + U24(0x6D41), + GLYPH(0x2F902), + U24(0x7DF4), + GLYPH(0xFAB0), + U24(0x8005), + GLYPH(0x2F97A), + U24(0x980B), + GLYPH(0x2F9FF), + U24(0x9F9C), + GLYPH(0xFACE), +}; + +#undef U16 +#undef U24 +#undef U32 +#undef GLYPH + +static_assert(sizeof sCJKCompatSVSTable == 5065, "Table generator has a bug."); diff --git a/gfx/thebes/D3D11Checks.cpp b/gfx/thebes/D3D11Checks.cpp new file mode 100644 index 0000000000..3f9afe30e0 --- /dev/null +++ b/gfx/thebes/D3D11Checks.cpp @@ -0,0 +1,489 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "D3D11Checks.h" +#include "DXVA2Manager.h" +#include "gfxConfig.h" +#include "GfxDriverInfo.h" +#include "gfxWindowsPlatform.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/StaticPrefs_media.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/layers/TextureD3D11.h" +#include "nsIGfxInfo.h" +#include <dxgi.h> +#include <dxgi1_2.h> +#include <d3d10_1.h> +#include <d3d11.h> + +namespace mozilla { +namespace gfx { + +using namespace mozilla::widget; +using mozilla::layers::AutoTextureLock; + +/* static */ +bool D3D11Checks::DoesRenderTargetViewNeedRecreating(ID3D11Device* aDevice) { + bool result = false; + // CreateTexture2D is known to crash on lower feature levels, see bugs + // 1170211 and 1089413. + if (aDevice->GetFeatureLevel() < D3D_FEATURE_LEVEL_10_0) { + return true; + } + + RefPtr<ID3D11DeviceContext> deviceContext; + aDevice->GetImmediateContext(getter_AddRefs(deviceContext)); + int backbufferWidth = 32; + int backbufferHeight = 32; + RefPtr<ID3D11Texture2D> offscreenTexture; + RefPtr<IDXGIKeyedMutex> keyedMutex; + + D3D11_TEXTURE2D_DESC offscreenTextureDesc = {0}; + offscreenTextureDesc.Width = backbufferWidth; + offscreenTextureDesc.Height = backbufferHeight; + offscreenTextureDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + offscreenTextureDesc.MipLevels = 0; + offscreenTextureDesc.ArraySize = 1; + offscreenTextureDesc.SampleDesc.Count = 1; + offscreenTextureDesc.SampleDesc.Quality = 0; + offscreenTextureDesc.Usage = D3D11_USAGE_DEFAULT; + offscreenTextureDesc.BindFlags = + D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; + offscreenTextureDesc.CPUAccessFlags = 0; + offscreenTextureDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX; + + HRESULT hr = aDevice->CreateTexture2D(&offscreenTextureDesc, NULL, + getter_AddRefs(offscreenTexture)); + if (FAILED(hr)) { + gfxCriticalNote << "DoesRecreatingCreateTexture2DFail"; + return false; + } + + hr = offscreenTexture->QueryInterface(__uuidof(IDXGIKeyedMutex), + (void**)getter_AddRefs(keyedMutex)); + if (FAILED(hr)) { + gfxCriticalNote << "DoesRecreatingKeyedMutexFailed"; + return false; + } + D3D11_RENDER_TARGET_VIEW_DESC offscreenRTVDesc; + offscreenRTVDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + offscreenRTVDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; + offscreenRTVDesc.Texture2D.MipSlice = 0; + + RefPtr<ID3D11RenderTargetView> offscreenRTView; + hr = aDevice->CreateRenderTargetView(offscreenTexture, &offscreenRTVDesc, + getter_AddRefs(offscreenRTView)); + if (FAILED(hr)) { + gfxCriticalNote << "DoesRecreatingCreateRenderTargetViewFailed"; + return false; + } + + { + // Acquire and clear + HRESULT hr; + AutoTextureLock lock(keyedMutex, hr, INFINITE); + FLOAT color1[4] = {1, 1, 0.5, 1}; + deviceContext->ClearRenderTargetView(offscreenRTView, color1); + } + + { + HRESULT hr; + AutoTextureLock lock(keyedMutex, hr, INFINITE); + FLOAT color2[4] = {1, 1, 0, 1}; + + deviceContext->ClearRenderTargetView(offscreenRTView, color2); + D3D11_TEXTURE2D_DESC desc; + + offscreenTexture->GetDesc(&desc); + desc.Usage = D3D11_USAGE_STAGING; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + desc.MiscFlags = 0; + desc.BindFlags = 0; + RefPtr<ID3D11Texture2D> cpuTexture; + hr = aDevice->CreateTexture2D(&desc, NULL, getter_AddRefs(cpuTexture)); + if (FAILED(hr)) { + gfxCriticalNote << "DoesRecreatingCreateCPUTextureFailed"; + return false; + } + + deviceContext->CopyResource(cpuTexture, offscreenTexture); + + D3D11_MAPPED_SUBRESOURCE mapped; + hr = deviceContext->Map(cpuTexture, 0, D3D11_MAP_READ, 0, &mapped); + if (FAILED(hr)) { + gfxCriticalNote << "DoesRecreatingMapFailed " << hexa(hr); + return false; + } + 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 (StaticPrefs::gfx_direct2d_force_enabled_AtStartup() || + gfxConfig::IsForcedOnByUser(Feature::HW_COMPOSITING)) { + result = true; + return true; + } + + if (GetModuleHandleW(L"igd10umd32.dll")) { + const wchar_t* checkModules[] = {L"dlumd32.dll", L"dlumd11.dll", + L"dlumd10.dll"}; + for (int i = 0; i < PR_ARRAY_SIZE(checkModules); i += 1) { + if (GetModuleHandleW(checkModules[i])) { + nsString displayLinkModuleVersionString; + gfxWindowsPlatform::GetDLLVersion(checkModules[i], + displayLinkModuleVersionString); + uint64_t displayLinkModuleVersion; + if (!ParseDriverVersion(displayLinkModuleVersionString, + &displayLinkModuleVersion)) { + gfxCriticalError() + << "DisplayLink: could not parse version " << checkModules[i]; + return false; + } + if (displayLinkModuleVersion <= V(8, 6, 1, 36484)) { + NS_ConvertUTF16toUTF8 version(displayLinkModuleVersionString); + gfxCriticalError(CriticalLog::DefaultOptions(false)) + << "DisplayLink: too old version " << version.get(); + return false; + } + } + } + } + result = true; + return true; +} + +static bool TryCreateTexture2D(ID3D11Device* device, D3D11_TEXTURE2D_DESC* desc, + D3D11_SUBRESOURCE_DATA* data, + RefPtr<ID3D11Texture2D>& texture) { + // Older Intel driver version (see bug 1221348 for version #s) crash when + // creating a texture with shared keyed mutex and data. + MOZ_SEH_TRY { + return !FAILED( + device->CreateTexture2D(desc, data, getter_AddRefs(texture))); + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + // For now we want to aggregrate all the crash signature to a known crash. + gfxDevCrash(LogReason::TextureCreation) + << "Crash creating texture. See bug 1221348."; + return false; + } +} + +// See bug 1083071. On some drivers, Direct3D 11 CreateShaderResourceView fails +// with E_OUTOFMEMORY. +static bool DoesTextureSharingWorkInternal(ID3D11Device* device, + DXGI_FORMAT format, UINT bindflags) { + // CreateTexture2D is known to crash on lower feature levels, see bugs + // 1170211 and 1089413. + if (device->GetFeatureLevel() < D3D_FEATURE_LEVEL_10_0) { + return false; + } + + if (StaticPrefs::gfx_direct2d_force_enabled_AtStartup() || + gfxConfig::IsForcedOnByUser(Feature::HW_COMPOSITING)) { + return true; + } + + if (GetModuleHandleW(L"atidxx32.dll")) { + nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo(); + if (gfxInfo) { + nsString vendorID, vendorID2; + gfxInfo->GetAdapterVendorID(vendorID); + gfxInfo->GetAdapterVendorID2(vendorID2); + if (vendorID.EqualsLiteral("0x8086") && vendorID2.IsEmpty()) { + if (!StaticPrefs::layers_amd_switchable_gfx_enabled_AtStartup()) { + return false; + } + gfxCriticalError(CriticalLog::DefaultOptions(false)) + << "PossiblyBrokenSurfaceSharing_UnexpectedAMDGPU"; + } + } + } + + RefPtr<ID3D11Texture2D> texture; + D3D11_TEXTURE2D_DESC desc; + const int texture_size = 32; + desc.Width = texture_size; + desc.Height = texture_size; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = format; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.CPUAccessFlags = 0; + desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX; + desc.BindFlags = bindflags; + + uint32_t color[texture_size * texture_size]; + for (size_t i = 0; i < sizeof(color) / sizeof(color[0]); i++) { + color[i] = 0xff00ffff; + } + // XXX If we pass the data directly at texture creation time we + // get a crash on Intel 8.5.10.[18xx-1994] drivers. + // We can work around this issue by doing UpdateSubresource. + if (!TryCreateTexture2D(device, &desc, nullptr, texture)) { + gfxCriticalNote << "DoesD3D11TextureSharingWork_TryCreateTextureFailure"; + return false; + } + + RefPtr<IDXGIKeyedMutex> sourceSharedMutex; + texture->QueryInterface(__uuidof(IDXGIKeyedMutex), + (void**)getter_AddRefs(sourceSharedMutex)); + if (FAILED(sourceSharedMutex->AcquireSync(0, 30 * 1000))) { + gfxCriticalError() << "DoesD3D11TextureSharingWork_SourceMutexTimeout"; + // only wait for 30 seconds + return false; + } + + RefPtr<ID3D11DeviceContext> deviceContext; + device->GetImmediateContext(getter_AddRefs(deviceContext)); + + int stride = texture_size * 4; + deviceContext->UpdateSubresource(texture, 0, nullptr, color, stride, + stride * texture_size); + + if (FAILED(sourceSharedMutex->ReleaseSync(0))) { + gfxCriticalError() + << "DoesD3D11TextureSharingWork_SourceReleaseSyncTimeout"; + return false; + } + + HANDLE shareHandle; + RefPtr<IDXGIResource> otherResource; + if (FAILED(texture->QueryInterface(__uuidof(IDXGIResource), + getter_AddRefs(otherResource)))) { + gfxCriticalError() << "DoesD3D11TextureSharingWork_GetResourceFailure"; + return false; + } + + if (FAILED(otherResource->GetSharedHandle(&shareHandle))) { + gfxCriticalError() << "DoesD3D11TextureSharingWork_GetSharedTextureFailure"; + return false; + } + + RefPtr<ID3D11Resource> sharedResource; + RefPtr<ID3D11Texture2D> sharedTexture; + if (FAILED(device->OpenSharedResource(shareHandle, __uuidof(ID3D11Resource), + getter_AddRefs(sharedResource)))) { + gfxCriticalError(CriticalLog::DefaultOptions(false)) + << "OpenSharedResource failed for format " << format; + return false; + } + + if (FAILED(sharedResource->QueryInterface(__uuidof(ID3D11Texture2D), + getter_AddRefs(sharedTexture)))) { + gfxCriticalError() << "DoesD3D11TextureSharingWork_GetSharedTextureFailure"; + return false; + } + + // create a staging texture for readback + RefPtr<ID3D11Texture2D> cpuTexture; + desc.Usage = D3D11_USAGE_STAGING; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + desc.MiscFlags = 0; + desc.BindFlags = 0; + if (FAILED(device->CreateTexture2D(&desc, nullptr, + getter_AddRefs(cpuTexture)))) { + gfxCriticalError() << "DoesD3D11TextureSharingWork_CreateTextureFailure"; + return false; + } + + RefPtr<IDXGIKeyedMutex> sharedMutex; + sharedResource->QueryInterface(__uuidof(IDXGIKeyedMutex), + (void**)getter_AddRefs(sharedMutex)); + { + HRESULT hr; + AutoTextureLock lock(sharedMutex, hr, 30 * 1000); + if (FAILED(hr)) { + gfxCriticalError() << "DoesD3D11TextureSharingWork_AcquireSyncTimeout"; + // only wait for 30 seconds + return false; + } + + // Copy to the cpu texture so that we can readback + deviceContext->CopyResource(cpuTexture, sharedTexture); + + // We only need to hold on to the mutex during the copy. + sharedMutex->ReleaseSync(0); + } + + D3D11_MAPPED_SUBRESOURCE mapped; + uint32_t resultColor = 0; + if (SUCCEEDED( + deviceContext->Map(cpuTexture, 0, D3D11_MAP_READ, 0, &mapped))) { + // read the texture + resultColor = *(uint32_t*)mapped.pData; + deviceContext->Unmap(cpuTexture, 0); + } else { + gfxCriticalError() << "DoesD3D11TextureSharingWork_MapFailed"; + return false; + } + + // check that the color we put in is the color we get out + if (resultColor != color[0]) { + // Shared surfaces seem to be broken on dual AMD & Intel HW when using the + // AMD GPU + gfxCriticalNote << "DoesD3D11TextureSharingWork_ColorMismatch"; + return false; + } + + RefPtr<ID3D11ShaderResourceView> sharedView; + + // This if(FAILED()) is the one that actually fails on systems affected by bug + // 1083071. + if (FAILED(device->CreateShaderResourceView(sharedTexture, NULL, + getter_AddRefs(sharedView)))) { + gfxCriticalNote << "CreateShaderResourceView failed for format" << format; + return false; + } + + return true; +} + +/* static */ +bool D3D11Checks::DoesTextureSharingWork(ID3D11Device* device) { + return DoesTextureSharingWorkInternal( + device, DXGI_FORMAT_B8G8R8A8_UNORM, + D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE); +} + +/* static */ +bool D3D11Checks::DoesAlphaTextureSharingWork(ID3D11Device* device) { + return DoesTextureSharingWorkInternal(device, DXGI_FORMAT_R8_UNORM, + D3D11_BIND_SHADER_RESOURCE); +} + +/* static */ +bool D3D11Checks::GetDxgiDesc(ID3D11Device* device, DXGI_ADAPTER_DESC* out) { + RefPtr<IDXGIDevice> dxgiDevice; + HRESULT hr = + device->QueryInterface(__uuidof(IDXGIDevice), getter_AddRefs(dxgiDevice)); + if (FAILED(hr)) { + return false; + } + + RefPtr<IDXGIAdapter> dxgiAdapter; + if (FAILED(dxgiDevice->GetAdapter(getter_AddRefs(dxgiAdapter)))) { + return false; + } + + return SUCCEEDED(dxgiAdapter->GetDesc(out)); +} + +/* static */ +void D3D11Checks::WarnOnAdapterMismatch(ID3D11Device* device) { + DXGI_ADAPTER_DESC desc; + PodZero(&desc); + GetDxgiDesc(device, &desc); + + nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo(); + nsString vendorID; + gfxInfo->GetAdapterVendorID(vendorID); + nsresult ec; + int32_t vendor = vendorID.ToInteger(&ec, 16); + if (vendor != desc.VendorId) { + gfxCriticalNote << "VendorIDMismatch V " << hexa(vendor) << " " + << hexa(desc.VendorId); + } +} + +/* static */ +bool D3D11Checks::DoesRemotePresentWork(IDXGIAdapter* adapter) { + // Remote presentation was added in DXGI 1.2, for Windows 8 and the Platform + // Update to Windows 7. + RefPtr<IDXGIAdapter2> check; + HRESULT hr = + adapter->QueryInterface(__uuidof(IDXGIAdapter2), getter_AddRefs(check)); + return SUCCEEDED(hr) && check; +} + +/* static */ D3D11Checks::VideoFormatOptionSet D3D11Checks::FormatOptions( + ID3D11Device* device) { + auto doesNV12Work = [&]() { + if (gfxVars::DXNV12Blocked()) { + return false; + } + + DXGI_ADAPTER_DESC desc; + PodZero(&desc); + if (!GetDxgiDesc(device, &desc)) { + // Failed to retrieve device information, assume it doesn't work + return false; + } + + UINT formatSupport; + HRESULT hr = device->CheckFormatSupport(DXGI_FORMAT_NV12, &formatSupport); + if (FAILED(hr) || !(formatSupport & D3D11_FORMAT_SUPPORT_TEXTURE2D)) { + return false; + } + + nsString version; + nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo(); + if (gfxInfo) { + gfxInfo->GetAdapterDriverVersion(version); + } + return DXVA2Manager::IsNV12Supported(desc.VendorId, desc.DeviceId, version); + }; + + auto doesP010Work = [&]() { + if (gfxVars::DXP010Blocked() && + !StaticPrefs::media_wmf_force_allow_p010_format()) { + return false; + } + UINT formatSupport; + HRESULT hr = device->CheckFormatSupport(DXGI_FORMAT_P010, &formatSupport); + return (SUCCEEDED(hr) && (formatSupport & D3D11_FORMAT_SUPPORT_TEXTURE2D)); + }; + + auto doesP016Work = [&]() { + if (gfxVars::DXP016Blocked() && + !StaticPrefs::media_wmf_force_allow_p010_format()) { + return false; + } + UINT formatSupport; + HRESULT hr = device->CheckFormatSupport(DXGI_FORMAT_P016, &formatSupport); + return (SUCCEEDED(hr) && (formatSupport & D3D11_FORMAT_SUPPORT_TEXTURE2D)); + }; + + VideoFormatOptionSet options; + if (!doesNV12Work()) { + // If the device doesn't support NV12, there's really no point testing for + // P010 and P016. + return options; + } + options += VideoFormatOption::NV12; + if (doesP010Work()) { + options += VideoFormatOption::P010; + } + if (doesP016Work()) { + options += VideoFormatOption::P016; + } + return options; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/thebes/D3D11Checks.h b/gfx/thebes/D3D11Checks.h new file mode 100644 index 0000000000..622cf3229c --- /dev/null +++ b/gfx/thebes/D3D11Checks.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_thebes_D3D11Checks_h +#define mozilla_gfx_thebes_D3D11Checks_h + +#include "mozilla/EnumSet.h" +#include "mozilla/EnumTypeTraits.h" + +struct ID3D11Device; +struct IDXGIAdapter; +struct DXGI_ADAPTER_DESC; + +namespace mozilla { +namespace gfx { + +struct D3D11Checks { + enum class VideoFormatOption { + NV12, + P010, + P016, + }; + using VideoFormatOptionSet = EnumSet<VideoFormatOption>; + + static bool DoesRenderTargetViewNeedRecreating(ID3D11Device* aDevice); + static bool DoesDeviceWork(); + static bool DoesTextureSharingWork(ID3D11Device* device); + static bool DoesAlphaTextureSharingWork(ID3D11Device* device); + static void WarnOnAdapterMismatch(ID3D11Device* device); + static bool GetDxgiDesc(ID3D11Device* device, DXGI_ADAPTER_DESC* out); + static bool DoesRemotePresentWork(IDXGIAdapter* adapter); + static VideoFormatOptionSet FormatOptions(ID3D11Device* device); +}; + +} // namespace gfx + +// Used for IPDL serialization. +// The 'value' have to be the biggest enum from D3D11Checks::Option. +template <> +struct MaxEnumValue<::mozilla::gfx::D3D11Checks::VideoFormatOption> { + static constexpr unsigned int value = + static_cast<unsigned int>(gfx::D3D11Checks::VideoFormatOption::P016); +}; + +} // namespace mozilla + +#endif // mozilla_gfx_thebes_D3D11Checks_h diff --git a/gfx/thebes/DeviceManagerDx.cpp b/gfx/thebes/DeviceManagerDx.cpp new file mode 100644 index 0000000000..c62f6b39f9 --- /dev/null +++ b/gfx/thebes/DeviceManagerDx.cpp @@ -0,0 +1,1448 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DeviceManagerDx.h" +#include "D3D11Checks.h" +#include "gfxConfig.h" +#include "GfxDriverInfo.h" +#include "gfxWindowsPlatform.h" +#include "mozilla/D3DMessageUtils.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/Telemetry.h" +#include "mozilla/WindowsVersion.h" +#include "mozilla/gfx/GPUParent.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/gfx/GraphicsMessages.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/layers/CompositorBridgeChild.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/DeviceAttachmentsD3D11.h" +#include "mozilla/layers/MLGDeviceD3D11.h" +#include "mozilla/layers/PaintThread.h" +#include "mozilla/Preferences.h" +#include "nsExceptionHandler.h" +#include "nsPrintfCString.h" +#include "nsString.h" + +#undef _WIN32_WINNT +#define _WIN32_WINNT _WIN32_WINNT_WINBLUE +#undef NTDDI_VERSION +#define NTDDI_VERSION NTDDI_WINBLUE + +#include <d3d11.h> +#include <dcomp.h> +#include <ddraw.h> + +namespace mozilla { +namespace gfx { + +using namespace mozilla::widget; +using namespace mozilla::layers; + +StaticAutoPtr<DeviceManagerDx> DeviceManagerDx::sInstance; + +// We don't have access to the D3D11CreateDevice type in gfxWindowsPlatform.h, +// since it doesn't include d3d11.h, so we use a static here. It should only +// be used within InitializeD3D11. +decltype(D3D11CreateDevice)* sD3D11CreateDeviceFn = nullptr; + +// It should only be used within CreateDirectCompositionDevice. +decltype(DCompositionCreateDevice2)* sDcompCreateDevice2Fn = nullptr; + +// It should only be used within CreateDCompSurfaceHandle +decltype(DCompositionCreateSurfaceHandle)* sDcompCreateSurfaceHandleFn = + nullptr; + +// We don't have access to the DirectDrawCreateEx type in gfxWindowsPlatform.h, +// since it doesn't include ddraw.h, so we use a static here. It should only +// be used within InitializeDirectDrawConfig. +decltype(DirectDrawCreateEx)* sDirectDrawCreateExFn = nullptr; + +/* static */ +void DeviceManagerDx::Init() { sInstance = new DeviceManagerDx(); } + +/* static */ +void DeviceManagerDx::Shutdown() { sInstance = nullptr; } + +DeviceManagerDx::DeviceManagerDx() + : mDeviceLock("gfxWindowsPlatform.mDeviceLock"), + mCompositorDeviceSupportsVideo(false) { + // Set up the D3D11 feature levels we can ask for. + if (IsWin8OrLater()) { + mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_11_1); + } + mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_11_0); + mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_10_1); + mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_10_0); +} + +bool DeviceManagerDx::LoadD3D11() { + FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING); + MOZ_ASSERT(d3d11.IsEnabled()); + + if (sD3D11CreateDeviceFn) { + return true; + } + + nsModuleHandle module(LoadLibrarySystem32(L"d3d11.dll")); + if (!module) { + d3d11.SetFailed(FeatureStatus::Unavailable, + "Direct3D11 not available on this computer", + "FEATURE_FAILURE_D3D11_LIB"_ns); + return false; + } + + sD3D11CreateDeviceFn = + (decltype(D3D11CreateDevice)*)GetProcAddress(module, "D3D11CreateDevice"); + if (!sD3D11CreateDeviceFn) { + // We should just be on Windows Vista or XP in this case. + d3d11.SetFailed(FeatureStatus::Unavailable, + "Direct3D11 not available on this computer", + "FEATURE_FAILURE_D3D11_FUNCPTR"_ns); + return false; + } + + mD3D11Module.steal(module); + return true; +} + +bool DeviceManagerDx::LoadDcomp() { + MOZ_ASSERT(gfxConfig::GetFeature(Feature::D3D11_COMPOSITING).IsEnabled()); + MOZ_ASSERT(gfxVars::UseWebRender()); + MOZ_ASSERT(gfxVars::UseWebRenderANGLE()); + MOZ_ASSERT(gfxVars::UseWebRenderDCompWin()); + + if (sDcompCreateDevice2Fn) { + return true; + } + + nsModuleHandle module(LoadLibrarySystem32(L"dcomp.dll")); + if (!module) { + return false; + } + + sDcompCreateDevice2Fn = (decltype(DCompositionCreateDevice2)*)GetProcAddress( + module, "DCompositionCreateDevice2"); + if (!sDcompCreateDevice2Fn) { + return false; + } + + // Load optional API for external compositing + sDcompCreateSurfaceHandleFn = + (decltype(DCompositionCreateSurfaceHandle)*)::GetProcAddress( + module, "DCompositionCreateSurfaceHandle"); + if (!sDcompCreateDevice2Fn) { + return false; + } + + mDcompModule.steal(module); + return true; +} + +void DeviceManagerDx::ReleaseD3D11() { + MOZ_ASSERT(!mCompositorDevice); + MOZ_ASSERT(!mContentDevice); + MOZ_ASSERT(!mVRDevice); + MOZ_ASSERT(!mDecoderDevice); + + mD3D11Module.reset(); + sD3D11CreateDeviceFn = nullptr; +} + +nsTArray<DXGI_OUTPUT_DESC1> DeviceManagerDx::EnumerateOutputs() { + RefPtr<IDXGIAdapter> adapter = GetDXGIAdapter(); + + if (!adapter) { + NS_WARNING("Failed to acquire a DXGI adapter for enumerating outputs."); + return nsTArray<DXGI_OUTPUT_DESC1>(); + } + + nsTArray<DXGI_OUTPUT_DESC1> outputs; + for (UINT i = 0;; ++i) { + RefPtr<IDXGIOutput> output = nullptr; + if (FAILED(adapter->EnumOutputs(i, getter_AddRefs(output)))) { + break; + } + + RefPtr<IDXGIOutput6> output6 = nullptr; + if (FAILED(output->QueryInterface(__uuidof(IDXGIOutput6), + getter_AddRefs(output6)))) { + break; + } + + DXGI_OUTPUT_DESC1 desc; + if (FAILED(output6->GetDesc1(&desc))) { + break; + } + + outputs.AppendElement(desc); + } + return outputs; +} + +bool DeviceManagerDx::GetOutputFromMonitor(HMONITOR monitor, + RefPtr<IDXGIOutput>* aOutOutput) { + RefPtr<IDXGIAdapter> adapter = GetDXGIAdapter(); + + if (!adapter) { + NS_WARNING("Failed to acquire a DXGI adapter for GetOutputFromMonitor."); + return false; + } + + for (UINT i = 0;; ++i) { + RefPtr<IDXGIOutput> output = nullptr; + if (FAILED(adapter->EnumOutputs(i, getter_AddRefs(output)))) { + break; + } + + DXGI_OUTPUT_DESC desc; + if (FAILED(output->GetDesc(&desc))) { + continue; + } + + if (desc.Monitor == monitor) { + *aOutOutput = output; + return true; + } + } + return false; +} + +void DeviceManagerDx::CheckHardwareStretchingSupport(HwStretchingSupport& aRv) { + RefPtr<IDXGIAdapter> adapter = GetDXGIAdapter(); + + if (!adapter) { + NS_WARNING( + "Failed to acquire a DXGI adapter for checking hardware stretching " + "support."); + ++aRv.mError; + return; + } + + for (UINT i = 0;; ++i) { + RefPtr<IDXGIOutput> output = nullptr; + HRESULT result = adapter->EnumOutputs(i, getter_AddRefs(output)); + if (result == DXGI_ERROR_NOT_FOUND) { + // No more outputs to check. + break; + } + + if (FAILED(result)) { + ++aRv.mError; + break; + } + + RefPtr<IDXGIOutput6> output6 = nullptr; + if (FAILED(output->QueryInterface(__uuidof(IDXGIOutput6), + getter_AddRefs(output6)))) { + ++aRv.mError; + continue; + } + + UINT flags = 0; + if (FAILED(output6->CheckHardwareCompositionSupport(&flags))) { + ++aRv.mError; + continue; + } + + bool fullScreen = flags & DXGI_HARDWARE_COMPOSITION_SUPPORT_FLAG_FULLSCREEN; + bool window = flags & DXGI_HARDWARE_COMPOSITION_SUPPORT_FLAG_WINDOWED; + if (fullScreen && window) { + ++aRv.mBoth; + } else if (fullScreen) { + ++aRv.mFullScreenOnly; + } else if (window) { + ++aRv.mWindowOnly; + } else { + ++aRv.mNone; + } + } +} + +#ifdef DEBUG +static inline bool ProcessOwnsCompositor() { + return XRE_GetProcessType() == GeckoProcessType_GPU || + XRE_GetProcessType() == GeckoProcessType_VR || + (XRE_IsParentProcess() && !gfxConfig::IsEnabled(Feature::GPU_PROCESS)); +} +#endif + +bool DeviceManagerDx::CreateCompositorDevices() { + MOZ_ASSERT(ProcessOwnsCompositor()); + + FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING); + MOZ_ASSERT(d3d11.IsEnabled()); + + if (int32_t sleepSec = + StaticPrefs::gfx_direct3d11_sleep_on_create_device_AtStartup()) { + printf_stderr("Attach to PID: %d\n", GetCurrentProcessId()); + Sleep(sleepSec * 1000); + } + + if (!LoadD3D11()) { + return false; + } + + CreateCompositorDevice(d3d11); + + if (!d3d11.IsEnabled()) { + MOZ_ASSERT(!mCompositorDevice); + ReleaseD3D11(); + + // Sync Advanced-Layers with D3D11. + if (gfxConfig::IsEnabled(Feature::ADVANCED_LAYERS)) { + gfxConfig::SetFailed(Feature::ADVANCED_LAYERS, FeatureStatus::Unavailable, + "Requires D3D11", "FEATURE_FAILURE_NO_D3D11"_ns); + } + return false; + } + + // We leak these everywhere and we need them our entire runtime anyway, let's + // leak it here as well. We keep the pointer to sD3D11CreateDeviceFn around + // as well for D2D1 and device resets. + mD3D11Module.disown(); + + MOZ_ASSERT(mCompositorDevice); + if (!d3d11.IsEnabled()) { + return false; + } + + // When WR is used, do not preload attachments for D3D11 Non-WR compositor. + // + // Fallback from WR to D3D11 Non-WR compositor without re-creating gpu process + // could happen when WR causes error. In this case, the attachments are loaded + // synchronously. + if (!gfx::gfxVars::UseWebRender() || gfx::gfxVars::UseSoftwareWebRender()) { + PreloadAttachmentsOnCompositorThread(); + } + + return true; +} + +bool DeviceManagerDx::CreateVRDevice() { + MOZ_ASSERT(ProcessOwnsCompositor()); + + if (mVRDevice) { + return true; + } + + if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + NS_WARNING("Direct3D11 Compositing required for VR"); + return false; + } + + if (!LoadD3D11()) { + return false; + } + + RefPtr<IDXGIAdapter1> adapter = GetDXGIAdapter(); + if (!adapter) { + NS_WARNING("Failed to acquire a DXGI adapter for VR"); + return false; + } + + UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + + HRESULT hr; + if (!CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, flags, hr, mVRDevice)) { + gfxCriticalError() << "Crash during D3D11 device creation for VR"; + return false; + } + + if (FAILED(hr) || !mVRDevice) { + NS_WARNING("Failed to acquire a D3D11 device for VR"); + return false; + } + + return true; +} + +bool DeviceManagerDx::CreateCanvasDevice() { + MOZ_ASSERT(ProcessOwnsCompositor()); + + if (mCanvasDevice) { + return true; + } + + if (!LoadD3D11()) { + return false; + } + + RefPtr<IDXGIAdapter1> adapter = GetDXGIAdapter(); + if (!adapter) { + NS_WARNING("Failed to acquire a DXGI adapter for Canvas"); + return false; + } + + UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + + HRESULT hr; + if (!CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, flags, hr, + mCanvasDevice)) { + gfxCriticalError() << "Crash during D3D11 device creation for Canvas"; + return false; + } + + if (FAILED(hr) || !mCanvasDevice) { + NS_WARNING("Failed to acquire a D3D11 device for Canvas"); + return false; + } + + if (XRE_IsGPUProcess()) { + Factory::SetDirect3D11Device(mCanvasDevice); + } + + return true; +} + +void DeviceManagerDx::CreateDirectCompositionDevice() { + if (!gfxVars::UseWebRenderDCompWin()) { + return; + } + + if (!mCompositorDevice) { + return; + } + + if (!LoadDcomp()) { + return; + } + + RefPtr<IDXGIDevice> dxgiDevice; + if (mCompositorDevice->QueryInterface( + IID_PPV_ARGS((IDXGIDevice**)getter_AddRefs(dxgiDevice))) != S_OK) { + return; + } + + HRESULT hr; + RefPtr<IDCompositionDesktopDevice> desktopDevice; + MOZ_SEH_TRY { + hr = sDcompCreateDevice2Fn( + dxgiDevice.get(), + IID_PPV_ARGS( + (IDCompositionDesktopDevice**)getter_AddRefs(desktopDevice))); + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { return; } + + if (!SUCCEEDED(hr)) { + return; + } + + RefPtr<IDCompositionDevice2> compositionDevice; + if (desktopDevice->QueryInterface(IID_PPV_ARGS( + (IDCompositionDevice2**)getter_AddRefs(compositionDevice))) != S_OK) { + return; + } + + mDirectCompositionDevice = compositionDevice; +} + +/* static */ +HANDLE DeviceManagerDx::CreateDCompSurfaceHandle() { + if (!sDcompCreateSurfaceHandleFn) { + return 0; + } + + HANDLE handle = 0; + HRESULT hr = sDcompCreateSurfaceHandleFn(COMPOSITIONOBJECT_ALL_ACCESS, + nullptr, &handle); + if (FAILED(hr)) { + return 0; + } + + return handle; +} + +void DeviceManagerDx::ImportDeviceInfo(const D3D11DeviceStatus& aDeviceStatus) { + MOZ_ASSERT(!ProcessOwnsCompositor()); + + mDeviceStatus = Some(aDeviceStatus); +} + +void DeviceManagerDx::ExportDeviceInfo(D3D11DeviceStatus* aOut) { + if (mDeviceStatus) { + *aOut = mDeviceStatus.value(); + } +} + +void DeviceManagerDx::CreateContentDevices() { + MOZ_ASSERT(gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)); + + if (!LoadD3D11()) { + return; + } + + // We should have been assigned a DeviceStatus from the parent process, + // GPU process, or the same process if using in-process compositing. + MOZ_ASSERT(mDeviceStatus); + + if (CreateContentDevice() == FeatureStatus::CrashedInHandler) { + DisableD3D11AfterCrash(); + } +} + +IDXGIAdapter1* DeviceManagerDx::GetDXGIAdapter() { + if (mAdapter) { + return mAdapter; + } + + nsModuleHandle dxgiModule(LoadLibrarySystem32(L"dxgi.dll")); + decltype(CreateDXGIFactory1)* createDXGIFactory1 = + (decltype(CreateDXGIFactory1)*)GetProcAddress(dxgiModule, + "CreateDXGIFactory1"); + + if (!createDXGIFactory1) { + return nullptr; + } + + // Try to use a DXGI 1.1 adapter in order to share resources + // across processes. + RefPtr<IDXGIFactory1> factory1; + HRESULT hr = + createDXGIFactory1(__uuidof(IDXGIFactory1), getter_AddRefs(factory1)); + if (FAILED(hr) || !factory1) { + // This seems to happen with some people running the iZ3D driver. + // They won't get acceleration. + return nullptr; + } + + if (!mDeviceStatus) { + // If we haven't created a device yet, and have no existing device status, + // then this must be the compositor device. Pick the first adapter we can. + if (FAILED(factory1->EnumAdapters1(0, getter_AddRefs(mAdapter)))) { + return nullptr; + } + } else { + // In the UI and GPU process, we clear mDeviceStatus on device reset, so we + // should never reach here. Furthermore, the UI process does not create + // devices when using a GPU process. + // + // So, this should only ever get called on the content process or RDD + // process + MOZ_ASSERT(XRE_IsContentProcess() || XRE_IsRDDProcess()); + + // In the child process, we search for the adapter that matches the parent + // process. The first adapter can be mismatched on dual-GPU systems. + for (UINT index = 0;; index++) { + RefPtr<IDXGIAdapter1> adapter; + if (FAILED(factory1->EnumAdapters1(index, getter_AddRefs(adapter)))) { + break; + } + + const DxgiAdapterDesc& preferred = mDeviceStatus->adapter(); + + DXGI_ADAPTER_DESC desc; + if (SUCCEEDED(adapter->GetDesc(&desc)) && + desc.AdapterLuid.HighPart == preferred.AdapterLuid.HighPart && + desc.AdapterLuid.LowPart == preferred.AdapterLuid.LowPart && + desc.VendorId == preferred.VendorId && + desc.DeviceId == preferred.DeviceId) { + mAdapter = adapter.forget(); + break; + } + } + } + + if (!mAdapter) { + return nullptr; + } + + // We leak this module everywhere, we might as well do so here as well. + dxgiModule.disown(); + return mAdapter; +} + +bool DeviceManagerDx::CreateCompositorDeviceHelper( + FeatureState& aD3d11, IDXGIAdapter1* aAdapter, bool aAttemptVideoSupport, + RefPtr<ID3D11Device>& aOutDevice) { + // Check if a failure was injected for testing. + if (StaticPrefs::gfx_testing_device_fail()) { + aD3d11.SetFailed(FeatureStatus::Failed, + "Direct3D11 device failure simulated by preference", + "FEATURE_FAILURE_D3D11_SIM"_ns); + return false; + } + + UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + + DXGI_ADAPTER_DESC desc; + aAdapter->GetDesc(&desc); + if (desc.VendorId != 0x1414) { + // 0x1414 is Microsoft (e.g. WARP) + // When not using WARP, use + // D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS to prevent + // bug 1092260. IE 11 also uses this flag. + flags |= D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS; + } + + if (aAttemptVideoSupport) { + flags |= D3D11_CREATE_DEVICE_VIDEO_SUPPORT; + } + + HRESULT hr; + RefPtr<ID3D11Device> device; + if (!CreateDevice(aAdapter, D3D_DRIVER_TYPE_UNKNOWN, flags, hr, device)) { + if (!aAttemptVideoSupport) { + gfxCriticalError() << "Crash during D3D11 device creation"; + aD3d11.SetFailed(FeatureStatus::CrashedInHandler, + "Crashed trying to acquire a D3D11 device", + "FEATURE_FAILURE_D3D11_DEVICE1"_ns); + } + return false; + } + + if (FAILED(hr) || !device) { + if (!aAttemptVideoSupport) { + aD3d11.SetFailed(FeatureStatus::Failed, + "Failed to acquire a D3D11 device", + "FEATURE_FAILURE_D3D11_DEVICE2"_ns); + } + return false; + } + if (!D3D11Checks::DoesDeviceWork()) { + if (!aAttemptVideoSupport) { + aD3d11.SetFailed(FeatureStatus::Broken, + "Direct3D11 device was determined to be broken", + "FEATURE_FAILURE_D3D11_BROKEN"_ns); + } + return false; + } + + aOutDevice = device; + return true; +} + +// Note that it's enough for us to just use a counter for a unique ID, +// even though the counter isn't synchronized between processes. If we +// start in the GPU process and wind up in the parent process, the +// whole graphics stack is blown away anyway. But just in case, we +// make gpu process IDs negative and parent process IDs positive. +static inline int32_t GetNextDeviceCounter() { + static int32_t sDeviceCounter = 0; + return XRE_IsGPUProcess() ? --sDeviceCounter : ++sDeviceCounter; +} + +void DeviceManagerDx::CreateCompositorDevice(FeatureState& d3d11) { + if (StaticPrefs::layers_d3d11_force_warp_AtStartup()) { + CreateWARPCompositorDevice(); + return; + } + + RefPtr<IDXGIAdapter1> adapter = GetDXGIAdapter(); + if (!adapter) { + d3d11.SetFailed(FeatureStatus::Unavailable, + "Failed to acquire a DXGI adapter", + "FEATURE_FAILURE_D3D11_DXGI"_ns); + return; + } + + if (XRE_IsGPUProcess() && !D3D11Checks::DoesRemotePresentWork(adapter)) { + d3d11.SetFailed(FeatureStatus::Unavailable, + "DXGI does not support out-of-process presentation", + "FEATURE_FAILURE_D3D11_REMOTE_PRESENT"_ns); + return; + } + + RefPtr<ID3D11Device> device; + if (!CreateCompositorDeviceHelper(d3d11, adapter, true, device)) { + // Try again without video support and record that it failed. + mCompositorDeviceSupportsVideo = false; + if (!CreateCompositorDeviceHelper(d3d11, adapter, false, device)) { + return; + } + } else { + mCompositorDeviceSupportsVideo = true; + } + + // Only test this when not using WARP since it can fail and cause + // GetDeviceRemovedReason to return weird values. + bool textureSharingWorks = D3D11Checks::DoesTextureSharingWork(device); + + DXGI_ADAPTER_DESC desc; + PodZero(&desc); + adapter->GetDesc(&desc); + + if (!textureSharingWorks) { + gfxConfig::SetFailed(Feature::D3D11_HW_ANGLE, FeatureStatus::Broken, + "Texture sharing doesn't work", + "FEATURE_FAILURE_HW_ANGLE_NEEDS_TEXTURE_SHARING"_ns); + } + if (D3D11Checks::DoesRenderTargetViewNeedRecreating(device)) { + gfxConfig::SetFailed(Feature::D3D11_HW_ANGLE, FeatureStatus::Broken, + "RenderTargetViews need recreating", + "FEATURE_FAILURE_HW_ANGLE_NEEDS_RTV_RECREATION"_ns); + } + if (XRE_IsParentProcess()) { + // It seems like this may only happen when we're using the NVIDIA gpu + D3D11Checks::WarnOnAdapterMismatch(device); + } + + uint32_t featureLevel = device->GetFeatureLevel(); + auto formatOptions = D3D11Checks::FormatOptions(device); + { + MutexAutoLock lock(mDeviceLock); + mCompositorDevice = device; + + int32_t sequenceNumber = GetNextDeviceCounter(); + mDeviceStatus = Some(D3D11DeviceStatus( + false, textureSharingWorks, featureLevel, DxgiAdapterDesc::From(desc), + sequenceNumber, formatOptions)); + } + mCompositorDevice->SetExceptionMode(0); +} + +bool DeviceManagerDx::CreateDevice(IDXGIAdapter* aAdapter, + D3D_DRIVER_TYPE aDriverType, UINT aFlags, + HRESULT& aResOut, + RefPtr<ID3D11Device>& aOutDevice) { + if (StaticPrefs::gfx_direct3d11_enable_debug_layer_AtStartup() || + StaticPrefs::gfx_direct3d11_break_on_error_AtStartup()) { + aFlags |= D3D11_CREATE_DEVICE_DEBUG; + } + + MOZ_SEH_TRY { + aResOut = sD3D11CreateDeviceFn( + aAdapter, aDriverType, nullptr, aFlags, mFeatureLevels.Elements(), + mFeatureLevels.Length(), D3D11_SDK_VERSION, getter_AddRefs(aOutDevice), + nullptr, nullptr); + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { return false; } + + if (StaticPrefs::gfx_direct3d11_break_on_error_AtStartup()) { + do { + if (!aOutDevice) break; + + RefPtr<ID3D11Debug> debug; + if (!SUCCEEDED(aOutDevice->QueryInterface(__uuidof(ID3D11Debug), + getter_AddRefs(debug)))) + break; + + RefPtr<ID3D11InfoQueue> infoQueue; + if (!SUCCEEDED(debug->QueryInterface(__uuidof(ID3D11InfoQueue), + getter_AddRefs(infoQueue)))) + break; + + D3D11_INFO_QUEUE_FILTER filter; + PodZero(&filter); + + // Disable warnings caused by Advanced Layers that are known and not + // problematic. + D3D11_MESSAGE_ID blockIDs[] = { + D3D11_MESSAGE_ID_DEVICE_DRAW_CONSTANT_BUFFER_TOO_SMALL}; + filter.DenyList.NumIDs = MOZ_ARRAY_LENGTH(blockIDs); + filter.DenyList.pIDList = blockIDs; + infoQueue->PushStorageFilter(&filter); + + infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_CORRUPTION, true); + infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR, true); + infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_WARNING, true); + } while (false); + } + + return true; +} + +void DeviceManagerDx::CreateWARPCompositorDevice() { + ScopedGfxFeatureReporter reporterWARP( + "D3D11-WARP", StaticPrefs::layers_d3d11_force_warp_AtStartup()); + FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING); + + HRESULT hr; + RefPtr<ID3D11Device> device; + + // Use D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS + // to prevent bug 1092260. IE 11 also uses this flag. + UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + if (!CreateDevice(nullptr, D3D_DRIVER_TYPE_WARP, flags, hr, device)) { + gfxCriticalError() << "Exception occurred initializing WARP D3D11 device!"; + d3d11.SetFailed(FeatureStatus::CrashedInHandler, + "Crashed creating a D3D11 WARP device", + "FEATURE_FAILURE_D3D11_WARP_DEVICE"_ns); + } + + if (FAILED(hr) || !device) { + // This should always succeed... in theory. + gfxCriticalError() << "Failed to initialize WARP D3D11 device! " + << hexa(hr); + d3d11.SetFailed(FeatureStatus::Failed, + "Failed to create a D3D11 WARP device", + "FEATURE_FAILURE_D3D11_WARP_DEVICE2"_ns); + return; + } + + // Only test for texture sharing on Windows 8 since it puts the device into + // an unusable state if used on Windows 7 + bool textureSharingWorks = false; + if (IsWin8OrLater()) { + textureSharingWorks = D3D11Checks::DoesTextureSharingWork(device); + } + + DXGI_ADAPTER_DESC desc; + D3D11Checks::GetDxgiDesc(device, &desc); + + int featureLevel = device->GetFeatureLevel(); + + auto formatOptions = D3D11Checks::FormatOptions(device); + { + MutexAutoLock lock(mDeviceLock); + mCompositorDevice = device; + + int32_t sequenceNumber = GetNextDeviceCounter(); + mDeviceStatus = Some(D3D11DeviceStatus( + true, textureSharingWorks, featureLevel, DxgiAdapterDesc::From(desc), + sequenceNumber, formatOptions)); + } + mCompositorDevice->SetExceptionMode(0); + + reporterWARP.SetSuccessful(); +} + +FeatureStatus DeviceManagerDx::CreateContentDevice() { + RefPtr<IDXGIAdapter1> adapter; + if (!mDeviceStatus->isWARP()) { + adapter = GetDXGIAdapter(); + if (!adapter) { + gfxCriticalNote << "Could not get a DXGI adapter"; + return FeatureStatus::Unavailable; + } + } + + HRESULT hr; + RefPtr<ID3D11Device> device; + + UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + D3D_DRIVER_TYPE type = + mDeviceStatus->isWARP() ? D3D_DRIVER_TYPE_WARP : D3D_DRIVER_TYPE_UNKNOWN; + if (!CreateDevice(adapter, type, flags, hr, device)) { + gfxCriticalNote + << "Recovered from crash while creating a D3D11 content device"; + gfxWindowsPlatform::RecordContentDeviceFailure( + TelemetryDeviceCode::Content); + return FeatureStatus::CrashedInHandler; + } + + if (FAILED(hr) || !device) { + gfxCriticalNote << "Failed to create a D3D11 content device: " << hexa(hr); + gfxWindowsPlatform::RecordContentDeviceFailure( + TelemetryDeviceCode::Content); + return FeatureStatus::Failed; + } + + // InitializeD2D() will abort early if the compositor device did not support + // texture sharing. If we're in the content process, we can't rely on the + // parent device alone: some systems have dual GPUs that are capable of + // binding the parent and child processes to different GPUs. As a safety net, + // we re-check texture sharing against the newly created D3D11 content device. + // If it fails, we won't use Direct2D. + if (XRE_IsContentProcess()) { + if (!D3D11Checks::DoesTextureSharingWork(device)) { + return FeatureStatus::Failed; + } + + DebugOnly<bool> ok = ContentAdapterIsParentAdapter(device); + MOZ_ASSERT(ok); + } + + { + MutexAutoLock lock(mDeviceLock); + mContentDevice = device; + } + mContentDevice->SetExceptionMode(0); + + RefPtr<ID3D10Multithread> multi; + hr = mContentDevice->QueryInterface(__uuidof(ID3D10Multithread), + getter_AddRefs(multi)); + if (SUCCEEDED(hr) && multi) { + multi->SetMultithreadProtected(TRUE); + } + return FeatureStatus::Available; +} + +RefPtr<ID3D11Device> DeviceManagerDx::CreateDecoderDevice() { + bool isAMD = false; + { + MutexAutoLock lock(mDeviceLock); + if (!mDeviceStatus) { + return nullptr; + } + isAMD = mDeviceStatus->adapter().VendorId == 0x1002; + } + + bool reuseDevice = false; + if (StaticPrefs::gfx_direct3d11_reuse_decoder_device() < 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 (StaticPrefs::gfx_direct3d11_reuse_decoder_device() > 0) { + reuseDevice = true; + } + + if (reuseDevice) { + if (mCompositorDevice && mCompositorDeviceSupportsVideo && + !mDecoderDevice) { + mDecoderDevice = mCompositorDevice; + + RefPtr<ID3D10Multithread> multi; + mDecoderDevice->QueryInterface(__uuidof(ID3D10Multithread), + getter_AddRefs(multi)); + if (multi) { + multi->SetMultithreadProtected(TRUE); + } + } + + if (mDecoderDevice) { + RefPtr<ID3D11Device> dev = mDecoderDevice; + return dev.forget(); + } + } + + if (!sD3D11CreateDeviceFn) { + // We should just be on Windows Vista or XP in this case. + return nullptr; + } + + RefPtr<IDXGIAdapter1> adapter = GetDXGIAdapter(); + if (!adapter) { + return nullptr; + } + + HRESULT hr; + RefPtr<ID3D11Device> device; + + UINT flags = D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS | + D3D11_CREATE_DEVICE_VIDEO_SUPPORT; + if (!CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, flags, hr, device)) { + return nullptr; + } + if (FAILED(hr) || !device || !D3D11Checks::DoesDeviceWork()) { + return nullptr; + } + + RefPtr<ID3D10Multithread> multi; + device->QueryInterface(__uuidof(ID3D10Multithread), getter_AddRefs(multi)); + if (multi) { + multi->SetMultithreadProtected(TRUE); + } + if (reuseDevice) { + mDecoderDevice = device; + } + return device; +} + +RefPtr<MLGDevice> DeviceManagerDx::GetMLGDevice() { + MutexAutoLock lock(mDeviceLock); + if (!mMLGDevice) { + MutexAutoUnlock unlock(mDeviceLock); + CreateMLGDevice(); + } + return mMLGDevice; +} + +static void DisableAdvancedLayers(FeatureStatus aStatus, + const nsCString aMessage, + const nsCString& aFailureId) { + if (!NS_IsMainThread()) { + NS_DispatchToMainThread(NS_NewRunnableFunction( + "DisableAdvancedLayers", [aStatus, aMessage, aFailureId]() -> void { + DisableAdvancedLayers(aStatus, aMessage, aFailureId); + })); + return; + } + + MOZ_ASSERT(NS_IsMainThread()); + + FeatureState& al = gfxConfig::GetFeature(Feature::ADVANCED_LAYERS); + if (!al.IsEnabled()) { + return; + } + + al.SetFailed(aStatus, aMessage.get(), aFailureId); + + FeatureFailure info(aStatus, aMessage, aFailureId); + if (GPUParent* gpu = GPUParent::GetSingleton()) { + Unused << gpu->SendUpdateFeature(Feature::ADVANCED_LAYERS, info); + } + + if (aFailureId.Length()) { + nsString failureId = NS_ConvertUTF8toUTF16(aFailureId.get()); + Telemetry::ScalarAdd(Telemetry::ScalarID::GFX_ADVANCED_LAYERS_FAILURE_ID, + failureId, 1); + } + + // Notify TelemetryEnvironment.jsm. + if (RefPtr<nsIObserverService> obs = + mozilla::services::GetObserverService()) { + obs->NotifyObservers(nullptr, "gfx-features-ready", nullptr); + } +} + +void DeviceManagerDx::CreateMLGDevice() { + MOZ_ASSERT(layers::CompositorThreadHolder::IsInCompositorThread()); + + RefPtr<ID3D11Device> d3d11Device = GetCompositorDevice(); + if (!d3d11Device) { + DisableAdvancedLayers(FeatureStatus::Unavailable, + "Advanced-layers requires a D3D11 device"_ns, + "FEATURE_FAILURE_NEED_D3D11_DEVICE"_ns); + return; + } + + RefPtr<MLGDeviceD3D11> device = new MLGDeviceD3D11(d3d11Device); + if (!device->Initialize()) { + DisableAdvancedLayers(FeatureStatus::Failed, device->GetFailureMessage(), + device->GetFailureId()); + return; + } + + // While the lock was unheld, we should not have created an MLGDevice, since + // this should only be called on the compositor thread. + MutexAutoLock lock(mDeviceLock); + MOZ_ASSERT(!mMLGDevice); + + // Only set the MLGDevice if the compositor device is still the same. + // Otherwise we could possibly have a bad MLGDevice if a device reset + // just occurred. + if (mCompositorDevice == d3d11Device) { + mMLGDevice = device; + } +} + +void DeviceManagerDx::ResetDevices() { + // Flush the paint thread before revoking all these singletons. This + // should ensure that the paint thread doesn't start mixing and matching + // old and new objects together. + if (PaintThread::Get()) { + CompositorBridgeChild* cbc = CompositorBridgeChild::Get(); + if (cbc) { + cbc->FlushAsyncPaints(); + } + } + + MutexAutoLock lock(mDeviceLock); + + mAdapter = nullptr; + mCompositorAttachments = nullptr; + mMLGDevice = nullptr; + mCompositorDevice = nullptr; + mContentDevice = nullptr; + mCanvasDevice = nullptr; + mImageDevice = nullptr; + mDirectCompositionDevice = nullptr; + mDeviceStatus = Nothing(); + mDeviceResetReason = Nothing(); + Factory::SetDirect3D11Device(nullptr); +} + +bool DeviceManagerDx::MaybeResetAndReacquireDevices() { + DeviceResetReason resetReason; + if (!HasDeviceReset(&resetReason)) { + return false; + } + + GPUProcessManager::RecordDeviceReset(resetReason); + + bool createCompositorDevice = !!mCompositorDevice; + bool createContentDevice = !!mContentDevice; + bool createCanvasDevice = !!mCanvasDevice; + bool createDirectCompositionDevice = !!mDirectCompositionDevice; + + ResetDevices(); + + if (createCompositorDevice && !CreateCompositorDevices()) { + // Just stop, don't try anything more + return true; + } + if (createContentDevice) { + CreateContentDevices(); + } + if (createCanvasDevice) { + CreateCanvasDevice(); + } + if (createDirectCompositionDevice) { + CreateDirectCompositionDevice(); + } + + return true; +} + +bool DeviceManagerDx::ContentAdapterIsParentAdapter(ID3D11Device* device) { + DXGI_ADAPTER_DESC desc; + if (!D3D11Checks::GetDxgiDesc(device, &desc)) { + gfxCriticalNote << "Could not query device DXGI adapter info"; + return false; + } + + const DxgiAdapterDesc& preferred = mDeviceStatus->adapter(); + + if (desc.VendorId != preferred.VendorId || + desc.DeviceId != preferred.DeviceId || + desc.SubSysId != preferred.SubSysId || + desc.AdapterLuid.HighPart != preferred.AdapterLuid.HighPart || + desc.AdapterLuid.LowPart != preferred.AdapterLuid.LowPart) { + gfxCriticalNote << "VendorIDMismatch P " << hexa(preferred.VendorId) << " " + << hexa(desc.VendorId); + return false; + } + + return true; +} + +static DeviceResetReason HResultToResetReason(HRESULT hr) { + switch (hr) { + case DXGI_ERROR_DEVICE_HUNG: + return DeviceResetReason::HUNG; + case DXGI_ERROR_DEVICE_REMOVED: + return DeviceResetReason::REMOVED; + case DXGI_ERROR_DEVICE_RESET: + return DeviceResetReason::RESET; + case DXGI_ERROR_DRIVER_INTERNAL_ERROR: + return DeviceResetReason::DRIVER_ERROR; + case DXGI_ERROR_INVALID_CALL: + return DeviceResetReason::INVALID_CALL; + case E_OUTOFMEMORY: + return DeviceResetReason::OUT_OF_MEMORY; + default: + MOZ_ASSERT(false); + } + return DeviceResetReason::OTHER; +} + +bool DeviceManagerDx::HasDeviceReset(DeviceResetReason* aOutReason) { + MutexAutoLock lock(mDeviceLock); + + if (mDeviceResetReason) { + if (aOutReason) { + *aOutReason = mDeviceResetReason.value(); + } + return true; + } + + DeviceResetReason reason; + if (GetAnyDeviceRemovedReason(&reason)) { + mDeviceResetReason = Some(reason); + if (aOutReason) { + *aOutReason = reason; + } + return true; + } + + return false; +} + +static inline bool DidDeviceReset(const RefPtr<ID3D11Device>& aDevice, + DeviceResetReason* aOutReason) { + if (!aDevice) { + return false; + } + HRESULT hr = aDevice->GetDeviceRemovedReason(); + if (hr == S_OK) { + return false; + } + + *aOutReason = HResultToResetReason(hr); + return true; +} + +bool DeviceManagerDx::GetAnyDeviceRemovedReason(DeviceResetReason* aOutReason) { + // Caller must own the lock, since we access devices directly, and can be + // called from any thread. + mDeviceLock.AssertCurrentThreadOwns(); + + if (DidDeviceReset(mCompositorDevice, aOutReason) || + DidDeviceReset(mContentDevice, aOutReason) || + DidDeviceReset(mCanvasDevice, aOutReason)) { + return true; + } + + if (XRE_IsParentProcess() && NS_IsMainThread() && + StaticPrefs::gfx_testing_device_reset()) { + Preferences::SetInt("gfx.testing.device-reset", 0); + *aOutReason = DeviceResetReason::FORCED_RESET; + return true; + } + + return false; +} + +void DeviceManagerDx::ForceDeviceReset(ForcedDeviceResetReason aReason) { + Telemetry::Accumulate(Telemetry::FORCED_DEVICE_RESET_REASON, + uint32_t(aReason)); + { + MutexAutoLock lock(mDeviceLock); + if (!mDeviceResetReason) { + mDeviceResetReason = Some(DeviceResetReason::FORCED_RESET); + } + } +} + +void DeviceManagerDx::DisableD3D11AfterCrash() { + gfxConfig::Disable(Feature::D3D11_COMPOSITING, + FeatureStatus::CrashedInHandler, + "Crashed while acquiring a Direct3D11 device", + "FEATURE_FAILURE_D3D11_CRASH"_ns); + ResetDevices(); +} + +RefPtr<ID3D11Device> DeviceManagerDx::GetCompositorDevice() { + MutexAutoLock lock(mDeviceLock); + return mCompositorDevice; +} + +RefPtr<ID3D11Device> DeviceManagerDx::GetContentDevice() { + MOZ_ASSERT(XRE_IsGPUProcess() || + gfxPlatform::GetPlatform()->DevicesInitialized()); + + MutexAutoLock lock(mDeviceLock); + return mContentDevice; +} + +RefPtr<ID3D11Device> DeviceManagerDx::GetImageDevice() { + MutexAutoLock lock(mDeviceLock); + if (mImageDevice) { + return mImageDevice; + } + + RefPtr<ID3D11Device> device = mContentDevice; + if (!device) { + device = mCompositorDevice; + } + + if (!device) { + return nullptr; + } + + RefPtr<ID3D10Multithread> multi; + HRESULT hr = + device->QueryInterface((ID3D10Multithread**)getter_AddRefs(multi)); + if (FAILED(hr) || !multi) { + gfxWarning() << "Multithread safety interface not supported. " << hr; + return nullptr; + } + multi->SetMultithreadProtected(TRUE); + + mImageDevice = device; + + return mImageDevice; +} + +RefPtr<ID3D11Device> DeviceManagerDx::GetVRDevice() { + MutexAutoLock lock(mDeviceLock); + if (!mVRDevice) { + CreateVRDevice(); + } + return mVRDevice; +} + +RefPtr<ID3D11Device> DeviceManagerDx::GetCanvasDevice() { + MutexAutoLock lock(mDeviceLock); + return mCanvasDevice; +} + +RefPtr<IDCompositionDevice2> DeviceManagerDx::GetDirectCompositionDevice() { + MutexAutoLock lock(mDeviceLock); + return mDirectCompositionDevice; +} + +unsigned DeviceManagerDx::GetCompositorFeatureLevel() const { + if (!mDeviceStatus) { + return 0; + } + return mDeviceStatus->featureLevel(); +} + +bool DeviceManagerDx::TextureSharingWorks() { + MutexAutoLock lock(mDeviceLock); + if (!mDeviceStatus) { + return false; + } + return mDeviceStatus->textureSharingWorks(); +} + +bool DeviceManagerDx::CanInitializeKeyedMutexTextures() { + MutexAutoLock lock(mDeviceLock); + return mDeviceStatus && StaticPrefs::gfx_direct3d11_allow_keyed_mutex() && + gfxVars::AllowD3D11KeyedMutex(); +} + +bool DeviceManagerDx::HasCrashyInitData() { + MutexAutoLock lock(mDeviceLock); + if (!mDeviceStatus) { + return false; + } + + return (mDeviceStatus->adapter().VendorId == 0x8086 && !IsWin10OrLater()); +} + +bool DeviceManagerDx::CheckRemotePresentSupport() { + MOZ_ASSERT(XRE_IsParentProcess()); + + RefPtr<IDXGIAdapter1> adapter = GetDXGIAdapter(); + if (!adapter) { + return false; + } + if (!D3D11Checks::DoesRemotePresentWork(adapter)) { + return false; + } + return true; +} + +bool DeviceManagerDx::IsWARP() { + MutexAutoLock lock(mDeviceLock); + if (!mDeviceStatus) { + return false; + } + return mDeviceStatus->isWARP(); +} + +bool DeviceManagerDx::CanUseNV12() { + MutexAutoLock lock(mDeviceLock); + if (!mDeviceStatus) { + return false; + } + return mDeviceStatus->formatOptions().contains( + D3D11Checks::VideoFormatOption::NV12); +} + +bool DeviceManagerDx::CanUseP010() { + MutexAutoLock lock(mDeviceLock); + if (!mDeviceStatus) { + return false; + } + return mDeviceStatus->formatOptions().contains( + D3D11Checks::VideoFormatOption::P010); +} + +bool DeviceManagerDx::CanUseP016() { + MutexAutoLock lock(mDeviceLock); + if (!mDeviceStatus) { + return false; + } + return mDeviceStatus->formatOptions().contains( + D3D11Checks::VideoFormatOption::P016); +} + +bool DeviceManagerDx::CanUseDComp() { + MutexAutoLock lock(mDeviceLock); + return !!mDirectCompositionDevice; +} + +void DeviceManagerDx::InitializeDirectDraw() { + MOZ_ASSERT(layers::CompositorThreadHolder::IsInCompositorThread()); + + if (mDirectDraw) { + // Already initialized. + return; + } + + FeatureState& ddraw = gfxConfig::GetFeature(Feature::DIRECT_DRAW); + if (!ddraw.IsEnabled()) { + return; + } + + // Check if DirectDraw is available on this system. + mDirectDrawDLL.own(LoadLibrarySystem32(L"ddraw.dll")); + if (!mDirectDrawDLL) { + ddraw.SetFailed(FeatureStatus::Unavailable, + "DirectDraw not available on this computer", + "FEATURE_FAILURE_DDRAW_LIB"_ns); + return; + } + + sDirectDrawCreateExFn = (decltype(DirectDrawCreateEx)*)GetProcAddress( + mDirectDrawDLL, "DirectDrawCreateEx"); + if (!sDirectDrawCreateExFn) { + ddraw.SetFailed(FeatureStatus::Unavailable, + "DirectDraw not available on this computer", + "FEATURE_FAILURE_DDRAW_LIB"_ns); + return; + } + + HRESULT hr; + MOZ_SEH_TRY { + hr = sDirectDrawCreateExFn(nullptr, getter_AddRefs(mDirectDraw), + IID_IDirectDraw7, nullptr); + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + ddraw.SetFailed(FeatureStatus::Failed, "Failed to create DirectDraw", + "FEATURE_FAILURE_DDRAW_LIB"_ns); + gfxCriticalNote << "DoesCreatingDirectDrawFailed"; + return; + } + if (FAILED(hr)) { + ddraw.SetFailed(FeatureStatus::Failed, "Failed to create DirectDraw", + "FEATURE_FAILURE_DDRAW_LIB"_ns); + gfxCriticalNote << "DoesCreatingDirectDrawFailed " << hexa(hr); + return; + } +} + +IDirectDraw7* DeviceManagerDx::GetDirectDraw() { return mDirectDraw; } + +void DeviceManagerDx::GetCompositorDevices( + RefPtr<ID3D11Device>* aOutDevice, + RefPtr<layers::DeviceAttachmentsD3D11>* aOutAttachments) { + RefPtr<ID3D11Device> device; + { + MutexAutoLock lock(mDeviceLock); + if (!mCompositorDevice) { + return; + } + if (mCompositorAttachments) { + *aOutDevice = mCompositorDevice; + *aOutAttachments = mCompositorAttachments; + return; + } + + // Otherwise, we'll try to create attachments outside the lock. + device = mCompositorDevice; + } + + // We save the attachments object even if it fails to initialize, so the + // compositor can grab the failure ID. + RefPtr<layers::DeviceAttachmentsD3D11> attachments = + layers::DeviceAttachmentsD3D11::Create(device); + { + MutexAutoLock lock(mDeviceLock); + if (device != mCompositorDevice) { + return; + } + mCompositorAttachments = attachments; + } + + *aOutDevice = device; + *aOutAttachments = attachments; +} + +/* static */ +void DeviceManagerDx::PreloadAttachmentsOnCompositorThread() { + if (!CompositorThread()) { + return; + } + + bool enableAL = gfxConfig::IsEnabled(Feature::ADVANCED_LAYERS) && + !gfx::gfxVars::UseSoftwareWebRender(); + + RefPtr<Runnable> task = NS_NewRunnableFunction( + "DeviceManagerDx::PreloadAttachmentsOnCompositorThread", + [enableAL]() -> void { + if (DeviceManagerDx* dm = DeviceManagerDx::Get()) { + if (enableAL) { + dm->GetMLGDevice(); + } else { + RefPtr<ID3D11Device> device; + RefPtr<layers::DeviceAttachmentsD3D11> attachments; + dm->GetCompositorDevices(&device, &attachments); + } + } + }); + CompositorThread()->Dispatch(task.forget()); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/thebes/DeviceManagerDx.h b/gfx/thebes/DeviceManagerDx.h new file mode 100644 index 0000000000..fe6d80a534 --- /dev/null +++ b/gfx/thebes/DeviceManagerDx.h @@ -0,0 +1,201 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_thebes_DeviceManagerDx_h +#define mozilla_gfx_thebes_DeviceManagerDx_h + +#include "gfxPlatform.h" +#include "gfxTelemetry.h" +#include "gfxTypes.h" +#include "mozilla/Maybe.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/gfx/GraphicsMessages.h" +#include "nsTArray.h" +#include "nsWindowsHelpers.h" + +#include <windows.h> +#include <objbase.h> + +#include <d3d11.h> +#include <dxgi.h> +#include <dxgi1_6.h> + +// This header is available in the June 2010 SDK and in the Win8 SDK +#include <d3dcommon.h> +// Win 8.0 SDK types we'll need when building using older sdks. +#if !defined(D3D_FEATURE_LEVEL_11_1) // defined in the 8.0 SDK only +# define D3D_FEATURE_LEVEL_11_1 static_cast<D3D_FEATURE_LEVEL>(0xb100) +# define D3D_FL9_1_REQ_TEXTURE2D_U_OR_V_DIMENSION 2048 +# define D3D_FL9_3_REQ_TEXTURE2D_U_OR_V_DIMENSION 4096 +#endif + +struct ID3D11Device; +struct IDCompositionDevice2; +struct IDirectDraw7; + +namespace mozilla { +class ScopedGfxFeatureReporter; +namespace layers { +class DeviceAttachmentsD3D11; +class MLGDevice; +} // namespace layers + +namespace gfx { +class FeatureState; + +class DeviceManagerDx final { + public: + static void Init(); + static void Shutdown(); + + DeviceManagerDx(); + + static DeviceManagerDx* Get() { return sInstance; } + + RefPtr<ID3D11Device> GetCompositorDevice(); + RefPtr<ID3D11Device> GetContentDevice(); + RefPtr<ID3D11Device> GetCanvasDevice(); + RefPtr<ID3D11Device> GetImageDevice(); + RefPtr<IDCompositionDevice2> GetDirectCompositionDevice(); + RefPtr<ID3D11Device> GetVRDevice(); + RefPtr<ID3D11Device> CreateDecoderDevice(); + RefPtr<layers::MLGDevice> GetMLGDevice(); + IDirectDraw7* GetDirectDraw(); + + unsigned GetCompositorFeatureLevel() const; + bool TextureSharingWorks(); + bool IsWARP(); + bool CanUseNV12(); + bool CanUseP010(); + bool CanUseP016(); + bool CanUseDComp(); + + // Returns true if we can create a texture with + // D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX and also + // upload texture data during the CreateTexture2D + // call. This crashes on some devices, so we might + // need to avoid it. + bool CanInitializeKeyedMutexTextures(); + + // Intel devices on older windows versions seem to occasionally have + // stability issues when supplying InitData to CreateTexture2D. + bool HasCrashyInitData(); + + // Enumerate and return all outputs on the current adapter. + nsTArray<DXGI_OUTPUT_DESC1> EnumerateOutputs(); + + // find the IDXGIOutput with a description.Monitor matching + // 'monitor'; returns false if not found or some error occurred. + bool GetOutputFromMonitor(HMONITOR monitor, RefPtr<IDXGIOutput>* aOutOutput); + + // Check if the current adapter supports hardware stretching + void CheckHardwareStretchingSupport(HwStretchingSupport& aRv); + + bool CreateCompositorDevices(); + void CreateContentDevices(); + void CreateDirectCompositionDevice(); + bool CreateCanvasDevice(); + + static HANDLE CreateDCompSurfaceHandle(); + + void GetCompositorDevices( + RefPtr<ID3D11Device>* aOutDevice, + RefPtr<layers::DeviceAttachmentsD3D11>* aOutAttachments); + + void ImportDeviceInfo(const D3D11DeviceStatus& aDeviceStatus); + 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); + + private: + // Pre-load any compositor resources that are expensive, and are needed when + // we attempt to create a compositor. + static void PreloadAttachmentsOnCompositorThread(); + + IDXGIAdapter1* GetDXGIAdapter(); + + void DisableD3D11AfterCrash(); + + void CreateCompositorDevice(mozilla::gfx::FeatureState& d3d11); + bool CreateCompositorDeviceHelper(mozilla::gfx::FeatureState& aD3d11, + IDXGIAdapter1* aAdapter, + bool aAttemptVideoSupport, + RefPtr<ID3D11Device>& aOutDevice); + + void CreateWARPCompositorDevice(); + void CreateMLGDevice(); + bool CreateVRDevice(); + + mozilla::gfx::FeatureStatus CreateContentDevice(); + + bool CreateDevice(IDXGIAdapter* aAdapter, D3D_DRIVER_TYPE aDriverType, + UINT aFlags, HRESULT& aResOut, + RefPtr<ID3D11Device>& aOutDevice); + + bool ContentAdapterIsParentAdapter(ID3D11Device* device); + + bool LoadD3D11(); + bool LoadDcomp(); + void ReleaseD3D11(); + + // Call GetDeviceRemovedReason on each device until one returns + // a failure. + bool GetAnyDeviceRemovedReason(DeviceResetReason* aOutReason); + + private: + static StaticAutoPtr<DeviceManagerDx> sInstance; + + // This is assigned during device creation. Afterwards, it is released if + // devices failed, and "forgotten" if devices succeeded (meaning, we leak + // the ref and unassign the module). + nsModuleHandle mD3D11Module; + + nsModuleHandle mDcompModule; + + mozilla::Mutex mDeviceLock; + nsTArray<D3D_FEATURE_LEVEL> mFeatureLevels; + RefPtr<IDXGIAdapter1> mAdapter; + RefPtr<ID3D11Device> mCompositorDevice; + RefPtr<ID3D11Device> mContentDevice; + RefPtr<ID3D11Device> mCanvasDevice; + RefPtr<ID3D11Device> mImageDevice; + RefPtr<ID3D11Device> mVRDevice; + RefPtr<ID3D11Device> mDecoderDevice; + RefPtr<IDCompositionDevice2> mDirectCompositionDevice; + RefPtr<layers::DeviceAttachmentsD3D11> mCompositorAttachments; + RefPtr<layers::MLGDevice> mMLGDevice; + bool mCompositorDeviceSupportsVideo; + + Maybe<D3D11DeviceStatus> mDeviceStatus; + + nsModuleHandle mDirectDrawDLL; + RefPtr<IDirectDraw7> mDirectDraw; + + Maybe<DeviceResetReason> mDeviceResetReason; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // mozilla_gfx_thebes_DeviceManagerDx_h diff --git a/gfx/thebes/DisplayConfigWindows.cpp b/gfx/thebes/DisplayConfigWindows.cpp new file mode 100644 index 0000000000..49fc13c580 --- /dev/null +++ b/gfx/thebes/DisplayConfigWindows.cpp @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <iostream> +#include <windows.h> +#include <wingdi.h> +#include <optional> +#include <vector> + +#include "DisplayConfigWindows.h" + +namespace mozilla { +namespace gfx { + +using namespace std; + +struct DisplayConfig { + vector<DISPLAYCONFIG_PATH_INFO> mPaths; + vector<DISPLAYCONFIG_MODE_INFO> mModes; +}; + +optional<DisplayConfig> GetDisplayConfig() { + LONG result; + + UINT32 numPaths; + UINT32 numModes; + vector<DISPLAYCONFIG_PATH_INFO> paths; + vector<DISPLAYCONFIG_MODE_INFO> modes; + do { + result = GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &numPaths, + &numModes); + if (result != ERROR_SUCCESS) { + return {}; + } + // allocate the recommended amount of space + paths.resize(numPaths); + modes.resize(numModes); + + result = QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &numPaths, paths.data(), + &numModes, modes.data(), NULL); + // try again if there wasn't enough space + } while (result == ERROR_INSUFFICIENT_BUFFER); + + if (result != ERROR_SUCCESS) return {}; + + // shrink to fit the actual number of modes and paths returned + modes.resize(numModes); + paths.resize(numPaths); + + return DisplayConfig{paths, modes}; +} + +bool HasScaledResolution() { + auto config = GetDisplayConfig(); + if (config) { + for (auto& path : config->mPaths) { + auto& modes = config->mModes; + int targetModeIndex = path.targetInfo.modeInfoIdx; + int sourceModeIndex = path.sourceInfo.modeInfoIdx; + + // Check if the source and target resolutions are different + if ((modes[targetModeIndex] + .targetMode.targetVideoSignalInfo.activeSize.cx != + modes[sourceModeIndex].sourceMode.width) || + (modes[targetModeIndex] + .targetMode.targetVideoSignalInfo.activeSize.cy != + modes[sourceModeIndex].sourceMode.height)) { + return true; + } + } + } + return false; +} + +void GetScaledResolutions(ScaledResolutionSet& aRv) { + auto config = GetDisplayConfig(); + if (config) { + for (auto& path : config->mPaths) { + auto& modes = config->mModes; + int targetModeIndex = path.targetInfo.modeInfoIdx; + int sourceModeIndex = path.sourceInfo.modeInfoIdx; + + // Check if the source and target resolutions are different + IntSize src(modes[sourceModeIndex].sourceMode.width, + modes[sourceModeIndex].sourceMode.height); + IntSize dst( + modes[targetModeIndex].targetMode.targetVideoSignalInfo.activeSize.cx, + modes[targetModeIndex] + .targetMode.targetVideoSignalInfo.activeSize.cy); + if (src != dst) { + aRv.AppendElement(std::pair<IntSize, IntSize>{src, dst}); + } + } + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/thebes/DisplayConfigWindows.h b/gfx/thebes/DisplayConfigWindows.h new file mode 100644 index 0000000000..fa5fae8b22 --- /dev/null +++ b/gfx/thebes/DisplayConfigWindows.h @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_thebes_DisplayConfigWindows_h +#define mozilla_gfx_thebes_DisplayConfigWindows_h + +#include <utility> // for std::pair +#include "mozilla/gfx/Point.h" // for IntSize +#include "nsTArray.h" + +namespace mozilla { +namespace gfx { + +extern bool HasScaledResolution(); + +typedef nsTArray<std::pair<IntSize, IntSize>> ScaledResolutionSet; +void GetScaledResolutions(ScaledResolutionSet& aRv); + +} // namespace gfx +} // namespace mozilla + +#endif // mozilla_gfx_thebes_DisplayConfigWindows_h diff --git a/gfx/thebes/DrawMode.h b/gfx/thebes/DrawMode.h new file mode 100644 index 0000000000..f9c2ac2b64 --- /dev/null +++ b/gfx/thebes/DrawMode.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DrawMode_h +#define DrawMode_h + +#include "mozilla/TypedEnumBits.h" + +// Options for how the text should be drawn +enum class DrawMode : int { + // GLYPH_FILL and GLYPH_STROKE draw into the current context + // and may be used together with bitwise OR. + GLYPH_FILL = 1 << 0, + // Note: using GLYPH_STROKE will destroy the current path. + GLYPH_STROKE = 1 << 1, + // Appends glyphs to the current path. Can NOT be used with + // GLYPH_FILL or GLYPH_STROKE. + GLYPH_PATH = 1 << 2, + // When GLYPH_FILL and GLYPH_STROKE are both set, draws the + // stroke underneath the fill. + GLYPH_STROKE_UNDERNEATH = 1 << 3 +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(DrawMode) +#endif diff --git a/gfx/thebes/PrintTarget.cpp b/gfx/thebes/PrintTarget.cpp new file mode 100644 index 0000000000..a9539404ac --- /dev/null +++ b/gfx/thebes/PrintTarget.cpp @@ -0,0 +1,205 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PrintTarget.h" + +#include "cairo.h" +#ifdef CAIRO_HAS_QUARTZ_SURFACE +# include "cairo-quartz.h" +#endif +#ifdef CAIRO_HAS_WIN32_SURFACE +# include "cairo-win32.h" +#endif +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/HelpersCairo.h" +#include "mozilla/gfx/Logging.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "nsUTF8Utils.h" + +// IPP spec disallow the job-name which is over 255 characters. +// RFC: https://tools.ietf.org/html/rfc2911#section-4.1.2 +#define IPP_JOB_NAME_LIMIT_LENGTH 255 + +namespace mozilla::gfx { + +PrintTarget::PrintTarget(cairo_surface_t* aCairoSurface, const IntSize& aSize) + : mCairoSurface(aCairoSurface), + mSize(aSize), + mIsFinished(false) +#ifdef DEBUG + , + mHasActivePage(false) +#endif + +{ +#if 0 + // aCairoSurface is null when our PrintTargetThebes subclass's ctor calls us. + // Once PrintTargetThebes is removed, enable this assertion. + MOZ_ASSERT(aCairoSurface && !cairo_surface_status(aCairoSurface), + "CreateOrNull factory methods should not call us without a " + "valid cairo_surface_t*"); +#endif + + // CreateOrNull factory methods hand over ownership of aCairoSurface, + // so we don't call cairo_surface_reference(aSurface) here. + + // This code was copied from gfxASurface::Init: +#ifdef MOZ_TREE_CAIRO + if (mCairoSurface && + cairo_surface_get_content(mCairoSurface) != CAIRO_CONTENT_COLOR) { + cairo_surface_set_subpixel_antialiasing( + mCairoSurface, CAIRO_SUBPIXEL_ANTIALIASING_DISABLED); + } +#endif +} + +PrintTarget::~PrintTarget() { + // null surfaces are allowed here + cairo_surface_destroy(mCairoSurface); + mCairoSurface = nullptr; +} + +already_AddRefed<DrawTarget> PrintTarget::MakeDrawTarget( + const IntSize& aSize, DrawEventRecorder* aRecorder) { + MOZ_ASSERT(mCairoSurface, + "We shouldn't have been constructed without a cairo surface"); + + // This should not be called outside of BeginPage()/EndPage() calls since + // some backends can only provide a valid DrawTarget at that time. + MOZ_ASSERT(mHasActivePage, "We can't guarantee a valid DrawTarget"); + + if (cairo_surface_status(mCairoSurface)) { + return nullptr; + } + + // Note than aSize may not be the same as mSize (the size of mCairoSurface). + // See the comments in our header. If the sizes are different a clip will + // be applied to mCairoSurface. + RefPtr<DrawTarget> dt = + Factory::CreateDrawTargetForCairoSurface(mCairoSurface, aSize); + if (!dt || !dt->IsValid()) { + return nullptr; + } + + if (aRecorder) { + dt = CreateRecordingDrawTarget(aRecorder, dt); + if (!dt || !dt->IsValid()) { + return nullptr; + } + } + + return dt.forget(); +} + +already_AddRefed<DrawTarget> PrintTarget::GetReferenceDrawTarget() { + if (!mRefDT) { + const IntSize size(1, 1); + + cairo_surface_t* similar; + switch (cairo_surface_get_type(mCairoSurface)) { +#ifdef CAIRO_HAS_WIN32_SURFACE + case CAIRO_SURFACE_TYPE_WIN32: + similar = cairo_win32_surface_create_with_dib( + CairoContentToCairoFormat(cairo_surface_get_content(mCairoSurface)), + size.width, size.height); + break; +#endif +#ifdef CAIRO_HAS_QUARTZ_SURFACE + case CAIRO_SURFACE_TYPE_QUARTZ: + similar = cairo_quartz_surface_create_cg_layer( + mCairoSurface, cairo_surface_get_content(mCairoSurface), size.width, + size.height); + break; +#endif + default: + similar = cairo_surface_create_similar( + mCairoSurface, cairo_surface_get_content(mCairoSurface), size.width, + size.height); + break; + } + + if (cairo_surface_status(similar)) { + return nullptr; + } + + RefPtr<DrawTarget> dt = + Factory::CreateDrawTargetForCairoSurface(similar, size); + + // The DT addrefs the surface, so we need drop our own reference to it: + cairo_surface_destroy(similar); + + if (!dt || !dt->IsValid()) { + return nullptr; + } + mRefDT = std::move(dt); + } + + return do_AddRef(mRefDT); +} + +/* static */ +void PrintTarget::AdjustPrintJobNameForIPP(const nsAString& aJobName, + nsCString& aAdjustedJobName) { + CopyUTF16toUTF8(aJobName, aAdjustedJobName); + + if (aAdjustedJobName.Length() > IPP_JOB_NAME_LIMIT_LENGTH) { + uint32_t length = RewindToPriorUTF8Codepoint( + aAdjustedJobName.get(), (IPP_JOB_NAME_LIMIT_LENGTH - 3U)); + aAdjustedJobName.SetLength(length); + aAdjustedJobName.AppendLiteral("..."); + } +} + +/* static */ +void PrintTarget::AdjustPrintJobNameForIPP(const nsAString& aJobName, + nsString& aAdjustedJobName) { + nsAutoCString jobName; + AdjustPrintJobNameForIPP(aJobName, jobName); + + CopyUTF8toUTF16(jobName, aAdjustedJobName); +} + +/* static */ +already_AddRefed<DrawTarget> PrintTarget::CreateRecordingDrawTarget( + DrawEventRecorder* aRecorder, DrawTarget* aDrawTarget) { + MOZ_ASSERT(aRecorder); + MOZ_ASSERT(aDrawTarget); + + RefPtr<DrawTarget> dt; + + if (aRecorder) { + // It doesn't really matter what we pass as the DrawTarget here. + dt = gfx::Factory::CreateRecordingDrawTarget(aRecorder, aDrawTarget, + aDrawTarget->GetRect()); + } + + if (!dt || !dt->IsValid()) { + gfxCriticalNote + << "Failed to create a recording DrawTarget for PrintTarget"; + return nullptr; + } + + return dt.forget(); +} + +void PrintTarget::Finish() { + if (mIsFinished) { + return; + } + mIsFinished = true; + + // null surfaces are allowed here + cairo_surface_finish(mCairoSurface); +} + +void PrintTarget::RegisterPageDoneCallback(PageDoneCallback&& aCallback) { + MOZ_ASSERT(aCallback && !IsSyncPagePrinting()); + mPageDoneCallback = std::move(aCallback); +} + +void PrintTarget::UnregisterPageDoneCallback() { mPageDoneCallback = nullptr; } + +} // namespace mozilla::gfx diff --git a/gfx/thebes/PrintTarget.h b/gfx/thebes/PrintTarget.h new file mode 100644 index 0000000000..c7b3c532e8 --- /dev/null +++ b/gfx/thebes/PrintTarget.h @@ -0,0 +1,174 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PRINTTARGET_H +#define MOZILLA_GFX_PRINTTARGET_H + +#include <functional> + +#include "mozilla/RefPtr.h" +#include "mozilla/gfx/2D.h" +#include "nsISupportsImpl.h" +#include "nsStringFwd.h" + +namespace mozilla { +namespace gfx { + +class DrawEventRecorder; + +/** + * A class that is used to draw output that is to be sent to a printer or print + * preview. + * + * This class wraps a cairo_surface_t* and provides access to it via a + * DrawTarget. The various checkpointing methods manage the state of the + * platform specific cairo_surface_t*. + */ +class PrintTarget { + public: + typedef std::function<void(nsresult)> PageDoneCallback; + + NS_INLINE_DECL_REFCOUNTING(PrintTarget); + + /// Must be matched 1:1 by an EndPrinting/AbortPrinting call. + virtual nsresult BeginPrinting(const nsAString& aTitle, + const nsAString& aPrintToFileName, + int32_t aStartPage, int32_t aEndPage) { + return NS_OK; + } + virtual nsresult EndPrinting() { return NS_OK; } + virtual nsresult AbortPrinting() { +#ifdef DEBUG + mHasActivePage = false; +#endif + return NS_OK; + } + virtual nsresult BeginPage() { +#ifdef DEBUG + MOZ_ASSERT(!mHasActivePage, "Missing EndPage() call"); + mHasActivePage = true; +#endif + return NS_OK; + } + virtual nsresult EndPage() { +#ifdef DEBUG + mHasActivePage = false; +#endif + return NS_OK; + } + + /** + * Releases the resources used by this PrintTarget. Typically this should be + * called after calling EndPrinting(). Calling this more than once is + * allowed, but subsequent calls are a no-op. + * + * Note that any DrawTarget obtained from this PrintTarget will no longer be + * useful after this method has been called. + */ + virtual void Finish(); + + /** + * Returns true if to print landscape our consumers must apply a 90 degrees + * rotation to our DrawTarget. + */ + virtual bool RotateNeededForLandscape() const { return false; } + + const IntSize& GetSize() const { return mSize; } + + /** + * Makes a DrawTarget to draw the printer output to, or returns null on + * failure. + * + * If aRecorder is passed a recording DrawTarget will be created instead of + * the type of DrawTarget that would normally be returned for a particular + * subclass of this class. This argument is only intended to be used in + * the e10s content process if printing output can't otherwise be transfered + * over to the parent process using the normal DrawTarget type. + * + * NOTE: this should only be called between BeginPage()/EndPage() calls, and + * the returned DrawTarget should not be drawn to after EndPage() has been + * called. + * + * XXX For consistency with the old code this takes a size parameter even + * though we already have the size passed to our subclass's CreateOrNull + * factory methods. The size passed to the factory method comes from + * nsIDeviceContextSpec::MakePrintTarget overrides, whereas the size + * passed to us comes from nsDeviceContext::CreateRenderingContext. In at + * least one case (nsDeviceContextSpecAndroid::MakePrintTarget) these are + * different. At some point we should align the two sources and get rid of + * this method's size parameter. + * + * XXX For consistency with the old code this returns a new DrawTarget for + * each call. Perhaps we can create and cache a DrawTarget in our subclass's + * CreateOrNull factory methods and return that on each call? Currently that + * seems to cause Mochitest failures on Windows though, which coincidentally + * is the only platform where we get passed an aRecorder. Probably the + * issue is that we get called more than once with a different aRecorder, so + * storing one recording DrawTarget for our lifetime doesn't currently work. + * + * XXX Could we pass aRecorder to our subclass's CreateOrNull factory methods? + * We'd need to check that our consumers always pass the same aRecorder for + * our entire lifetime. + * + * XXX Once PrintTargetThebes is removed this can become non-virtual. + * + * XXX In the long run, this class and its sub-classes should be converted to + * use STL classes and mozilla::RefCounted<> so the can be moved to Moz2D. + * + * TODO: Consider adding a SetDPI method that calls + * cairo_surface_set_fallback_resolution. + */ + virtual already_AddRefed<DrawTarget> MakeDrawTarget( + const IntSize& aSize, DrawEventRecorder* aRecorder = nullptr); + + /** + * Returns a reference DrawTarget. Unlike MakeDrawTarget, this method is not + * restricted to being called between BeginPage()/EndPage() calls, and the + * returned DrawTarget is still valid to use after EndPage() has been called. + */ + virtual already_AddRefed<DrawTarget> GetReferenceDrawTarget(); + + /** + * If IsSyncPagePrinting returns true, then a user can assume the content of + * a page was already printed after EndPage(). + * If IsSyncPagePrinting returns false, then a user should register a + * callback function using RegisterPageDoneCallback to receive page print + * done notifications. + */ + virtual bool IsSyncPagePrinting() const { return true; } + void RegisterPageDoneCallback(PageDoneCallback&& aCallback); + void UnregisterPageDoneCallback(); + + static void AdjustPrintJobNameForIPP(const nsAString& aJobName, + nsCString& aAdjustedJobName); + static void AdjustPrintJobNameForIPP(const nsAString& aJobName, + nsString& aAdjustedJobName); + + protected: + // Only created via subclass's constructors + explicit PrintTarget(cairo_surface_t* aCairoSurface, const IntSize& aSize); + + // Protected because we're refcounted + virtual ~PrintTarget(); + + static already_AddRefed<DrawTarget> CreateRecordingDrawTarget( + DrawEventRecorder* aRecorder, DrawTarget* aDrawTarget); + + cairo_surface_t* mCairoSurface; + RefPtr<DrawTarget> mRefDT; // reference DT + + IntSize mSize; + bool mIsFinished; +#ifdef DEBUG + bool mHasActivePage; +#endif + + PageDoneCallback mPageDoneCallback; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_PRINTTARGET_H */ diff --git a/gfx/thebes/PrintTargetCG.h b/gfx/thebes/PrintTargetCG.h new file mode 100644 index 0000000000..23d369bf27 --- /dev/null +++ b/gfx/thebes/PrintTargetCG.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PRINTTARGETCG_H +#define MOZILLA_GFX_PRINTTARGETCG_H + +#include <Carbon/Carbon.h> +#include "PrintTarget.h" + +namespace mozilla { +namespace gfx { + +/** + * CoreGraphics printing target. + */ +class PrintTargetCG final : public PrintTarget { + public: + static already_AddRefed<PrintTargetCG> CreateOrNull( + PMPrintSession aPrintSession, PMPageFormat aPageFormat, + PMPrintSettings aPrintSettings, const IntSize& aSize); + + nsresult BeginPrinting(const nsAString& aTitle, + const nsAString& aPrintToFileName, int32_t aStartPage, + int32_t aEndPage) final; + nsresult EndPrinting() final; + nsresult AbortPrinting() final; + nsresult BeginPage() final; + nsresult EndPage() final; + + already_AddRefed<DrawTarget> GetReferenceDrawTarget() final; + + private: + PrintTargetCG(PMPrintSession aPrintSession, PMPageFormat aPageFormat, + PMPrintSettings aPrintSettings, const IntSize& aSize); + ~PrintTargetCG(); + + PMPrintSession mPrintSession; + PMPageFormat mPageFormat; + PMPrintSettings mPrintSettings; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_PRINTTARGETCG_H */ diff --git a/gfx/thebes/PrintTargetCG.mm b/gfx/thebes/PrintTargetCG.mm new file mode 100644 index 0000000000..42c7ddca45 --- /dev/null +++ b/gfx/thebes/PrintTargetCG.mm @@ -0,0 +1,198 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PrintTargetCG.h" + +#include "cairo.h" +#include "cairo-quartz.h" +#include "mozilla/gfx/HelpersCairo.h" +#include "nsObjCExceptions.h" + +namespace mozilla { +namespace gfx { + +PrintTargetCG::PrintTargetCG(PMPrintSession aPrintSession, PMPageFormat aPageFormat, + PMPrintSettings aPrintSettings, const IntSize& aSize) + : PrintTarget(/* aCairoSurface */ nullptr, aSize), + mPrintSession(aPrintSession), + mPageFormat(aPageFormat), + mPrintSettings(aPrintSettings) { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + MOZ_ASSERT(mPrintSession && mPageFormat && mPrintSettings); + + ::PMRetain(mPrintSession); + ::PMRetain(mPageFormat); + ::PMRetain(mPrintSettings); + + // TODO: Add memory reporting like gfxQuartzSurface. + // RecordMemoryUsed(mSize.height * 4 + sizeof(gfxQuartzSurface)); + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +PrintTargetCG::~PrintTargetCG() { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + ::PMRelease(mPrintSession); + ::PMRelease(mPageFormat); + ::PMRelease(mPrintSettings); + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +/* static */ already_AddRefed<PrintTargetCG> PrintTargetCG::CreateOrNull( + PMPrintSession aPrintSession, PMPageFormat aPageFormat, PMPrintSettings aPrintSettings, + const IntSize& aSize) { + if (!Factory::CheckSurfaceSize(aSize)) { + return nullptr; + } + + RefPtr<PrintTargetCG> target = + new PrintTargetCG(aPrintSession, aPageFormat, aPrintSettings, aSize); + + return target.forget(); +} + +static size_t PutBytesNull(void* info, const void* buffer, size_t count) { return count; } + +already_AddRefed<DrawTarget> PrintTargetCG::GetReferenceDrawTarget() { + if (!mRefDT) { + const IntSize size(1, 1); + + CGDataConsumerCallbacks callbacks = {PutBytesNull, nullptr}; + CGDataConsumerRef consumer = CGDataConsumerCreate(nullptr, &callbacks); + CGContextRef pdfContext = CGPDFContextCreate(consumer, nullptr, nullptr); + CGDataConsumerRelease(consumer); + + cairo_surface_t* similar = + cairo_quartz_surface_create_for_cg_context(pdfContext, size.width, size.height); + + CGContextRelease(pdfContext); + + if (cairo_surface_status(similar)) { + return nullptr; + } + + RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForCairoSurface(similar, size); + + // The DT addrefs the surface, so we need drop our own reference to it: + cairo_surface_destroy(similar); + + if (!dt || !dt->IsValid()) { + return nullptr; + } + mRefDT = dt.forget(); + } + + return do_AddRef(mRefDT); +} + +nsresult PrintTargetCG::BeginPrinting(const nsAString& aTitle, const nsAString& aPrintToFileName, + int32_t aStartPage, int32_t aEndPage) { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + // Print Core of Application Service sent print job with names exceeding + // 255 bytes. This is a workaround until fix it. + // (https://openradar.appspot.com/34428043) + nsAutoString adjustedTitle; + PrintTarget::AdjustPrintJobNameForIPP(aTitle, adjustedTitle); + + if (!adjustedTitle.IsEmpty()) { + CFStringRef cfString = ::CFStringCreateWithCharacters( + NULL, reinterpret_cast<const UniChar*>(adjustedTitle.BeginReading()), + adjustedTitle.Length()); + if (cfString) { + ::PMPrintSettingsSetJobName(mPrintSettings, cfString); + ::CFRelease(cfString); + } + } + + OSStatus status; + status = ::PMSetFirstPage(mPrintSettings, aStartPage, false); + NS_ASSERTION(status == noErr, "PMSetFirstPage failed"); + status = ::PMSetLastPage(mPrintSettings, aEndPage, false); + NS_ASSERTION(status == noErr, "PMSetLastPage failed"); + + status = ::PMSessionBeginCGDocumentNoDialog(mPrintSession, mPrintSettings, mPageFormat); + + return status == noErr ? NS_OK : NS_ERROR_ABORT; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult PrintTargetCG::EndPrinting() { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + ::PMSessionEndDocumentNoDialog(mPrintSession); + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult PrintTargetCG::AbortPrinting() { +#ifdef DEBUG + mHasActivePage = false; +#endif + return EndPrinting(); +} + +nsresult PrintTargetCG::BeginPage() { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + PMSessionError(mPrintSession); + OSStatus status = ::PMSessionBeginPageNoDialog(mPrintSession, mPageFormat, NULL); + if (status != noErr) { + return NS_ERROR_ABORT; + } + + CGContextRef context; + // This call will fail if it wasn't called between the PMSessionBeginPage/ + // PMSessionEndPage calls: + ::PMSessionGetCGGraphicsContext(mPrintSession, &context); + + if (!context) { + return NS_ERROR_FAILURE; + } + + unsigned int width = static_cast<unsigned int>(mSize.width); + unsigned int height = static_cast<unsigned int>(mSize.height); + + // Initially, origin is at bottom-left corner of the paper. + // Here, we translate it to top-left corner of the paper. + CGContextTranslateCTM(context, 0, height); + CGContextScaleCTM(context, 1.0, -1.0); + + cairo_surface_t* surface = cairo_quartz_surface_create_for_cg_context(context, width, height); + + if (cairo_surface_status(surface)) { + return NS_ERROR_FAILURE; + } + + mCairoSurface = surface; + + return PrintTarget::BeginPage(); + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult PrintTargetCG::EndPage() { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + cairo_surface_finish(mCairoSurface); + mCairoSurface = nullptr; + + OSStatus status = ::PMSessionEndPageNoDialog(mPrintSession); + if (status != noErr) { + return NS_ERROR_ABORT; + } + + return PrintTarget::EndPage(); + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/thebes/PrintTargetPDF.cpp b/gfx/thebes/PrintTargetPDF.cpp new file mode 100644 index 0000000000..fd74d8780f --- /dev/null +++ b/gfx/thebes/PrintTargetPDF.cpp @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PrintTargetPDF.h" + +#include "cairo.h" +#include "cairo-pdf.h" +#include "mozilla/AppShutdown.h" + +namespace mozilla::gfx { + +static cairo_status_t write_func(void* closure, const unsigned char* data, + unsigned int length) { + if (AppShutdown::IsShuttingDown()) { + return CAIRO_STATUS_SUCCESS; + } + nsCOMPtr<nsIOutputStream> out = reinterpret_cast<nsIOutputStream*>(closure); + do { + uint32_t wrote = 0; + if (NS_FAILED(out->Write((const char*)data, length, &wrote))) { + break; + } + data += wrote; + length -= wrote; + } while (length > 0); + NS_ASSERTION(length == 0, "not everything was written to the file"); + return CAIRO_STATUS_SUCCESS; +} + +PrintTargetPDF::PrintTargetPDF(cairo_surface_t* aCairoSurface, + const IntSize& aSize, nsIOutputStream* aStream) + : PrintTarget(aCairoSurface, aSize), mStream(aStream) {} + +PrintTargetPDF::~PrintTargetPDF() { + // We get called first, then PrintTarget's dtor. That means that mStream + // is destroyed before PrintTarget's dtor calls cairo_surface_destroy. This + // can be a problem if Finish() hasn't been called on us, since + // cairo_surface_destroy will then call cairo_surface_finish and that will + // end up invoking write_func above with the by now dangling pointer mStream + // that mCairoSurface stored. To prevent that from happening we must call + // Flush here before mStream is deleted. + Finish(); +} + +/* static */ +already_AddRefed<PrintTargetPDF> PrintTargetPDF::CreateOrNull( + nsIOutputStream* aStream, const IntSize& aSizeInPoints) { + cairo_surface_t* surface = cairo_pdf_surface_create_for_stream( + write_func, (void*)aStream, aSizeInPoints.width, aSizeInPoints.height); + if (cairo_surface_status(surface)) { + return nullptr; + } + + // The new object takes ownership of our surface reference. + RefPtr<PrintTargetPDF> target = + new PrintTargetPDF(surface, aSizeInPoints, aStream); + return target.forget(); +} + +nsresult PrintTargetPDF::EndPage() { + cairo_surface_show_page(mCairoSurface); + return PrintTarget::EndPage(); +} + +void PrintTargetPDF::Finish() { + if (mIsFinished || AppShutdown::IsShuttingDown()) { + // We don't want to call Close() on mStream more than once, and we don't + // want to block shutdown if for some reason the user shuts down the + // browser mid print. + return; + } + PrintTarget::Finish(); + mStream->Close(); +} + +} // namespace mozilla::gfx diff --git a/gfx/thebes/PrintTargetPDF.h b/gfx/thebes/PrintTargetPDF.h new file mode 100644 index 0000000000..489f6b373a --- /dev/null +++ b/gfx/thebes/PrintTargetPDF.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PRINTTARGETPDF_H +#define MOZILLA_GFX_PRINTTARGETPDF_H + +#include "nsCOMPtr.h" +#include "nsIOutputStream.h" +#include "PrintTarget.h" + +namespace mozilla { +namespace gfx { + +/** + * PDF printing target. + */ +class PrintTargetPDF final : public PrintTarget { + public: + static already_AddRefed<PrintTargetPDF> CreateOrNull( + nsIOutputStream* aStream, const IntSize& aSizeInPoints); + + nsresult EndPage() override; + void Finish() override; + + private: + PrintTargetPDF(cairo_surface_t* aCairoSurface, const IntSize& aSize, + nsIOutputStream* aStream); + virtual ~PrintTargetPDF(); + + nsCOMPtr<nsIOutputStream> mStream; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_PRINTTARGETPDF_H */ diff --git a/gfx/thebes/PrintTargetPS.cpp b/gfx/thebes/PrintTargetPS.cpp new file mode 100644 index 0000000000..06bb25377a --- /dev/null +++ b/gfx/thebes/PrintTargetPS.cpp @@ -0,0 +1,96 @@ +/* -*- 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 "PrintTargetPS.h" + +#include "cairo.h" +#include "cairo-ps.h" + +namespace mozilla::gfx { + +static cairo_status_t write_func(void* closure, const unsigned char* data, + unsigned int length) { + nsCOMPtr<nsIOutputStream> out = reinterpret_cast<nsIOutputStream*>(closure); + do { + uint32_t wrote = 0; + if (NS_FAILED(out->Write((const char*)data, length, &wrote))) { + break; + } + data += wrote; + length -= wrote; + } while (length > 0); + NS_ASSERTION(length == 0, "not everything was written to the file"); + return CAIRO_STATUS_SUCCESS; +} + +PrintTargetPS::PrintTargetPS(cairo_surface_t* aCairoSurface, + const IntSize& aSize, nsIOutputStream* aStream, + PageOrientation aOrientation) + : PrintTarget(aCairoSurface, aSize), + mStream(aStream), + mOrientation(aOrientation) {} + +PrintTargetPS::~PrintTargetPS() { + // We get called first, then PrintTarget's dtor. That means that mStream + // is destroyed before PrintTarget's dtor calls cairo_surface_destroy. This + // can be a problem if Finish() hasn't been called on us, since + // cairo_surface_destroy will then call cairo_surface_finish and that will + // end up invoking write_func above with the by now dangling pointer mStream + // that mCairoSurface stored. To prevent that from happening we must call + // Flush here before mStream is deleted. + Finish(); +} + +/* static */ +already_AddRefed<PrintTargetPS> PrintTargetPS::CreateOrNull( + nsIOutputStream* aStream, IntSize aSizeInPoints, + PageOrientation aOrientation) { + // The PS output does not specify the page size so to print landscape we need + // to rotate the drawing 90 degrees and print on portrait paper. If printing + // landscape, swap the width/height supplied to cairo to select a portrait + // print area. Our consumers are responsible for checking + // RotateForLandscape() and applying a rotation transform if true. + if (aOrientation == LANDSCAPE) { + std::swap(aSizeInPoints.width, aSizeInPoints.height); + } + + cairo_surface_t* surface = cairo_ps_surface_create_for_stream( + write_func, (void*)aStream, aSizeInPoints.width, aSizeInPoints.height); + if (cairo_surface_status(surface)) { + return nullptr; + } + cairo_ps_surface_restrict_to_level(surface, CAIRO_PS_LEVEL_2); + + // The new object takes ownership of our surface reference. + RefPtr<PrintTargetPS> target = + new PrintTargetPS(surface, aSizeInPoints, aStream, aOrientation); + return target.forget(); +} + +nsresult PrintTargetPS::BeginPrinting(const nsAString& aTitle, + const nsAString& aPrintToFileName, + int32_t aStartPage, int32_t aEndPage) { + 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 mozilla::gfx diff --git a/gfx/thebes/PrintTargetPS.h b/gfx/thebes/PrintTargetPS.h new file mode 100644 index 0000000000..8207992748 --- /dev/null +++ b/gfx/thebes/PrintTargetPS.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PRINTINGTARGETPS_H +#define MOZILLA_GFX_PRINTINGTARGETPS_H + +#include "nsCOMPtr.h" +#include "nsIOutputStream.h" +#include "PrintTarget.h" + +namespace mozilla { +namespace gfx { + +/** + * PostScript printing target. + */ +class PrintTargetPS final : public PrintTarget { + public: + enum PageOrientation { PORTRAIT, LANDSCAPE }; + + static already_AddRefed<PrintTargetPS> CreateOrNull( + nsIOutputStream* aStream, IntSize aSizeInPoints, + PageOrientation aOrientation); + + virtual nsresult BeginPrinting(const nsAString& aTitle, + const nsAString& aPrintToFileName, + int32_t aStartPage, int32_t aEndPage) override; + nsresult EndPage() override; + void Finish() override; + + bool GetRotateForLandscape() { return (mOrientation == LANDSCAPE); } + + private: + PrintTargetPS(cairo_surface_t* aCairoSurface, const IntSize& aSize, + nsIOutputStream* aStream, PageOrientation aOrientation); + virtual ~PrintTargetPS(); + + nsCOMPtr<nsIOutputStream> mStream; + PageOrientation mOrientation; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_PRINTINGTARGETPS_H */ diff --git a/gfx/thebes/PrintTargetRecording.cpp b/gfx/thebes/PrintTargetRecording.cpp new file mode 100644 index 0000000000..4d73995184 --- /dev/null +++ b/gfx/thebes/PrintTargetRecording.cpp @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PrintTargetRecording.h" + +#include "cairo.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Logging.h" + +namespace mozilla { +namespace gfx { + +PrintTargetRecording::PrintTargetRecording(cairo_surface_t* aCairoSurface, + const IntSize& aSize) + : PrintTarget(aCairoSurface, aSize) {} + +/* static */ +already_AddRefed<PrintTargetRecording> PrintTargetRecording::CreateOrNull( + const IntSize& aSize) { + if (!Factory::CheckSurfaceSize(aSize)) { + return nullptr; + } + + // Perhaps surprisingly, this surface is never actually drawn to. This class + // creates a DrawTargetRecording using CreateRecordingDrawTarget, and + // that needs another DrawTarget to be passed to it. You might expect the + // type of the DrawTarget that is passed to matter because it would seem + // logical to encoded its type in the recording, and on replaying the + // recording a DrawTarget of the same type would be created. However, the + // passed DrawTarget's type doesn't seem to be encoded any more accurately + // than just "BackendType::CAIRO". Even if it were, the code that replays the + // recording is PrintTranslator::TranslateRecording which (indirectly) calls + // MakePrintTarget on the type of nsIDeviceContextSpecProxy that is created + // for the platform that we're running on, and the type of DrawTarget that + // that returns is hardcoded. + // + // The only reason that we use cairo_recording_surface_create here is: + // + // * It's pretty much the only cairo_*_surface_create methods that's both + // available on all platforms and doesn't require allocating a + // potentially large surface. + // + // * Since we need a DrawTarget to pass to CreateRecordingDrawTarget we + // might as well leverage our base class's machinery to create a + // DrawTarget (it's as good a way as any other that will work), and to do + // that we need a cairo_surface_t. + // + // So the fact that this is a "recording" PrintTarget and the function that + // we call here is cairo_recording_surface_create is simply a coincidence. We + // could use any cairo_*_surface_create method and this class would still + // work. + // + cairo_surface_t* surface = + cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA, nullptr); + + if (cairo_surface_status(surface)) { + return nullptr; + } + + // The new object takes ownership of our surface reference. + RefPtr<PrintTargetRecording> target = + new PrintTargetRecording(surface, aSize); + + return target.forget(); +} + +already_AddRefed<DrawTarget> PrintTargetRecording::MakeDrawTarget( + const IntSize& aSize, DrawEventRecorder* aRecorder) { + MOZ_ASSERT(aRecorder, "A DrawEventRecorder is required"); + + if (!aRecorder) { + return nullptr; + } + + RefPtr<DrawTarget> dt = PrintTarget::MakeDrawTarget(aSize, nullptr); + if (dt) { + dt = CreateRecordingDrawTarget(aRecorder, dt); + if (!dt || !dt->IsValid()) { + return nullptr; + } + } + + return dt.forget(); +} + +already_AddRefed<DrawTarget> PrintTargetRecording::CreateRecordingDrawTarget( + DrawEventRecorder* aRecorder, DrawTarget* aDrawTarget) { + MOZ_ASSERT(aRecorder); + MOZ_ASSERT(aDrawTarget); + + RefPtr<DrawTarget> dt; + + if (aRecorder) { + // It doesn't really matter what we pass as the DrawTarget here. + dt = gfx::Factory::CreateRecordingDrawTarget(aRecorder, aDrawTarget, + aDrawTarget->GetRect()); + } + + if (!dt || !dt->IsValid()) { + gfxCriticalNote + << "Failed to create a recording DrawTarget for PrintTarget"; + return nullptr; + } + + return dt.forget(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/thebes/PrintTargetRecording.h b/gfx/thebes/PrintTargetRecording.h new file mode 100644 index 0000000000..dccf35e615 --- /dev/null +++ b/gfx/thebes/PrintTargetRecording.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PRINTTARGETRECORDING_H +#define MOZILLA_GFX_PRINTTARGETRECORDING_H + +#include "PrintTarget.h" + +namespace mozilla { +namespace gfx { + +/** + * Recording printing target. + * + * This exists for use on e10s's content process in order to record print + * output, send it over to the parent process, and replay it on a DrawTarget + * there for printing. + */ +class PrintTargetRecording final : public PrintTarget { + public: + static already_AddRefed<PrintTargetRecording> CreateOrNull( + const IntSize& aSize); + + already_AddRefed<DrawTarget> MakeDrawTarget( + const IntSize& aSize, DrawEventRecorder* aRecorder = nullptr) override; + + private: + PrintTargetRecording(cairo_surface_t* aCairoSurface, const IntSize& aSize); + + already_AddRefed<DrawTarget> CreateWrapAndRecordDrawTarget( + DrawEventRecorder* aRecorder, DrawTarget* aDrawTarget); +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_PRINTTARGETRECORDING_H */ diff --git a/gfx/thebes/PrintTargetSkPDF.cpp b/gfx/thebes/PrintTargetSkPDF.cpp new file mode 100644 index 0000000000..6cf7d3729e --- /dev/null +++ b/gfx/thebes/PrintTargetSkPDF.cpp @@ -0,0 +1,126 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PrintTargetSkPDF.h" + +#include "mozilla/gfx/2D.h" +#include "nsString.h" +#include <vector> + +namespace mozilla::gfx { + +PrintTargetSkPDF::PrintTargetSkPDF(const IntSize& aSize, + UniquePtr<SkWStream> aStream) + : PrintTarget(/* not using cairo_surface_t */ nullptr, aSize), + mOStream(std::move(aStream)), + mPageCanvas(nullptr), + mRefCanvas(nullptr) {} + +PrintTargetSkPDF::~PrintTargetSkPDF() { + Finish(); // ensure stream is flushed + + // Make sure mPDFDoc and mRefPDFDoc are destroyed before our member streams + // (which they wrap) are destroyed: + mPDFDoc = nullptr; + mRefPDFDoc = nullptr; +} + +/* static */ +already_AddRefed<PrintTargetSkPDF> PrintTargetSkPDF::CreateOrNull( + UniquePtr<SkWStream> aStream, const IntSize& aSizeInPoints) { + return do_AddRef(new PrintTargetSkPDF(aSizeInPoints, std::move(aStream))); +} + +nsresult PrintTargetSkPDF::BeginPrinting(const nsAString& aTitle, + const nsAString& aPrintToFileName, + int32_t aStartPage, int32_t aEndPage) { + // We need to create the SkPDFDocument here rather than in CreateOrNull + // because it's only now that we are given aTitle which we want for the + // PDF metadata. + + SkPDF::Metadata metadata; + metadata.fTitle = NS_ConvertUTF16toUTF8(aTitle).get(); + metadata.fCreator = "Firefox"; + SkTime::DateTime now; + SkTime::GetDateTime(&now); + metadata.fCreation = now; + metadata.fModified = now; + + // SkDocument stores a non-owning raw pointer to aStream + mPDFDoc = SkPDF::MakeDocument(mOStream.get(), metadata); + + return mPDFDoc ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult PrintTargetSkPDF::BeginPage() { + mPageCanvas = mPDFDoc->beginPage(mSize.width, mSize.height); + + return !mPageCanvas ? NS_ERROR_FAILURE : PrintTarget::BeginPage(); +} + +nsresult PrintTargetSkPDF::EndPage() { + mPageCanvas = nullptr; + mPageDT = nullptr; + return PrintTarget::EndPage(); +} + +nsresult PrintTargetSkPDF::EndPrinting() { + mPDFDoc->close(); + if (mRefPDFDoc) { + mRefPDFDoc->close(); + } + mPageCanvas = nullptr; + mPageDT = nullptr; + return NS_OK; +} + +void PrintTargetSkPDF::Finish() { + if (mIsFinished) { + return; + } + mOStream->flush(); + PrintTarget::Finish(); +} + +already_AddRefed<DrawTarget> PrintTargetSkPDF::MakeDrawTarget( + const IntSize& aSize, DrawEventRecorder* aRecorder) { + if (aRecorder) { + return PrintTarget::MakeDrawTarget(aSize, aRecorder); + } + // MOZ_ASSERT(aSize == mSize, "Should mPageCanvas size match?"); + if (!mPageCanvas) { + return nullptr; + } + mPageDT = Factory::CreateDrawTargetWithSkCanvas(mPageCanvas); + if (!mPageDT) { + mPageCanvas = nullptr; + return nullptr; + } + return do_AddRef(mPageDT); +} + +already_AddRefed<DrawTarget> PrintTargetSkPDF::GetReferenceDrawTarget() { + if (!mRefDT) { + SkPDF::Metadata metadata; + // SkDocument stores a non-owning raw pointer to aStream + mRefPDFDoc = SkPDF::MakeDocument(&mRefOStream, metadata); + if (!mRefPDFDoc) { + return nullptr; + } + mRefCanvas = mRefPDFDoc->beginPage(mSize.width, mSize.height); + if (!mRefCanvas) { + return nullptr; + } + RefPtr<DrawTarget> dt = Factory::CreateDrawTargetWithSkCanvas(mRefCanvas); + if (!dt) { + return nullptr; + } + mRefDT = std::move(dt); + } + + return do_AddRef(mRefDT); +} + +} // namespace mozilla::gfx diff --git a/gfx/thebes/PrintTargetSkPDF.h b/gfx/thebes/PrintTargetSkPDF.h new file mode 100644 index 0000000000..9cbe9e4ab3 --- /dev/null +++ b/gfx/thebes/PrintTargetSkPDF.h @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PRINTTARGETSKPDF_H +#define MOZILLA_GFX_PRINTTARGETSKPDF_H + +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" +#include "PrintTarget.h" +#include "skia/include/core/SkCanvas.h" +#include "skia/include/docs/SkPDFDocument.h" +#include "skia/include/core/SkStream.h" + +namespace mozilla { +namespace gfx { + +/** + * Skia PDF printing target. + */ +class PrintTargetSkPDF final : public PrintTarget { + public: + // The returned PrintTargetSkPDF keeps a raw pointer to the passed SkWStream + // but does not own it. Callers are responsible for ensuring that passed + // stream outlives the returned PrintTarget. + static already_AddRefed<PrintTargetSkPDF> CreateOrNull( + UniquePtr<SkWStream> aStream, const IntSize& aSizeInPoints); + + nsresult BeginPrinting(const nsAString& aTitle, + const nsAString& aPrintToFileName, int32_t aStartPage, + int32_t aEndPage) override; + nsresult EndPrinting() override; + void Finish() override; + + nsresult BeginPage() override; + nsresult EndPage() override; + + already_AddRefed<DrawTarget> MakeDrawTarget( + const IntSize& aSize, DrawEventRecorder* aRecorder = nullptr) final; + + already_AddRefed<DrawTarget> GetReferenceDrawTarget() final; + + private: + PrintTargetSkPDF(const IntSize& aSize, UniquePtr<SkWStream> aStream); + virtual ~PrintTargetSkPDF(); + + // Do not hand out references to this object. It holds a non-owning + // reference to mOStreame, so must not outlive mOStream. + sk_sp<SkDocument> mPDFDoc; + + // The stream that the SkDocument outputs to. + UniquePtr<SkWStream> mOStream; + + // The current page's SkCanvas and its wrapping DrawTarget: + // Canvas is owned by mPDFDoc, which handles its deletion. + SkCanvas* mPageCanvas; + RefPtr<DrawTarget> mPageDT; + + // Members needed to provide a reference DrawTarget: + sk_sp<SkDocument> mRefPDFDoc; + // Canvas owned by mRefPDFDoc, which handles its deletion. + SkCanvas* mRefCanvas; + SkDynamicMemoryWStream mRefOStream; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_PRINTTARGETSKPDF_H */ diff --git a/gfx/thebes/PrintTargetThebes.cpp b/gfx/thebes/PrintTargetThebes.cpp new file mode 100644 index 0000000000..e121b0a7e2 --- /dev/null +++ b/gfx/thebes/PrintTargetThebes.cpp @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PrintTargetThebes.h" + +#include "gfxASurface.h" +#include "gfxPlatform.h" +#include "mozilla/gfx/Logging.h" + +namespace mozilla::gfx { + +/* static */ +already_AddRefed<PrintTargetThebes> PrintTargetThebes::CreateOrNull( + gfxASurface* aSurface) { + MOZ_ASSERT(aSurface); + + if (!aSurface || aSurface->CairoStatus()) { + return nullptr; + } + + RefPtr<PrintTargetThebes> target = new PrintTargetThebes(aSurface); + + return target.forget(); +} + +PrintTargetThebes::PrintTargetThebes(gfxASurface* aSurface) + : PrintTarget(nullptr, aSurface->GetSize()), mGfxSurface(aSurface) {} + +already_AddRefed<DrawTarget> PrintTargetThebes::MakeDrawTarget( + const IntSize& aSize, DrawEventRecorder* aRecorder) { + // This should not be called outside of BeginPage()/EndPage() calls since + // some backends can only provide a valid DrawTarget at that time. + MOZ_ASSERT(mHasActivePage, "We can't guarantee a valid DrawTarget"); + + RefPtr<gfx::DrawTarget> dt = + gfxPlatform::CreateDrawTargetForSurface(mGfxSurface, aSize); + if (!dt || !dt->IsValid()) { + return nullptr; + } + + if (aRecorder) { + dt = CreateRecordingDrawTarget(aRecorder, dt); + if (!dt || !dt->IsValid()) { + return nullptr; + } + } + + return dt.forget(); +} + +already_AddRefed<DrawTarget> PrintTargetThebes::GetReferenceDrawTarget() { + if (!mRefDT) { + RefPtr<gfx::DrawTarget> dt = + gfxPlatform::CreateDrawTargetForSurface(mGfxSurface, mSize); + if (!dt || !dt->IsValid()) { + return nullptr; + } + mRefDT = dt->CreateSimilarDrawTarget(IntSize(1, 1), dt->GetFormat()); + } + + return do_AddRef(mRefDT); +} + +nsresult PrintTargetThebes::BeginPrinting(const nsAString& aTitle, + const nsAString& aPrintToFileName, + int32_t aStartPage, + int32_t aEndPage) { + return mGfxSurface->BeginPrinting(aTitle, aPrintToFileName); +} + +nsresult PrintTargetThebes::EndPrinting() { return mGfxSurface->EndPrinting(); } + +nsresult PrintTargetThebes::AbortPrinting() { +#ifdef DEBUG + mHasActivePage = false; +#endif + return mGfxSurface->AbortPrinting(); +} + +nsresult PrintTargetThebes::BeginPage() { +#ifdef DEBUG + mHasActivePage = true; +#endif + return mGfxSurface->BeginPage(); +} + +nsresult PrintTargetThebes::EndPage() { +#ifdef DEBUG + mHasActivePage = false; +#endif + return mGfxSurface->EndPage(); +} + +void PrintTargetThebes::Finish() { return mGfxSurface->Finish(); } + +} // namespace mozilla::gfx diff --git a/gfx/thebes/PrintTargetThebes.h b/gfx/thebes/PrintTargetThebes.h new file mode 100644 index 0000000000..1a3b72365d --- /dev/null +++ b/gfx/thebes/PrintTargetThebes.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PRINTTARGETTHEBES_H +#define MOZILLA_GFX_PRINTTARGETTHEBES_H + +#include "mozilla/gfx/PrintTarget.h" + +class gfxASurface; + +namespace mozilla { +namespace gfx { + +/** + * XXX Remove this class. + * + * This class should go away once all the logic from the gfxASurface subclasses + * has been moved to new PrintTarget subclasses and we no longer need to + * wrap a gfxASurface. + * + * When removing this class, be sure to make PrintTarget::MakeDrawTarget + * non-virtual! + */ +class PrintTargetThebes final : public PrintTarget { + public: + static already_AddRefed<PrintTargetThebes> CreateOrNull( + gfxASurface* aSurface); + + nsresult BeginPrinting(const nsAString& aTitle, + const nsAString& aPrintToFileName, int32_t aStartPage, + int32_t aEndPage) override; + nsresult EndPrinting() override; + nsresult AbortPrinting() override; + nsresult BeginPage() override; + nsresult EndPage() override; + void Finish() override; + + already_AddRefed<DrawTarget> MakeDrawTarget( + const IntSize& aSize, DrawEventRecorder* aRecorder = nullptr) override; + + already_AddRefed<DrawTarget> GetReferenceDrawTarget() final; + + private: + // Only created via CreateOrNull + explicit PrintTargetThebes(gfxASurface* aSurface); + + RefPtr<gfxASurface> mGfxSurface; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_PRINTTARGETTHEBES_H */ diff --git a/gfx/thebes/PrintTargetWindows.cpp b/gfx/thebes/PrintTargetWindows.cpp new file mode 100644 index 0000000000..e97a1187e5 --- /dev/null +++ b/gfx/thebes/PrintTargetWindows.cpp @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PrintTargetWindows.h" + +#include "cairo-win32.h" +#include "mozilla/gfx/HelpersCairo.h" +#include "nsCoord.h" +#include "nsString.h" + +namespace mozilla { +namespace gfx { + +PrintTargetWindows::PrintTargetWindows(cairo_surface_t* aCairoSurface, + const IntSize& aSize, HDC aDC) + : PrintTarget(aCairoSurface, aSize), mDC(aDC) { + // TODO: At least add basic memory reporting. + // 4 * mSize.width * mSize.height + sizeof(PrintTargetWindows) ? +} + +/* static */ +already_AddRefed<PrintTargetWindows> PrintTargetWindows::CreateOrNull(HDC aDC) { + // Figure out the paper size, the actual surface size will be the printable + // area which is likely smaller, but the size here is later used to create the + // draw target where the full page size is needed. + // Note: we only scale the printing using the LOGPIXELSY, + // so we use that when calculating the surface width as well as the height. + int32_t heightDPI = ::GetDeviceCaps(aDC, LOGPIXELSY); + float width = + (::GetDeviceCaps(aDC, PHYSICALWIDTH) * POINTS_PER_INCH_FLOAT) / heightDPI; + float height = + (::GetDeviceCaps(aDC, PHYSICALHEIGHT) * POINTS_PER_INCH_FLOAT) / + heightDPI; + IntSize size = IntSize::Truncate(width, height); + + if (!Factory::CheckSurfaceSize(size)) { + return nullptr; + } + + cairo_surface_t* surface = cairo_win32_printing_surface_create(aDC); + + if (cairo_surface_status(surface)) { + return nullptr; + } + + // The new object takes ownership of our surface reference. + RefPtr<PrintTargetWindows> target = + new PrintTargetWindows(surface, size, aDC); + + return target.forget(); +} + +nsresult PrintTargetWindows::BeginPrinting(const nsAString& aTitle, + const nsAString& aPrintToFileName, + int32_t aStartPage, + int32_t aEndPage) { + const uint32_t DOC_TITLE_LENGTH = MAX_PATH - 1; + + DOCINFOW docinfo; + + nsString titleStr(aTitle); + if (titleStr.Length() > DOC_TITLE_LENGTH) { + titleStr.SetLength(DOC_TITLE_LENGTH - 3); + titleStr.AppendLiteral("..."); + } + + nsString docName(aPrintToFileName); + docinfo.cbSize = sizeof(docinfo); + docinfo.lpszDocName = + titleStr.Length() > 0 ? titleStr.get() : L"Mozilla Document"; + docinfo.lpszOutput = docName.Length() > 0 ? docName.get() : nullptr; + docinfo.lpszDatatype = nullptr; + docinfo.fwType = 0; + + // If the user selected Microsoft Print to PDF or XPS Document Printer, then + // the following StartDoc call will put up a dialog window to prompt the + // user to provide the name and location of the file to be saved. A zero or + // negative return value indicates failure. In that case we want to check + // whether that is because the user hit Cancel, since we want to treat that + // specially to avoid notifying the user that the print "failed" in that + // case. + // XXX We should perhaps introduce a new NS_ERROR_USER_CANCELLED errer. + int result = ::StartDocW(mDC, &docinfo); + if (result <= 0) { + if (::GetLastError() == ERROR_CANCELLED) { + return NS_ERROR_ABORT; + } + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult PrintTargetWindows::EndPrinting() { + int result = ::EndDoc(mDC); + return (result <= 0) ? NS_ERROR_FAILURE : NS_OK; +} + +nsresult PrintTargetWindows::AbortPrinting() { + PrintTarget::AbortPrinting(); + int result = ::AbortDoc(mDC); + return (result <= 0) ? NS_ERROR_FAILURE : NS_OK; +} + +nsresult PrintTargetWindows::BeginPage() { + PrintTarget::BeginPage(); + int result = ::StartPage(mDC); + return (result <= 0) ? NS_ERROR_FAILURE : NS_OK; +} + +nsresult PrintTargetWindows::EndPage() { + cairo_surface_show_page(mCairoSurface); + PrintTarget::EndPage(); + int result = ::EndPage(mDC); + return (result <= 0) ? NS_ERROR_FAILURE : NS_OK; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/thebes/PrintTargetWindows.h b/gfx/thebes/PrintTargetWindows.h new file mode 100644 index 0000000000..4080835e68 --- /dev/null +++ b/gfx/thebes/PrintTargetWindows.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PRINTTARGETWINDOWS_H +#define MOZILLA_GFX_PRINTTARGETWINDOWS_H + +#include "PrintTarget.h" + +/* include windows.h for the HDC definitions that we need. */ +#include <windows.h> + +namespace mozilla { +namespace gfx { + +/** + * Windows printing target. + */ +class PrintTargetWindows final : public PrintTarget { + public: + static already_AddRefed<PrintTargetWindows> CreateOrNull(HDC aDC); + + nsresult BeginPrinting(const nsAString& aTitle, + const nsAString& aPrintToFileName, int32_t aStartPage, + int32_t aEndPage) override; + nsresult EndPrinting() override; + nsresult AbortPrinting() override; + nsresult BeginPage() override; + nsresult EndPage() override; + + private: + PrintTargetWindows(cairo_surface_t* aCairoSurface, const IntSize& aSize, + HDC aDC); + HDC mDC; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_PRINTTARGETWINDOWS_H */ diff --git a/gfx/thebes/SharedFontList-impl.h b/gfx/thebes/SharedFontList-impl.h new file mode 100644 index 0000000000..7eb04cf305 --- /dev/null +++ b/gfx/thebes/SharedFontList-impl.h @@ -0,0 +1,375 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SharedFontList_impl_h +#define SharedFontList_impl_h + +#include "SharedFontList.h" + +#include "base/shared_memory.h" + +#include "gfxFontUtils.h" +#include "nsClassHashtable.h" +#include "nsDataHashtable.h" +#include "nsXULAppAPI.h" +#include "mozilla/UniquePtr.h" + +// This is split out from SharedFontList.h because that header is included +// quite widely (via gfxPlatformFontList.h, gfxTextRun.h, etc), and other code +// such as the generated DOM bindings code gets upset at (indirect) inclusion +// of <windows.h> via SharedMemoryBasic.h. So this header, which defines the +// actual shared-memory FontList class, is included only by the .cpp files that +// implement or directly interface with the font list, to avoid polluting other +// headers. + +namespace mozilla { +namespace fontlist { + +/** + * Data used to initialize a font family alias (a "virtual" family that refers + * to some or all of the faces of another family, used when alternate family + * names are found in the font resource for localization or for styled + * subfamilies). AliasData records are collected incrementally while scanning + * the fonts, and then used to set up the Aliases list in the shared font list. + */ +struct AliasData { + nsTArray<Pointer> mFaces; + nsCString mBaseFamily; + uint32_t mIndex = 0; + FontVisibility mVisibility = FontVisibility::Unknown; + bool mBundled = false; + bool mBadUnderline = false; + bool mForceClassic = false; + + void InitFromFamily(const Family* aFamily, const nsCString& aBaseFamily) { + mBaseFamily = aBaseFamily; + mIndex = aFamily->Index(); + mVisibility = aFamily->Visibility(); + mBundled = aFamily->IsBundled(); + mBadUnderline = aFamily->IsBadUnderlineFamily(); + mForceClassic = aFamily->IsForceClassic(); + } +}; + +/** + * The Shared Font List is a collection of data that lives in shared memory + * so that all processes can use it, rather than maintaining their own copies, + * and provides the metadata needed for CSS font-matching (a list of all the + * available font families and their faces, style properties, etc, as well as + * character coverage). + * + * An important assumption is that all processes see the same collection of + * installed fonts; therefore it is valid for them all to share the same set + * of font metadata. The data is updated only by the parent process; content + * processes have read-only access to it. + * + * The total size of this data varies greatly depending on the user's installed + * fonts; and it is not known at startup because we load a lot of the font data + * on first use rather than preloading during initialization (because that's + * too expensive/slow). + * + * Therefore, the shared memory area needs to be able to grow during the + * session; we can't predict how much space will be needed, and we can't afford + * to pre-allocate such a huge block that it could never overflow. To handle + * this, we maintain a (generally short) array of blocks of shared memory, + * and then allocate our Family, Face, etc. objects within these. Because we + * only ever add data (never delete what we've already stored), we can use a + * simplified allocator that doesn't ever need to free blocks; the only time + * the memory is released during a session is the (rare) case where a font is + * installed or deleted while the browser is running, and in this case we just + * delete the entire shared font list and start afresh. + */ +class FontList { + public: + friend struct Pointer; + + explicit FontList(uint32_t aGeneration); + ~FontList(); + + /** + * Initialize the master list of installed font families. This must be + * set during font-list creation, before the list is shared with any + * content processes. All installed font families known to the browser + * appear in this list, although some may be marked as "hidden" so that + * they are not exposed to the font-family property. + * + * The passed-in array may be modified (to eliminate duplicates of bundled + * fonts, or restrict the available list to a specified subset), so if the + * caller intends to make further use of it this should be kept in mind. + * + * Once initialized, the master family list is immutable; in the (rare) + * event that the system's collection of installed fonts changes, we discard + * the FontList and create a new one. + * + * In some cases, a font family may be known by multiple names (e.g. + * localizations in multiple languages, or there may be legacy family names + * that correspond to specific styled faces like "Arial Black"). Such names + * do not appear in this master list, but are referred to as aliases (see + * SetAliases below); the alias list need not be populated before the font + * list is shared to content processes and used. + * + * Only used in the parent process. + */ + void SetFamilyNames(nsTArray<Family::InitData>& aFamilies); + + /** + * Aliases are Family records whose Face entries are already part of another + * family (either because the family has multiple localized names, or because + * the alias family is a legacy name like "Arial Narrow" that is a subset of + * the faces in the main "Arial" family). The table of aliases is initialized + * from a hash of alias family name -> array of Face records. + * + * Like the master family list, the list of family aliases is immutable once + * initialized. + * + * Only used in the parent process. + */ + void SetAliases(nsClassHashtable<nsCStringHashKey, AliasData>& aAliasTable); + + /** + * Local names are PostScript or Full font names of individual faces, used + * to look up faces for @font-face { src: local(...) } rules. Some platforms + * (e.g. macOS) can look up local names directly using platform font APIs, + * in which case the local names table here is unused. + * + * The list of local names is immutable once initialized. Local font name + * lookups may occur before this list has been set up, in which case they + * will use the SearchForLocalFace method. + * + * Only used in the parent process. + */ + void SetLocalNames(nsDataHashtable<nsCStringHashKey, LocalFaceRec::InitData>& + aLocalNameTable); + + /** + * Look up a Family record by name, typically to satisfy the font-family + * property or a font family listed in preferences. + */ + Family* FindFamily(const nsCString& aName, bool aPrimaryNameOnly = false); + + /** + * Look up an individual Face by PostScript or Full name, for @font-face + * rules using src:local(...). This requires the local names list to have + * been initialized. + */ + LocalFaceRec* FindLocalFace(const nsCString& aName); + + /** + * Search families for a face with local name aName; should only be used if + * the mLocalFaces array has not yet been set up, as this will be a more + * expensive search than FindLocalFace. + */ + void SearchForLocalFace(const nsACString& aName, Family** aFamily, + Face** aFace); + + /** + * Return the localized name for the given family in the current system + * locale (if multiple localizations are available). + */ + nsCString LocalizedFamilyName(const Family* aFamily); + + bool Initialized() { return mBlocks.Length() > 0 && NumFamilies() > 0; } + + uint32_t NumFamilies() { return GetHeader().mFamilyCount; } + Family* Families() { + return static_cast<Family*>(GetHeader().mFamilies.ToPtr(this)); + } + + uint32_t NumAliases() { return GetHeader().mAliasCount; } + Family* AliasFamilies() { + return static_cast<Family*>(GetHeader().mAliases.ToPtr(this)); + } + + uint32_t NumLocalFaces() { return GetHeader().mLocalFaceCount; } + LocalFaceRec* LocalFaces() { + return static_cast<LocalFaceRec*>(GetHeader().mLocalFaces.ToPtr(this)); + } + + /** + * Ask the font list to initialize the character map for a given face. + */ + void LoadCharMapFor(Face& aFace, const Family* aFamily); + + /** + * Allocate shared-memory space for a record of aSize bytes. The returned + * pointer will be 32-bit aligned. (This method may trigger the allocation of + * a new shared memory block, if required.) + * + * Only used in the parent process. + */ + Pointer Alloc(uint32_t aSize); + + /** + * Convert a native pointer to a shared-memory Pointer record that can be + * passed between processes. + */ + Pointer ToSharedPointer(const void* aPtr); + + uint32_t GetGeneration() { return GetHeader().mGeneration; } + + /** + * Header fields present in every shared-memory block. The mBlockSize field + * is not modified after initial block creation (before the block has been + * shared to any other process), and the mAllocated field is used only by + * the parent process, so neither of these needs to be std::atomic<>. + */ + struct BlockHeader { + uint32_t mAllocated; // Space allocated from this block. + uint32_t mBlockSize; // Total size of this block. + }; + + /** + * Header info that is stored at the beginning of the first shared-memory + * block for the font list. + * (Subsequent blocks have only the mBlockHeader.) + * The mGeneration and mFamilyCount fields are set by the parent process + * during font-list construction, before the list has been shared with any + * other process, and subsequently never change; therefore, we don't need + * to use std::atomic<> for these. + */ + struct Header { + BlockHeader mBlockHeader; + uint32_t mGeneration; // Font-list generation ID + uint32_t mFamilyCount; // Number of font families in the list + std::atomic<uint32_t> mBlockCount; // Total number of blocks that exist + std::atomic<uint32_t> mAliasCount; // Number of family aliases + std::atomic<uint32_t> mLocalFaceCount; // Number of local face names + Pointer mFamilies; // Pointer to array of |mFamilyCount| families + Pointer mAliases; // Pointer to array of |mAliasCount| aliases + Pointer mLocalFaces; // Pointer to array of |mLocalFaceCount| face records + }; + + /** + * Used by the parent process to pass a handle to a shared block to a + * specific child process. This is used when a child process requests + * an additional block that was not already passed to it (because the + * list has changed/grown since the child was first initialized). + */ + void ShareShmBlockToProcess(uint32_t aIndex, base::ProcessId aPid, + base::SharedMemoryHandle* aOut) { + MOZ_RELEASE_ASSERT(mReadOnlyShmems.Length() == mBlocks.Length()); + if (aIndex >= mReadOnlyShmems.Length()) { + // Block index out of range + *aOut = base::SharedMemory::NULLHandle(); + } + if (!mReadOnlyShmems[aIndex]->ShareToProcess(aPid, aOut)) { + MOZ_CRASH("failed to share block"); + } + } + + /** + * Collect an array of handles to all the shmem blocks, ready to be + * shared to the given process. This is used at child process startup + * to pass the complete list at once. + */ + void ShareBlocksToProcess(nsTArray<base::SharedMemoryHandle>* aBlocks, + base::ProcessId aPid); + + /** + * Support for memory reporter. + */ + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + size_t AllocatedShmemSize() const; + + /** + * Using a larger block size will speed up allocation, at the cost of more + * wasted space in the shared memory (on average). + */ +#if ANDROID + // Android devices usually have a much smaller number of fonts than desktop + // systems, and memory is more constrained, so use a smaller default block + // size. + static constexpr uint32_t SHM_BLOCK_SIZE = 64 * 1024; +#else + static constexpr uint32_t SHM_BLOCK_SIZE = 256 * 1024; +#endif + static_assert(SHM_BLOCK_SIZE <= (1 << Pointer::kBlockShift), + "SHM_BLOCK_SIZE too large"); + + private: + struct ShmBlock { + // Takes ownership of aShmem. Note that in a child process, aShmem will be + // mapped as read-only. + explicit ShmBlock(mozilla::UniquePtr<base::SharedMemory>&& aShmem) + : mShmem(std::move(aShmem)) {} + + // Get pointer to the mapped memory. + void* Memory() const { return mShmem->memory(); } + + // Because only the parent process does allocation, this doesn't need to + // be an atomic. + // Further allocations may happen within the block after it has been shared + // to content processes, but as their access is read-only and they cannot + // do allocations themselves, they never look at this field; only the parent + // reads or updates it. + uint32_t& Allocated() const { + MOZ_ASSERT(XRE_IsParentProcess()); + return static_cast<BlockHeader*>(Memory())->mAllocated; + } + + // This is stored by the parent process during block creation and never + // changes, so does not need to be atomic. + // Note that some blocks may be larger than SHM_BLOCK_SIZE, if needed for + // individual large allocations. + uint32_t& BlockSize() const { + MOZ_ASSERT(XRE_IsParentProcess()); + return static_cast<BlockHeader*>(Memory())->mBlockSize; + } + + mozilla::UniquePtr<base::SharedMemory> mShmem; + }; + + Header& GetHeader() { + // It's invalid to try and access this before the first block exists. + MOZ_ASSERT(mBlocks.Length() > 0); + return *static_cast<Header*>(Pointer(0, 0).ToPtr(this)); + } + + /** + * Create a new shared memory block and append to the FontList's list + * of blocks. + * + * Only used in the parent process. + */ + bool AppendShmBlock(uint32_t aSizeNeeded); + + /** + * Used by child processes to ensure all the blocks are registered. + * Returns false on failure. + */ + [[nodiscard]] bool UpdateShmBlocks(); + + /** + * This makes a *sync* IPC call to get a shared block from the parent. + * As such, it may block for a while if the parent is busy; fortunately, + * we'll generally only call this a handful of times in the course of an + * entire session. If the requested block does not yet exist (because the + * child is wanting to allocate an object, and there wasn't room in any + * existing block), the parent will create a new shared block and return it. + * This may (in rare cases) return null, if the parent has recreated the + * font list and we actually need to reinitialize. + */ + ShmBlock* GetBlockFromParent(uint32_t aIndex); + + void DetachShmBlocks(); + + /** + * Array of pointers to the shared-memory block records. + * NOTE: if mBlocks.Length() < GetHeader().mBlockCount, then the parent has + * added a block (or blocks) to the list, and we need to update! + */ + nsTArray<mozilla::UniquePtr<ShmBlock>> mBlocks; + + /** + * Auxiliary array, used only in the parent process; holds read-only copies + * of the shmem blocks; these are what will be shared to child processes. + */ + nsTArray<mozilla::UniquePtr<base::SharedMemory>> mReadOnlyShmems; +}; + +} // namespace fontlist +} // namespace mozilla + +#endif /* SharedFontList_impl_h */ diff --git a/gfx/thebes/SharedFontList.cpp b/gfx/thebes/SharedFontList.cpp new file mode 100644 index 0000000000..f7e2cb67f2 --- /dev/null +++ b/gfx/thebes/SharedFontList.cpp @@ -0,0 +1,1131 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SharedFontList-impl.h" +#include "gfxPlatformFontList.h" +#include "gfxFontUtils.h" +#include "gfxFont.h" +#include "nsReadableUtils.h" +#include "prerror.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/Logging.h" + +#define LOG_FONTLIST(args) \ + MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug, args) +#define LOG_FONTLIST_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug) + +namespace mozilla { +namespace fontlist { + +static double WSSDistance(const Face* aFace, const gfxFontStyle& aStyle) { + double stretchDist = StretchDistance(aFace->mStretch, aStyle.stretch); + double styleDist = StyleDistance(aFace->mStyle, aStyle.style); + double weightDist = WeightDistance(aFace->mWeight, aStyle.weight); + + // Sanity-check that the distances are within the expected range + // (update if implementation of the distance functions is changed). + MOZ_ASSERT(stretchDist >= 0.0 && stretchDist <= 2000.0); + MOZ_ASSERT(styleDist >= 0.0 && styleDist <= 500.0); + MOZ_ASSERT(weightDist >= 0.0 && weightDist <= 1600.0); + + // weight/style/stretch priority: stretch >> style >> weight + // so we multiply the stretch and style values to make them dominate + // the result + return stretchDist * kStretchFactor + styleDist * kStyleFactor + + weightDist * kWeightFactor; +} + +void* Pointer::ToPtr(FontList* aFontList) const { + if (IsNull()) { + return nullptr; + } + uint32_t block = Block(); + // If the Pointer refers to a block we have not yet mapped in this process, + // we first need to retrieve new block handle(s) from the parent and update + // our mBlocks list. + if (block >= aFontList->mBlocks.Length()) { + if (XRE_IsParentProcess()) { + // Shouldn't happen! A content process tried to pass a bad Pointer? + return nullptr; + } + // UpdateShmBlocks can fail, if the parent has replaced the font list with + // a new generation. In that case we just return null, and whatever font + // the content process was trying to use will appear unusable for now. It's + // about to receive a notification of the new font list anyhow, at which + // point it will flush its caches and reflow everything, so the temporary + // failure of this font will be forgotten. + // We also return null if we're not on the main thread, as we cannot safely + // do the IPC messaging needed here. + if (!NS_IsMainThread() || !aFontList->UpdateShmBlocks()) { + return nullptr; + } + MOZ_ASSERT(block < aFontList->mBlocks.Length()); + } + return static_cast<char*>(aFontList->mBlocks[block]->Memory()) + Offset(); +} + +void String::Assign(const nsACString& aString, FontList* aList) { + // We only assign to previously-empty strings; they are never modified + // after initial assignment. + MOZ_ASSERT(mPointer.IsNull()); + mLength = aString.Length(); + mPointer = aList->Alloc(mLength + 1); + char* p = static_cast<char*>(mPointer.ToPtr(aList)); + std::memcpy(p, aString.BeginReading(), mLength); + p[mLength] = '\0'; +} + +Family::Family(FontList* aList, const InitData& aData) + : mFaceCount(0), + mKey(aList, aData.mKey), + mName(aList, aData.mName), + mCharacterMap(Pointer::Null()), + mFaces(Pointer::Null()), + mIndex(aData.mIndex), + mVisibility(aData.mVisibility), + mIsSimple(false), + mIsBundled(aData.mBundled), + mIsBadUnderlineFamily(aData.mBadUnderline), + mIsForceClassic(aData.mForceClassic), + mIsAltLocale(aData.mAltLocale) {} + +class SetCharMapRunnable : public mozilla::Runnable { + public: + SetCharMapRunnable(uint32_t aListGeneration, Face* aFace, + gfxCharacterMap* aCharMap) + : Runnable("SetCharMapRunnable"), + mListGeneration(aListGeneration), + mFace(aFace), + mCharMap(aCharMap) {} + + NS_IMETHOD Run() override { + auto* list = gfxPlatformFontList::PlatformFontList()->SharedFontList(); + if (!list || list->GetGeneration() != mListGeneration) { + return NS_OK; + } + dom::ContentChild::GetSingleton()->SendSetCharacterMap( + mListGeneration, list->ToSharedPointer(mFace), *mCharMap); + return NS_OK; + } + + private: + uint32_t mListGeneration; + Face* mFace; + RefPtr<gfxCharacterMap> mCharMap; +}; + +void Face::SetCharacterMap(FontList* aList, gfxCharacterMap* aCharMap) { + if (!XRE_IsParentProcess()) { + if (NS_IsMainThread()) { + Pointer ptr = aList->ToSharedPointer(this); + dom::ContentChild::GetSingleton()->SendSetCharacterMap( + aList->GetGeneration(), ptr, *aCharMap); + } else { + NS_DispatchToMainThread( + new SetCharMapRunnable(aList->GetGeneration(), this, aCharMap)); + } + return; + } + auto pfl = gfxPlatformFontList::PlatformFontList(); + mCharacterMap = pfl->GetShmemCharMap(aCharMap); +} + +void Family::AddFaces(FontList* aList, const nsTArray<Face::InitData>& aFaces) { + MOZ_ASSERT(XRE_IsParentProcess()); + if (mFaceCount > 0) { + // Already initialized! + return; + } + + uint32_t count = aFaces.Length(); + bool isSimple = false; + // A family is "simple" (i.e. simplified style selection may be used instead + // of the full CSS font-matching algorithm) if there is at maximum one normal, + // bold, italic, and bold-italic face; in this case, they are stored at known + // positions in the mFaces array. + const Face::InitData* slots[4] = {nullptr, nullptr, nullptr, nullptr}; + if (count >= 2 && count <= 4) { + // Check if this can be treated as a "simple" family + isSimple = true; + for (const auto& f : aFaces) { + if (!f.mWeight.IsSingle() || !f.mStretch.IsSingle() || + !f.mStyle.IsSingle()) { + isSimple = false; + break; + } + if (!f.mStretch.Min().IsNormal()) { + isSimple = false; + break; + } + // Figure out which slot (0-3) this face belongs in + size_t slot = 0; + static_assert((kBoldMask | kItalicMask) == 0b11, "bad bold/italic bits"); + if (f.mWeight.Min().IsBold()) { + slot |= kBoldMask; + } + if (f.mStyle.Min().IsItalic() || f.mStyle.Min().IsOblique()) { + slot |= kItalicMask; + } + if (slots[slot]) { + // More than one face mapped to the same slot - not a simple family! + isSimple = false; + break; + } + slots[slot] = &f; + } + if (isSimple) { + // Ensure all 4 slots will exist, even if some are empty. + count = 4; + } + } + + // Allocate space for the face records, and initialize them. + // coverity[suspicious_sizeof] + Pointer p = aList->Alloc(count * sizeof(Pointer)); + auto facePtrs = static_cast<Pointer*>(p.ToPtr(aList)); + for (size_t i = 0; i < count; i++) { + if (isSimple && !slots[i]) { + facePtrs[i] = Pointer::Null(); + } else { + const auto* initData = isSimple ? slots[i] : &aFaces[i]; + Pointer fp = aList->Alloc(sizeof(Face)); + auto* face = static_cast<Face*>(fp.ToPtr(aList)); + (void)new (face) Face(aList, *initData); + facePtrs[i] = fp; + if (initData->mCharMap) { + face->SetCharacterMap(aList, initData->mCharMap); + } + } + } + + mIsSimple = isSimple; + mFaces = p; + mFaceCount.store(count); + + if (LOG_FONTLIST_ENABLED()) { + const nsCString& fam = DisplayName().AsString(aList); + for (unsigned j = 0; j < aFaces.Length(); j++) { + nsAutoCString weight, style, stretch; + aFaces[j].mWeight.ToString(weight); + aFaces[j].mStyle.ToString(style); + aFaces[j].mStretch.ToString(stretch); + LOG_FONTLIST( + ("(shared-fontlist) family (%s) added face (%s) index %u, weight " + "%s, style %s, stretch %s", + fam.get(), aFaces[j].mDescriptor.get(), aFaces[j].mIndex, + weight.get(), style.get(), stretch.get())); + } + } +} + +void Family::FindAllFacesForStyle(FontList* aList, const gfxFontStyle& aStyle, + nsTArray<Face*>& aFaceList, + bool aIgnoreSizeTolerance) const { + MOZ_ASSERT(aFaceList.IsEmpty()); + if (!IsInitialized()) { + return; + } + + Pointer* facePtrs = Faces(aList); + if (!facePtrs) { + return; + } + + // If the family has only one face, we simply return it; no further + // checking needed. + if (NumFaces() == 1) { + MOZ_ASSERT(!facePtrs[0].IsNull()); + aFaceList.AppendElement(static_cast<Face*>(facePtrs[0].ToPtr(aList))); + 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 (mIsSimple) { + // Family has no more than the "standard" 4 faces, at fixed indexes; + // calculate which one we want. + // Note that we cannot simply return it as not all 4 faces are necessarily + // present. + bool wantBold = aStyle.weight >= FontWeight(600); + bool wantItalic = !aStyle.style.IsNormal(); + uint8_t faceIndex = + (wantItalic ? kItalicMask : 0) | (wantBold ? kBoldMask : 0); + + // if the desired style is available, return it directly + Face* face = static_cast<Face*>(facePtrs[faceIndex].ToPtr(aList)); + if (face && face->HasValidDescriptor()) { + aFaceList.AppendElement(face); + return; + } + + // order to check fallback faces in a simple family, depending on requested + // style + static const uint8_t simpleFallbacks[4][3] = { + {kBoldFaceIndex, kItalicFaceIndex, + kBoldItalicFaceIndex}, // fallback sequence for Regular + {kRegularFaceIndex, kBoldItalicFaceIndex, kItalicFaceIndex}, // Bold + {kBoldItalicFaceIndex, kRegularFaceIndex, kBoldFaceIndex}, // Italic + {kItalicFaceIndex, kBoldFaceIndex, kRegularFaceIndex} // BoldItalic + }; + const uint8_t* order = simpleFallbacks[faceIndex]; + + for (uint8_t trial = 0; trial < 3; ++trial) { + // check remaining faces in order of preference to find the first that + // actually exists + face = static_cast<Face*>(facePtrs[order[trial]].ToPtr(aList)); + if (face && face->HasValidDescriptor()) { + aFaceList.AppendElement(face); + return; + } + } + + // We can only reach here if we failed to resolve the face pointer, which + // can happen if we're on a stylo thread and caught the font list being + // updated; in that case we just fail quietly and let font fallback do + // something for the time being. + return; + } + + // Pick the font(s) that are closest to the desired weight, style, and + // stretch. Iterate over all fonts, measuring the weight/style distance. + // Because of unicode-range values, there may be more than one font for a + // given but the 99% use case is only a single font entry per + // weight/style/stretch distance value. To optimize this, only add entries + // to the matched font array when another entry already has the same + // weight/style/stretch distance and add the last matched font entry. For + // normal platform fonts with a single font entry for each + // weight/style/stretch combination, only the last matched font entry will + // be added. + + double minDistance = INFINITY; + Face* matched = nullptr; + for (uint32_t i = 0; i < NumFaces(); i++) { + Face* face = static_cast<Face*>(facePtrs[i].ToPtr(aList)); + if (face) { + // weight/style/stretch priority: stretch >> style >> weight + double distance = WSSDistance(face, aStyle); + if (distance < minDistance) { + matched = face; + if (!aFaceList.IsEmpty()) { + aFaceList.Clear(); + } + minDistance = distance; + } else if (distance == minDistance) { + if (matched) { + aFaceList.AppendElement(matched); + } + matched = face; + } + } + } + + MOZ_ASSERT(matched, "didn't match a font within a family"); + if (matched) { + aFaceList.AppendElement(matched); + } +} + +Face* Family::FindFaceForStyle(FontList* aList, const gfxFontStyle& aStyle, + bool aIgnoreSizeTolerance) const { + AutoTArray<Face*, 4> faces; + FindAllFacesForStyle(aList, aStyle, faces, aIgnoreSizeTolerance); + return faces.IsEmpty() ? nullptr : faces[0]; +} + +void Family::SearchAllFontsForChar(FontList* aList, + GlobalFontMatch* aMatchData) { + const SharedBitSet* charmap = + static_cast<const SharedBitSet*>(mCharacterMap.ToPtr(aList)); + if (!charmap) { + // If the face list is not yet initialized, or if character maps have + // not been loaded, go ahead and do this now (by sending a message to the + // parent process, if we're running in a child). + // After this, all faces should have their mCharacterMap set up, and the + // family's mCharacterMap should also be set; but in the code below we + // don't assume this all succeeded, so it still checks. + if (!gfxPlatformFontList::PlatformFontList()->InitializeFamily(this, + true)) { + return; + } + charmap = static_cast<const SharedBitSet*>(mCharacterMap.ToPtr(aList)); + } + if (charmap && !charmap->test(aMatchData->mCh)) { + return; + } + + uint32_t numFaces = NumFaces(); + uint32_t charMapsLoaded = 0; // number of faces whose charmap is loaded + Pointer* facePtrs = Faces(aList); + if (!facePtrs) { + return; + } + for (uint32_t i = 0; i < numFaces; i++) { + Face* face = static_cast<Face*>(facePtrs[i].ToPtr(aList)); + if (!face) { + continue; + } + MOZ_ASSERT(face->HasValidDescriptor()); + // Get the face's character map, if available (may be null!) + charmap = + static_cast<const SharedBitSet*>(face->mCharacterMap.ToPtr(aList)); + if (charmap) { + ++charMapsLoaded; + } + // Check style distance if the char is supported, or if charmap not known + // (so that we don't trigger cmap-loading for faces that would be a worse + // match than what we've already found). + if (!charmap || charmap->test(aMatchData->mCh)) { + double distance = WSSDistance(face, aMatchData->mStyle); + if (distance < aMatchData->mMatchDistance) { + // It's a better style match: get a fontEntry, and if we haven't + // already checked character coverage, do it now (note that + // HasCharacter() will trigger loading the fontEntry's cmap, if + // needed). + RefPtr<gfxFontEntry> fe = + gfxPlatformFontList::PlatformFontList()->GetOrCreateFontEntry(face, + this); + if (!fe) { + continue; + } + if (!charmap && !fe->HasCharacter(aMatchData->mCh)) { + continue; + } + if (aMatchData->mPresentation != eFontPresentation::Any) { + RefPtr<gfxFont> font = fe->FindOrMakeFont(&aMatchData->mStyle); + if (!font) { + continue; + } + bool hasColorGlyph = + font->HasColorGlyphFor(aMatchData->mCh, aMatchData->mNextCh); + if (hasColorGlyph != PrefersColor(aMatchData->mPresentation)) { + distance += kPresentationMismatch; + if (distance >= aMatchData->mMatchDistance) { + continue; + } + } + } + aMatchData->mBestMatch = fe; + aMatchData->mMatchDistance = distance; + aMatchData->mMatchedSharedFamily = this; + } + } + } + if (mCharacterMap.IsNull() && charMapsLoaded == numFaces) { + SetupFamilyCharMap(aList); + } +} + +void Family::SetFacePtrs(FontList* aList, nsTArray<Pointer>& aFaces) { + if (aFaces.Length() >= 2 && aFaces.Length() <= 4) { + // Check whether the faces meet the criteria for a "simple" family: no more + // than one each of Regular, Bold, Italic, BoldItalic styles. If so, store + // them at the appropriate slots in mFaces and set the mIsSimple flag to + // accelerate font-matching. + bool isSimple = true; + Pointer slots[4] = {Pointer::Null(), Pointer::Null(), Pointer::Null(), + Pointer::Null()}; + for (const Pointer& fp : aFaces) { + const Face* f = static_cast<const Face*>(fp.ToPtr(aList)); + if (!f->mWeight.IsSingle() || !f->mStyle.IsSingle() || + !f->mStretch.IsSingle()) { + isSimple = false; + break; + } + if (!f->mStretch.Min().IsNormal()) { + isSimple = false; + break; + } + size_t slot = 0; + if (f->mWeight.Min().IsBold()) { + slot |= kBoldMask; + } + if (f->mStyle.Min().IsItalic() || f->mStyle.Min().IsOblique()) { + slot |= kItalicMask; + } + if (!slots[slot].IsNull()) { + isSimple = false; + break; + } + slots[slot] = fp; + } + if (isSimple) { + size_t size = 4 * sizeof(Pointer); + mFaces = aList->Alloc(size); + memcpy(mFaces.ToPtr(aList), slots, size); + mFaceCount.store(4); + mIsSimple = true; + return; + } + } + size_t size = aFaces.Length() * sizeof(Pointer); + mFaces = aList->Alloc(size); + memcpy(mFaces.ToPtr(aList), aFaces.Elements(), size); + mFaceCount.store(aFaces.Length()); +} + +void Family::SetupFamilyCharMap(FontList* aList) { + // Set the character map of the family to the union of all the face cmaps, + // to allow font fallback searches to more rapidly reject the family. + if (!mCharacterMap.IsNull()) { + return; + } + if (!XRE_IsParentProcess()) { + // |this| could be a Family record in either the Families() or Aliases() + // arrays + dom::ContentChild::GetSingleton()->SendSetupFamilyCharMap( + aList->GetGeneration(), aList->ToSharedPointer(this)); + return; + } + gfxSparseBitSet familyMap; + Pointer firstMapShmPointer; + SharedBitSet* firstMap = nullptr; + bool merged = false; + Pointer* faces = Faces(aList); + if (!faces) { + return; + } + for (size_t i = 0; i < NumFaces(); i++) { + auto f = static_cast<Face*>(faces[i].ToPtr(aList)); + if (!f) { + continue; // Skip missing face (in an incomplete "simple" family) + } + auto faceMap = static_cast<SharedBitSet*>(f->mCharacterMap.ToPtr(aList)); + if (!faceMap) { + continue; // If there's a face where setting up the cmap failed, we skip + // it as unusable. + } + if (!firstMap) { + firstMap = faceMap; + firstMapShmPointer = f->mCharacterMap; + } else if (faceMap != firstMap) { + if (!merged) { + familyMap.Union(*firstMap); + merged = true; + } + familyMap.Union(*faceMap); + } + } + // If we created a merged cmap, we need to save that on the family; or if we + // found no usable cmaps at all, we need to store the empty familyMap so that + // we won't repeatedly attempt this for an unusable family. + if (merged || firstMapShmPointer.IsNull()) { + mCharacterMap = + gfxPlatformFontList::PlatformFontList()->GetShmemCharMap(&familyMap); + } else { + // If all [usable] faces had the same cmap, we can just share it. + mCharacterMap = firstMapShmPointer; + } +} + +FontList::FontList(uint32_t aGeneration) { + if (XRE_IsParentProcess()) { + // Create the initial shared block, and initialize Header + if (AppendShmBlock(SHM_BLOCK_SIZE)) { + Header& header = GetHeader(); + header.mBlockHeader.mAllocated = sizeof(Header); + header.mGeneration = aGeneration; + header.mFamilyCount = 0; + header.mBlockCount.store(1); + header.mAliasCount.store(0); + header.mLocalFaceCount.store(0); + header.mFamilies = Pointer::Null(); + header.mAliases = Pointer::Null(); + header.mLocalFaces = Pointer::Null(); + } else { + MOZ_CRASH("parent: failed to initialize FontList"); + } + } else { + // Initialize using the list of shmem blocks passed by the parent via + // SetXPCOMProcessAttributes. + auto& blocks = dom::ContentChild::GetSingleton()->SharedFontListBlocks(); + for (auto handle : blocks) { + auto newShm = MakeUnique<base::SharedMemory>(); + if (!newShm->IsHandleValid(handle)) { + // Bail out and let UpdateShmBlocks try to do its thing below. + break; + } + if (!newShm->SetHandle(handle, true)) { + MOZ_CRASH("failed to set shm handle"); + } + if (!newShm->Map(SHM_BLOCK_SIZE) || !newShm->memory()) { + MOZ_CRASH("failed to map shared memory"); + } + uint32_t size = static_cast<BlockHeader*>(newShm->memory())->mBlockSize; + MOZ_ASSERT(size >= SHM_BLOCK_SIZE); + if (size != SHM_BLOCK_SIZE) { + newShm->Unmap(); + if (!newShm->Map(size) || !newShm->memory()) { + MOZ_CRASH("failed to map shared memory"); + } + } + mBlocks.AppendElement(new ShmBlock(std::move(newShm))); + } + blocks.Clear(); + // Update in case of any changes since the initial message was sent. + for (unsigned retryCount = 0; retryCount < 3; ++retryCount) { + if (UpdateShmBlocks()) { + return; + } + // The only reason for UpdateShmBlocks to fail is if the parent recreated + // the list after we read its first block, but before we finished getting + // them all, and so the generation check failed on a subsequent request. + // Discarding whatever we've got and retrying should get us a new, + // consistent set of memory blocks in this case. If this doesn't work + // after a couple of retries, bail out. + DetachShmBlocks(); + } + NS_WARNING("child: failed to initialize shared FontList"); + } +} + +FontList::~FontList() { DetachShmBlocks(); } + +bool FontList::AppendShmBlock(uint32_t aSizeNeeded) { + MOZ_ASSERT(XRE_IsParentProcess()); + uint32_t size = std::max(aSizeNeeded, SHM_BLOCK_SIZE); + auto newShm = MakeUnique<base::SharedMemory>(); + if (!newShm->CreateFreezeable(size)) { + MOZ_CRASH("failed to create shared memory"); + return false; + } + if (!newShm->Map(size) || !newShm->memory()) { + MOZ_CRASH("failed to map shared memory"); + return false; + } + auto readOnly = MakeUnique<base::SharedMemory>(); + if (!newShm->ReadOnlyCopy(readOnly.get())) { + MOZ_CRASH("failed to create read-only copy"); + return false; + } + + ShmBlock* block = new ShmBlock(std::move(newShm)); + block->Allocated() = sizeof(BlockHeader); + block->BlockSize() = size; + + mBlocks.AppendElement(block); + GetHeader().mBlockCount.store(mBlocks.Length()); + + mReadOnlyShmems.AppendElement(std::move(readOnly)); + + return true; +} + +void FontList::DetachShmBlocks() { + for (auto& i : mBlocks) { + i->mShmem = nullptr; + } + mBlocks.Clear(); + mReadOnlyShmems.Clear(); +} + +FontList::ShmBlock* FontList::GetBlockFromParent(uint32_t aIndex) { + MOZ_ASSERT(!XRE_IsParentProcess()); + // If we have no existing blocks, we don't want a generation check yet; + // the header in the first block will define the generation of this list + uint32_t generation = aIndex == 0 ? 0 : GetGeneration(); + base::SharedMemoryHandle handle = base::SharedMemory::NULLHandle(); + if (!dom::ContentChild::GetSingleton()->SendGetFontListShmBlock( + generation, aIndex, &handle)) { + return nullptr; + } + auto newShm = MakeUnique<base::SharedMemory>(); + if (!newShm->IsHandleValid(handle)) { + return nullptr; + } + if (!newShm->SetHandle(handle, true)) { + MOZ_CRASH("failed to set shm handle"); + } + if (!newShm->Map(SHM_BLOCK_SIZE) || !newShm->memory()) { + MOZ_CRASH("failed to map shared memory"); + } + uint32_t size = static_cast<BlockHeader*>(newShm->memory())->mBlockSize; + MOZ_ASSERT(size >= SHM_BLOCK_SIZE); + if (size != SHM_BLOCK_SIZE) { + newShm->Unmap(); + if (!newShm->Map(size) || !newShm->memory()) { + MOZ_CRASH("failed to map shared memory"); + } + } + return new ShmBlock(std::move(newShm)); +} + +bool FontList::UpdateShmBlocks() { + MOZ_ASSERT(!XRE_IsParentProcess()); + while (!mBlocks.Length() || mBlocks.Length() < GetHeader().mBlockCount) { + ShmBlock* newBlock = GetBlockFromParent(mBlocks.Length()); + if (!newBlock) { + return false; + } + mBlocks.AppendElement(newBlock); + } + return true; +} + +void FontList::ShareBlocksToProcess(nsTArray<base::SharedMemoryHandle>* aBlocks, + base::ProcessId aPid) { + MOZ_RELEASE_ASSERT(mReadOnlyShmems.Length() == mBlocks.Length()); + for (auto& shmem : mReadOnlyShmems) { + base::SharedMemoryHandle* handle = + aBlocks->AppendElement(base::SharedMemory::NULLHandle()); + if (!shmem->ShareToProcess(aPid, handle)) { + // If something went wrong here, we just bail out; the child will need to + // request the blocks as needed, at some performance cost. (Although in + // practice this may mean resources are so constrained the child process + // isn't really going to work at all. But that's not our problem here.) + aBlocks->Clear(); + return; + } + } +} + +Pointer FontList::Alloc(uint32_t aSize) { + // Only the parent process does allocation. + MOZ_ASSERT(XRE_IsParentProcess()); + + // 4-byte alignment is good enough for anything we allocate in the font list, + // as our "Pointer" (block index/offset) is a 32-bit value even on x64. + auto align = [](uint32_t aSize) -> size_t { return (aSize + 3u) & ~3u; }; + + aSize = align(aSize); + + int32_t blockIndex = -1; + uint32_t curAlloc, size; + + if (aSize < SHM_BLOCK_SIZE - sizeof(BlockHeader)) { + // Try to allocate in the most recently added block first, as this is + // highly likely to succeed; if not, try earlier blocks (to fill gaps). + const int32_t blockCount = mBlocks.Length(); + for (blockIndex = blockCount - 1; blockIndex >= 0; --blockIndex) { + size = mBlocks[blockIndex]->BlockSize(); + curAlloc = mBlocks[blockIndex]->Allocated(); + if (size - curAlloc >= aSize) { + break; + } + } + } + + if (blockIndex < 0) { + // Couldn't find enough space (or the requested size is too large to use + // a part of a block): create a new block. + if (!AppendShmBlock(aSize + sizeof(BlockHeader))) { + return Pointer::Null(); + } + blockIndex = mBlocks.Length() - 1; + curAlloc = mBlocks[blockIndex]->Allocated(); + } + + // We've found a block; allocate space from it, and return + mBlocks[blockIndex]->Allocated() = curAlloc + aSize; + + return Pointer(blockIndex, curAlloc); +} + +void FontList::SetFamilyNames(nsTArray<Family::InitData>& aFamilies) { + // Only the parent process should ever assign the list of families. + MOZ_ASSERT(XRE_IsParentProcess()); + + Header& header = GetHeader(); + MOZ_ASSERT(!header.mFamilyCount); + + gfxPlatformFontList::PlatformFontList()->ApplyWhitelist(aFamilies); + aFamilies.Sort(); + + size_t count = aFamilies.Length(); + + // Check for duplicate family entries (can occur if there is a bundled font + // that has the same name as a system-installed one); in this case we keep + // the bundled one as it will always be exposed. + if (count > 1) { + const nsCString* prevKey = &aFamilies[0].mKey; + for (size_t i = 1; i < count; ++i) { + if (aFamilies[i].mKey.Equals(*prevKey)) { + // Decide whether to discard the current entry or the preceding one + size_t discard = + aFamilies[i].mBundled && !aFamilies[i - 1].mBundled ? i - 1 : i; + aFamilies.RemoveElementAt(discard); + --count; + --i; + } + } + } + + header.mFamilies = Alloc(count * sizeof(Family)); + if (header.mFamilies.IsNull()) { + return; + } + + Family* families = static_cast<Family*>(header.mFamilies.ToPtr(this)); + for (size_t i = 0; i < count; i++) { + (void)new (&families[i]) Family(this, aFamilies[i]); + LOG_FONTLIST(("(shared-fontlist) family %u (%s)", (unsigned)i, + aFamilies[i].mName.get())); + } + + header.mFamilyCount = count; +} + +void FontList::SetAliases( + nsClassHashtable<nsCStringHashKey, AliasData>& aAliasTable) { + MOZ_ASSERT(XRE_IsParentProcess()); + + Header& header = GetHeader(); + + // Build an array of Family::InitData records based on the entries in + // aAliasTable, then sort them and store into the fontlist. + nsTArray<Family::InitData> aliasArray; + aliasArray.SetCapacity(aAliasTable.Count()); + for (auto i = aAliasTable.Iter(); !i.Done(); i.Next()) { + aliasArray.AppendElement(Family::InitData( + i.Key(), i.Data()->mBaseFamily, i.Data()->mIndex, i.Data()->mVisibility, + i.Data()->mBundled, i.Data()->mBadUnderline, i.Data()->mForceClassic, + true)); + } + aliasArray.Sort(); + + size_t count = aliasArray.Length(); + if (count < header.mAliasCount) { + // This shouldn't happen, but handle it safely by just bailing out. + NS_WARNING("cannot reduce number of aliases"); + return; + } + fontlist::Pointer ptr = Alloc(count * sizeof(Family)); + Family* aliases = static_cast<Family*>(ptr.ToPtr(this)); + for (size_t i = 0; i < count; i++) { + (void)new (&aliases[i]) Family(this, aliasArray[i]); + LOG_FONTLIST(("(shared-fontlist) alias family %u (%s)", (unsigned)i, + aliasArray[i].mName.get())); + aliases[i].SetFacePtrs(this, aAliasTable.Get(aliasArray[i].mKey)->mFaces); + if (LOG_FONTLIST_ENABLED()) { + const auto& faces = aAliasTable.Get(aliasArray[i].mKey)->mFaces; + for (unsigned j = 0; j < faces.Length(); j++) { + auto face = static_cast<const fontlist::Face*>(faces[j].ToPtr(this)); + const nsCString& desc = face->mDescriptor.AsString(this); + nsAutoCString weight, style, stretch; + face->mWeight.ToString(weight); + face->mStyle.ToString(style); + face->mStretch.ToString(stretch); + LOG_FONTLIST( + ("(shared-fontlist) face (%s) index %u, weight %s, style %s, " + "stretch %s", + desc.get(), face->mIndex, weight.get(), style.get(), + stretch.get())); + } + } + } + + // Set the pointer before the count, so that any other process trying to read + // will not risk out-of-bounds access to the array. + header.mAliases = ptr; + header.mAliasCount.store(count); +} + +void FontList::SetLocalNames( + nsDataHashtable<nsCStringHashKey, LocalFaceRec::InitData>& + aLocalNameTable) { + MOZ_ASSERT(XRE_IsParentProcess()); + Header& header = GetHeader(); + if (header.mLocalFaceCount > 0) { + return; // already been done! + } + nsTArray<nsCString> faceArray; + faceArray.SetCapacity(aLocalNameTable.Count()); + for (auto i = aLocalNameTable.Iter(); !i.Done(); i.Next()) { + faceArray.AppendElement(i.Key()); + } + faceArray.Sort(); + size_t count = faceArray.Length(); + Family* families = Families(); + fontlist::Pointer ptr = Alloc(count * sizeof(LocalFaceRec)); + LocalFaceRec* faces = static_cast<LocalFaceRec*>(ptr.ToPtr(this)); + for (size_t i = 0; i < count; i++) { + (void)new (&faces[i]) LocalFaceRec(); + const auto& rec = aLocalNameTable.Get(faceArray[i]); + faces[i].mKey.Assign(faceArray[i], this); + // Local face name records will refer to the canonical family name; we don't + // need to search aliases here. + const auto* family = FindFamily(rec.mFamilyName, /*aPrimaryNameOnly*/ true); + if (!family) { + // Skip this record if the family was excluded by the font whitelist pref. + continue; + } + faces[i].mFamilyIndex = family - families; + if (rec.mFaceIndex == uint32_t(-1)) { + // The InitData record contains an mFaceDescriptor rather than an index, + // so now we need to look for the appropriate index in the family. + faces[i].mFaceIndex = 0; + const Pointer* faceList = + static_cast<const Pointer*>(family->Faces(this)); + for (uint32_t j = 0; j < family->NumFaces(); j++) { + if (!faceList[j].IsNull()) { + const Face* f = static_cast<const Face*>(faceList[j].ToPtr(this)); + if (f && rec.mFaceDescriptor == f->mDescriptor.AsString(this)) { + faces[i].mFaceIndex = j; + break; + } + } + } + } else { + faces[i].mFaceIndex = rec.mFaceIndex; + } + } + header.mLocalFaces = ptr; + header.mLocalFaceCount.store(count); +} + +nsCString FontList::LocalizedFamilyName(const Family* aFamily) { + // If the given family was created for an alternate locale or legacy name, + // search for a standard family that corresponds to it. This is a linear + // search of the font list, but (a) this is only used to show names in + // Preferences, so is not performance-critical for layout etc.; and (b) few + // such family names are normally present anyway, the vast majority of fonts + // just have a single family name and we return it directly. + if (aFamily->IsAltLocaleFamily()) { + // Currently only the Windows backend actually does this; on other systems, + // the family index is unused and will be kNoIndex for all fonts. + if (aFamily->Index() != Family::kNoIndex) { + const Family* families = Families(); + for (uint32_t i = 0; i < NumFamilies(); ++i) { + if (families[i].Index() == aFamily->Index() && + families[i].IsBundled() == aFamily->IsBundled() && + !families[i].IsAltLocaleFamily()) { + return families[i].DisplayName().AsString(this); + } + } + } + } + + // For standard families (or if we failed to find the expected standard + // family for some reason), just return the DisplayName. + return aFamily->DisplayName().AsString(this); +} + +Family* FontList::FindFamily(const nsCString& aName, bool aPrimaryNameOnly) { + struct FamilyNameComparator { + FamilyNameComparator(FontList* aList, const nsCString& aTarget) + : mList(aList), mTarget(aTarget) {} + + int operator()(const Family& aVal) const { + return mTarget.Compare(aVal.Key().BeginReading(mList)); + } + + private: + FontList* mList; + const nsCString& mTarget; + }; + + Header& header = GetHeader(); + + Family* families = Families(); + if (!families) { + return nullptr; + } + + size_t match; + if (BinarySearchIf(families, 0, header.mFamilyCount, + FamilyNameComparator(this, aName), &match)) { + return &families[match]; + } + + if (aPrimaryNameOnly) { + return nullptr; + } + + if (header.mAliasCount) { + Family* aliases = AliasFamilies(); + size_t match; + if (aliases && BinarySearchIf(aliases, 0, header.mAliasCount, + FamilyNameComparator(this, aName), &match)) { + return &aliases[match]; + } + } + +#ifdef XP_WIN + // For Windows only, because of how DWrite munges font family names in some + // cases (see + // https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Components.PostAttachments/00/02/24/90/36/WPF%20Font%20Selection%20Model.pdf + // and discussion on the OpenType list), try stripping a possible style-name + // suffix from the end of the requested family name. + // After the deferred font loader has finished, this is no longer needed as + // the "real" family names will have been found in AliasFamilies() above. + if (aName.Contains(' ')) { + auto pfl = gfxPlatformFontList::PlatformFontList(); + if (header.mAliasCount) { + // Aliases have been fully loaded by the parent process, so just discard + // any stray mAliasTable and mLocalNameTable entries from earlier calls + // to this code, and return. + pfl->mAliasTable.Clear(); + pfl->mLocalNameTable.Clear(); + return nullptr; + } + + // Do we already have an aliasData record for this name? If so, we just + // return its base family. + if (auto lookup = pfl->mAliasTable.Lookup(aName)) { + return FindFamily(lookup.Data()->mBaseFamily, true); + } + + // Strip the style suffix (after last space in the name) to get a "base" + // family name. + const char* data = aName.BeginReading(); + int32_t index = aName.Length(); + while (--index > 0) { + if (data[index] == ' ') { + break; + } + } + if (index <= 0) { + return nullptr; + } + nsAutoCString base(Substring(aName, 0, index)); + if (BinarySearchIf(families, 0, header.mFamilyCount, + FamilyNameComparator(this, base), &match)) { + // This may be a possible base family to satisfy the search; call + // ReadFaceNamesForFamily and see if the desired name ends up in + // mAliasTable. + // Note that ReadFaceNamesForFamily may store entries in mAliasTable + // (and mLocalNameTable), but if this is happening in a content + // process (which is the common case) those entries will not be saved + // into the shared font list; they're just used here until the "real" + // alias list is ready, then discarded. + Family* baseFamily = &families[match]; + pfl->ReadFaceNamesForFamily(baseFamily, false); + if (auto lookup = pfl->mAliasTable.Lookup(aName)) { + if (lookup.Data()->mFaces.Length() != baseFamily->NumFaces()) { + // If the alias family doesn't have all the faces of the base family, + // then style matching may end up resolving to a face that isn't + // supposed to be available in the legacy styled family. To ensure + // such mis-styling will get fixed, we start the async font info + // loader (if it hasn't yet been triggered), which will pull in the + // full metadata we need and then force a reflow. + pfl->InitOtherFamilyNames(/* aDeferOtherFamilyNamesLoading */ true); + } + return baseFamily; + } + } + } +#endif + + return nullptr; +} + +LocalFaceRec* FontList::FindLocalFace(const nsCString& aName) { + struct FaceNameComparator { + FaceNameComparator(FontList* aList, const nsCString& aTarget) + : mList(aList), mTarget(aTarget) {} + + int operator()(const LocalFaceRec& aVal) const { + return mTarget.Compare(aVal.mKey.BeginReading(mList)); + } + + private: + FontList* mList; + const nsCString& mTarget; + }; + + Header& header = GetHeader(); + + LocalFaceRec* faces = LocalFaces(); + size_t match; + if (faces && BinarySearchIf(faces, 0, header.mLocalFaceCount, + FaceNameComparator(this, aName), &match)) { + return &faces[match]; + } + + return nullptr; +} + +void FontList::SearchForLocalFace(const nsACString& aName, Family** aFamily, + Face** aFace) { + Header& header = GetHeader(); + MOZ_ASSERT(header.mLocalFaceCount == 0, + "do not use when local face names are already set up!"); + LOG_FONTLIST( + ("(shared-fontlist) local face search for (%s)", aName.BeginReading())); + char initial = aName[0]; + Family* families = Families(); + if (!families) { + return; + } + for (uint32_t i = 0; i < header.mFamilyCount; i++) { + Family* family = &families[i]; + if (family->Key().BeginReading(this)[0] != initial) { + continue; + } + LOG_FONTLIST(("(shared-fontlist) checking family (%s)", + family->Key().AsString(this).BeginReading())); + if (!family->IsInitialized()) { + if (!gfxPlatformFontList::PlatformFontList()->InitializeFamily(family)) { + continue; + } + } + Pointer* faces = family->Faces(this); + if (!faces) { + continue; + } + for (uint32_t j = 0; j < family->NumFaces(); j++) { + Face* face = static_cast<Face*>(faces[j].ToPtr(this)); + if (!face) { + continue; + } + nsAutoCString psname, fullname; + if (gfxPlatformFontList::PlatformFontList()->ReadFaceNames( + family, face, psname, fullname)) { + LOG_FONTLIST(("(shared-fontlist) read psname (%s) fullname (%s)", + psname.get(), fullname.get())); + ToLowerCase(psname); + ToLowerCase(fullname); + if (aName == psname || aName == fullname) { + *aFamily = family; + *aFace = face; + return; + } + } + } + } +} + +Pointer FontList::ToSharedPointer(const void* aPtr) { + const char* p = (const char*)aPtr; + const uint32_t blockCount = mBlocks.Length(); + for (uint32_t i = 0; i < blockCount; ++i) { + const char* blockAddr = (const char*)mBlocks[i]->Memory(); + if (p >= blockAddr && p < blockAddr + SHM_BLOCK_SIZE) { + return Pointer(i, p - blockAddr); + } + } + MOZ_DIAGNOSTIC_ASSERT(false, "invalid shared-memory pointer"); + return Pointer::Null(); +} + +size_t FontList::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +size_t FontList::SizeOfExcludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + size_t result = mBlocks.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& b : mBlocks) { + result += aMallocSizeOf(b.get()) + aMallocSizeOf(b->mShmem.get()); + } + return result; +} + +size_t FontList::AllocatedShmemSize() const { + size_t result = 0; + for (const auto& b : mBlocks) { + result += b->BlockSize(); + } + return result; +} + +} // namespace fontlist +} // namespace mozilla diff --git a/gfx/thebes/SharedFontList.h b/gfx/thebes/SharedFontList.h new file mode 100644 index 0000000000..6ef2a7744c --- /dev/null +++ b/gfx/thebes/SharedFontList.h @@ -0,0 +1,393 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SharedFontList_h +#define SharedFontList_h + +#include "gfxFontEntry.h" +#include <atomic> + +class gfxCharacterMap; +struct gfxFontStyle; +struct GlobalFontMatch; + +namespace mozilla { +namespace fontlist { + +class FontList; // See the separate SharedFontList-impl.h header + +/** + * A Pointer in the shared font list contains a packed index/offset pair, + * with a 12-bit index into the array of shared-memory blocks, and a 20-bit + * offset into the block. + * The maximum size of each block is therefore 2^20 bytes (1 MB) if sub-parts + * of the block are to be allocated; however, a larger block (up to 2^32 bytes) + * can be created and used as a single allocation if necessary. + */ +struct Pointer { + private: + friend class FontList; + static const uint32_t kIndexBits = 12u; + static const uint32_t kBlockShift = 20u; + static_assert(kIndexBits + kBlockShift == 32u, "bad Pointer bit count"); + + static const uint32_t kNullValue = 0xffffffffu; + static const uint32_t kOffsetMask = (1u << kBlockShift) - 1; + + public: + static Pointer Null() { return Pointer(); } + + Pointer() : mBlockAndOffset(kNullValue) {} + + Pointer(uint32_t aBlock, uint32_t aOffset) + : mBlockAndOffset((aBlock << kBlockShift) | aOffset) { + MOZ_ASSERT(aBlock < (1u << kIndexBits) && aOffset < (1u << kBlockShift)); + } + + Pointer(const Pointer& aOther) { + mBlockAndOffset.store(aOther.mBlockAndOffset); + } + + Pointer(Pointer&& aOther) { mBlockAndOffset.store(aOther.mBlockAndOffset); } + + /** + * Check if a Pointer has the null value. + * + * NOTE! + * In a child process, it is possible that conversion to a "real" pointer + * using ToPtr() will fail even when IsNull() is false, so calling code + * that may run in child processes must be prepared to handle this. + */ + bool IsNull() const { return mBlockAndOffset == kNullValue; } + + uint32_t Block() const { return mBlockAndOffset >> kBlockShift; } + + uint32_t Offset() const { return mBlockAndOffset & kOffsetMask; } + + /** + * Convert a fontlist::Pointer to a standard C++ pointer. This requires the + * FontList, which will know where the shared memory block is mapped in + * the current process's address space. + * + * NOTE! + * In child processes this may fail and return nullptr, even if IsNull() is + * false, in cases where the font list is in the process of being rebuilt. + */ + void* ToPtr(FontList* aFontList) const; + + Pointer& operator=(const Pointer& aOther) { + mBlockAndOffset.store(aOther.mBlockAndOffset); + return *this; + } + + Pointer& operator=(Pointer&& aOther) { + mBlockAndOffset.store(aOther.mBlockAndOffset); + return *this; + } + + // We store the block index and the offset within the block as a single + // atomic 32-bit value so we can safely modify a Pointer without other + // processes seeing a broken (partially-updated) value. + std::atomic<uint32_t> mBlockAndOffset; +}; + +/** + * Family and face names are stored as String records, which hold a length + * (in utf-8 code units) and a Pointer to the string's UTF-8 characters. + */ +struct String { + String() : mPointer(Pointer::Null()), mLength(0) {} + + String(FontList* aList, const nsACString& aString) + : mPointer(Pointer::Null()) { + Assign(aString, aList); + } + + const nsCString AsString(FontList* aList) const { + MOZ_ASSERT(!mPointer.IsNull()); + // It's tempting to use AssignLiteral here so that we get an nsCString that + // simply wraps the character data in the shmem block without needing to + // allocate or copy. But that's unsafe because in the event of font-list + // reinitalization, that shared memory will be unmapped; then any copy of + // the nsCString that may still be around will crash if accessed. + return nsCString(static_cast<const char*>(mPointer.ToPtr(aList)), mLength); + } + + void Assign(const nsACString& aString, FontList* aList); + + const char* BeginReading(FontList* aList) const { + MOZ_ASSERT(!mPointer.IsNull()); + auto str = static_cast<const char*>(mPointer.ToPtr(aList)); + return str ? str : ""; + } + + uint32_t Length() const { return mLength; } + + /** + * Return whether the String has been set to a value. + * + * NOTE! + * In a child process, accessing the value could fail even if IsNull() + * returned false. In this case, the nsCString constructor used by AsString() + * will be passed a null pointer, and return an empty string despite the + * non-zero Length() recorded here. + */ + bool IsNull() const { return mPointer.IsNull(); } + + private: + Pointer mPointer; + uint32_t mLength; +}; + +/** + * A Face record represents an individual font resource; it has the style + * properties needed for font matching, as well as a pointer to a character + * map that records the supported character set. This may be Null if we have + * not yet loaded the data. + * The mDescriptor and mIndex fields provide the information needed to + * instantiate a (platform-specific) font reference that can be used with + * platform font APIs; their content depends on the requirements of the + * platform APIs (e.g. font PostScript name, file pathname, serialized + * fontconfig pattern, etc). + */ +struct Face { + // Data required to initialize a Face + struct InitData { + nsCString mDescriptor; // descriptor that can be used to instantiate a + // platform font reference + uint16_t mIndex; // an index used with descriptor (on some platforms) + bool mFixedPitch; // is the face fixed-pitch (monospaced)? + mozilla::WeightRange mWeight; // CSS font-weight value + mozilla::StretchRange mStretch; // CSS font-stretch value + mozilla::SlantStyleRange mStyle; // CSS font-style value + RefPtr<gfxCharacterMap> mCharMap; // character map, or null if not loaded + }; + + // Note that mCharacterMap is not set from the InitData by this constructor; + // the caller must use SetCharacterMap to handle that separately if required. + Face(FontList* aList, const InitData& aData) + : mDescriptor(aList, aData.mDescriptor), + mIndex(aData.mIndex), + mFixedPitch(aData.mFixedPitch), + mWeight(aData.mWeight), + mStretch(aData.mStretch), + mStyle(aData.mStyle), + mCharacterMap(Pointer::Null()) {} + + bool HasValidDescriptor() const { + return !mDescriptor.IsNull() && mIndex != uint16_t(-1); + } + + void SetCharacterMap(FontList* aList, gfxCharacterMap* aCharMap); + + String mDescriptor; + uint16_t mIndex; + bool mFixedPitch; + mozilla::WeightRange mWeight; + mozilla::StretchRange mStretch; + mozilla::SlantStyleRange mStyle; + Pointer mCharacterMap; +}; + +/** + * A Family record represents an available (installed) font family; it has + * a name (for display purposes) and a key (lowercased, for case-insensitive + * lookups), as well as a pointer to an array of Faces. Depending on the + * platform, the array of faces may be lazily initialized the first time we + * want to use the family. + */ +struct Family { + static constexpr uint32_t kNoIndex = uint32_t(-1); + + // Data required to initialize a Family + struct InitData { + InitData(const nsACString& aKey, // lookup key (lowercased) + const nsACString& aName, // display name + uint32_t aIndex = kNoIndex, // [win] system collection index + FontVisibility aVisibility = FontVisibility::Unknown, + bool aBundled = false, // [win] font was bundled with the app + // rather than system-installed + bool aBadUnderline = false, // underline-position in font is bad + bool aForceClassic = false, // [win] use "GDI classic" rendering + bool aAltLocale = false // font is alternate localized family + ) + : mKey(aKey), + mName(aName), + mIndex(aIndex), + mVisibility(aVisibility), + mBundled(aBundled), + mBadUnderline(aBadUnderline), + mForceClassic(aForceClassic), + mAltLocale(aAltLocale) {} + bool operator<(const InitData& aRHS) const { return mKey < aRHS.mKey; } + bool operator==(const InitData& aRHS) const { + return mKey == aRHS.mKey && mName == aRHS.mName && + mVisibility == aRHS.mVisibility && mBundled == aRHS.mBundled && + mBadUnderline == aRHS.mBadUnderline; + } + const nsCString mKey; + const nsCString mName; + uint32_t mIndex; + FontVisibility mVisibility; + bool mBundled; + bool mBadUnderline; + bool mForceClassic; + bool mAltLocale; + }; + + /** + * Font families are considered "simple" if they contain only 4 faces with + * style attributes corresponding to Regular, Bold, Italic, and BoldItalic + * respectively, or a subset of these (e.g. only Regular and Bold). In this + * case, the faces are stored at predefined positions in the mFaces array, + * and a simplified (faster) style-matching algorithm can be used. + */ + enum { + // Indexes into mFaces for families where mIsSimple is true + kRegularFaceIndex = 0, + kBoldFaceIndex = 1, + kItalicFaceIndex = 2, + kBoldItalicFaceIndex = 3, + // mask values for selecting face with bold and/or italic attributes + kBoldMask = 0x01, + kItalicMask = 0x02 + }; + + Family(FontList* aList, const InitData& aData); + + void AddFaces(FontList* aList, const nsTArray<Face::InitData>& aFaces); + + void SetFacePtrs(FontList* aList, nsTArray<Pointer>& aFaces); + + const String& Key() const { return mKey; } + + const String& DisplayName() const { return mName; } + + uint32_t Index() const { return mIndex; } + bool IsBundled() const { return mIsBundled; } + + uint32_t NumFaces() const { + MOZ_ASSERT(IsInitialized()); + return mFaceCount; + } + + Pointer* Faces(FontList* aList) const { + MOZ_ASSERT(IsInitialized()); + return static_cast<Pointer*>(mFaces.ToPtr(aList)); + } + + FontVisibility Visibility() const { return mVisibility; } + bool IsHidden() const { return Visibility() == FontVisibility::Hidden; } + + bool IsBadUnderlineFamily() const { return mIsBadUnderlineFamily; } + bool IsForceClassic() const { return mIsForceClassic; } + bool IsSimple() const { return mIsSimple; } + bool IsAltLocaleFamily() const { return mIsAltLocale; } + + // IsInitialized indicates whether the family has been populated with faces, + // and is therefore ready to use. + // It is possible that character maps have not yet been loaded. + bool IsInitialized() const { return !mFaces.IsNull(); } + + // IsFullyInitialized indicates that not only faces but also character maps + // have been set up, so the family can be searched without the possibility + // that IPC messaging will be triggered. + bool IsFullyInitialized() const { + return IsInitialized() && !mCharacterMap.IsNull(); + } + + void FindAllFacesForStyle(FontList* aList, const gfxFontStyle& aStyle, + nsTArray<Face*>& aFaceList, + bool aIgnoreSizeTolerance = false) const; + + Face* FindFaceForStyle(FontList* aList, const gfxFontStyle& aStyle, + bool aIgnoreSizeTolerance = false) const; + + void SearchAllFontsForChar(FontList* aList, GlobalFontMatch* aMatchData); + + void SetupFamilyCharMap(FontList* aList); + + private: + std::atomic<uint32_t> mFaceCount; + String mKey; + String mName; + Pointer mCharacterMap; // If non-null, union of character coverage of all + // faces in the family + Pointer mFaces; // Pointer to array of |mFaceCount| face pointers + uint32_t mIndex; // [win] Top bit set indicates app-bundled font family + FontVisibility mVisibility; + bool mIsSimple; // family allows simplified style matching: mFaces contains + // exactly 4 entries [Regular, Bold, Italic, BoldItalic]. + bool mIsBundled : 1; + bool mIsBadUnderlineFamily : 1; + bool mIsForceClassic : 1; + bool mIsAltLocale : 1; +}; + +/** + * For platforms where we build an index of local font face names (PS-name + * and fullname of the font) to support @font-face{src:local(...)}, we map + * each face name to an index into the family list, and an index into the + * family's list of faces. + */ +struct LocalFaceRec { + /** + * The InitData struct needs to record the family name rather than index, + * as we may be collecting these records at the same time as building the + * family list, so we don't yet know the final family index. + * Likewise, in some cases we don't know the final face index because the + * faces may be re-sorted to fit into predefined positions in a "simple" + * family (if we're reading names before the family has been fully set up). + * In that case, we'll store uint32_t(-1) as mFaceIndex, and record the + * string descriptor instead. + * When actually recorded in the FontList's mLocalFaces array, the family + * will be stored as a simple index into the mFamilies array, and the face + * as an index into the family's mFaces. + */ + struct InitData { + nsCString mFamilyName; + nsCString mFaceDescriptor; + uint32_t mFaceIndex = uint32_t(-1); + InitData(const nsACString& aFamily, const nsACString& aFace) + : mFamilyName(aFamily), mFaceDescriptor(aFace) {} + InitData(const nsACString& aFamily, uint32_t aFaceIndex) + : mFamilyName(aFamily), mFaceIndex(aFaceIndex) {} + InitData() = default; + }; + String mKey; + uint32_t mFamilyIndex; // Index into the font list's Families array + uint32_t mFaceIndex; // Index into the family's Faces array +}; + +} // namespace fontlist +} // namespace mozilla + +#include "ipc/IPCMessageUtils.h" + +namespace IPC { + +template <> +struct ParamTraits<mozilla::fontlist::Pointer> { + typedef mozilla::fontlist::Pointer paramType; + static void Write(Message* aMsg, const paramType& aParam) { + uint32_t v = aParam.mBlockAndOffset; + WriteParam(aMsg, v); + } + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + uint32_t v; + if (ReadParam(aMsg, aIter, &v)) { + aResult->mBlockAndOffset.store(v); + return true; + } + return false; + } +}; + +} // namespace IPC + +#undef ERROR // This is defined via Windows.h, but conflicts with some bindings + // code when this gets included in the same compilation unit. + +#endif /* SharedFontList_h */ diff --git a/gfx/thebes/SkMemoryReporter.cpp b/gfx/thebes/SkMemoryReporter.cpp new file mode 100644 index 0000000000..d8448908f8 --- /dev/null +++ b/gfx/thebes/SkMemoryReporter.cpp @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SkMemoryReporter.h" + +#include "skia/include/core/SkGraphics.h" + +namespace mozilla { +namespace gfx { + +NS_IMETHODIMP +SkMemoryReporter::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + MOZ_COLLECT_REPORT("explicit/skia-font-cache", KIND_HEAP, UNITS_BYTES, + SkGraphics::GetFontCacheUsed(), + "Memory used in the skia font cache."); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(SkMemoryReporter, nsIMemoryReporter); + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/thebes/SkMemoryReporter.h b/gfx/thebes/SkMemoryReporter.h new file mode 100644 index 0000000000..e092b48541 --- /dev/null +++ b/gfx/thebes/SkMemoryReporter.h @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_SKMEMORYREPORTER_H +#define GFX_SKMEMORYREPORTER_H + +#include "nsIMemoryReporter.h" + +namespace mozilla { +namespace gfx { + +class SkMemoryReporter final : public nsIMemoryReporter { + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override; + + private: + ~SkMemoryReporter() = default; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* GFX_SKMEMORYREPORTER_H */ diff --git a/gfx/thebes/SoftwareVsyncSource.cpp b/gfx/thebes/SoftwareVsyncSource.cpp new file mode 100644 index 0000000000..e3fc64f8f7 --- /dev/null +++ b/gfx/thebes/SoftwareVsyncSource.cpp @@ -0,0 +1,134 @@ +/* -*- 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() = default; + +void SoftwareDisplay::EnableVsync() { + MOZ_ASSERT(mVsyncThread->IsRunning()); + if (NS_IsMainThread()) { + if (mVsyncEnabled) { + return; + } + mVsyncEnabled = true; + + mVsyncThread->message_loop()->PostTask(NewRunnableMethod( + "SoftwareDisplay::EnableVsync", this, &SoftwareDisplay::EnableVsync)); + return; + } + + MOZ_ASSERT(IsInSoftwareVsyncThread()); + TimeStamp vsyncTime = TimeStamp::Now(); + TimeStamp outputTime = vsyncTime + mVsyncRate; + NotifyVsync(vsyncTime, outputTime); +} + +void SoftwareDisplay::DisableVsync() { + MOZ_ASSERT(mVsyncThread->IsRunning()); + if (NS_IsMainThread()) { + if (!mVsyncEnabled) { + return; + } + mVsyncEnabled = false; + + mVsyncThread->message_loop()->PostTask(NewRunnableMethod( + "SoftwareDisplay::DisableVsync", 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(const mozilla::TimeStamp& aVsyncTimestamp, + const mozilla::TimeStamp& aOutputTimestamp) { + 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, aOutputTimestamp); + + // 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(); + } + + TimeStamp outputTime = nextVsync + mVsyncRate; + + mCurrentVsyncTask = + NewCancelableRunnableMethod<mozilla::TimeStamp, mozilla::TimeStamp>( + "SoftwareDisplay::NotifyVsync", this, &SoftwareDisplay::NotifyVsync, + nextVsync, outputTime); + + RefPtr<Runnable> addrefedTask = mCurrentVsyncTask; + mVsyncThread->message_loop()->PostDelayedTask(addrefedTask.forget(), + delay.ToMilliseconds()); +} + +void SoftwareDisplay::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + DisableVsync(); + mVsyncThread->Stop(); + delete mVsyncThread; +} diff --git a/gfx/thebes/SoftwareVsyncSource.h b/gfx/thebes/SoftwareVsyncSource.h new file mode 100644 index 0000000000..addf714520 --- /dev/null +++ b/gfx/thebes/SoftwareVsyncSource.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef GFX_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 { + public: + SoftwareDisplay(); + void EnableVsync() override; + void DisableVsync() override; + bool IsVsyncEnabled() override; + bool IsInSoftwareVsyncThread(); + void NotifyVsync(const mozilla::TimeStamp& aVsyncTimestamp, + const mozilla::TimeStamp& aOutputTimestamp) override; + mozilla::TimeDuration GetVsyncRate() override; + void ScheduleNextVsync(mozilla::TimeStamp aVsyncTimestamp); + void Shutdown() override; + + virtual ~SoftwareDisplay(); + + private: + mozilla::TimeDuration mVsyncRate; + // Use a chromium thread because nsITimers* fire on the main thread + base::Thread* mVsyncThread; + RefPtr<mozilla::CancelableRunnable> + mCurrentVsyncTask; // only access on vsync thread + bool mVsyncEnabled; // Only access on main thread +}; // SoftwareDisplay + +// Fallback option to use a software timer to mimic vsync. Useful for gtests +// To mimic a hardware vsync thread, we create a dedicated software timer +// vsync thread. +class SoftwareVsyncSource : public mozilla::gfx::VsyncSource { + public: + SoftwareVsyncSource(); + virtual ~SoftwareVsyncSource(); + + Display& GetGlobalDisplay() override { + MOZ_ASSERT(mGlobalDisplay != nullptr); + return *mGlobalDisplay; + } + + private: + RefPtr<SoftwareDisplay> mGlobalDisplay; +}; + +#endif /* GFX_SOFTWARE_VSYNC_SOURCE_H */ diff --git a/gfx/thebes/StandardFonts-linux.inc b/gfx/thebes/StandardFonts-linux.inc new file mode 100644 index 0000000000..3a62262ead --- /dev/null +++ b/gfx/thebes/StandardFonts-linux.inc @@ -0,0 +1,494 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// List of standard font families present in a default English install +// of Ubuntu 20.04: +static const char* kBaseFonts_Ubuntu_20_04[] = { + "aakar", + "Abyssinica SIL", + "Ani", + "AnjaliOldLipi", + "Bitstream Charter", + "C059", + "Century Schoolbook L", + "Chandas", + "Chilanka", + "Courier 10 Pitch", + "D050000L", + "DejaVu Sans", + "DejaVu Sans Mono", + "DejaVu Serif", + "Dingbats", + "Droid Sans Fallback", + "Dyuthi", + "FreeMono", + "FreeSans", + "FreeSerif", + "Gargi", + "Garuda", + "Gayathri", + "Gayathri Thin", + "Gubbi", + "Jamrul", + "KacstArt", + "KacstBook", + "KacstDecorative", + "KacstDigital", + "KacstFarsi", + "KacstLetter", + "KacstNaskh", + "KacstOffice", + "KacstOne", + "KacstPen", + "KacstPoster", + "KacstQurn", + "KacstScreen", + "KacstTitle", + "KacstTitleL", + "Kalapi", + "Kalimati", + "Karumbi", + "Keraleeyam", + "Khmer OS", + "Khmer OS System", + "Kinnari", + "Laksaman", + "Liberation Mono", + "Liberation Sans", + "Liberation Sans Narrow", + "Liberation Serif", + "Likhan", + "LKLUG", + "Lohit Assamese", + "Lohit Bengali", + "Lohit Devanagari", + "Lohit Gujarati", + "Lohit Gurmukhi", + "Lohit Kannada", + "Lohit Malayalam", + "Lohit Odia", + "Lohit Tamil", + "Lohit Tamil Classical", + "Lohit Telugu", + "Loma", + "Manjari", + "Manjari Thin", + "Meera", + "Mitra Mono", + "mry_KacstQurn", + "Mukti Narrow", + "Mukti Narrow Bold", + "Nakula", + "Navilu", + "Nimbus Mono L", + "Nimbus Mono PS", + "Nimbus Roman", + "Nimbus Roman No9 L", + "Nimbus Sans", + "Nimbus Sans L", + "Nimbus Sans Narrow", + "Norasi", + "Noto Color Emoji", + "Noto Mono", + "Noto Sans CJK HK", + "Noto Sans CJK JP", + "Noto Sans CJK KR", + "Noto Sans CJK SC", + "Noto Sans CJK TC", + "Noto Sans Mono CJK HK", + "Noto Sans Mono CJK JP", + "Noto Sans Mono CJK KR", + "Noto Sans Mono CJK SC", + "Noto Sans Mono CJK TC", + "Noto Serif CJK JP", + "Noto Serif CJK KR", + "Noto Serif CJK SC", + "Noto Serif CJK TC", + "OpenSymbol", + "ori1Uni", + "P052", + "Padauk", + "Padauk Book", + "padmaa", + "padmaa-Bold.1.1", + "padmmaa", + "Pagul", + "Phetsarath OT", + "Pothana2000", + "Purisa", + "Rachana", + "RaghuMalayalamSans", + "Rasa", + "Rasa Light", + "Rasa Medium", + "Rasa SemiBold", + "Rekha", + "Saab", + "Sahadeva", + "Samanata", + "Samyak Devanagari", + "Samyak Gujarati", + "Samyak Malayalam", + "Samyak Tamil", + "Sarai", + "Sawasdee", + "Standard Symbols L", + "Standard Symbols PS", + "Suruma", + "Tibetan Machine Uni", + "Tlwg Mono", + "Tlwg Typewriter", + "Tlwg Typist", + "Tlwg Typo", + "Ubuntu", + "Ubuntu Condensed", + "Ubuntu Light", + "Ubuntu Mono", + "Ubuntu Thin", + "Umpush", + "Uroob", + "URW Bookman", + "URW Bookman L", + "URW Chancery L", + "URW Gothic", + "URW Gothic L", + "URW Palladio L", + "utkal", + "Vemana2000", + "Waree", + "Yrsa", + "Yrsa Light", + "Yrsa Medium", + "Yrsa SemiBold", + "Z003", + "गार्गी", + "नालिमाटी", + "অনি Dvf", + "মিত্র", + "মুক্তি", + "মুক্তি পাতনা", +}; + +// Additional font families installed when all languages are enabled via the +// Language Support utility on Ubuntu 20.04: +static const char* kLangFonts_Ubuntu_20_04[] = { + "Aharoni CLM", + "AlArabiya", + "AlBattar", + "AlHor", + "AlManzomah", + "AlYarmook", + "Amiri", + "Amiri Quran", + "Amiri Quran Colored", + "AR PL UKai CN", + "AR PL UKai HK", + "AR PL UKai TW", + "AR PL UKai TW MBE", + "AR PL UMing CN", + "AR PL UMing HK", + "AR PL UMing TW", + "AR PL UMing TW MBE", + "Arab", + "Caladings CLM", + "Cortoba", + "David CLM", + "Dimnah", + "Drugulin CLM", + "Electron", + "Ellinia CLM", + "Ezra SIL", + "Ezra SIL SR", + "Frank Ruehl CLM", + "Furat", + "Granada", + "Graph", + "Hadasim CLM", + "Hani", + "Haramain", + "Homa", + "Hor", + "Japan", + "Jet", + "Kayrawan", + "Keter YG", + "Khalid", + "Khmer OS Battambang", + "Khmer OS Bokor", + "Khmer OS Content", + "Khmer OS Fasthand", + "Khmer OS Freehand", + "Khmer OS Metal Chrieng", + "Khmer OS Muol", + "Khmer OS Muol Light", + "Khmer OS Muol Pali", + "Khmer OS Siemreap", + "Mashq", + "Mashq-Bold", + "Metal", + "Miriam CLM", + "Miriam Mono CLM", + "Nachlieli CLM", + "Nada", + "Nagham", + "Nazli", + "Nice", + "Noto Sans CJK HK Black", + "Noto Sans CJK HK DemiLight", + "Noto Sans CJK HK Light", + "Noto Sans CJK HK Medium", + "Noto Sans CJK HK Thin", + "Noto Sans CJK JP Black", + "Noto Sans CJK JP DemiLight", + "Noto Sans CJK JP Light", + "Noto Sans CJK JP Medium", + "Noto Sans CJK JP Thin", + "Noto Sans CJK KR Black", + "Noto Sans CJK KR DemiLight", + "Noto Sans CJK KR Light", + "Noto Sans CJK KR Medium", + "Noto Sans CJK KR Thin", + "Noto Sans CJK SC Black", + "Noto Sans CJK SC DemiLight", + "Noto Sans CJK SC Light", + "Noto Sans CJK SC Medium", + "Noto Sans CJK SC Thin", + "Noto Sans CJK TC Black", + "Noto Sans CJK TC DemiLight", + "Noto Sans CJK TC Light", + "Noto Sans CJK TC Medium", + "Noto Sans CJK TC Thin", + "Noto Serif CJK JP Black", + "Noto Serif CJK JP ExtraLight", + "Noto Serif CJK JP Light", + "Noto Serif CJK JP Medium", + "Noto Serif CJK JP SemiBold", + "Noto Serif CJK KR Black", + "Noto Serif CJK KR ExtraLight", + "Noto Serif CJK KR Light", + "Noto Serif CJK KR Medium", + "Noto Serif CJK KR SemiBold", + "Noto Serif CJK SC Black", + "Noto Serif CJK SC ExtraLight", + "Noto Serif CJK SC Light", + "Noto Serif CJK SC Medium", + "Noto Serif CJK SC SemiBold", + "Noto Serif CJK TC Black", + "Noto Serif CJK TC ExtraLight", + "Noto Serif CJK TC Light", + "Noto Serif CJK TC Medium", + "Noto Serif CJK TC SemiBold", + "Ostorah", + "Ouhod", + "Ouhod-Bold", + "Petra", + "Rasheeq", + "Rasheeq-Bold", + "Rehan", + "Salem", + "Scheherazade", + "Shado", + "Sharjah", + "Shofar", + "Simple CLM", + "Sindbad", + "Stam Ashkenaz CLM", + "Stam Sefarad CLM", + "Tarablus", + "Tholoth", + "Titr", + "UKIJ 3D", + "UKIJ Basma", + "UKIJ Bom", + "UKIJ Chechek", + "UKIJ Chiwer Kesme", + "UKIJ CJK", + "UKIJ Diwani", + "UKIJ Diwani Kawak", + "UKIJ Diwani Tom", + "UKIJ Diwani Yantu", + "UKIJ Ekran", + "UKIJ Elipbe", + "UKIJ Elipbe_Chekitlik", + "UKIJ Esliye", + "UKIJ Esliye Chiwer", + "UKIJ Esliye Neqish", + "UKIJ Esliye Qara", + "UKIJ Esliye Tom", + "UKIJ Imaret", + "UKIJ Inchike", + "UKIJ Jelliy", + "UKIJ Junun", + "UKIJ Kawak", + "UKIJ Kawak 3D", + "UKIJ Kesme", + "UKIJ Kesme Tuz", + "UKIJ Kufi", + "UKIJ Kufi 3D", + "UKIJ Kufi Chiwer", + "UKIJ Kufi Gul", + "UKIJ Kufi Kawak", + "UKIJ Kufi Tar", + "UKIJ Kufi Uz", + "UKIJ Kufi Yay", + "UKIJ Kufi Yolluq", + "UKIJ Mejnun", + "UKIJ Mejnuntal", + "UKIJ Merdane", + "UKIJ Moy Qelem", + "UKIJ Nasq", + "UKIJ Nasq Zilwa", + "UKIJ Orqun Basma", + "UKIJ Orqun Yazma", + "UKIJ Orxun-Yensey", + "UKIJ Qara", + "UKIJ Qolyazma", + "UKIJ Qolyazma Tez", + "UKIJ Qolyazma Tuz", + "UKIJ Qolyazma Yantu", + "UKIJ Ruqi", + "UKIJ Saet", + "UKIJ Sulus", + "UKIJ Sulus Tom", + "UKIJ Teng", + "UKIJ Tiken", + "UKIJ Title", + "UKIJ Tor", + "UKIJ Tughra", + "UKIJ Tuz", + "UKIJ Tuz Basma", + "UKIJ Tuz Gezit", + "UKIJ Tuz Kitab", + "UKIJ Tuz Neqish", + "UKIJ Tuz Qara", + "UKIJ Tuz Tom", + "UKIJ Tuz Tor", + "UKIJ Zilwa", + "UKIJ_Mac Basma", + "UKIJ_Mac Ekran", + "Yehuda CLM", + "מרים", +}; + +// List of standard font families installed on Fedora 32 Workstation: +static const char* kBaseFonts_Fedora_32[] = { + "Abyssinica SIL", + "C059", + "Caladea", + "Cantarell", + "Cantarell Extra Bold", + "Cantarell Light", + "Cantarell Thin", + "Carlito", + "Comfortaa", + "Comfortaa Light", + "D050000L", + "DejaVu Math TeX Gyre", + "DejaVu Sans", + "DejaVu Sans Condensed", + "DejaVu Sans Light", + "DejaVu Sans Mono", + "DejaVu Serif", + "DejaVu Serif Condensed", + "Droid Arabic Kufi", + "Droid Sans", + "Droid Sans Armenian", + "Droid Sans Devanagari", + "Droid Sans Ethiopic", + "Droid Sans Fallback", + "Droid Sans Georgian", + "Droid Sans Hebrew", + "Droid Sans Japanese", + "Droid Sans Tamil", + "Droid Sans Thai", + "Jomolhari", + "Khmer OS", + "Khmer OS Content", + "Khmer OS System", + "Liberation Mono", + "Liberation Sans", + "Liberation Serif", + "Lohit Assamese", + "Lohit Bengali", + "Lohit Devanagari", + "Lohit Gujarati", + "Lohit Kannada", + "Lohit Odia", + "Lohit Tamil", + "Lohit Telugu", + "Meera", + "Mingzat", + "Montserrat", + "Montserrat Black", + "Montserrat ExtraBold", + "Montserrat ExtraLight", + "Montserrat Light", + "Montserrat Medium", + "Montserrat SemiBold", + "Montserrat Thin", + "Nimbus Mono PS", + "Nimbus Roman", + "Nimbus Sans", + "Nimbus Sans Narrow", + "Noto Color Emoji", + "Noto Sans CJK HK", + "Noto Sans CJK HK Black", + "Noto Sans CJK HK DemiLight", + "Noto Sans CJK HK Light", + "Noto Sans CJK HK Medium", + "Noto Sans CJK HK Thin", + "Noto Sans CJK JP", + "Noto Sans CJK JP Black", + "Noto Sans CJK JP DemiLight", + "Noto Sans CJK JP Light", + "Noto Sans CJK JP Medium", + "Noto Sans CJK JP Thin", + "Noto Sans CJK KR", + "Noto Sans CJK KR Black", + "Noto Sans CJK KR DemiLight", + "Noto Sans CJK KR Light", + "Noto Sans CJK KR Medium", + "Noto Sans CJK KR Thin", + "Noto Sans CJK SC", + "Noto Sans CJK SC Black", + "Noto Sans CJK SC DemiLight", + "Noto Sans CJK SC Light", + "Noto Sans CJK SC Medium", + "Noto Sans CJK SC Thin", + "Noto Sans CJK TC", + "Noto Sans CJK TC Black", + "Noto Sans CJK TC DemiLight", + "Noto Sans CJK TC Light", + "Noto Sans CJK TC Medium", + "Noto Sans CJK TC Thin", + "Noto Sans Gurmukhi", + "Noto Sans Mono CJK HK", + "Noto Sans Mono CJK JP", + "Noto Sans Mono CJK KR", + "Noto Sans Mono CJK SC", + "Noto Sans Mono CJK TC", + "Noto Sans Sinhala", + "Nuosu SIL", + "OpenSymbol", + "P052", + "Padauk", + "PakType Naskh Basic", + "PT Sans", + "PT Sans Narrow", + "Source Code Pro", + "Source Code Pro Black", + "Source Code Pro ExtraLight", + "Source Code Pro Light", + "Source Code Pro Medium", + "Source Code Pro Semibold", + "Standard Symbols PS", + "STIX", + "STIX Two Math", + "STIX Two Text", + "Symbola", + "URW Bookman", + "URW Gothic", + "Waree", + "Z003", +}; diff --git a/gfx/thebes/StandardFonts-macos.inc b/gfx/thebes/StandardFonts-macos.inc new file mode 100644 index 0000000000..4eb89494df --- /dev/null +++ b/gfx/thebes/StandardFonts-macos.inc @@ -0,0 +1,181 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// List of standard font families installed as part of macOS 10.15 "Catalina" +// from https://developer.apple.com/fonts/system-fonts/ +static const char* kBaseFonts[] = { + "Al Bayan", + "Al Nile", + "Al Tarikh", + "American Typewriter", + "Andale Mono", + "Apple Braille", + "Apple Chancery", + "Apple Color Emoji", + "Apple SD Gothic Neo", + "Apple Symbols", + "AppleGothic", + "AppleMyungjo", + "Arial", + "Arial Black", + "Arial Hebrew", + "Arial Hebrew Scholar", + "Arial Narrow", + "Arial Rounded MT Bold", + "Arial Unicode MS", + "Avenir", + "Avenir Next", + "Ayuthaya", + "Baghdad", + "Bangla MN", + "Bangla Sangam MN", + "Baskerville", + "Bodoni 72", + "Bodoni 72 Oldstyle", + "Bodoni 72 Smallcaps", + "Bodoni Ornaments", + "Bradley Hand", + "Brush Script MT", + "Chalkboard", + "Chalkboard SE", + "Chalkduster", + "Charter", + "Cochin", + "Copperplate", + "Corsiva Hebrew", + "Courier", + "Courier New", + "Damascus", + "DecoType Naskh", + "Devanagari MT", + "Devanagari Sangam MN", + "Didot", + "DIN Alternate", + "DIN Condensed", + "Diwan Kufi", + "Diwan Thuluth", + "Euphemia UCAS", + "Farah", + "Farisi", + "Futura", + "GB18030 Bitmap", + "Geeza Pro", + "Geneva", + "Georgia", + "Gill Sans", + "Gujarati MT", + "Gujarati Sangam MN", + "Gurmukhi MN", + "Gurmukhi MT", + "Gurmukhi Sangam MN", + "Heiti SC", + "Heiti TC", + "Helvetica", + "Helvetica Neue", + "Hiragino Maru Gothic ProN", + "Hiragino Mincho ProN", + "Hiragino Sans", + "Hiragino Sans GB", + "Hoefler Text", + "Impact", + "InaiMathi", + "ITF Devanagari", + "ITF Devanagari Marathi", + "Kailasa", + "Kannada MN", + "Kannada Sangam MN", + "Kefa", + "Khmer MN", + "Khmer Sangam MN", + "Kohinoor Bangla", + "Kohinoor Devanagari", + "Kohinoor Telugu", + "Kokonor", + "Krungthep", + "KufiStandardGK", + "Lao MN", + "Lao Sangam MN", + "Lucida Grande", + "Luminari", + "Malayalam MN", + "Malayalam Sangam MN", + "Marker Felt", + "Menlo", + "Microsoft Sans Serif", + "Mishafi", + "Mishafi Gold", + "Monaco", + "Mshtakan", + "Muna", + "Myanmar MN", + "Myanmar Sangam MN", + "Nadeem Regular", + "New Peninim MT", + "Noteworthy", + "Noto Nastaliq Urdu", + "Optima", + "Oriya MN", + "Oriya Sangam MN", + "Palatino", + "Papyrus", + "Phosphate", + "PingFang HK", + "PingFang SC", + "PingFang TC", + "Plantagenet Cherokee", + "PT Mono", + "PT Sans", + "PT Sans Caption", + "PT Sans Narrow", + "PT Serif", + "PT Serif Caption", + "Raanana", + "Rockwell", + "Sana", + "Sathu", + "Savoye LET", + "Shree Devanagari 714", + "SignPainter", + "Silom", + "Sinhala MN", + "Sinhala Sangam MN", + "Skia", + "Snell Roundhand", + "Songti SC", + "Songti TC", + "STIXGeneral", + "STIXIntegralsD", + "STIXIntegralsSm", + "STIXIntegralsUp", + "STIXIntegralsUpD", + "STIXIntegralsUpSm", + "STIXNonUnicode", + "STIXSizeFiveSym", + "STIXSizeFourSym", + "STIXSizeOneSym", + "STIXSizeThreeSym", + "STIXSizeTwoSym", + "STIXVariants", + "STSong", + "Sukhumvit Set", + "Symbol", + "Tahoma", + "Tamil MN", + "Tamil Sangam MN", + "Telugu MN", + "Telugu Sangam MN", + "Thonburi", + "Times", + "Times New Roman", + "Trattatello", + "Trebuchet MS", + "Verdana", + "Waseem", + "Webdings", + "Wingdings", + "Wingdings 2", + "Wingdings 3", + "Zapf Dingbats", + "Zapfino", +}; diff --git a/gfx/thebes/StandardFonts-win10.inc b/gfx/thebes/StandardFonts-win10.inc new file mode 100644 index 0000000000..c9306e5307 --- /dev/null +++ b/gfx/thebes/StandardFonts-win10.inc @@ -0,0 +1,201 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// List of standard font families installed as part of Windows 10 +// from https://docs.microsoft.com/en-us/typography/fonts/windows_10_font_list +// TODO: check whether we need to list legacy styled family names like "... Light". +static const char* kBaseFonts[] = { + "AlternateGothic2 BT", + "Arial", + "Arial Black", + "Bahnschrift", + "Bahnschrift Light", + "Bahnschrift SemiBold", + "Bahnschrift SemiLight", + "Calibri", + "Calibri Light", + "Cambria", + "Cambria Math", + "Candara", + "Comic Sans MS", + "Consolas", + "Constantia", + "Corbel", + "Courier New", + "Ebrima", + "Franklin Gothic Medium", + "Gabriola", + "Gadugi", + "Georgia", + "HoloLens MDL2 Assets", + "Impact", + "Javanese Text", + "Leelawadee UI", + "Leelawadee UI Semilight", + "Lucida Console", + "Lucida Sans Unicode", + "Malgun Gothic", + "Malgun Gothic Semilight", + "Marlett", + "Microsoft Himalaya", + "Microsoft JhengHei", + "Microsoft JhengHei Light", + "Microsoft JhengHei UI", + "Microsoft JhengHei UI Light", + "Microsoft New Tai Lue", + "Microsoft PhagsPa", + "Microsoft Sans Serif", + "Microsoft Tai Le", + "Microsoft YaHei", + "Microsoft YaHei Light", + "Microsoft YaHei UI", + "Microsoft YaHei UI Light", + "Microsoft Yi Baiti", + "MingLiU-ExtB", + "MingLiU_HKSCS-ExtB", + "Mongolian Baiti", + "MS Gothic", + "MS PGothic", + "MS UI Gothic", + "MV Boli", + "Myanmar Text", + "Nirmala UI", + "Nirmala UI Semilight", + "NSimSun", + "Palatino Linotype", + "PMingLiU-ExtB", + "Segoe MDL2 Assets", + "Segoe Print", + "Segoe Script", + "Segoe UI", + "Segoe UI Black", + "Segoe UI Emoji", + "Segoe UI Historic", + "Segoe UI Light", + "Segoe UI Semibold", + "Segoe UI Semilight", + "Segoe UI Symbol", + "SimSun", + "SimSun-ExtB", + "Sitka Banner", + "Sitka Display", + "Sitka Heading", + "Sitka Small", + "Sitka Subheading", + "Sitka Text", + "Sylfaen", + "Symbol", + "Tahoma", + "Times New Roman", + "Trebuchet MS", + "Verdana", + "Webdings", + "Wingdings", + "Yu Gothic", + "Yu Gothic Light", + "Yu Gothic Medium", + "Yu Gothic UI", + "Yu Gothic UI Light", + "Yu Gothic UI Semibold", + "Yu Gothic UI Semilight", +}; + +// Additional fonts provided by language-pack installation. +static const char* kLangPackFonts[] = { + "Aharoni Bold", // Hebrew Supplemental Fonts + "Aldhabi", // Arabic Script Supplemental Fonts + "Andalus", // Arabic Script Supplemental Fonts + "Angsana New", // Thai Supplemental Fonts + "AngsanaUPC", // Thai Supplemental Fonts + "Aparajita", // Devanagari Supplemental Fonts + "Arabic Typesetting", // Arabic Script Supplemental Fonts + "Batang", // Korean Supplemental Fonts + "BatangChe", // Korean Supplemental Fonts + "BIZ UDGothic", // Japanese Supplemental Fonts + "BIZ UDMincho", // Japanese Supplemental Fonts + "BIZ UDPGothic", // Japanese Supplemental Fonts + "BIZ UDPMincho", // Japanese Supplemental Fonts + "Browallia New", // Thai Supplemental Fonts + "BrowalliaUPC", // Thai Supplemental Fonts + "Cordia New", // Thai Supplemental Fonts + "CordiaUPC", // Thai Supplemental Fonts + "DaunPenh", // Khmer Supplemental Fonts + "David", // Hebrew Supplemental Fonts + "DengXian", // Chinese (Simplified) Supplemental Fonts + "DFKai-SB", // Chinese (Traditional) Supplemental Fonts + "DilleniaUPC", // Thai Supplemental Fonts + "DokChampa", // Lao Supplemental Fonts + "Dotum", // Korean Supplemental Fonts + "DotumChe", // Korean Supplemental Fonts + "Estrangelo Edessa", // Syriac Supplemental Fonts + "EucrosiaUPC", // Thai Supplemental Fonts + "Euphemia", // Canadian Aboriginal Syllabics Supplemental Fonts + "FangSong", // Chinese (Simplified) Supplemental Fonts + "FrankRuehl", // Hebrew Supplemental Fonts + "FreesiaUPC", // Thai Supplemental Fonts + "Gautami", // Telugu Supplemental Fonts + "Gisha", // Hebrew Supplemental Fonts + "Gulim", // Korean Supplemental Fonts + "GulimChe", // Korean Supplemental Fonts + "Gungsuh", // Korean Supplemental Fonts + "GungsuhChe", // Korean Supplemental Fonts + "IrisUPC", // Thai Supplemental Fonts + "Iskoola Pota", // Sinhala Supplemental Fonts + "JasmineUPC", // Thai Supplemental Fonts + "KaiTi", // Chinese (Simplified) Supplemental Fonts + "Kalinga", // Odia Supplemental Fonts + "Kartika", // Malayalam Supplemental Fonts + "Khmer UI", // Khmer Supplemental Fonts + "KodchiangUPC", // Thai Supplemental Fonts + "Kokila", // Devanagari Supplemental Fonts + "Lao UI", // Lao Supplemental Fonts + "Latha", // Tamil Supplemental Fonts + "Leelawadee", // Thai Supplemental Fonts + "Levenim MT", // Hebrew Supplemental Fonts + "LilyUPC", // Thai Supplemental Fonts + "Mangal", // Devanagari Supplemental Fonts + "Meiryo", // Japanese Supplemental Fonts + "Meiryo UI", // Japanese Supplemental Fonts + "Microsoft Uighur", // Arabic Script Supplemental Fonts + "MingLiU", // Chinese (Traditional) Supplemental Fonts + "MingLiU_HKSCS", // Chinese (Traditional) Supplemental Fonts + "Miriam", // Hebrew Supplemental Fonts + "MoolBoran", // Khmer Supplemental Fonts + "MS Mincho", // Japanese Supplemental Fonts + "MS PMincho", // Japanese Supplemental Fonts + "Narkisim", // Hebrew Supplemental Fonts + "Nyala", // Ethiopic Supplemental Fonts + "Plantagenet Cherokee", // Cherokee Supplemental Fonts + "PMingLiU", // Chinese (Traditional) Supplemental Fonts + "Raavi", // Gurmukhi Supplemental Fonts + "Rod", // Hebrew Supplemental Fonts + "Sakkal Majalla", // Arabic Script Supplemental Fonts + "Sanskrit Text", // Devanagari Supplemental Fonts + "Shonar Bangla", // Bangla Script Supplemental Fonts + "Shruti", // Gujarati Supplemental Fonts + "SimHei", // Chinese (Simplified) Supplemental Fonts + "Simplified Arabic", // Arabic Script Supplemental Fonts + "Traditional Arabic", // Arabic Script Supplemental Fonts + "Tunga", // Kannada Supplemental Fonts + "UD Digi Kyokasho", // Japanese Supplemental Fonts + "UD Digi Kyokasho N-R", // Japanese Supplemental Fonts + "UD Digi Kyokasho NK-B", // Japanese Supplemental Fonts + "UD Digi Kyokasho NK-R", // Japanese Supplemental Fonts + "UD Digi Kyokasho NP-B", // Japanese Supplemental Fonts + "UD Digi Kyokasho NP-R", // Japanese Supplemental Fonts + "Urdu Typesetting", // Arabic Script Supplemental Fonts + "Utsaah", // Devanagari Supplemental Fonts + "Vani", // Telugu Supplemental Fonts + "Vijaya", // Tamil Supplemental Fonts + "Vrinda", // Bangla Script Supplemental Fonts + "Yu Mincho", // Japanese Supplemental Fonts +// Latin/Greek/Cyrillic scripts are already well-supported by the base fonts, +// so we do not include these even when the LangPack collection is enabled. +// "Arial Nova", // Pan-European Supplemental Fonts - EXCLUDED +// "Georgia Pro", // Pan-European Supplemental Fonts - EXCLUDED +// "Gill Sans Nova", // Pan-European Supplemental Fonts - EXCLUDED +// "Neue Haas Grotesk Text Pro", // Pan-European Supplemental Fonts - EXCLUDED +// "Rockwell Nova", // Pan-European Supplemental Fonts - EXCLUDED +// "Verdana Pro", // Pan-European Supplemental Fonts - EXCLUDED +}; diff --git a/gfx/thebes/ThebesRLBox.h b/gfx/thebes/ThebesRLBox.h new file mode 100644 index 0000000000..9fb0b829a0 --- /dev/null +++ b/gfx/thebes/ThebesRLBox.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef THEBES_RLBOX +#define THEBES_RLBOX + +#include "ThebesRLBoxTypes.h" + +// Load general firefox configuration of RLBox +#include "mozilla/rlbox/rlbox_config.h" + +#ifdef MOZ_WASM_SANDBOXING_GRAPHITE +# include "mozilla/rlbox/rlbox_lucet_sandbox.hpp" +#else +// Extra configuration for no-op sandbox +# define RLBOX_USE_STATIC_CALLS() rlbox_noop_sandbox_lookup_symbol +# include "mozilla/rlbox/rlbox_noop_sandbox.hpp" +#endif + +#include "mozilla/rlbox/rlbox.hpp" + +// Struct info needed for rlbox_load_structs_from_library +#include "graphite2/Font.h" +#include "graphite2/GraphiteExtra.h" +#include "graphite2/Segment.h" + +#include "graphite2/GraphiteStructsForRLBox.h" +rlbox_load_structs_from_library(graphite); + +#endif diff --git a/gfx/thebes/ThebesRLBoxTypes.h b/gfx/thebes/ThebesRLBoxTypes.h new file mode 100644 index 0000000000..9ef287b3fa --- /dev/null +++ b/gfx/thebes/ThebesRLBoxTypes.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef THEBES_RLBOX_TYPES +#define THEBES_RLBOX_TYPES + +#include "mozilla/rlbox/rlbox_types.hpp" + +#ifdef MOZ_WASM_SANDBOXING_GRAPHITE +namespace rlbox { +class rlbox_lucet_sandbox; +} +using rlbox_gr_sandbox_type = rlbox::rlbox_lucet_sandbox; +#else +using rlbox_gr_sandbox_type = rlbox::rlbox_noop_sandbox; +#endif + +using rlbox_sandbox_gr = rlbox::rlbox_sandbox<rlbox_gr_sandbox_type>; +template <typename T> +using sandbox_callback_gr = rlbox::sandbox_callback<T, rlbox_gr_sandbox_type>; +template <typename T> +using tainted_gr = rlbox::tainted<T, rlbox_gr_sandbox_type>; +template <typename T> +using tainted_opaque_gr = rlbox::tainted_opaque<T, rlbox_gr_sandbox_type>; +template <typename T> +using tainted_volatile_gr = rlbox::tainted_volatile<T, rlbox_gr_sandbox_type>; +using rlbox::tainted_boolean_hint; + +#endif diff --git a/gfx/thebes/VsyncSource.cpp b/gfx/thebes/VsyncSource.cpp new file mode 100644 index 0000000000..ba678254e0 --- /dev/null +++ b/gfx/thebes/VsyncSource.cpp @@ -0,0 +1,277 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "VsyncSource.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "mozilla/VsyncDispatcher.h" +#include "MainThreadUtils.h" + +namespace mozilla { +namespace gfx { + +void VsyncSource::EnableCompositorVsyncDispatcher( + 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().EnableCompositorVsyncDispatcher( + aCompositorVsyncDispatcher); +} + +void VsyncSource::DisableCompositorVsyncDispatcher( + CompositorVsyncDispatcher* aCompositorVsyncDispatcher) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + // See also EnableCompositorVsyncDispatcher(). + GetGlobalDisplay().DisableCompositorVsyncDispatcher( + aCompositorVsyncDispatcher); +} + +void VsyncSource::RegisterCompositorVsyncDispatcher( + CompositorVsyncDispatcher* aCompositorVsyncDispatcher) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + GetGlobalDisplay().RegisterCompositorVsyncDispatcher( + aCompositorVsyncDispatcher); +} + +void VsyncSource::DeregisterCompositorVsyncDispatcher( + CompositorVsyncDispatcher* aCompositorVsyncDispatcher) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + GetGlobalDisplay().DeregisterCompositorVsyncDispatcher( + aCompositorVsyncDispatcher); +} + +void VsyncSource::AddGenericObserver(VsyncObserver* aObserver) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + GetGlobalDisplay().AddGenericObserver(aObserver); +} + +void VsyncSource::RemoveGenericObserver(VsyncObserver* aObserver) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + GetGlobalDisplay().RemoveGenericObserver(aObserver); +} + +void VsyncSource::MoveListenersToNewSource( + const RefPtr<VsyncSource>& aNewSource) { + GetGlobalDisplay().MoveListenersToNewSource(aNewSource); +} + +RefPtr<RefreshTimerVsyncDispatcher> +VsyncSource::GetRefreshTimerVsyncDispatcher() { + MOZ_ASSERT(XRE_IsParentProcess()); + // See also AddCompositorVsyncDispatcher(). + return GetGlobalDisplay().GetRefreshTimerVsyncDispatcher(); +} + +VsyncSource::Display::Display() + : mDispatcherLock("display dispatcher lock"), + mRefreshTimerNeedsVsync(false), + mHasGenericObservers(false) { + MOZ_ASSERT(NS_IsMainThread()); + mRefreshTimerVsyncDispatcher = new RefreshTimerVsyncDispatcher(this); +} + +VsyncSource::Display::~Display() { + MOZ_ASSERT(NS_IsMainThread()); + MutexAutoLock lock(mDispatcherLock); + mRefreshTimerVsyncDispatcher = nullptr; + MOZ_ASSERT(mRegisteredCompositorVsyncDispatchers.Length() == 0); + MOZ_ASSERT(mEnabledCompositorVsyncDispatchers.Length() == 0); +} + +void VsyncSource::Display::NotifyVsync(const TimeStamp& aVsyncTimestamp, + const TimeStamp& aOutputTimestamp) { + // Called on the vsync thread + MutexAutoLock lock(mDispatcherLock); + + // mRefreshTimerVsyncDispatcher might be null here if MoveListenersToNewSource + // was called concurrently with this function and won the race to acquire + // mDispatcherLock. In this case the new VsyncSource that is replacing this + // one will handle notifications from now on, so we can abort. + if (!mRefreshTimerVsyncDispatcher) { + return; + } + + // If the task posted to the main thread from the last NotifyVsync call + // hasn't been processed yet, then don't send another one. Otherwise we might + // end up flooding the main thread. + bool dispatchToMainThread = + mHasGenericObservers && + (mLastVsyncIdSentToMainThread == mLastMainThreadProcessedVsyncId); + + mVsyncId = mVsyncId.Next(); + const VsyncEvent event(mVsyncId, aVsyncTimestamp, aOutputTimestamp); + + for (size_t i = 0; i < mEnabledCompositorVsyncDispatchers.Length(); i++) { + mEnabledCompositorVsyncDispatchers[i]->NotifyVsync(event); + } + + mRefreshTimerVsyncDispatcher->NotifyVsync(event); + + if (dispatchToMainThread) { + mLastVsyncIdSentToMainThread = mVsyncId; + NS_DispatchToMainThread(NewRunnableMethod<VsyncEvent>( + "VsyncSource::Display::NotifyGenericObservers", this, + &VsyncSource::Display::NotifyGenericObservers, event)); + } +} + +void VsyncSource::Display::NotifyGenericObservers(VsyncEvent aEvent) { + MOZ_ASSERT(NS_IsMainThread()); + for (size_t i = 0; i < mGenericObservers.Length(); i++) { + mGenericObservers[i]->NotifyVsync(aEvent); + } + + { // Scope lock + MutexAutoLock lock(mDispatcherLock); + mLastMainThreadProcessedVsyncId = aEvent.mId; + } +} + +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::RegisterCompositorVsyncDispatcher( + CompositorVsyncDispatcher* aCompositorVsyncDispatcher) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aCompositorVsyncDispatcher); + { // scope lock + MutexAutoLock lock(mDispatcherLock); + mRegisteredCompositorVsyncDispatchers.AppendElement( + aCompositorVsyncDispatcher); + } +} + +void VsyncSource::Display::DeregisterCompositorVsyncDispatcher( + CompositorVsyncDispatcher* aCompositorVsyncDispatcher) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aCompositorVsyncDispatcher); + { // Scope lock + MutexAutoLock lock(mDispatcherLock); + mRegisteredCompositorVsyncDispatchers.RemoveElement( + aCompositorVsyncDispatcher); + } +} + +void VsyncSource::Display::EnableCompositorVsyncDispatcher( + CompositorVsyncDispatcher* aCompositorVsyncDispatcher) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aCompositorVsyncDispatcher); + { // scope lock + MutexAutoLock lock(mDispatcherLock); + if (!mEnabledCompositorVsyncDispatchers.Contains( + aCompositorVsyncDispatcher)) { + mEnabledCompositorVsyncDispatchers.AppendElement( + aCompositorVsyncDispatcher); + } + } + UpdateVsyncStatus(); +} + +void VsyncSource::Display::DisableCompositorVsyncDispatcher( + CompositorVsyncDispatcher* aCompositorVsyncDispatcher) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aCompositorVsyncDispatcher); + { // Scope lock + MutexAutoLock lock(mDispatcherLock); + if (mEnabledCompositorVsyncDispatchers.Contains( + aCompositorVsyncDispatcher)) { + mEnabledCompositorVsyncDispatchers.RemoveElement( + aCompositorVsyncDispatcher); + } + } + UpdateVsyncStatus(); +} + +void VsyncSource::Display::AddGenericObserver(VsyncObserver* aObserver) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aObserver); + mGenericObservers.AppendElement(aObserver); + + UpdateVsyncStatus(); +} + +void VsyncSource::Display::RemoveGenericObserver(VsyncObserver* aObserver) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aObserver); + mGenericObservers.RemoveElement(aObserver); + + UpdateVsyncStatus(); +} + +void VsyncSource::Display::MoveListenersToNewSource( + const RefPtr<VsyncSource>& aNewSource) { + MOZ_ASSERT(NS_IsMainThread()); + VsyncSource::Display& aNewDisplay = aNewSource->GetGlobalDisplay(); + MutexAutoLock lock(mDispatcherLock); + MutexAutoLock newLock(aNewDisplay.mDispatcherLock); + aNewDisplay.mRegisteredCompositorVsyncDispatchers.AppendElements( + std::move(mRegisteredCompositorVsyncDispatchers)); + aNewDisplay.mEnabledCompositorVsyncDispatchers.AppendElements( + std::move(mEnabledCompositorVsyncDispatchers)); + aNewDisplay.mGenericObservers.AppendElements(std::move(mGenericObservers)); + + for (size_t i = 0; + i < aNewDisplay.mRegisteredCompositorVsyncDispatchers.Length(); i++) { + aNewDisplay.mRegisteredCompositorVsyncDispatchers[i]->MoveToSource( + aNewSource); + } + + aNewDisplay.mRefreshTimerVsyncDispatcher = mRefreshTimerVsyncDispatcher; + mRefreshTimerVsyncDispatcher->MoveToDisplay(&aNewDisplay); + mRefreshTimerVsyncDispatcher = nullptr; +} + +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 = !mEnabledCompositorVsyncDispatchers.IsEmpty() || + mRefreshTimerNeedsVsync || !mGenericObservers.IsEmpty(); + mHasGenericObservers = !mGenericObservers.IsEmpty(); + } + + if (enableVsync) { + EnableVsync(); + } else { + DisableVsync(); + } + + if (IsVsyncEnabled() != enableVsync) { + NS_WARNING("Vsync status did not change."); + } +} + +RefPtr<RefreshTimerVsyncDispatcher> +VsyncSource::Display::GetRefreshTimerVsyncDispatcher() { + return mRefreshTimerVsyncDispatcher; +} + +void VsyncSource::Shutdown() { GetGlobalDisplay().Shutdown(); } + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/thebes/VsyncSource.h b/gfx/thebes/VsyncSource.h new file mode 100644 index 0000000000..95a0cf34d3 --- /dev/null +++ b/gfx/thebes/VsyncSource.h @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_VSYNCSOURCE_H +#define GFX_VSYNCSOURCE_H + +#include "nsTArray.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Mutex.h" +#include "mozilla/TimeStamp.h" +#include "nsISupportsImpl.h" +#include "mozilla/layers/LayersTypes.h" + +namespace mozilla { +class RefreshTimerVsyncDispatcher; +class CompositorVsyncDispatcher; +class VsyncObserver; +struct VsyncEvent; + +class VsyncIdType {}; +typedef layers::BaseTransactionId<VsyncIdType> VsyncId; + +namespace gfx { + +// Controls how and when to enable/disable vsync. Lives as long as the +// gfxPlatform does on the parent process +class VsyncSource { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VsyncSource) + + typedef mozilla::RefreshTimerVsyncDispatcher RefreshTimerVsyncDispatcher; + typedef mozilla::CompositorVsyncDispatcher CompositorVsyncDispatcher; + + public: + // Controls vsync unique to each display and unique on each platform + class Display { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Display) + public: + Display(); + + // Notified when this display's vsync callback occurs, on the vsync thread + // Different platforms give different aVsyncTimestamp values. + // macOS: TimeStamp::Now() or the output time of the previous vsync + // callback, whichever is older. + // Windows: It's messy, see gfxWindowsPlatform. + // Android: TODO + // + // @param aVsyncTimestamp The time of the Vsync that just occured. Needs to + // be at or before the time of the NotifyVsync call. + // @param aOutputTimestamp The estimated timestamp at which drawing will + // appear on the screen, if the drawing happens within a certain + // (unknown) budget. Useful for Audio/Video sync. On platforms where + // this timestamp is provided by the system (macOS), it is a much more + // stable and consistent timing source than the time at which the vsync + // callback is called. + virtual void NotifyVsync(const TimeStamp& aVsyncTimestamp, + const TimeStamp& aOutputTimestamp); + void NotifyGenericObservers(VsyncEvent aEvent); + + RefPtr<RefreshTimerVsyncDispatcher> GetRefreshTimerVsyncDispatcher(); + + void RegisterCompositorVsyncDispatcher( + CompositorVsyncDispatcher* aCompositorVsyncDispatcher); + void DeregisterCompositorVsyncDispatcher( + CompositorVsyncDispatcher* aCompositorVsyncDispatcher); + void EnableCompositorVsyncDispatcher( + CompositorVsyncDispatcher* aCompositorVsyncDispatcher); + void DisableCompositorVsyncDispatcher( + CompositorVsyncDispatcher* aCompositorVsyncDispatcher); + void AddGenericObserver(VsyncObserver* aObserver); + void RemoveGenericObserver(VsyncObserver* aObserver); + + void MoveListenersToNewSource(const RefPtr<VsyncSource>& aNewSource); + 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; + + protected: + virtual ~Display(); + + private: + void UpdateVsyncStatus(); + + Mutex mDispatcherLock; + bool mRefreshTimerNeedsVsync; + nsTArray<RefPtr<CompositorVsyncDispatcher>> + mEnabledCompositorVsyncDispatchers; + nsTArray<RefPtr<CompositorVsyncDispatcher>> + mRegisteredCompositorVsyncDispatchers; + RefPtr<RefreshTimerVsyncDispatcher> mRefreshTimerVsyncDispatcher; + nsTArray<RefPtr<VsyncObserver>> + mGenericObservers; // can only be touched from the main thread + VsyncId mVsyncId; + VsyncId mLastVsyncIdSentToMainThread; // hold mDispatcherLock to touch + VsyncId mLastMainThreadProcessedVsyncId; // hold mDispatcherLock to touch + bool mHasGenericObservers; // hold mDispatcherLock to touch + }; + + void EnableCompositorVsyncDispatcher( + CompositorVsyncDispatcher* aCompositorVsyncDispatcher); + void DisableCompositorVsyncDispatcher( + CompositorVsyncDispatcher* aCompositorVsyncDispatcher); + void RegisterCompositorVsyncDispatcher( + CompositorVsyncDispatcher* aCompositorVsyncDispatcher); + void DeregisterCompositorVsyncDispatcher( + CompositorVsyncDispatcher* aCompositorVsyncDispatcher); + + // Add and remove a generic observer for vsync. Note that keeping an observer + // registered means vsync will keep firing, which may impact power usage. So + // this is intended only for "short term" vsync observers. These methods must + // be called on the parent process main thread, and the observer will likewise + // be notified on the parent process main thread. + void AddGenericObserver(VsyncObserver* aObserver); + void RemoveGenericObserver(VsyncObserver* aObserver); + + void MoveListenersToNewSource(const RefPtr<VsyncSource>& aNewSource); + + RefPtr<RefreshTimerVsyncDispatcher> GetRefreshTimerVsyncDispatcher(); + virtual Display& GetGlobalDisplay() = 0; // Works across all displays + void Shutdown(); + + protected: + virtual ~VsyncSource() = default; +}; + +} // namespace gfx + +struct VsyncEvent { + VsyncId mId; + TimeStamp mTime; + TimeStamp mOutputTime; // estimate + + VsyncEvent(const VsyncId& aId, const TimeStamp& aVsyncTime, + const TimeStamp& aOutputTime) + : mId(aId), mTime(aVsyncTime), mOutputTime(aOutputTime) {} + VsyncEvent() = default; +}; + +} // namespace mozilla + +#endif /* GFX_VSYNCSOURCE_H */ diff --git a/gfx/thebes/cairo-xlib-utils.h b/gfx/thebes/cairo-xlib-utils.h new file mode 100644 index 0000000000..0125ff12cb --- /dev/null +++ b/gfx/thebes/cairo-xlib-utils.h @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CAIROXLIBUTILS_H_ +#define CAIROXLIBUTILS_H_ + +#include "cairo.h" +#include <X11/Xlib.h> + +CAIRO_BEGIN_DECLS + +/** + * This callback encapsulates Xlib-based rendering. We assume that the + * execution of the callback is equivalent to compositing some RGBA image of + * size (bounds_width, bounds_height) onto the drawable at offset (offset_x, + * offset_y), clipped to the union of the clip_rects if num_rects is greater + * than zero. This includes the assumption that the same RGBA image + * is composited if you call the callback multiple times with the same closure, + * display and visual during a single cairo_draw_with_xlib call. + * + * @return True on success, False on non-recoverable error + */ +typedef cairo_bool_t (*cairo_xlib_drawing_callback)( + void* closure, Screen* screen, Drawable drawable, Visual* visual, + short offset_x, short offset_y, XRectangle* clip_rects, + unsigned int num_rects); + +/** + * This structure captures the result of the native drawing, in case the + * caller may wish to reapply the drawing efficiently later. + */ +typedef struct { + cairo_surface_t* surface; + cairo_bool_t uniform_alpha; + cairo_bool_t uniform_color; + double alpha; /* valid only if uniform_alpha is TRUE */ + double r, g, b; /* valid only if uniform_color is TRUE */ +} cairo_xlib_drawing_result_t; + +/** + * This type specifies whether the native drawing callback draws all pixels + * in its bounds opaquely, independent of the contents of the target drawable, + * or whether it leaves pixels transparent/translucent or depends on the + * existing contents of the target drawable in some way. + */ +typedef enum _cairo_xlib_drawing_opacity { + CAIRO_XLIB_DRAWING_OPAQUE, + CAIRO_XLIB_DRAWING_TRANSPARENT +} cairo_xlib_drawing_opacity_t; + +/** + * This type encodes the capabilities of the native drawing callback. + * + * If CAIRO_XLIB_DRAWING_SUPPORTS_OFFSET is set, 'offset_x' and 'offset_y' + * can be nonzero in the call to the callback; otherwise they will be zero. + * + * If CAIRO_XLIB_DRAWING_SUPPORTS_CLIP_RECT is set, then 'num_rects' can be + * zero or one in the call to the callback. If + * CAIRO_XLIB_DRAWING_SUPPORTS_CLIP_LIST is set, then 'num_rects' can be + * anything in the call to the callback. Otherwise 'num_rects' will be zero. + * Do not set both of these values. + * + * If CAIRO_XLIB_DRAWING_SUPPORTS_ALTERNATE_SCREEN is set, then 'screen' can + * be any screen on any display, otherwise it will be the default screen of + * the display passed into cairo_draw_with_xlib. + * + * If CAIRO_XLIB_DRAWING_SUPPORTS_NONDEFAULT_VISUAL is set, then 'visual' can be + * any visual, otherwise it will be equal to + * DefaultVisualOfScreen (screen). + */ +typedef enum { + CAIRO_XLIB_DRAWING_SUPPORTS_OFFSET = 0x01, + CAIRO_XLIB_DRAWING_SUPPORTS_CLIP_RECT = 0x02, + CAIRO_XLIB_DRAWING_SUPPORTS_CLIP_LIST = 0x04, + CAIRO_XLIB_DRAWING_SUPPORTS_ALTERNATE_SCREEN = 0x08, + CAIRO_XLIB_DRAWING_SUPPORTS_NONDEFAULT_VISUAL = 0x10 +} cairo_xlib_drawing_support_t; + +/** + * Draw Xlib output into any cairo context. All cairo transforms and effects + * are honored, including the current operator. This is equivalent to a + * cairo_set_source_surface and then cairo_paint. + * @param cr the context to draw into + * @param callback the code to perform Xlib rendering + * @param closure associated data + * @param dpy an X display to use in case the cairo context has no associated X + * display + * @param width the width of the putative image drawn by the callback + * @param height the height of the putative image drawn by the callback + * @param is_opaque set to CAIRO_XLIB_DRAWING_IS_OPAQUE to indicate + * that all alpha values of the putative image will be 1.0; the pixels drawn + * into the Drawable must not depend on the prior contents of the Drawable in + * any way + * @param capabilities the capabilities of the callback as described above. + * @param result if non-NULL, we *may* fill in the struct with information about + * the rendered image. 'surface' may be filled in with a surface representing + * the image, similar to the target of 'cr'. If 'uniform_alpha' is True then + * every pixel of the image has the same alpha value 'alpha'. If + * 'uniform_color' is True then every pixel of the image has the same RGB + * color (r, g, b). If the image has uniform color and alpha (or alpha is zero, + * in which case the color is always uniform) then we won't bother returning + * a surface for it. + */ +void cairo_draw_with_xlib(cairo_t* cr, cairo_xlib_drawing_callback callback, + void* closure, Display* dpy, unsigned int width, + unsigned int height, + cairo_xlib_drawing_opacity_t is_opaque, + cairo_xlib_drawing_support_t capabilities, + cairo_xlib_drawing_result_t* result); + +CAIRO_END_DECLS + +#endif /*CAIROXLIBUTILS_H_*/ diff --git a/gfx/thebes/d3dkmtQueryStatistics.h b/gfx/thebes/d3dkmtQueryStatistics.h new file mode 100644 index 0000000000..c3491f8a06 --- /dev/null +++ b/gfx/thebes/d3dkmtQueryStatistics.h @@ -0,0 +1,157 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +/* 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 0000000000..0fa6c65f82 --- /dev/null +++ b/gfx/thebes/genLanguageTagList.pl @@ -0,0 +1,86 @@ +#!/usr/bin/env perl + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# This tool is used to prepare a list of valid language subtags from the +# IANA registry (http://www.iana.org/assignments/language-subtag-registry). + +# Run as +# +# perl genLanguageTagList.pl language-subtag-registry > gfxLanguageTagList.cpp +# +# where language-subtag-registry is a copy of the IANA registry file. + +use strict; + +my $timestamp = gmtime(); +print <<__END; +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Derived from the IANA language subtag registry by genLanguageTagList.pl. + * + * Created on $timestamp. + * + * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * * + */ + +__END + +my $isLanguage = 0; + +while (<>) { + # strip leading/trailing whitespace, if any + chomp; + s/^\s+//; + s/\s+$//; + + # assume File-Date precedes the actual list; + # record the date, and begin assignment to an array of valid tags + if (m/^File-Date:\s*(.+)$/) { + print "// Based on IANA registry dated $1\n\n"; + print "static const uint32_t sLanguageTagList[] = {"; + next; + } + + if (m/^%%/) { + $isLanguage = 0; + next; + } + + # we only care about records of type 'language' + if (m/^Type:\s*(.+)$/) { + $isLanguage = ($1 eq 'language'); + next; + } + + # append the tag to our string, with ";" as a delimiter + if ($isLanguage && m/^Subtag:\s*([a-z]{2,3})\s*$/) { + my $tagstr = $1; + print "\n TRUETYPE_TAG(", + join(",", map { $_ eq " " ? " 0 " : "'" . $_ . "'" } split(//, substr($tagstr . " ", 0, 4))), + "), // ", $tagstr; + next; + } + + if ($isLanguage && m/^Description:\s*(.+)$/) { + print " = $1"; + $isLanguage = 0; # only print first Description field + next; + } +} + +# at end of file, terminate our assignment to the array of tags +print <<__END; + + 0x0 // end of language code list +}; + +/* + * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * * + */ +__END diff --git a/gfx/thebes/gencjkcisvs.py b/gfx/thebes/gencjkcisvs.py new file mode 100644 index 0000000000..72a5588435 --- /dev/null +++ b/gfx/thebes/gencjkcisvs.py @@ -0,0 +1,89 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +from __future__ import absolute_import +import os.path +import re +import sys + +f = open(sys.argv[1] if len(sys.argv) > 1 else "StandardizedVariants.txt") + +line = f.readline() +m = re.compile("^# (StandardizedVariants(-\d+(\.\d+)*)?\.txt)").search(line) +fileversion = m.group(1) +vsdict = {} +r = re.compile( + "^([0-9A-F]{4,6}) (FE0[0-9A-F]); CJK COMPATIBILITY IDEOGRAPH-([0-9A-F]{4,6});" +) +while True: + line = f.readline() + if not line: + break + if "CJK COMPATIBILITY IDEOGRAPH-" not in line: + continue + + m = r.search(line) + unified = int(m.group(1), 16) + vs = int(m.group(2), 16) + compat = int(m.group(3), 16) + + if vs not in vsdict: + vsdict[vs] = {} + vsdict[vs][unified] = compat + +f.close + +offsets = [] +length = 10 + 11 * len(vsdict) +for (k, mappings) in sorted(vsdict.items()): + offsets.append(length) + length += 4 + 5 * len(mappings) + +f = open(sys.argv[2] if len(sys.argv) > 2 else "CJKCompatSVS.cpp", "wb") +f.write( + """// Generated by %s. Do not edit. + +#include <stdint.h> + +#define U16(v) (((v) >> 8) & 0xFF), ((v) & 0xFF) +#define U24(v) (((v) >> 16) & 0xFF), (((v) >> 8) & 0xFF), ((v) & 0xFF) +#define U32(v) (((v) >> 24) & 0xFF), (((v) >> 16) & 0xFF), (((v) >> 8) & 0xFF), ((v) & 0xFF) +#define GLYPH(v) U16(v >= 0x2F800 ? (v) - (0x2F800 - 0xFB00) : (v)) + +// Fallback mappings for CJK Compatibility Ideographs Standardized Variants +// taken from %s. +// Using OpenType format 14 cmap subtable structure to reuse the lookup code +// for fonts. The glyphID field is used to store the corresponding codepoints +// CJK Compatibility Ideographs. To fit codepoints into the 16-bit glyphID +// field, CJK Compatibility Ideographs Supplement (U+2F800..U+2FA1F) will be +// mapped to 0xFB00..0xFD1F. +extern const uint8_t sCJKCompatSVSTable[] = { +""" + % (os.path.basename(sys.argv[0]), fileversion) +) +f.write(" U16(14), // format\n") +f.write(" U32(%d), // length\n" % length) +f.write(" U32(%d), // numVarSelectorRecords\n" % len(vsdict)) +for i, k in enumerate(sorted(vsdict.keys())): + f.write( + " U24(0x%04X), U32(0), U32(%d), // varSelectorRecord[%d]\n" + % (k, offsets[i], i) + ) +for (k, mappings) in sorted(vsdict.items()): + f.write(" // 0x%04X\n" % k) + f.write(" U32(%d), // numUVSMappings\n" % len(mappings)) + for (unified, compat) in sorted(mappings.items()): + f.write(" U24(0x%04X), GLYPH(0x%04X),\n" % (unified, compat)) +f.write( + """}; + +#undef U16 +#undef U24 +#undef U32 +#undef GLYPH + +static_assert(sizeof sCJKCompatSVSTable == %d, "Table generator has a bug."); +""" + % length +) diff --git a/gfx/thebes/gfx2DGlue.h b/gfx/thebes/gfx2DGlue.h new file mode 100644 index 0000000000..578d742502 --- /dev/null +++ b/gfx/thebes/gfx2DGlue.h @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_2D_GLUE_H +#define GFX_2D_GLUE_H + +#include "gfxMatrix.h" +#include "gfxPoint.h" +#include "gfxRect.h" +#include "gfxTypes.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/gfx/Rect.h" +#include "mozilla/gfx/Types.h" + +namespace mozilla { +namespace gfx { + +inline Rect ToRect(const gfxRect& aRect) { + return Rect(Float(aRect.X()), Float(aRect.Y()), Float(aRect.Width()), + Float(aRect.Height())); +} + +inline RectDouble ToRectDouble(const gfxRect& aRect) { + return RectDouble(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); +} + +inline Matrix ToMatrix(const gfxMatrix& aMatrix) { + return Matrix(Float(aMatrix._11), Float(aMatrix._12), Float(aMatrix._21), + Float(aMatrix._22), Float(aMatrix._31), Float(aMatrix._32)); +} + +inline gfxMatrix ThebesMatrix(const Matrix& aMatrix) { + return gfxMatrix(aMatrix._11, aMatrix._12, aMatrix._21, aMatrix._22, + aMatrix._31, aMatrix._32); +} + +inline Point ToPoint(const gfxPoint& aPoint) { + return Point(Float(aPoint.x), Float(aPoint.y)); +} + +inline Size ToSize(const gfxSize& aSize) { + return Size(Float(aSize.width), Float(aSize.height)); +} + +inline gfxPoint ThebesPoint(const Point& aPoint) { + return gfxPoint(aPoint.x, aPoint.y); +} + +inline gfxSize ThebesSize(const Size& aSize) { + return gfxSize(aSize.width, aSize.height); +} + +inline gfxRect ThebesRect(const Rect& aRect) { + return gfxRect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); +} + +inline gfxRect ThebesRect(const IntRect& aRect) { + return gfxRect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); +} + +inline gfxRect ThebesRect(const RectDouble& aRect) { + return gfxRect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); +} + +inline gfxImageFormat SurfaceFormatToImageFormat(SurfaceFormat aFormat) { + switch (aFormat) { + case SurfaceFormat::B8G8R8A8: + return SurfaceFormat::A8R8G8B8_UINT32; + case SurfaceFormat::B8G8R8X8: + return SurfaceFormat::X8R8G8B8_UINT32; + case SurfaceFormat::R5G6B5_UINT16: + return SurfaceFormat::R5G6B5_UINT16; + case SurfaceFormat::A8: + return SurfaceFormat::A8; + default: + return SurfaceFormat::UNKNOWN; + } +} + +inline SurfaceFormat ImageFormatToSurfaceFormat(gfxImageFormat aFormat) { + switch (aFormat) { + case SurfaceFormat::A8R8G8B8_UINT32: + return SurfaceFormat::B8G8R8A8; + case SurfaceFormat::X8R8G8B8_UINT32: + return SurfaceFormat::B8G8R8X8; + case SurfaceFormat::R5G6B5_UINT16: + return SurfaceFormat::R5G6B5_UINT16; + case SurfaceFormat::A8: + return SurfaceFormat::A8; + default: + case SurfaceFormat::UNKNOWN: + return SurfaceFormat::B8G8R8A8; + } +} + +inline gfxContentType ContentForFormat(const SurfaceFormat& aFormat) { + switch (aFormat) { + case SurfaceFormat::R5G6B5_UINT16: + case SurfaceFormat::B8G8R8X8: + case SurfaceFormat::R8G8B8X8: + return gfxContentType::COLOR; + case SurfaceFormat::A8: + return gfxContentType::ALPHA; + case SurfaceFormat::B8G8R8A8: + case SurfaceFormat::R8G8B8A8: + default: + return gfxContentType::COLOR_ALPHA; + } +} + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/thebes/gfxASurface.cpp b/gfx/thebes/gfxASurface.cpp new file mode 100644 index 0000000000..3a04b1f9c2 --- /dev/null +++ b/gfx/thebes/gfxASurface.cpp @@ -0,0 +1,524 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIMemoryReporter.h" +#include "nsMemory.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Base64.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/Attributes.h" +#include "mozilla/MemoryReporting.h" +#include "nsISupportsImpl.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/gfx/HelpersCairo.h" +#include "gfx2DGlue.h" + +#include "gfxASurface.h" +#include "gfxContext.h" +#include "gfxImageSurface.h" +#include "gfxPlatform.h" +#include "gfxRect.h" + +#include "cairo.h" +#include <algorithm> + +#ifdef CAIRO_HAS_WIN32_SURFACE +# include "gfxWindowsSurface.h" +#endif + +#ifdef MOZ_X11 +# include "gfxXlibSurface.h" +#endif + +#ifdef CAIRO_HAS_QUARTZ_SURFACE +# include "gfxQuartzSurface.h" +#endif + +#include <stdio.h> +#include <limits.h> + +#include "nsComponentManagerUtils.h" +#include "nsISupportsUtils.h" +#include "nsCOMPtr.h" +#include "nsServiceManagerUtils.h" +#include "nsString.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +static cairo_user_data_key_t gfxasurface_pointer_key; + +gfxASurface::gfxASurface() + : mSurface(nullptr), + mFloatingRefs(0), + mBytesRecorded(0), + mSurfaceValid(false) { + MOZ_COUNT_CTOR(gfxASurface); +} + +gfxASurface::~gfxASurface() { + RecordMemoryFreed(); + + MOZ_COUNT_DTOR(gfxASurface); +} + +// Surfaces use refcounting that's tied to the cairo surface refcnt, to avoid +// refcount mismatch issues. +nsrefcnt gfxASurface::AddRef(void) { + if (mSurfaceValid) { + if (mFloatingRefs) { + // eat a floating ref + mFloatingRefs--; + } else { + cairo_surface_reference(mSurface); + } + + return (nsrefcnt)cairo_surface_get_reference_count(mSurface); + } + // the surface isn't valid, but we still need to refcount + // the gfxASurface + return ++mFloatingRefs; +} + +nsrefcnt gfxASurface::Release(void) { + if (mSurfaceValid) { + NS_ASSERTION( + mFloatingRefs == 0, + "gfxASurface::Release with floating refs still hanging around!"); + + // Note that there is a destructor set on user data for mSurface, + // which will delete this gfxASurface wrapper when the surface's refcount + // goes out of scope. + nsrefcnt refcnt = (nsrefcnt)cairo_surface_get_reference_count(mSurface); + cairo_surface_destroy(mSurface); + + // |this| may not be valid any more, don't use it! + + return --refcnt; + } + if (--mFloatingRefs == 0) { + delete this; + return 0; + } + return mFloatingRefs; +} + +void gfxASurface::SurfaceDestroyFunc(void* data) { + gfxASurface* surf = (gfxASurface*)data; + // fprintf (stderr, "Deleting wrapper for %p (wrapper: %p)\n", surf->mSurface, + // data); + delete surf; +} + +gfxASurface* gfxASurface::GetSurfaceWrapper(cairo_surface_t* csurf) { + if (!csurf) return nullptr; + return (gfxASurface*)cairo_surface_get_user_data(csurf, + &gfxasurface_pointer_key); +} + +void gfxASurface::SetSurfaceWrapper(cairo_surface_t* csurf, + gfxASurface* asurf) { + if (!csurf) return; + cairo_surface_set_user_data(csurf, &gfxasurface_pointer_key, asurf, + SurfaceDestroyFunc); +} + +already_AddRefed<gfxASurface> gfxASurface::Wrap(cairo_surface_t* csurf, + const IntSize& aSize) { + RefPtr<gfxASurface> result; + + /* Do we already have a wrapper for this surface? */ + result = GetSurfaceWrapper(csurf); + if (result) { + // fprintf(stderr, "Existing wrapper for %p -> %p\n", csurf, result); + return result.forget(); + } + + /* No wrapper; figure out the surface type and create it */ + cairo_surface_type_t stype = cairo_surface_get_type(csurf); + + if (stype == CAIRO_SURFACE_TYPE_IMAGE) { + result = new gfxImageSurface(csurf); + } +#ifdef CAIRO_HAS_WIN32_SURFACE + else if (stype == CAIRO_SURFACE_TYPE_WIN32 || + stype == CAIRO_SURFACE_TYPE_WIN32_PRINTING) { + result = new gfxWindowsSurface(csurf); + } +#endif +#ifdef MOZ_X11 + else if (stype == CAIRO_SURFACE_TYPE_XLIB) { + result = new gfxXlibSurface(csurf); + } +#endif +#ifdef CAIRO_HAS_QUARTZ_SURFACE + else if (stype == CAIRO_SURFACE_TYPE_QUARTZ) { + result = new gfxQuartzSurface(csurf, aSize); + } +#endif + else { + result = new gfxUnknownSurface(csurf, aSize); + } + + // fprintf(stderr, "New wrapper for %p -> %p\n", csurf, result); + + return result.forget(); +} + +void gfxASurface::Init(cairo_surface_t* surface, bool existingSurface) { + SetSurfaceWrapper(surface, this); + MOZ_ASSERT(surface, "surface should be a valid pointer"); + + mSurface = surface; + mSurfaceValid = !cairo_surface_status(surface); + if (!mSurfaceValid) { + gfxWarning() << "ASurface Init failed with Cairo status " + << cairo_surface_status(surface) << " on " << hexa(surface); + } + + if (existingSurface || !mSurfaceValid) { + mFloatingRefs = 0; + } else { + mFloatingRefs = 1; +#ifdef MOZ_TREE_CAIRO + if (cairo_surface_get_content(surface) != CAIRO_CONTENT_COLOR) { + cairo_surface_set_subpixel_antialiasing( + surface, CAIRO_SUBPIXEL_ANTIALIASING_DISABLED); + } +#endif + } +} + +gfxSurfaceType gfxASurface::GetType() const { + if (!mSurfaceValid) return (gfxSurfaceType)-1; + + return (gfxSurfaceType)cairo_surface_get_type(mSurface); +} + +gfxContentType gfxASurface::GetContentType() const { + if (!mSurfaceValid) return (gfxContentType)-1; + + return (gfxContentType)cairo_surface_get_content(mSurface); +} + +void gfxASurface::SetDeviceOffset(const gfxPoint& offset) { + if (!mSurfaceValid) return; + cairo_surface_set_device_offset(mSurface, offset.x, offset.y); +} + +gfxPoint gfxASurface::GetDeviceOffset() const { + if (!mSurfaceValid) return gfxPoint(0.0, 0.0); + gfxPoint pt; + cairo_surface_get_device_offset(mSurface, &pt.x, &pt.y); + return pt; +} + +void gfxASurface::Flush() const { + if (!mSurfaceValid) return; + cairo_surface_flush(mSurface); + gfxPlatform::ClearSourceSurfaceForSurface(const_cast<gfxASurface*>(this)); +} + +void gfxASurface::MarkDirty() { + if (!mSurfaceValid) return; + cairo_surface_mark_dirty(mSurface); + gfxPlatform::ClearSourceSurfaceForSurface(this); +} + +void gfxASurface::MarkDirty(const gfxRect& r) { + if (!mSurfaceValid) return; + cairo_surface_mark_dirty_rectangle(mSurface, (int)r.X(), (int)r.Y(), + (int)r.Width(), (int)r.Height()); + gfxPlatform::ClearSourceSurfaceForSurface(this); +} + +void gfxASurface::SetData(const cairo_user_data_key_t* key, void* user_data, + thebes_destroy_func_t destroy) { + if (!mSurfaceValid) return; + cairo_surface_set_user_data(mSurface, key, user_data, destroy); +} + +void* gfxASurface::GetData(const cairo_user_data_key_t* key) { + if (!mSurfaceValid) return nullptr; + return cairo_surface_get_user_data(mSurface, key); +} + +void gfxASurface::Finish() { + // null surfaces are allowed here + cairo_surface_finish(mSurface); +} + +already_AddRefed<gfxASurface> gfxASurface::CreateSimilarSurface( + gfxContentType aContent, const IntSize& aSize) { + if (!mSurface || !mSurfaceValid) { + return nullptr; + } + + cairo_surface_t* surface = cairo_surface_create_similar( + mSurface, cairo_content_t(int(aContent)), aSize.width, aSize.height); + if (cairo_surface_status(surface)) { + cairo_surface_destroy(surface); + return nullptr; + } + + RefPtr<gfxASurface> result = Wrap(surface, aSize); + cairo_surface_destroy(surface); + return result.forget(); +} + +already_AddRefed<gfxImageSurface> gfxASurface::CopyToARGB32ImageSurface() { + if (!mSurface || !mSurfaceValid) { + return nullptr; + } + + const IntSize size = GetSize(); + RefPtr<gfxImageSurface> imgSurface = + new gfxImageSurface(size, SurfaceFormat::A8R8G8B8_UINT32); + + RefPtr<DrawTarget> dt = gfxPlatform::CreateDrawTargetForSurface( + imgSurface, IntSize(size.width, size.height)); + RefPtr<SourceSurface> source = + gfxPlatform::GetSourceSurfaceForSurface(dt, this); + + dt->CopySurface(source, IntRect(0, 0, size.width, size.height), IntPoint()); + + return imgSurface.forget(); +} + +int gfxASurface::CairoStatus() { + if (!mSurfaceValid) return -1; + + return cairo_surface_status(mSurface); +} + +nsresult gfxASurface::BeginPrinting(const nsAString& aTitle, + const nsAString& aPrintToFileName) { + return NS_OK; +} + +nsresult gfxASurface::EndPrinting() { return NS_OK; } + +nsresult gfxASurface::AbortPrinting() { return NS_OK; } + +nsresult gfxASurface::BeginPage() { return NS_OK; } + +nsresult gfxASurface::EndPage() { return NS_OK; } + +gfxContentType gfxASurface::ContentFromFormat(gfxImageFormat format) { + switch (format) { + case SurfaceFormat::A8R8G8B8_UINT32: + return gfxContentType::COLOR_ALPHA; + case SurfaceFormat::X8R8G8B8_UINT32: + case SurfaceFormat::R5G6B5_UINT16: + return gfxContentType::COLOR; + case SurfaceFormat::A8: + return gfxContentType::ALPHA; + + case SurfaceFormat::UNKNOWN: + default: + return gfxContentType::COLOR; + } +} + +int32_t gfxASurface::BytePerPixelFromFormat(gfxImageFormat format) { + switch (format) { + case SurfaceFormat::A8R8G8B8_UINT32: + case SurfaceFormat::X8R8G8B8_UINT32: + return 4; + case SurfaceFormat::R5G6B5_UINT16: + return 2; + case SurfaceFormat::A8: + return 1; + default: + NS_WARNING("Unknown byte per pixel value for Image format"); + } + return 0; +} + +/** Memory reporting **/ + +static const char* sDefaultSurfaceDescription = + "Memory used by gfx surface of the given type."; + +struct SurfaceMemoryReporterAttrs { + const char* path; + const char* description; +}; + +static const SurfaceMemoryReporterAttrs sSurfaceMemoryReporterAttrs[] = { + {"gfx-surface-image", nullptr}, + {"gfx-surface-pdf", nullptr}, + {"gfx-surface-ps", nullptr}, + {"gfx-surface-xlib", + "Memory used by xlib surfaces to store pixmaps. This memory lives in " + "the X server's process rather than in this application, so the bytes " + "accounted for here aren't counted in vsize, resident, explicit, or any " + "of " + "the other measurements on this page."}, + {"gfx-surface-xcb", nullptr}, + {"gfx-surface-glitz???", nullptr}, // should never be used + {"gfx-surface-quartz", nullptr}, + {"gfx-surface-win32", nullptr}, + {"gfx-surface-beos", nullptr}, + {"gfx-surface-directfb???", nullptr}, // should never be used + {"gfx-surface-svg", nullptr}, + {"gfx-surface-os2", nullptr}, + {"gfx-surface-win32printing", nullptr}, + {"gfx-surface-quartzimage", nullptr}, + {"gfx-surface-script", nullptr}, + {"gfx-surface-qpainter", nullptr}, + {"gfx-surface-recording", nullptr}, + {"gfx-surface-vg", nullptr}, + {"gfx-surface-gl", nullptr}, + {"gfx-surface-drm", nullptr}, + {"gfx-surface-tee", nullptr}, + {"gfx-surface-xml", nullptr}, + {"gfx-surface-skia", nullptr}, + {"gfx-surface-subsurface", nullptr}, +}; + +static_assert(MOZ_ARRAY_LENGTH(sSurfaceMemoryReporterAttrs) == + size_t(gfxSurfaceType::Max), + "sSurfaceMemoryReporterAttrs exceeds max capacity"); +static_assert(uint32_t(CAIRO_SURFACE_TYPE_SKIA) == + uint32_t(gfxSurfaceType::Skia), + "CAIRO_SURFACE_TYPE_SKIA not equal to gfxSurfaceType::Skia"); + +/* Surface size memory reporting */ + +class SurfaceMemoryReporter final : public nsIMemoryReporter { + ~SurfaceMemoryReporter() = default; + + // We can touch this array on several different threads, and we don't + // want to introduce memory barriers when recording the memory used. To + // assure dynamic race checkers like TSan that this is OK, we use + // relaxed memory ordering here. + static Atomic<size_t, Relaxed> + sSurfaceMemoryUsed[size_t(gfxSurfaceType::Max)]; + + public: + static void AdjustUsedMemory(gfxSurfaceType aType, int32_t aBytes) { + // A read-modify-write operation like += would require a memory barrier + // here, which would defeat the purpose of using relaxed memory + // ordering. So separate out the read and write operations. + sSurfaceMemoryUsed[size_t(aType)] = + sSurfaceMemoryUsed[size_t(aType)] + aBytes; + }; + + // This memory reporter is sometimes allocated on the compositor thread, + // but always released on the main thread, so its refcounting needs to be + // threadsafe. + NS_DECL_THREADSAFE_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + const size_t len = ArrayLength(sSurfaceMemoryReporterAttrs); + for (size_t i = 0; i < len; i++) { + int64_t amount = sSurfaceMemoryUsed[i]; + + if (amount != 0) { + const char* path = sSurfaceMemoryReporterAttrs[i].path; + const char* desc = sSurfaceMemoryReporterAttrs[i].description; + if (!desc) { + desc = sDefaultSurfaceDescription; + } + + aHandleReport->Callback(""_ns, nsCString(path), KIND_OTHER, UNITS_BYTES, + amount, nsCString(desc), aData); + } + } + + return NS_OK; + } +}; + +Atomic<size_t, Relaxed> + SurfaceMemoryReporter::sSurfaceMemoryUsed[size_t(gfxSurfaceType::Max)]; + +NS_IMPL_ISUPPORTS(SurfaceMemoryReporter, nsIMemoryReporter) + +void gfxASurface::RecordMemoryUsedForSurfaceType(gfxSurfaceType aType, + int32_t aBytes) { + if (int(aType) < 0 || aType >= gfxSurfaceType::Max) { + NS_WARNING("Invalid type to RecordMemoryUsedForSurfaceType!"); + return; + } + + static bool registered = false; + if (!registered) { + RegisterStrongMemoryReporter(new SurfaceMemoryReporter()); + registered = true; + } + + SurfaceMemoryReporter::AdjustUsedMemory(aType, aBytes); +} + +void gfxASurface::RecordMemoryUsed(int32_t aBytes) { + RecordMemoryUsedForSurfaceType(GetType(), aBytes); + mBytesRecorded += aBytes; +} + +void gfxASurface::RecordMemoryFreed() { + if (mBytesRecorded) { + RecordMemoryUsedForSurfaceType(GetType(), -mBytesRecorded); + mBytesRecorded = 0; + } +} + +size_t gfxASurface::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + // We don't measure mSurface because cairo doesn't allow it. + return 0; +} + +size_t gfxASurface::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +/* static */ +uint8_t gfxASurface::BytesPerPixel(gfxImageFormat aImageFormat) { + switch (aImageFormat) { + case SurfaceFormat::A8R8G8B8_UINT32: + return 4; + case SurfaceFormat::X8R8G8B8_UINT32: + return 4; + case SurfaceFormat::R5G6B5_UINT16: + return 2; + case SurfaceFormat::A8: + return 1; + case SurfaceFormat::UNKNOWN: + default: + MOZ_ASSERT_UNREACHABLE("Not really sure what you want me to say here"); + return 0; + } +} + +void gfxASurface::SetOpaqueRect(const gfxRect& aRect) { + if (aRect.IsEmpty()) { + mOpaqueRect = nullptr; + } else if (!!mOpaqueRect) { + *mOpaqueRect = aRect; + } else { + mOpaqueRect = MakeUnique<gfxRect>(aRect); + } +} + +/* static */ const gfxRect& gfxASurface::GetEmptyOpaqueRect() { + static const gfxRect empty(0, 0, 0, 0); + return empty; +} + +const IntSize gfxASurface::GetSize() const { return IntSize(-1, -1); } + +SurfaceFormat gfxASurface::GetSurfaceFormat() const { + if (!mSurfaceValid) { + return SurfaceFormat::UNKNOWN; + } + return GfxFormatForCairoSurface(mSurface); +} + +already_AddRefed<gfxImageSurface> gfxASurface::GetAsImageSurface() { + return nullptr; +} diff --git a/gfx/thebes/gfxASurface.h b/gfx/thebes/gfxASurface.h new file mode 100644 index 0000000000..4968c6b19e --- /dev/null +++ b/gfx/thebes/gfxASurface.h @@ -0,0 +1,192 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_ASURFACE_H +#define GFX_ASURFACE_H + +#include "mozilla/MemoryReporting.h" +#include "mozilla/UniquePtr.h" + +#include "gfxPoint.h" +#include "gfxRect.h" +#include "gfxTypes.h" +#include "nscore.h" +#include "nsSize.h" +#include "mozilla/gfx/Rect.h" + +#include "nsStringFwd.h" + +class gfxImageSurface; + +template <typename T> +struct already_AddRefed; + +/** + * A surface is something you can draw on. Instantiate a subclass of this + * abstract class, and use gfxContext to draw on this surface. + */ +class gfxASurface { + public: +#ifdef MOZILLA_INTERNAL_API + nsrefcnt AddRef(void); + nsrefcnt Release(void); +#else + virtual nsrefcnt AddRef(void); + virtual nsrefcnt Release(void); +#endif + + public: + /** Wrap the given cairo surface and return a gfxASurface for it. + * This adds a reference to csurf (owned by the returned gfxASurface). + */ + static already_AddRefed<gfxASurface> Wrap( + cairo_surface_t* csurf, + const mozilla::gfx::IntSize& aSize = mozilla::gfx::IntSize(-1, -1)); + + /*** this DOES NOT addref the surface */ + cairo_surface_t* CairoSurface() { return mSurface; } + + gfxSurfaceType GetType() const; + + gfxContentType GetContentType() const; + + void SetDeviceOffset(const gfxPoint& offset); + gfxPoint GetDeviceOffset() const; + + void Flush() const; + void MarkDirty(); + void MarkDirty(const gfxRect& r); + + /* Printing backend functions */ + virtual nsresult BeginPrinting(const nsAString& aTitle, + const nsAString& aPrintToFileName); + virtual nsresult EndPrinting(); + virtual nsresult AbortPrinting(); + virtual nsresult BeginPage(); + virtual nsresult EndPage(); + + void SetData(const cairo_user_data_key_t* key, void* user_data, + thebes_destroy_func_t destroy); + void* GetData(const cairo_user_data_key_t* key); + + virtual void Finish(); + + /** + * Create an offscreen surface that can be efficiently copied into + * this surface (at least if tiling is not involved). + * Returns null on error. + */ + virtual already_AddRefed<gfxASurface> CreateSimilarSurface( + gfxContentType aType, const mozilla::gfx::IntSize& aSize); + + /** + * Returns an image surface for this surface, or nullptr if not supported. + * This will not copy image data, just wraps an image surface around + * pixel data already available in memory. + */ + virtual already_AddRefed<gfxImageSurface> GetAsImageSurface(); + + /** + * Creates a new ARGB32 image surface with the same contents as this surface. + * Returns null on error. + */ + already_AddRefed<gfxImageSurface> CopyToARGB32ImageSurface(); + + int CairoStatus(); + + static gfxContentType ContentFromFormat(gfxImageFormat format); + + /** + * Record number of bytes for given surface type. Use positive bytes + * for allocations and negative bytes for deallocations. + */ + static void RecordMemoryUsedForSurfaceType(gfxSurfaceType aType, + int32_t aBytes); + + /** + * Same as above, but use current surface type as returned by GetType(). + * The bytes will be accumulated until RecordMemoryFreed is called, + * in which case the value that was recorded for this surface will + * be freed. + */ + void RecordMemoryUsed(int32_t aBytes); + void RecordMemoryFreed(); + + virtual int32_t KnownMemoryUsed() { return mBytesRecorded; } + + virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + // gfxASurface has many sub-classes. This method indicates if a sub-class + // is capable of measuring its own size accurately. If not, the caller + // must fall back to a computed size. (Note that gfxASurface can actually + // measure itself, but we must |return false| here because it serves as the + // (conservative) default for all the sub-classes. Therefore, this + // function should only be called on a |gfxASurface*| that actually points + // to a sub-class of gfxASurface.) + virtual bool SizeOfIsMeasured() const { return false; } + + static int32_t BytePerPixelFromFormat(gfxImageFormat format); + + virtual const mozilla::gfx::IntSize GetSize() const; + + virtual mozilla::gfx::SurfaceFormat GetSurfaceFormat() const; + + void SetOpaqueRect(const gfxRect& aRect); + + const gfxRect& GetOpaqueRect() { + if (!!mOpaqueRect) return *mOpaqueRect; + return GetEmptyOpaqueRect(); + } + + static uint8_t BytesPerPixel(gfxImageFormat aImageFormat); + + protected: + gfxASurface(); + + static gfxASurface* GetSurfaceWrapper(cairo_surface_t* csurf); + static void SetSurfaceWrapper(cairo_surface_t* csurf, gfxASurface* asurf); + + // NB: Init() *must* be called from within subclass's + // constructors. It's unsafe to call it after the ctor finishes; + // leaks and use-after-frees are possible. + void Init(cairo_surface_t* surface, bool existingSurface = false); + + // out-of-line helper to allow GetOpaqueRect() to be inlined + // without including gfxRect.h here + static const gfxRect& GetEmptyOpaqueRect(); + + virtual ~gfxASurface(); + + cairo_surface_t* mSurface; + mozilla::UniquePtr<gfxRect> mOpaqueRect; + + private: + static void SurfaceDestroyFunc(void* data); + + int32_t mFloatingRefs; + int32_t mBytesRecorded; + + protected: + bool mSurfaceValid; +}; + +/** + * An Unknown surface; used to wrap unknown cairo_surface_t returns from cairo + */ +class gfxUnknownSurface : public gfxASurface { + public: + gfxUnknownSurface(cairo_surface_t* surf, const mozilla::gfx::IntSize& aSize) + : mSize(aSize) { + Init(surf, true); + } + + virtual ~gfxUnknownSurface() = default; + const mozilla::gfx::IntSize GetSize() const override { return mSize; } + + private: + mozilla::gfx::IntSize mSize; +}; + +#endif /* GFX_ASURFACE_H */ diff --git a/gfx/thebes/gfxAlphaRecovery.cpp b/gfx/thebes/gfxAlphaRecovery.cpp new file mode 100644 index 0000000000..98a78ed3cb --- /dev/null +++ b/gfx/thebes/gfxAlphaRecovery.cpp @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxAlphaRecovery.h" + +#include "gfxImageSurface.h" + +#define MOZILLA_SSE_INCLUDE_HEADER_FOR_SSE2 +#include "mozilla/SSE.h" + +/* static */ +bool gfxAlphaRecovery::RecoverAlpha(gfxImageSurface* blackSurf, + const gfxImageSurface* whiteSurf) { + mozilla::gfx::IntSize size = blackSurf->GetSize(); + + if (size != whiteSurf->GetSize() || + (blackSurf->Format() != mozilla::gfx::SurfaceFormat::A8R8G8B8_UINT32 && + blackSurf->Format() != mozilla::gfx::SurfaceFormat::X8R8G8B8_UINT32) || + (whiteSurf->Format() != mozilla::gfx::SurfaceFormat::A8R8G8B8_UINT32 && + whiteSurf->Format() != mozilla::gfx::SurfaceFormat::X8R8G8B8_UINT32)) + return false; + +#ifdef MOZILLA_MAY_SUPPORT_SSE2 + if (mozilla::supports_sse2() && RecoverAlphaSSE2(blackSurf, whiteSurf)) { + return true; + } +#endif + + blackSurf->Flush(); + whiteSurf->Flush(); + + unsigned char* blackData = blackSurf->Data(); + unsigned char* whiteData = whiteSurf->Data(); + + for (int32_t i = 0; i < size.height; ++i) { + uint32_t* blackPixel = reinterpret_cast<uint32_t*>(blackData); + const uint32_t* whitePixel = reinterpret_cast<uint32_t*>(whiteData); + for (int32_t j = 0; j < size.width; ++j) { + uint32_t recovered = RecoverPixel(blackPixel[j], whitePixel[j]); + blackPixel[j] = recovered; + } + blackData += blackSurf->Stride(); + whiteData += whiteSurf->Stride(); + } + + blackSurf->MarkDirty(); + + return true; +} diff --git a/gfx/thebes/gfxAlphaRecovery.h b/gfx/thebes/gfxAlphaRecovery.h new file mode 100644 index 0000000000..53b38f2ac0 --- /dev/null +++ b/gfx/thebes/gfxAlphaRecovery.h @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _GFXALPHARECOVERY_H_ +#define _GFXALPHARECOVERY_H_ + +#include "mozilla/SSE.h" +#include "gfxTypes.h" +#include "mozilla/gfx/Rect.h" + +class gfxImageSurface; + +class gfxAlphaRecovery { + public: + /** + * Some SIMD fast-paths only can be taken if the relative + * byte-alignment of images' pointers and strides meets certain + * criteria. Aligning image pointers and strides by + * |GoodAlignmentLog2()| below will ensure that fast-paths aren't + * skipped because of misalignment. Fast-paths may still be taken + * even if GoodAlignmentLog2() is not met, in some conditions. + */ + static uint32_t GoodAlignmentLog2() { return 4; /* for SSE2 */ } + + /* Given two surfaces of equal size with the same rendering, one onto a + * black background and the other onto white, recovers alpha values from + * the difference and sets the alpha values on the black surface. + * The surfaces must have format RGB24 or ARGB32. + * Returns true on success. + */ + static bool RecoverAlpha(gfxImageSurface* blackSurface, + const gfxImageSurface* whiteSurface); + +#ifdef MOZILLA_MAY_SUPPORT_SSE2 + /* This does the same as the previous function, but uses SSE2 + * optimizations. Usually this should not be called directly. Be sure to + * check mozilla::supports_sse2() before calling this function. + */ + static bool RecoverAlphaSSE2(gfxImageSurface* blackSurface, + const gfxImageSurface* whiteSurface); + + /** + * A common use-case for alpha recovery is to paint into a + * temporary "white image", then paint onto a subrect of the + * surface, the "black image", into which alpha-recovered pixels + * are eventually to be written. This function returns a rect + * aligned so that recovering alpha for that rect will hit SIMD + * fast-paths, if possible. It's not always possible to align + * |aRect| so that fast-paths will be taken. + * + * The returned rect is always a superset of |aRect|. + */ + static mozilla::gfx::IntRect AlignRectForSubimageRecovery( + const mozilla::gfx::IntRect& aRect, gfxImageSurface* aSurface); +#else + static mozilla::gfx::IntRect AlignRectForSubimageRecovery( + const mozilla::gfx::IntRect& aRect, gfxImageSurface*) { + return aRect; + } +#endif + + /** from cairo-xlib-utils.c, modified */ + /** + * Given the RGB data for two image surfaces, one a source image composited + * with OVER onto a black background, and one a source image composited with + * OVER onto a white background, reconstruct the original image data into + * black_data. + * + * Consider a single color channel and a given pixel. Suppose the original + * premultiplied color value was C and the alpha value was A. Let the final + * on-black color be B and the final on-white color be W. All values range + * over 0-255. + * + * Then B=C and W=(255*(255 - A) + C*255)/255. Solving for A, we get + * A=255 - (W - C). Therefore it suffices to leave the black_data color + * data alone and set the alpha values using that simple formula. It shouldn't + * matter what color channel we pick for the alpha computation, but we'll + * pick green because if we went through a color channel downsample the green + * bits are likely to be the most accurate. + * + * This function needs to be in the header file since it's used by both + * gfxRecoverAlpha.cpp and gfxRecoverAlphaSSE2.cpp. + */ + + static inline uint32_t RecoverPixel(uint32_t black, uint32_t white) { + const uint32_t GREEN_MASK = 0x0000FF00; + const uint32_t ALPHA_MASK = 0xFF000000; + + /* |diff| here is larger when the source image pixel is more + transparent. If both renderings are from the same source image + composited with OVER, then the color values on white will always be + greater than those on black, so |diff| would not overflow. However, + overflow may happen, for example, when a plugin plays a video and + the image is rapidly changing. If there is overflow, then behave as + if we limit to the difference to + >= 0, which will make the rendering opaque. (Without this overflow + will make the rendering transparent.) */ + uint32_t diff = (white & GREEN_MASK) - (black & GREEN_MASK); + /* |diff| is 0xFFFFxx00 on overflow and 0x0000xx00 otherwise, so use + this to limit the transparency. */ + uint32_t limit = diff & ALPHA_MASK; + /* The alpha bits of the result */ + uint32_t alpha = (ALPHA_MASK - (diff << 16)) | limit; + + return alpha | (black & ~ALPHA_MASK); + } +}; + +#endif /* _GFXALPHARECOVERY_H_ */ diff --git a/gfx/thebes/gfxAlphaRecoverySSE2.cpp b/gfx/thebes/gfxAlphaRecoverySSE2.cpp new file mode 100644 index 0000000000..d64cb18bad --- /dev/null +++ b/gfx/thebes/gfxAlphaRecoverySSE2.cpp @@ -0,0 +1,234 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxAlphaRecovery.h" +#include "gfxImageSurface.h" +#include "nsDebug.h" +#include <emmintrin.h> + +// This file should only be compiled on x86 and x64 systems. Additionally, +// you'll need to compile it with -msse2 if you're using GCC on x86. + +#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64)) +__declspec(align(16)) static uint32_t greenMaski[] = {0x0000ff00, 0x0000ff00, + 0x0000ff00, 0x0000ff00}; +__declspec(align(16)) static uint32_t alphaMaski[] = {0xff000000, 0xff000000, + 0xff000000, 0xff000000}; +#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +static uint32_t greenMaski[] __attribute__((aligned(16))) = { + 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00}; +static uint32_t alphaMaski[] __attribute__((aligned(16))) = { + 0xff000000, 0xff000000, 0xff000000, 0xff000000}; +#elif defined(__SUNPRO_CC) && (defined(__i386) || defined(__x86_64__)) +# pragma align 16(greenMaski, alphaMaski) +static uint32_t greenMaski[] = {0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00}; +static uint32_t alphaMaski[] = {0xff000000, 0xff000000, 0xff000000, 0xff000000}; +#endif + +bool gfxAlphaRecovery::RecoverAlphaSSE2(gfxImageSurface* blackSurf, + const gfxImageSurface* whiteSurf) { + mozilla::gfx::IntSize size = blackSurf->GetSize(); + + if (size != whiteSurf->GetSize() || + (blackSurf->Format() != mozilla::gfx::SurfaceFormat::A8R8G8B8_UINT32 && + blackSurf->Format() != mozilla::gfx::SurfaceFormat::X8R8G8B8_UINT32) || + (whiteSurf->Format() != mozilla::gfx::SurfaceFormat::A8R8G8B8_UINT32 && + whiteSurf->Format() != mozilla::gfx::SurfaceFormat::X8R8G8B8_UINT32)) + return false; + + blackSurf->Flush(); + whiteSurf->Flush(); + + unsigned char* blackData = blackSurf->Data(); + unsigned char* whiteData = whiteSurf->Data(); + + if ((NS_PTR_TO_UINT32(blackData) & 0xf) != + (NS_PTR_TO_UINT32(whiteData) & 0xf) || + (blackSurf->Stride() - whiteSurf->Stride()) & 0xf) { + // Cannot keep these in alignment. + return false; + } + + __m128i greenMask = _mm_load_si128((__m128i*)greenMaski); + __m128i alphaMask = _mm_load_si128((__m128i*)alphaMaski); + + for (int32_t i = 0; i < size.height; ++i) { + int32_t j = 0; + // Loop single pixels until at 4 byte alignment. + while (NS_PTR_TO_UINT32(blackData) & 0xf && j < size.width) { + *((uint32_t*)blackData) = + RecoverPixel(*reinterpret_cast<uint32_t*>(blackData), + *reinterpret_cast<uint32_t*>(whiteData)); + blackData += 4; + whiteData += 4; + j++; + } + // This extra loop allows the compiler to do some more clever registry + // management and makes it about 5% faster than with only the 4 pixel + // at a time loop. + for (; j < size.width - 8; j += 8) { + __m128i black1 = _mm_load_si128((__m128i*)blackData); + __m128i white1 = _mm_load_si128((__m128i*)whiteData); + __m128i black2 = _mm_load_si128((__m128i*)(blackData + 16)); + __m128i white2 = _mm_load_si128((__m128i*)(whiteData + 16)); + + // Execute the same instructions as described in RecoverPixel, only + // using an SSE2 packed saturated subtract. + white1 = _mm_subs_epu8(white1, black1); + white2 = _mm_subs_epu8(white2, black2); + white1 = _mm_subs_epu8(greenMask, white1); + white2 = _mm_subs_epu8(greenMask, white2); + // Producing the final black pixel in an XMM register and storing + // that is actually faster than doing a masked store since that + // does an unaligned storage. We have the black pixel in a register + // anyway. + black1 = _mm_andnot_si128(alphaMask, black1); + black2 = _mm_andnot_si128(alphaMask, black2); + white1 = _mm_slli_si128(white1, 2); + white2 = _mm_slli_si128(white2, 2); + white1 = _mm_and_si128(alphaMask, white1); + white2 = _mm_and_si128(alphaMask, white2); + black1 = _mm_or_si128(white1, black1); + black2 = _mm_or_si128(white2, black2); + + _mm_store_si128((__m128i*)blackData, black1); + _mm_store_si128((__m128i*)(blackData + 16), black2); + blackData += 32; + whiteData += 32; + } + for (; j < size.width - 4; j += 4) { + __m128i black = _mm_load_si128((__m128i*)blackData); + __m128i white = _mm_load_si128((__m128i*)whiteData); + + white = _mm_subs_epu8(white, black); + white = _mm_subs_epu8(greenMask, white); + black = _mm_andnot_si128(alphaMask, black); + white = _mm_slli_si128(white, 2); + white = _mm_and_si128(alphaMask, white); + black = _mm_or_si128(white, black); + _mm_store_si128((__m128i*)blackData, black); + blackData += 16; + whiteData += 16; + } + // Loop single pixels until we're done. + while (j < size.width) { + *((uint32_t*)blackData) = + RecoverPixel(*reinterpret_cast<uint32_t*>(blackData), + *reinterpret_cast<uint32_t*>(whiteData)); + blackData += 4; + whiteData += 4; + j++; + } + blackData += blackSurf->Stride() - j * 4; + whiteData += whiteSurf->Stride() - j * 4; + } + + blackSurf->MarkDirty(); + + return true; +} + +static int32_t ByteAlignment(int32_t aAlignToLog2, int32_t aX, int32_t aY = 0, + int32_t aStride = 1) { + return (aX + aStride * aY) & ((1 << aAlignToLog2) - 1); +} + +/*static*/ mozilla::gfx::IntRect gfxAlphaRecovery::AlignRectForSubimageRecovery( + const mozilla::gfx::IntRect& aRect, gfxImageSurface* aSurface) { + NS_ASSERTION( + mozilla::gfx::SurfaceFormat::A8R8G8B8_UINT32 == aSurface->Format(), + "Thebes grew support for non-ARGB32 COLOR_ALPHA?"); + static const int32_t kByteAlignLog2 = GoodAlignmentLog2(); + static const int32_t bpp = 4; + static const int32_t pixPerAlign = (1 << kByteAlignLog2) / bpp; + // + // We're going to create a subimage of the surface with size + // <sw,sh> for alpha recovery, and want a SIMD fast-path. The + // rect <x,y, w,h> /needs/ to be redrawn, but it might not be + // properly aligned for SIMD. So we want to find a rect <x',y', + // w',h'> that's a superset of what needs to be redrawn but is + // properly aligned. Proper alignment is + // + // BPP * (x' + y' * sw) \cong 0 (mod ALIGN) + // BPP * w' \cong BPP * sw (mod ALIGN) + // + // (We assume the pixel at surface <0,0> is already ALIGN'd.) + // That rect (obviously) has to fit within the surface bounds, and + // we should also minimize the extra pixels redrawn only for + // alignment's sake. So we also want + // + // minimize <x',y', w',h'> + // 0 <= x' <= x + // 0 <= y' <= y + // w <= w' <= sw + // h <= h' <= sh + // + // This is a messy integer non-linear programming problem, except + // ... we can assume that ALIGN/BPP is a very small constant. So, + // brute force is viable. The algorithm below will find a + // solution if one exists, but isn't guaranteed to find the + // minimum solution. (For SSE2, ALIGN/BPP = 4, so it'll do at + // most 64 iterations below). In what's likely the common case, + // an already-aligned rectangle, it only needs 1 iteration. + // + // Is this alignment worth doing? Recovering alpha will take work + // proportional to w*h (assuming alpha recovery computation isn't + // memory bound). This analysis can lead to O(w+h) extra work + // (with small constants). In exchange, we expect to shave off a + // ALIGN/BPP constant by using SIMD-ized alpha recovery. So as + // w*h diverges from w+h, the win factor approaches ALIGN/BPP. We + // only really care about the w*h >> w+h case anyway; others + // should be fast enough even with the overhead. (Unless the cost + // of repainting the expanded rect is high, but in that case + // SIMD-ized alpha recovery won't make a difference so this code + // shouldn't be called.) + // + mozilla::gfx::IntSize surfaceSize = aSurface->GetSize(); + const int32_t stride = bpp * surfaceSize.width; + if (stride != aSurface->Stride()) { + NS_WARNING("Unexpected stride, falling back on slow alpha recovery"); + return aRect; + } + + const int32_t x = aRect.X(), y = aRect.Y(), w = aRect.Width(), + h = aRect.Height(); + const int32_t r = x + w; + const int32_t sw = surfaceSize.width; + const int32_t strideAlign = ByteAlignment(kByteAlignLog2, stride); + + // The outer two loops below keep the rightmost (|r| above) and + // bottommost pixels in |aRect| fixed wrt <x,y>, to ensure that we + // return only a superset of the original rect. These loops + // search for an aligned top-left pixel by trying to expand <x,y> + // left and up by <dx,dy> pixels, respectively. + // + // Then if a properly-aligned top-left pixel is found, the + // innermost loop tries to find an aligned stride by moving the + // rightmost pixel rightward by dr. + int32_t dx, dy, dr; + for (dy = 0; (dy < pixPerAlign) && (y - dy >= 0); ++dy) { + for (dx = 0; (dx < pixPerAlign) && (x - dx >= 0); ++dx) { + if (0 != ByteAlignment(kByteAlignLog2, bpp * (x - dx), y - dy, stride)) { + continue; + } + for (dr = 0; (dr < pixPerAlign) && (r + dr <= sw); ++dr) { + if (strideAlign == ByteAlignment(kByteAlignLog2, bpp * (w + dr + dx))) { + goto FOUND_SOLUTION; + } + } + } + } + + // Didn't find a solution. + return aRect; + +FOUND_SOLUTION: + mozilla::gfx::IntRect solution = + mozilla::gfx::IntRect(x - dx, y - dy, w + dr + dx, h + dy); + MOZ_ASSERT( + mozilla::gfx::IntRect(0, 0, sw, surfaceSize.height).Contains(solution), + "'Solution' extends outside surface bounds!"); + return solution; +} diff --git a/gfx/thebes/gfxAndroidPlatform.cpp b/gfx/thebes/gfxAndroidPlatform.cpp new file mode 100644 index 0000000000..e1e7d78608 --- /dev/null +++ b/gfx/thebes/gfxAndroidPlatform.cpp @@ -0,0 +1,389 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "base/basictypes.h" + +#include "gfxAndroidPlatform.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/CountingAllocatorBase.h" +#include "mozilla/intl/LocaleService.h" +#include "mozilla/intl/OSPreferences.h" +#include "mozilla/jni/Utils.h" +#include "mozilla/layers/AndroidHardwareBuffer.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_webgl.h" +#include "mozilla/widget/AndroidVsync.h" + +#include "gfx2DGlue.h" +#include "gfxFT2FontList.h" +#include "gfxImageSurface.h" +#include "gfxTextRun.h" +#include "nsXULAppAPI.h" +#include "nsIScreen.h" +#include "nsServiceManagerUtils.h" +#include "nsUnicodeProperties.h" +#include "cairo.h" +#include "VsyncSource.h" + +#include "ft2build.h" +#include FT_FREETYPE_H +#include FT_MODULE_H + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::gfx; +using namespace mozilla::unicode; +using mozilla::intl::LocaleService; +using mozilla::intl::OSPreferences; + +static FT_Library gPlatformFTLibrary = nullptr; + +class FreetypeReporter final : public nsIMemoryReporter, + public CountingAllocatorBase<FreetypeReporter> { + private: + ~FreetypeReporter() {} + + public: + NS_DECL_ISUPPORTS + + static void* Malloc(FT_Memory, long size) { return CountingMalloc(size); } + + static void Free(FT_Memory, void* p) { return CountingFree(p); } + + static void* Realloc(FT_Memory, long cur_size, long new_size, void* p) { + return CountingRealloc(p, new_size); + } + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + MOZ_COLLECT_REPORT("explicit/freetype", KIND_HEAP, UNITS_BYTES, + MemoryAllocated(), "Memory used by Freetype."); + + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(FreetypeReporter, nsIMemoryReporter) + +template <> +CountingAllocatorBase<FreetypeReporter>::AmountType + CountingAllocatorBase<FreetypeReporter>::sAmount(0); + +static FT_MemoryRec_ sFreetypeMemoryRecord; + +gfxAndroidPlatform::gfxAndroidPlatform() { + // A custom allocator. It counts allocations, enabling memory reporting. + sFreetypeMemoryRecord.user = nullptr; + sFreetypeMemoryRecord.alloc = FreetypeReporter::Malloc; + sFreetypeMemoryRecord.free = FreetypeReporter::Free; + sFreetypeMemoryRecord.realloc = FreetypeReporter::Realloc; + + // These two calls are equivalent to FT_Init_FreeType(), but allow us to + // provide a custom memory allocator. + FT_New_Library(&sFreetypeMemoryRecord, &gPlatformFTLibrary); + FT_Add_Default_Modules(gPlatformFTLibrary); + + Factory::SetFTLibrary(gPlatformFTLibrary); + + RegisterStrongMemoryReporter(new FreetypeReporter()); + + mOffscreenFormat = GetScreenDepth() == 16 ? SurfaceFormat::R5G6B5_UINT16 + : SurfaceFormat::X8R8G8B8_UINT32; + + if (StaticPrefs::gfx_android_rgb16_force_AtStartup()) { + mOffscreenFormat = SurfaceFormat::R5G6B5_UINT16; + } +} + +gfxAndroidPlatform::~gfxAndroidPlatform() { + FT_Done_Library(gPlatformFTLibrary); + gPlatformFTLibrary = nullptr; + layers::AndroidHardwareBufferManager::Shutdown(); + layers::AndroidHardwareBufferApi::Shutdown(); +} + +void gfxAndroidPlatform::InitAcceleration() { + gfxPlatform::InitAcceleration(); + if (XRE_IsParentProcess() && jni::GetAPIVersion() >= 26) { + if (StaticPrefs::gfx_use_ahardwarebuffer_content_AtStartup()) { + gfxVars::SetUseAHardwareBufferContent(true); + } + if (StaticPrefs::webgl_enable_ahardwarebuffer()) { + gfxVars::SetUseAHardwareBufferSharedSurface(true); + } + } + if (gfx::gfxVars::UseAHardwareBufferContent() || + gfxVars::UseAHardwareBufferSharedSurface()) { + layers::AndroidHardwareBufferApi::Init(); + layers::AndroidHardwareBufferManager::Init(); + } +} + +already_AddRefed<gfxASurface> gfxAndroidPlatform::CreateOffscreenSurface( + const IntSize& aSize, gfxImageFormat aFormat) { + if (!Factory::AllowedSurfaceSize(aSize)) { + return nullptr; + } + + RefPtr<gfxASurface> newSurface; + newSurface = new gfxImageSurface(aSize, aFormat); + + return newSurface.forget(); +} + +static bool IsJapaneseLocale() { + static bool sInitialized = false; + static bool sIsJapanese = false; + + if (!sInitialized) { + sInitialized = true; + + nsAutoCString appLocale; + LocaleService::GetInstance()->GetAppLocaleAsBCP47(appLocale); + + const nsDependentCSubstring lang(appLocale, 0, 2); + if (lang.EqualsLiteral("ja")) { + sIsJapanese = true; + } else { + OSPreferences::GetInstance()->GetSystemLocale(appLocale); + + const nsDependentCSubstring lang(appLocale, 0, 2); + if (lang.EqualsLiteral("ja")) { + sIsJapanese = true; + } + } + } + + return sIsJapanese; +} + +void gfxAndroidPlatform::GetCommonFallbackFonts( + uint32_t aCh, Script aRunScript, eFontPresentation aPresentation, + nsTArray<const char*>& aFontList) { + static const char kDroidSansJapanese[] = "Droid Sans Japanese"; + static const char kMotoyaLMaru[] = "MotoyaLMaru"; + static const char kNotoSansCJKJP[] = "Noto Sans CJK JP"; + static const char kNotoColorEmoji[] = "Noto Color Emoji"; + + if (PrefersColor(aPresentation)) { + aFontList.AppendElement(kNotoColorEmoji); + } + + if (IS_IN_BMP(aCh)) { + // try language-specific "Droid Sans *" and "Noto Sans *" fonts for + // certain blocks, as most devices probably have these + uint8_t block = (aCh >> 8) & 0xff; + switch (block) { + case 0x05: + aFontList.AppendElement("Noto Sans Hebrew"); + aFontList.AppendElement("Droid Sans Hebrew"); + aFontList.AppendElement("Noto Sans Armenian"); + aFontList.AppendElement("Droid Sans Armenian"); + break; + case 0x06: + aFontList.AppendElement("Noto Sans Arabic"); + aFontList.AppendElement("Droid Sans Arabic"); + break; + case 0x09: + aFontList.AppendElement("Noto Sans Devanagari"); + aFontList.AppendElement("Noto Sans Bengali"); + aFontList.AppendElement("Droid Sans Devanagari"); + break; + case 0x0a: + aFontList.AppendElement("Noto Sans Gurmukhi"); + aFontList.AppendElement("Noto Sans Gujarati"); + break; + case 0x0b: + aFontList.AppendElement("Noto Sans Tamil"); + aFontList.AppendElement("Noto Sans Oriya"); + aFontList.AppendElement("Droid Sans Tamil"); + break; + case 0x0c: + aFontList.AppendElement("Noto Sans Telugu"); + aFontList.AppendElement("Noto Sans Kannada"); + break; + case 0x0d: + aFontList.AppendElement("Noto Sans Malayalam"); + aFontList.AppendElement("Noto Sans Sinhala"); + break; + case 0x0e: + aFontList.AppendElement("Noto Sans Thai"); + aFontList.AppendElement("Noto Sans Lao"); + aFontList.AppendElement("Droid Sans Thai"); + break; + case 0x0f: + aFontList.AppendElement("Noto Sans Tibetan"); + break; + case 0x10: + case 0x2d: + aFontList.AppendElement("Noto Sans Georgian"); + aFontList.AppendElement("Droid Sans Georgian"); + break; + case 0x12: + case 0x13: + aFontList.AppendElement("Noto Sans Ethiopic"); + aFontList.AppendElement("Droid Sans Ethiopic"); + break; + case 0x21: + case 0x23: + case 0x24: + case 0x26: + case 0x27: + case 0x29: + aFontList.AppendElement("Noto Sans Symbols"); + break; + case 0xf9: + case 0xfa: + if (IsJapaneseLocale()) { + aFontList.AppendElement(kMotoyaLMaru); + aFontList.AppendElement(kNotoSansCJKJP); + aFontList.AppendElement(kDroidSansJapanese); + } + break; + default: + if (block >= 0x2e && block <= 0x9f && IsJapaneseLocale()) { + aFontList.AppendElement(kMotoyaLMaru); + aFontList.AppendElement(kNotoSansCJKJP); + aFontList.AppendElement(kDroidSansJapanese); + } + break; + } + } + // and try Droid Sans Fallback as a last resort + aFontList.AppendElement("Droid Sans Fallback"); +} + +gfxPlatformFontList* gfxAndroidPlatform::CreatePlatformFontList() { + gfxPlatformFontList* list = new gfxFT2FontList(); + if (NS_SUCCEEDED(list->InitFontList())) { + return list; + } + gfxPlatformFontList::Shutdown(); + return nullptr; +} + +void gfxAndroidPlatform::ReadSystemFontList( + nsTArray<SystemFontListEntry>* aFontList) { + gfxFT2FontList::PlatformFontList()->ReadSystemFontList(aFontList); +} + +bool gfxAndroidPlatform::FontHintingEnabled() { + // In "mobile" builds, we sometimes use non-reflow-zoom, so we + // might not want hinting. Let's see. + +#ifdef MOZ_WIDGET_ANDROID + // On Android, we currently only use gecko to render web + // content that can always be be non-reflow-zoomed. So turn off + // hinting. + // + // XXX when gecko-android-java is used as an "app runtime", we may + // want to re-enable hinting for non-browser processes there. + return false; +#endif // MOZ_WIDGET_ANDROID + + // Currently, we don't have any other targets, but if/when we do, + // decide how to handle them here. + + MOZ_ASSERT_UNREACHABLE("oops, what platform is this?"); + return gfxPlatform::FontHintingEnabled(); +} + +bool gfxAndroidPlatform::RequiresLinearZoom() { +#ifdef MOZ_WIDGET_ANDROID + // On Android, we currently only use gecko to render web + // content that can always be be non-reflow-zoomed. + // + // XXX when gecko-android-java is used as an "app runtime", we may + // want to use linear zoom only for the web browser process, not other apps. + return true; +#endif + + MOZ_ASSERT_UNREACHABLE("oops, what platform is this?"); + return gfxPlatform::RequiresLinearZoom(); +} + +class AndroidVsyncSource final : public VsyncSource { + public: + class Display final : public VsyncSource::Display, + public widget::AndroidVsync::Observer { + public: + Display() : mAndroidVsync(widget::AndroidVsync::GetInstance()) {} + ~Display() { DisableVsync(); } + + bool IsVsyncEnabled() override { + MOZ_ASSERT(NS_IsMainThread()); + return mObservingVsync; + } + + void EnableVsync() override { + MOZ_ASSERT(NS_IsMainThread()); + + if (mObservingVsync) { + return; + } + mAndroidVsync->RegisterObserver(this, widget::AndroidVsync::RENDER); + mObservingVsync = true; + } + + void DisableVsync() override { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mObservingVsync) { + return; + } + mAndroidVsync->UnregisterObserver(this, widget::AndroidVsync::RENDER); + mObservingVsync = false; + } + + TimeDuration GetVsyncRate() override { + return mAndroidVsync->GetVsyncRate(); + } + + void Shutdown() override { DisableVsync(); } + + // Override for the widget::AndroidVsync::Observer method + void OnVsync(const TimeStamp& aTimeStamp) override { + // Use the timebase from the frame callback as the vsync time, unless it + // is in the future. + TimeStamp now = TimeStamp::Now(); + TimeStamp vsyncTime = aTimeStamp < now ? aTimeStamp : now; + TimeStamp outputTime = vsyncTime + GetVsyncRate(); + + NotifyVsync(vsyncTime, outputTime); + } + + private: + RefPtr<widget::AndroidVsync> mAndroidVsync; + bool mObservingVsync = false; + TimeDuration mVsyncDuration; + }; + + Display& GetGlobalDisplay() final { return GetDisplayInstance(); } + + private: + virtual ~AndroidVsyncSource() = default; + + static Display& GetDisplayInstance() { + static RefPtr<Display> globalDisplay = new Display(); + return *globalDisplay; + } +}; + +already_AddRefed<mozilla::gfx::VsyncSource> +gfxAndroidPlatform::CreateHardwareVsyncSource() { + // Vsync was introduced since JB (API 16~18) but inaccurate. Enable only for + // KK (API 19) and later. + if (AndroidBridge::Bridge() && + AndroidBridge::Bridge()->GetAPIVersion() >= 19) { + RefPtr<AndroidVsyncSource> vsyncSource = new AndroidVsyncSource(); + return vsyncSource.forget(); + } + + NS_WARNING("Vsync not supported. Falling back to software vsync"); + return gfxPlatform::CreateHardwareVsyncSource(); +} diff --git a/gfx/thebes/gfxAndroidPlatform.h b/gfx/thebes/gfxAndroidPlatform.h new file mode 100644 index 0000000000..19073bdf13 --- /dev/null +++ b/gfx/thebes/gfxAndroidPlatform.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_PLATFORM_ANDROID_H +#define GFX_PLATFORM_ANDROID_H + +#include "gfxPlatform.h" +#include "gfxUserFontSet.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" + +namespace mozilla { +namespace dom { +class FontListEntry; +}; +}; // namespace mozilla +using mozilla::dom::FontListEntry; + +class gfxAndroidPlatform final : public gfxPlatform { + public: + gfxAndroidPlatform(); + virtual ~gfxAndroidPlatform(); + + static gfxAndroidPlatform* GetPlatform() { + return (gfxAndroidPlatform*)gfxPlatform::GetPlatform(); + } + + already_AddRefed<gfxASurface> CreateOffscreenSurface( + const IntSize& aSize, gfxImageFormat aFormat) override; + + gfxImageFormat GetOffscreenFormat() override { return mOffscreenFormat; } + + // platform implementations of font functions + gfxPlatformFontList* CreatePlatformFontList() override; + + void ReadSystemFontList( + nsTArray<mozilla::dom::SystemFontListEntry>* aFontList) override; + + void GetCommonFallbackFonts(uint32_t aCh, Script aRunScript, + eFontPresentation aPresentation, + nsTArray<const char*>& aFontList) override; + + bool FontHintingEnabled() override; + bool RequiresLinearZoom() override; + + already_AddRefed<mozilla::gfx::VsyncSource> CreateHardwareVsyncSource() + override; + + protected: + void InitAcceleration() override; + + bool AccelerateLayersByDefault() override { return true; } + + bool CheckVariationFontSupport() override { + // We build with in-tree FreeType, so we know it is a new enough + // version to support variations. + return true; + } + + private: + gfxImageFormat mOffscreenFormat; +}; + +#endif /* GFX_PLATFORM_ANDROID_H */ diff --git a/gfx/thebes/gfxBaseSharedMemorySurface.cpp b/gfx/thebes/gfxBaseSharedMemorySurface.cpp new file mode 100644 index 0000000000..0ef7bd33d0 --- /dev/null +++ b/gfx/thebes/gfxBaseSharedMemorySurface.cpp @@ -0,0 +1,10 @@ +// vim:set ts=4 sts=2 sw=2 et cin: +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxBaseSharedMemorySurface.h" +#include "cairo.h" + +const cairo_user_data_key_t SHM_KEY = {0}; diff --git a/gfx/thebes/gfxBaseSharedMemorySurface.h b/gfx/thebes/gfxBaseSharedMemorySurface.h new file mode 100644 index 0000000000..77a7a2c363 --- /dev/null +++ b/gfx/thebes/gfxBaseSharedMemorySurface.h @@ -0,0 +1,168 @@ +// vim:set ts=4 sts=2 sw=2 et cin: +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_SHARED_MEMORYSURFACE_H +#define GFX_SHARED_MEMORYSURFACE_H + +#include "mozilla/gfx/2D.h" +#include "mozilla/ipc/Shmem.h" +#include "mozilla/ipc/SharedMemory.h" + +#include "gfxASurface.h" +#include "gfxImageSurface.h" +#include "pratom.h" + +typedef struct _cairo_user_data_key cairo_user_data_key_t; + +struct SharedImageInfo { + int32_t width; + int32_t height; + gfxImageFormat format; + int32_t readCount; +}; + +inline SharedImageInfo* GetShmInfoPtr(const mozilla::ipc::Shmem& aShmem) { + return reinterpret_cast<SharedImageInfo*>( + aShmem.get<char>() + aShmem.Size<char>() - sizeof(SharedImageInfo)); +} + +extern const cairo_user_data_key_t SHM_KEY; + +template <typename Base, typename Sub> +class gfxBaseSharedMemorySurface : public Base { + typedef mozilla::ipc::SharedMemory SharedMemory; + typedef mozilla::ipc::Shmem Shmem; + + protected: + virtual ~gfxBaseSharedMemorySurface() { + MOZ_COUNT_DTOR(gfxBaseSharedMemorySurface); + } + + public: + /** + * Return a new gfxSharedImageSurface around a shmem segment newly + * allocated by this function. |aAllocator| is the object used to + * allocate the new shmem segment. Null is returned if creating + * the surface failed. + * + * NB: the *caller* is responsible for freeing the Shmem allocated + * by this function. + */ + template <class ShmemAllocator> + static already_AddRefed<Sub> Create( + ShmemAllocator* aAllocator, const mozilla::gfx::IntSize& aSize, + gfxImageFormat aFormat, + SharedMemory::SharedMemoryType aShmType = SharedMemory::TYPE_BASIC) { + return Create<ShmemAllocator, false>(aAllocator, aSize, aFormat, aShmType); + } + + /** + * Return a new gfxSharedImageSurface that wraps a shmem segment + * already created by the Create() above. Bad things will happen + * if an attempt is made to wrap any other shmem segment. Null is + * returned if creating the surface failed. + */ + static already_AddRefed<Sub> Open(const Shmem& aShmem) { + SharedImageInfo* shmInfo = GetShmInfoPtr(aShmem); + mozilla::gfx::IntSize size(shmInfo->width, shmInfo->height); + if (!mozilla::gfx::Factory::CheckSurfaceSize(size)) return nullptr; + + gfxImageFormat format = shmInfo->format; + long stride = gfxImageSurface::ComputeStride(size, format); + + RefPtr<Sub> s = new Sub(size, stride, format, aShmem); + // We didn't create this Shmem and so don't free it on errors + return (s->CairoStatus() != 0) ? nullptr : s.forget(); + } + + template <class ShmemAllocator> + static already_AddRefed<Sub> CreateUnsafe( + ShmemAllocator* aAllocator, const mozilla::gfx::IntSize& aSize, + gfxImageFormat aFormat, + SharedMemory::SharedMemoryType aShmType = SharedMemory::TYPE_BASIC) { + return Create<ShmemAllocator, true>(aAllocator, aSize, aFormat, aShmType); + } + + Shmem& GetShmem() { return mShmem; } + + static bool IsSharedImage(gfxASurface* aSurface) { + return (aSurface && aSurface->GetType() == gfxSurfaceType::Image && + aSurface->GetData(&SHM_KEY)); + } + + protected: + gfxBaseSharedMemorySurface(const mozilla::gfx::IntSize& aSize, long aStride, + gfxImageFormat aFormat, const Shmem& aShmem) + : Base(aShmem.get<unsigned char>(), aSize, aStride, aFormat) { + MOZ_COUNT_CTOR(gfxBaseSharedMemorySurface); + + mShmem = aShmem; + this->SetData(&SHM_KEY, this, nullptr); + } + + private: + void WriteShmemInfo() { + SharedImageInfo* shmInfo = GetShmInfoPtr(mShmem); + shmInfo->width = this->mSize.width; + shmInfo->height = this->mSize.height; + shmInfo->format = this->mFormat; + shmInfo->readCount = 0; + } + + int32_t ReadLock() { + SharedImageInfo* shmInfo = GetShmInfoPtr(mShmem); + return PR_ATOMIC_INCREMENT(&shmInfo->readCount); + } + + int32_t ReadUnlock() { + SharedImageInfo* shmInfo = GetShmInfoPtr(mShmem); + return PR_ATOMIC_DECREMENT(&shmInfo->readCount); + } + + int32_t GetReadCount() { + SharedImageInfo* shmInfo = GetShmInfoPtr(mShmem); + return shmInfo->readCount; + } + + static size_t GetAlignedSize(const mozilla::gfx::IntSize& aSize, + long aStride) { +#define MOZ_ALIGN_WORD(x) (((x) + 3) & ~3) + return MOZ_ALIGN_WORD(sizeof(SharedImageInfo) + aSize.height * aStride); + } + + template <class ShmemAllocator, bool Unsafe> + static already_AddRefed<Sub> Create(ShmemAllocator* aAllocator, + const mozilla::gfx::IntSize& aSize, + gfxImageFormat aFormat, + SharedMemory::SharedMemoryType aShmType) { + if (!mozilla::gfx::Factory::CheckSurfaceSize(aSize)) return nullptr; + + Shmem shmem; + long stride = gfxImageSurface::ComputeStride(aSize, aFormat); + size_t size = GetAlignedSize(aSize, stride); + if (!Unsafe) { + if (!aAllocator->AllocShmem(size, aShmType, &shmem)) return nullptr; + } else { + if (!aAllocator->AllocUnsafeShmem(size, aShmType, &shmem)) return nullptr; + } + + RefPtr<Sub> s = new Sub(aSize, stride, aFormat, shmem); + if (s->CairoStatus() != 0) { + aAllocator->DeallocShmem(shmem); + return nullptr; + } + s->WriteShmemInfo(); + return s.forget(); + } + + Shmem mShmem; + + // Calling these is very bad, disallow it + gfxBaseSharedMemorySurface(const gfxBaseSharedMemorySurface&); + gfxBaseSharedMemorySurface& operator=(const gfxBaseSharedMemorySurface&); +}; + +#endif /* GFX_SHARED_MEMORYSURFACE_H */ diff --git a/gfx/thebes/gfxBlur.cpp b/gfx/thebes/gfxBlur.cpp new file mode 100644 index 0000000000..0c58414b67 --- /dev/null +++ b/gfx/thebes/gfxBlur.cpp @@ -0,0 +1,1244 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxBlur.h" + +#include "gfx2DGlue.h" +#include "gfxContext.h" +#include "gfxPlatform.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Blur.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/Maybe.h" +#include "nsExpirationTracker.h" +#include "nsClassHashtable.h" +#include "gfxUtils.h" +#include <limits> +#include <cmath> + +using namespace mozilla; +using namespace mozilla::gfx; + +gfxAlphaBoxBlur::~gfxAlphaBoxBlur() = default; + +already_AddRefed<gfxContext> gfxAlphaBoxBlur::Init(gfxContext* aDestinationCtx, + const gfxRect& aRect, + const IntSize& aSpreadRadius, + const IntSize& aBlurRadius, + const gfxRect* aDirtyRect, + const gfxRect* aSkipRect, + bool aUseHardwareAccel) { + DrawTarget* refDT = aDestinationCtx->GetDrawTarget(); + Maybe<Rect> dirtyRect = aDirtyRect ? Some(ToRect(*aDirtyRect)) : Nothing(); + Maybe<Rect> skipRect = aSkipRect ? Some(ToRect(*aSkipRect)) : Nothing(); + RefPtr<DrawTarget> dt = InitDrawTarget( + refDT, ToRect(aRect), aSpreadRadius, aBlurRadius, + dirtyRect.ptrOr(nullptr), skipRect.ptrOr(nullptr), aUseHardwareAccel); + if (!dt) { + return nullptr; + } + + RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt); + MOZ_ASSERT(context); // already checked for target above + context->SetMatrix(Matrix::Translation(-mBlur.GetRect().TopLeft())); + return context.forget(); +} + +already_AddRefed<DrawTarget> gfxAlphaBoxBlur::InitDrawTarget( + const DrawTarget* aReferenceDT, const Rect& aRect, + const IntSize& aSpreadRadius, const IntSize& aBlurRadius, + const Rect* aDirtyRect, const Rect* aSkipRect, bool aUseHardwareAccel) { + mBlur.Init(aRect, aSpreadRadius, aBlurRadius, aDirtyRect, aSkipRect); + size_t blurDataSize = mBlur.GetSurfaceAllocationSize(); + if (blurDataSize == 0) { + return nullptr; + } + + BackendType backend = aReferenceDT->GetBackendType(); + + // Check if the backend has an accelerated DrawSurfaceWithShadow. + // Currently, only D2D1.1 supports this. + // Otherwise, DrawSurfaceWithShadow only supports square blurs without spread. + // When blurring small draw targets such as short spans text, the cost of + // creating and flushing an accelerated draw target may exceed the speedup + // gained from the faster blur. It's up to the users of this blur + // to determine whether they want to use hardware acceleration. + if (aBlurRadius.IsSquare() && aSpreadRadius.IsEmpty() && aUseHardwareAccel && + backend == BackendType::DIRECT2D1_1) { + mAccelerated = true; + } + + if (aReferenceDT->IsCaptureDT()) { + if (mAccelerated) { + mDrawTarget = Factory::CreateCaptureDrawTarget(backend, mBlur.GetSize(), + SurfaceFormat::A8); + } else { + mDrawTarget = Factory::CreateCaptureDrawTargetForData( + backend, mBlur.GetSize(), SurfaceFormat::A8, mBlur.GetStride(), + blurDataSize); + } + } else if (mAccelerated) { + // Note: CreateShadowDrawTarget is only implemented for Cairo, so we don't + // care about mimicking this in the DrawTargetCapture case. + mDrawTarget = aReferenceDT->CreateShadowDrawTarget( + mBlur.GetSize(), SurfaceFormat::A8, + AlphaBoxBlur::CalculateBlurSigma(aBlurRadius.width)); + if (mDrawTarget) { + // See Bug 1526045 - this is to force DT initialization. + mDrawTarget->ClearRect(gfx::Rect()); + } + } else { + // Make an alpha-only surface to draw on. We will play with the data after + // everything is drawn to create a blur effect. + // This will be freed when the DrawTarget dies + mData = static_cast<uint8_t*>(calloc(1, blurDataSize)); + if (!mData) { + return nullptr; + } + mDrawTarget = + Factory::DoesBackendSupportDataDrawtarget(backend) + ? Factory::CreateDrawTargetForData(backend, mData, mBlur.GetSize(), + mBlur.GetStride(), + SurfaceFormat::A8) + : gfxPlatform::CreateDrawTargetForData( + mData, mBlur.GetSize(), mBlur.GetStride(), SurfaceFormat::A8); + } + + if (!mDrawTarget || !mDrawTarget->IsValid()) { + if (mData) { + free(mData); + } + + return nullptr; + } + + if (mData) { + mDrawTarget->AddUserData(reinterpret_cast<UserDataKey*>(mDrawTarget.get()), + mData, free); + } + + mDrawTarget->SetTransform(Matrix::Translation(-mBlur.GetRect().TopLeft())); + return do_AddRef(mDrawTarget); +} + +already_AddRefed<SourceSurface> gfxAlphaBoxBlur::DoBlur( + const sRGBColor* aShadowColor, IntPoint* aOutTopLeft) { + if (aOutTopLeft) { + *aOutTopLeft = mBlur.GetRect().TopLeft(); + } + + RefPtr<SourceSurface> blurMask; + if (mData) { + mBlur.Blur(mData); + blurMask = mDrawTarget->Snapshot(); + } else if (mAccelerated) { + blurMask = mDrawTarget->Snapshot(); + RefPtr<DrawTarget> blurDT = mDrawTarget->CreateSimilarDrawTarget( + blurMask->GetSize(), SurfaceFormat::A8); + if (!blurDT) { + return nullptr; + } + blurDT->DrawSurfaceWithShadow( + blurMask, Point(0, 0), DeviceColor::MaskOpaqueWhite(), Point(0, 0), + AlphaBoxBlur::CalculateBlurSigma(mBlur.GetBlurRadius().width), + CompositionOp::OP_OVER); + blurMask = blurDT->Snapshot(); + } else if (mDrawTarget->IsCaptureDT()) { + mDrawTarget->Blur(mBlur); + blurMask = mDrawTarget->Snapshot(); + } + + if (!aShadowColor) { + return blurMask.forget(); + } + + RefPtr<DrawTarget> shadowDT = mDrawTarget->CreateSimilarDrawTarget( + blurMask->GetSize(), SurfaceFormat::B8G8R8A8); + if (!shadowDT) { + return nullptr; + } + ColorPattern shadowColor(ToDeviceColor(*aShadowColor)); + shadowDT->MaskSurface(shadowColor, blurMask, Point(0, 0)); + + return shadowDT->Snapshot(); +} + +void gfxAlphaBoxBlur::Paint(gfxContext* aDestinationCtx) { + if ((mDrawTarget && !mDrawTarget->IsCaptureDT()) && !mAccelerated && !mData) { + return; + } + + DrawTarget* dest = aDestinationCtx->GetDrawTarget(); + if (!dest) { + NS_WARNING("Blurring not supported for Thebes contexts!"); + return; + } + + RefPtr<gfxPattern> thebesPat = aDestinationCtx->GetPattern(); + Pattern* pat = thebesPat->GetPattern(dest, nullptr); + if (!pat) { + NS_WARNING("Failed to get pattern for blur!"); + return; + } + + IntPoint topLeft; + RefPtr<SourceSurface> mask = DoBlur(nullptr, &topLeft); + if (!mask) { + NS_ERROR("Failed to create mask!"); + return; + } + + // Avoid a semi-expensive clip operation if we can, otherwise + // clip to the dirty rect + Rect* dirtyRect = mBlur.GetDirtyRect(); + if (dirtyRect) { + dest->PushClipRect(*dirtyRect); + } + + Matrix oldTransform = dest->GetTransform(); + Matrix newTransform = oldTransform; + newTransform.PreTranslate(topLeft); + dest->SetTransform(newTransform); + + dest->MaskSurface(*pat, mask, Point(0, 0)); + + dest->SetTransform(oldTransform); + + if (dirtyRect) { + dest->PopClip(); + } +} + +IntSize gfxAlphaBoxBlur::CalculateBlurRadius(const gfxPoint& aStd) { + mozilla::gfx::Point std(Float(aStd.x), Float(aStd.y)); + IntSize size = AlphaBoxBlur::CalculateBlurRadius(std); + return IntSize(size.width, size.height); +} + +struct BlurCacheKey : public PLDHashEntryHdr { + typedef const BlurCacheKey& KeyType; + typedef const BlurCacheKey* KeyTypePointer; + enum { ALLOW_MEMMOVE = true }; + + IntSize mMinSize; + IntSize mBlurRadius; + sRGBColor mShadowColor; + BackendType mBackend; + RectCornerRadii mCornerRadii; + bool mIsInset; + + // Only used for inset blurs + IntSize mInnerMinSize; + + BlurCacheKey(const IntSize& aMinSize, const IntSize& aBlurRadius, + const RectCornerRadii* aCornerRadii, + const sRGBColor& aShadowColor, BackendType aBackendType) + : BlurCacheKey(aMinSize, IntSize(0, 0), aBlurRadius, aCornerRadii, + aShadowColor, false, aBackendType) {} + + explicit BlurCacheKey(const BlurCacheKey* aOther) + : mMinSize(aOther->mMinSize), + mBlurRadius(aOther->mBlurRadius), + mShadowColor(aOther->mShadowColor), + mBackend(aOther->mBackend), + mCornerRadii(aOther->mCornerRadii), + mIsInset(aOther->mIsInset), + mInnerMinSize(aOther->mInnerMinSize) {} + + explicit BlurCacheKey(const IntSize& aOuterMinSize, + const IntSize& aInnerMinSize, + const IntSize& aBlurRadius, + const RectCornerRadii* aCornerRadii, + const sRGBColor& aShadowColor, bool aIsInset, + BackendType aBackendType) + : mMinSize(aOuterMinSize), + mBlurRadius(aBlurRadius), + mShadowColor(aShadowColor), + mBackend(aBackendType), + mCornerRadii(aCornerRadii ? *aCornerRadii : RectCornerRadii()), + mIsInset(aIsInset), + mInnerMinSize(aInnerMinSize) {} + + BlurCacheKey(BlurCacheKey&&) = default; + + static PLDHashNumber HashKey(const KeyTypePointer aKey) { + PLDHashNumber hash = 0; + hash = AddToHash(hash, aKey->mMinSize.width, aKey->mMinSize.height); + hash = AddToHash(hash, aKey->mBlurRadius.width, aKey->mBlurRadius.height); + + hash = AddToHash( + hash, HashBytes(&aKey->mShadowColor.r, sizeof(aKey->mShadowColor.r))); + hash = AddToHash( + hash, HashBytes(&aKey->mShadowColor.g, sizeof(aKey->mShadowColor.g))); + hash = AddToHash( + hash, HashBytes(&aKey->mShadowColor.b, sizeof(aKey->mShadowColor.b))); + hash = AddToHash( + hash, HashBytes(&aKey->mShadowColor.a, sizeof(aKey->mShadowColor.a))); + + for (int i = 0; i < 4; i++) { + hash = AddToHash(hash, aKey->mCornerRadii[i].width, + aKey->mCornerRadii[i].height); + } + + hash = AddToHash(hash, (uint32_t)aKey->mBackend); + + if (aKey->mIsInset) { + hash = AddToHash(hash, aKey->mInnerMinSize.width, + aKey->mInnerMinSize.height); + } + return hash; + } + + bool KeyEquals(KeyTypePointer aKey) const { + if (aKey->mMinSize == mMinSize && aKey->mBlurRadius == mBlurRadius && + aKey->mCornerRadii == mCornerRadii && + aKey->mShadowColor == mShadowColor && aKey->mBackend == mBackend) { + if (mIsInset) { + return (mInnerMinSize == aKey->mInnerMinSize); + } + + return true; + } + + return false; + } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } +}; + +/** + * This class is what is cached. It need to be allocated in an object separated + * to the cache entry to be able to be tracked by the nsExpirationTracker. + * */ +struct BlurCacheData { + BlurCacheData(SourceSurface* aBlur, const IntMargin& aBlurMargin, + BlurCacheKey&& aKey) + : mBlur(aBlur), mBlurMargin(aBlurMargin), mKey(std::move(aKey)) {} + + BlurCacheData(BlurCacheData&& aOther) = default; + + nsExpirationState* GetExpirationState() { return &mExpirationState; } + + nsExpirationState mExpirationState; + RefPtr<SourceSurface> mBlur; + IntMargin mBlurMargin; + BlurCacheKey mKey; +}; + +/** + * This class implements a cache with no maximum size, that retains the + * SourceSurfaces used to draw the blurs. + * + * An entry stays in the cache as long as it is used often. + */ +class BlurCache final : public nsExpirationTracker<BlurCacheData, 4> { + public: + BlurCache() + : nsExpirationTracker<BlurCacheData, 4>(GENERATION_MS, "BlurCache") {} + + virtual void NotifyExpired(BlurCacheData* aObject) override { + RemoveObject(aObject); + mHashEntries.Remove(aObject->mKey); + } + + BlurCacheData* Lookup(const IntSize& aMinSize, const IntSize& aBlurRadius, + const RectCornerRadii* aCornerRadii, + const sRGBColor& aShadowColor, + BackendType aBackendType) { + BlurCacheData* blur = mHashEntries.Get(BlurCacheKey( + aMinSize, aBlurRadius, aCornerRadii, aShadowColor, aBackendType)); + if (blur) { + MarkUsed(blur); + } + + return blur; + } + + BlurCacheData* LookupInsetBoxShadow(const IntSize& aOuterMinSize, + const IntSize& aInnerMinSize, + const IntSize& aBlurRadius, + const RectCornerRadii* aCornerRadii, + const sRGBColor& aShadowColor, + BackendType aBackendType) { + bool insetBoxShadow = true; + BlurCacheKey key(aOuterMinSize, aInnerMinSize, aBlurRadius, aCornerRadii, + aShadowColor, insetBoxShadow, aBackendType); + BlurCacheData* blur = mHashEntries.Get(key); + if (blur) { + MarkUsed(blur); + } + + return blur; + } + + // Returns true if we successfully register the blur in the cache, false + // otherwise. + bool RegisterEntry(BlurCacheData* aValue) { + nsresult rv = AddObject(aValue); + if (NS_FAILED(rv)) { + // We are OOM, and we cannot track this object. We don't want stall + // entries in the hash table (since the expiration tracker is responsible + // for removing the cache entries), so we avoid putting that entry in the + // table, which is a good things considering we are short on memory + // anyway, we probably don't want to retain things. + return false; + } + mHashEntries.Put(aValue->mKey, aValue); + return true; + } + + protected: + static const uint32_t GENERATION_MS = 1000; + /** + * FIXME use nsTHashtable to avoid duplicating the BlurCacheKey. + * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47 + */ + nsClassHashtable<BlurCacheKey, BlurCacheData> mHashEntries; +}; + +static BlurCache* gBlurCache = nullptr; + +static IntSize ComputeMinSizeForShadowShape(const RectCornerRadii* aCornerRadii, + const IntSize& aBlurRadius, + IntMargin& aOutSlice, + const IntSize& aRectSize) { + Size cornerSize(0, 0); + if (aCornerRadii) { + const RectCornerRadii& corners = *aCornerRadii; + for (const auto i : mozilla::AllPhysicalCorners()) { + cornerSize.width = std::max(cornerSize.width, corners[i].width); + cornerSize.height = std::max(cornerSize.height, corners[i].height); + } + } + + IntSize margin = IntSize::Ceil(cornerSize) + aBlurRadius; + aOutSlice = + IntMargin(margin.height, margin.width, margin.height, margin.width); + + IntSize minSize(aOutSlice.LeftRight() + 1, aOutSlice.TopBottom() + 1); + + // If aRectSize is smaller than minSize, the border-image approach won't + // work; there's no way to squeeze parts of the min box-shadow source + // image such that the result looks correct. So we need to adjust minSize + // in such a way that we can later draw it without stretching in the affected + // dimension. We also need to adjust "slice" to ensure that we're not trying + // to slice away more than we have. + if (aRectSize.width < minSize.width) { + minSize.width = aRectSize.width; + aOutSlice.left = 0; + aOutSlice.right = 0; + } + if (aRectSize.height < minSize.height) { + minSize.height = aRectSize.height; + aOutSlice.top = 0; + aOutSlice.bottom = 0; + } + + MOZ_ASSERT(aOutSlice.LeftRight() <= minSize.width); + MOZ_ASSERT(aOutSlice.TopBottom() <= minSize.height); + return minSize; +} + +static void CacheBlur(DrawTarget* aDT, const IntSize& aMinSize, + const IntSize& aBlurRadius, + const RectCornerRadii* aCornerRadii, + const sRGBColor& aShadowColor, + const IntMargin& aBlurMargin, SourceSurface* aBoxShadow) { + BlurCacheKey key(aMinSize, aBlurRadius, aCornerRadii, aShadowColor, + aDT->GetBackendType()); + BlurCacheData* data = + new BlurCacheData(aBoxShadow, aBlurMargin, std::move(key)); + if (!gBlurCache->RegisterEntry(data)) { + delete data; + } +} + +// Blurs a small surface and creates the colored box shadow. +static already_AddRefed<SourceSurface> CreateBoxShadow( + DrawTarget* aDestDrawTarget, const IntSize& aMinSize, + const RectCornerRadii* aCornerRadii, const IntSize& aBlurRadius, + const sRGBColor& aShadowColor, bool aMirrorCorners, + IntMargin& aOutBlurMargin) { + gfxAlphaBoxBlur blur; + Rect minRect(Point(0, 0), Size(aMinSize)); + Rect blurRect(minRect); + // If mirroring corners, we only need to draw the top-left quadrant. + // Use ceil to preserve the remaining 1x1 middle area for minimized box + // shadows. + if (aMirrorCorners) { + blurRect.SizeTo(ceil(blurRect.Width() * 0.5f), + ceil(blurRect.Height() * 0.5f)); + } + IntSize zeroSpread(0, 0); + RefPtr<DrawTarget> blurDT = + blur.InitDrawTarget(aDestDrawTarget, blurRect, zeroSpread, aBlurRadius); + if (!blurDT) { + return nullptr; + } + + ColorPattern black(DeviceColor::MaskOpaqueBlack()); + + if (aCornerRadii) { + RefPtr<Path> roundedRect = + MakePathForRoundedRect(*blurDT, minRect, *aCornerRadii); + blurDT->Fill(roundedRect, black); + } else { + blurDT->FillRect(minRect, black); + } + + IntPoint topLeft; + RefPtr<SourceSurface> result = blur.DoBlur(&aShadowColor, &topLeft); + if (!result) { + return nullptr; + } + + // Since blurRect is at (0, 0), we can find the inflated margin by + // negating the new rect origin, which would have been negative if + // the rect was inflated. + aOutBlurMargin = IntMargin(-topLeft.y, -topLeft.x, -topLeft.y, -topLeft.x); + + return result.forget(); +} + +static already_AddRefed<SourceSurface> GetBlur( + gfxContext* aDestinationCtx, const IntSize& aRectSize, + const IntSize& aBlurRadius, const RectCornerRadii* aCornerRadii, + const sRGBColor& aShadowColor, bool aMirrorCorners, + IntMargin& aOutBlurMargin, IntMargin& aOutSlice, IntSize& aOutMinSize) { + if (!gBlurCache) { + gBlurCache = new BlurCache(); + } + + IntSize minSize = ComputeMinSizeForShadowShape(aCornerRadii, aBlurRadius, + aOutSlice, aRectSize); + + // We can get seams using the min size rect when drawing to the destination + // rect if we have a non-pixel aligned destination transformation. In those + // cases, fallback to just rendering the destination rect. During printing, we + // record all the Moz 2d commands and replay them on the parent side with + // Cairo. Cairo printing uses StretchDIBits to stretch the surface. However, + // since our source image is only 1px for some parts, we make thousands of + // calls. Instead just render the blur ourself here as one image and send it + // over for printing. + // TODO: May need to change this with the blob renderer in WR since it also + // records. + Matrix destMatrix = aDestinationCtx->CurrentMatrix(); + bool useDestRect = !destMatrix.IsRectilinear() || + destMatrix.HasNonIntegerTranslation() || + aDestinationCtx->GetDrawTarget()->IsRecording(); + if (useDestRect) { + minSize = aRectSize; + } + + int32_t maxTextureSize = gfxPlatform::MaxTextureSize(); + if (minSize.width > maxTextureSize || minSize.height > maxTextureSize) { + return nullptr; + } + + aOutMinSize = minSize; + + DrawTarget* destDT = aDestinationCtx->GetDrawTarget(); + + if (!useDestRect) { + BlurCacheData* cached = + gBlurCache->Lookup(minSize, aBlurRadius, aCornerRadii, aShadowColor, + destDT->GetBackendType()); + if (cached) { + // See CreateBoxShadow() for these values + aOutBlurMargin = cached->mBlurMargin; + RefPtr<SourceSurface> blur = cached->mBlur; + return blur.forget(); + } + } + + RefPtr<SourceSurface> boxShadow = + CreateBoxShadow(destDT, minSize, aCornerRadii, aBlurRadius, aShadowColor, + aMirrorCorners, aOutBlurMargin); + if (!boxShadow) { + return nullptr; + } + + if (RefPtr<SourceSurface> opt = destDT->OptimizeSourceSurface(boxShadow)) { + boxShadow = opt; + } + + if (!useDestRect) { + CacheBlur(destDT, minSize, aBlurRadius, aCornerRadii, aShadowColor, + aOutBlurMargin, boxShadow); + } + return boxShadow.forget(); +} + +void gfxAlphaBoxBlur::ShutdownBlurCache() { + delete gBlurCache; + gBlurCache = nullptr; +} + +static Rect RectWithEdgesTRBL(Float aTop, Float aRight, Float aBottom, + Float aLeft) { + return Rect(aLeft, aTop, aRight - aLeft, aBottom - aTop); +} + +static bool ShouldStretchSurface(DrawTarget* aDT, SourceSurface* aSurface) { + // Use stretching if possible, since it leads to less seams when the + // destination is transformed. However, don't do this if we're using cairo, + // because if cairo is using pixman it won't render anything for large + // stretch factors because pixman's internal fixed point precision is not + // high enough to handle those scale factors. + return aDT->GetBackendType() != BackendType::CAIRO; +} + +static void RepeatOrStretchSurface(DrawTarget* aDT, SourceSurface* aSurface, + const Rect& aDest, const Rect& aSrc, + const Rect& aSkipRect) { + if (aSkipRect.Contains(aDest)) { + return; + } + + if (ShouldStretchSurface(aDT, aSurface)) { + aDT->DrawSurface(aSurface, aDest, aSrc); + return; + } + + SurfacePattern pattern(aSurface, ExtendMode::REPEAT, + Matrix::Translation(aDest.TopLeft() - aSrc.TopLeft()), + SamplingFilter::GOOD, RoundedToInt(aSrc)); + aDT->FillRect(aDest, pattern); +} + +static void DrawCorner(DrawTarget* aDT, SourceSurface* aSurface, + const Rect& aDest, const Rect& aSrc, + const Rect& aSkipRect) { + if (aSkipRect.Contains(aDest)) { + return; + } + + aDT->DrawSurface(aSurface, aDest, aSrc); +} + +static void DrawMinBoxShadow(DrawTarget* aDestDrawTarget, + SourceSurface* aSourceBlur, const Rect& aDstOuter, + const Rect& aDstInner, const Rect& aSrcOuter, + const Rect& aSrcInner, const Rect& aSkipRect, + bool aMiddle = false) { + // Corners: top left, top right, bottom left, bottom right + DrawCorner(aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.X(), aDstInner.Y(), + aDstOuter.X()), + RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.X(), aSrcInner.Y(), + aSrcOuter.X()), + aSkipRect); + + DrawCorner(aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstOuter.Y(), aDstOuter.XMost(), aDstInner.Y(), + aDstInner.XMost()), + RectWithEdgesTRBL(aSrcOuter.Y(), aSrcOuter.XMost(), aSrcInner.Y(), + aSrcInner.XMost()), + aSkipRect); + + DrawCorner(aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.X(), + aDstOuter.YMost(), aDstOuter.X()), + RectWithEdgesTRBL(aSrcInner.YMost(), aSrcInner.X(), + aSrcOuter.YMost(), aSrcOuter.X()), + aSkipRect); + + DrawCorner(aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstInner.YMost(), aDstOuter.XMost(), + aDstOuter.YMost(), aDstInner.XMost()), + RectWithEdgesTRBL(aSrcInner.YMost(), aSrcOuter.XMost(), + aSrcOuter.YMost(), aSrcInner.XMost()), + aSkipRect); + + // Edges: top, left, right, bottom + RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.XMost(), + aDstInner.Y(), aDstInner.X()), + RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(), + aSrcInner.Y(), aSrcInner.X()), + aSkipRect); + RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstInner.Y(), aDstInner.X(), + aDstInner.YMost(), aDstOuter.X()), + RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(), + aSrcInner.YMost(), aSrcOuter.X()), + aSkipRect); + + RepeatOrStretchSurface( + aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstInner.Y(), aDstOuter.XMost(), aDstInner.YMost(), + aDstInner.XMost()), + RectWithEdgesTRBL(aSrcInner.Y(), aSrcOuter.XMost(), aSrcInner.YMost(), + aSrcInner.XMost()), + aSkipRect); + RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.XMost(), + aDstOuter.YMost(), aDstInner.X()), + RectWithEdgesTRBL(aSrcInner.YMost(), aSrcInner.XMost(), + aSrcOuter.YMost(), aSrcInner.X()), + aSkipRect); + + // Middle part + if (aMiddle) { + RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstInner.Y(), aDstInner.XMost(), + aDstInner.YMost(), aDstInner.X()), + RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.XMost(), + aSrcInner.YMost(), aSrcInner.X()), + aSkipRect); + } +} + +static void DrawMirroredRect(DrawTarget* aDT, SourceSurface* aSurface, + const Rect& aDest, const Point& aSrc, + Float aScaleX, Float aScaleY) { + SurfacePattern pattern( + aSurface, ExtendMode::CLAMP, + Matrix::Scaling(aScaleX, aScaleY) + .PreTranslate(-aSrc) + .PostTranslate(aScaleX < 0 ? aDest.XMost() : aDest.X(), + aScaleY < 0 ? aDest.YMost() : aDest.Y())); + aDT->FillRect(aDest, pattern); +} + +static void DrawMirroredBoxShadow(DrawTarget* aDT, SourceSurface* aSurface, + const Rect& aDestRect) { + Point center(ceil(aDestRect.X() + aDestRect.Width() / 2), + ceil(aDestRect.Y() + aDestRect.Height() / 2)); + Rect topLeft(aDestRect.X(), aDestRect.Y(), center.x - aDestRect.X(), + center.y - aDestRect.Y()); + Rect bottomRight(topLeft.BottomRight(), aDestRect.Size() - topLeft.Size()); + Rect topRight(bottomRight.X(), topLeft.Y(), bottomRight.Width(), + topLeft.Height()); + Rect bottomLeft(topLeft.X(), bottomRight.Y(), topLeft.Width(), + bottomRight.Height()); + DrawMirroredRect(aDT, aSurface, topLeft, Point(), 1, 1); + DrawMirroredRect(aDT, aSurface, topRight, Point(), -1, 1); + DrawMirroredRect(aDT, aSurface, bottomLeft, Point(), 1, -1); + DrawMirroredRect(aDT, aSurface, bottomRight, Point(), -1, -1); +} + +static void DrawMirroredCorner(DrawTarget* aDT, SourceSurface* aSurface, + const Rect& aDest, const Point& aSrc, + const Rect& aSkipRect, Float aScaleX, + Float aScaleY) { + if (aSkipRect.Contains(aDest)) { + return; + } + + DrawMirroredRect(aDT, aSurface, aDest, aSrc, aScaleX, aScaleY); +} + +static void RepeatOrStretchMirroredSurface(DrawTarget* aDT, + SourceSurface* aSurface, + const Rect& aDest, const Rect& aSrc, + const Rect& aSkipRect, Float aScaleX, + Float aScaleY) { + if (aSkipRect.Contains(aDest)) { + return; + } + + if (ShouldStretchSurface(aDT, aSurface)) { + aScaleX *= aDest.Width() / aSrc.Width(); + aScaleY *= aDest.Height() / aSrc.Height(); + DrawMirroredRect(aDT, aSurface, aDest, aSrc.TopLeft(), aScaleX, aScaleY); + return; + } + + SurfacePattern pattern( + aSurface, ExtendMode::REPEAT, + Matrix::Scaling(aScaleX, aScaleY) + .PreTranslate(-aSrc.TopLeft()) + .PostTranslate(aScaleX < 0 ? aDest.XMost() : aDest.X(), + aScaleY < 0 ? aDest.YMost() : aDest.Y()), + SamplingFilter::GOOD, RoundedToInt(aSrc)); + aDT->FillRect(aDest, pattern); +} + +static void DrawMirroredMinBoxShadow( + DrawTarget* aDestDrawTarget, SourceSurface* aSourceBlur, + const Rect& aDstOuter, const Rect& aDstInner, const Rect& aSrcOuter, + const Rect& aSrcInner, const Rect& aSkipRect, bool aMiddle = false) { + // Corners: top left, top right, bottom left, bottom right + // Compute quadrant bounds and then clip them to corners along + // dimensions where we need to stretch from min size. + Point center(ceil(aDstOuter.X() + aDstOuter.Width() / 2), + ceil(aDstOuter.Y() + aDstOuter.Height() / 2)); + Rect topLeft(aDstOuter.X(), aDstOuter.Y(), center.x - aDstOuter.X(), + center.y - aDstOuter.Y()); + Rect bottomRight(topLeft.BottomRight(), aDstOuter.Size() - topLeft.Size()); + Rect topRight(bottomRight.X(), topLeft.Y(), bottomRight.Width(), + topLeft.Height()); + Rect bottomLeft(topLeft.X(), bottomRight.Y(), topLeft.Width(), + bottomRight.Height()); + + // Check if the middle part has been minimized along each dimension. + // If so, those will be strecthed/drawn separately and need to be clipped out. + if (aSrcInner.Width() == 1) { + topLeft.SetRightEdge(aDstInner.X()); + topRight.SetLeftEdge(aDstInner.XMost()); + bottomLeft.SetRightEdge(aDstInner.X()); + bottomRight.SetLeftEdge(aDstInner.XMost()); + } + if (aSrcInner.Height() == 1) { + topLeft.SetBottomEdge(aDstInner.Y()); + topRight.SetBottomEdge(aDstInner.Y()); + bottomLeft.SetTopEdge(aDstInner.YMost()); + bottomRight.SetTopEdge(aDstInner.YMost()); + } + + DrawMirroredCorner(aDestDrawTarget, aSourceBlur, topLeft, aSrcOuter.TopLeft(), + aSkipRect, 1, 1); + DrawMirroredCorner(aDestDrawTarget, aSourceBlur, topRight, + aSrcOuter.TopLeft(), aSkipRect, -1, 1); + DrawMirroredCorner(aDestDrawTarget, aSourceBlur, bottomLeft, + aSrcOuter.TopLeft(), aSkipRect, 1, -1); + DrawMirroredCorner(aDestDrawTarget, aSourceBlur, bottomRight, + aSrcOuter.TopLeft(), aSkipRect, -1, -1); + + // Edges: top, bottom, left, right + // Draw middle edges where they need to be stretched. The top and left + // sections that are part of the top-left quadrant will be mirrored to + // the bottom and right sections, respectively. + if (aSrcInner.Width() == 1) { + Rect dstTop = RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.XMost(), + aDstInner.Y(), aDstInner.X()); + Rect srcTop = RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(), + aSrcInner.Y(), aSrcInner.X()); + Rect dstBottom = RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.XMost(), + aDstOuter.YMost(), aDstInner.X()); + Rect srcBottom = RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(), + aSrcInner.Y(), aSrcInner.X()); + // If we only need to stretch along the X axis and we're drawing + // the middle section, just sample all the way to the center of the + // source on the Y axis to avoid extra draw calls. + if (aMiddle && aSrcInner.Height() != 1) { + dstTop.SetBottomEdge(center.y); + srcTop.SetHeight(dstTop.Height()); + dstBottom.SetTopEdge(dstTop.YMost()); + srcBottom.SetHeight(dstBottom.Height()); + } + RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstTop, srcTop, + aSkipRect, 1, 1); + RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstBottom, + srcBottom, aSkipRect, 1, -1); + } + + if (aSrcInner.Height() == 1) { + Rect dstLeft = RectWithEdgesTRBL(aDstInner.Y(), aDstInner.X(), + aDstInner.YMost(), aDstOuter.X()); + Rect srcLeft = RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(), + aSrcInner.YMost(), aSrcOuter.X()); + Rect dstRight = RectWithEdgesTRBL(aDstInner.Y(), aDstOuter.XMost(), + aDstInner.YMost(), aDstInner.XMost()); + Rect srcRight = RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(), + aSrcInner.YMost(), aSrcOuter.X()); + // Only stretching on Y axis, so sample source to the center of the X axis. + if (aMiddle && aSrcInner.Width() != 1) { + dstLeft.SetRightEdge(center.x); + srcLeft.SetWidth(dstLeft.Width()); + dstRight.SetLeftEdge(dstLeft.XMost()); + srcRight.SetWidth(dstRight.Width()); + } + RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstLeft, + srcLeft, aSkipRect, 1, 1); + RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstRight, + srcRight, aSkipRect, -1, 1); + } + + // If we need to stretch along both dimensions, then the middle part + // must be drawn separately. + if (aMiddle && aSrcInner.Width() == 1 && aSrcInner.Height() == 1) { + RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstInner.Y(), aDstInner.XMost(), + aDstInner.YMost(), aDstInner.X()), + RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.XMost(), + aSrcInner.YMost(), aSrcInner.X()), + aSkipRect); + } +} + +/*** + * We draw a blurred a rectangle by only blurring a smaller rectangle and + * splitting the rectangle into 9 parts. + * First, a small minimum source rect is calculated and used to create a blur + * mask since the actual blurring itself is expensive. Next, we use the mask + * with the given shadow color to create a minimally-sized box shadow of the + * right color. Finally, we cut out the 9 parts from the box-shadow source and + * paint each part in the right place, stretching the non-corner parts to fill + * the space between the corners. + */ + +/* static */ +void gfxAlphaBoxBlur::BlurRectangle(gfxContext* aDestinationCtx, + const gfxRect& aRect, + const RectCornerRadii* aCornerRadii, + const gfxPoint& aBlurStdDev, + const sRGBColor& aShadowColor, + const gfxRect& aDirtyRect, + const gfxRect& aSkipRect) { + if (!RectIsInt32Safe(ToRect(aRect))) { + return; + } + + IntSize blurRadius = CalculateBlurRadius(aBlurStdDev); + bool mirrorCorners = !aCornerRadii || aCornerRadii->AreRadiiSame(); + + IntRect rect = RoundedToInt(ToRect(aRect)); + IntMargin blurMargin; + IntMargin slice; + IntSize minSize; + RefPtr<SourceSurface> boxShadow = + GetBlur(aDestinationCtx, rect.Size(), blurRadius, aCornerRadii, + aShadowColor, mirrorCorners, blurMargin, slice, minSize); + if (!boxShadow) { + return; + } + + DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget(); + destDrawTarget->PushClipRect(ToRect(aDirtyRect)); + + // Copy the right parts from boxShadow into destDrawTarget. The middle parts + // will be stretched, border-image style. + + Rect srcOuter(Point(blurMargin.left, blurMargin.top), Size(minSize)); + Rect srcInner(srcOuter); + srcOuter.Inflate(Margin(blurMargin)); + srcInner.Deflate(Margin(slice)); + + Rect dstOuter(rect); + Rect dstInner(rect); + dstOuter.Inflate(Margin(blurMargin)); + dstInner.Deflate(Margin(slice)); + + Rect skipRect = ToRect(aSkipRect); + + if (minSize == rect.Size()) { + // The target rect is smaller than the minimal size so just draw the surface + if (mirrorCorners) { + DrawMirroredBoxShadow(destDrawTarget, boxShadow, dstOuter); + } else { + destDrawTarget->DrawSurface(boxShadow, dstOuter, srcOuter); + } + } else { + if (mirrorCorners) { + DrawMirroredMinBoxShadow(destDrawTarget, boxShadow, dstOuter, dstInner, + srcOuter, srcInner, skipRect, true); + } else { + DrawMinBoxShadow(destDrawTarget, boxShadow, dstOuter, dstInner, srcOuter, + srcInner, skipRect, true); + } + } + + // A note about anti-aliasing and seems between adjacent parts: + // We don't explicitly disable anti-aliasing in the DrawSurface calls above, + // so if there's a transform on destDrawTarget that is not pixel-aligned, + // there will be seams between adjacent parts of the box-shadow. It's hard to + // avoid those without the use of an intermediate surface. + // You might think that we could avoid those by just turning off AA, but there + // is a problem with that: Box-shadow rendering needs to clip out the + // element's border box, and we'd like that clip to have anti-aliasing - + // especially if the element has rounded corners! So we can't do that unless + // we have a way to say "Please anti-alias the clip, but don't antialias the + // destination rect of the DrawSurface call". + + destDrawTarget->PopClip(); +} + +static already_AddRefed<Path> GetBoxShadowInsetPath( + DrawTarget* aDrawTarget, const Rect aOuterRect, const Rect aInnerRect, + const RectCornerRadii* aInnerClipRadii) { + /*** + * We create an inset path by having two rects. + * + * ----------------------- + * | ________________ | + * | | | | + * | | | | + * | ------------------ | + * |_____________________| + * + * The outer rect and the inside rect. The path + * creates a frame around the content where we draw the inset shadow. + */ + RefPtr<PathBuilder> builder = + aDrawTarget->CreatePathBuilder(FillRule::FILL_EVEN_ODD); + AppendRectToPath(builder, aOuterRect, true); + + if (aInnerClipRadii) { + AppendRoundedRectToPath(builder, aInnerRect, *aInnerClipRadii, false); + } else { + AppendRectToPath(builder, aInnerRect, false); + } + return builder->Finish(); +} + +static void FillDestinationPath( + gfxContext* aDestinationCtx, const Rect& aDestinationRect, + const Rect& aShadowClipRect, const sRGBColor& aShadowColor, + const RectCornerRadii* aInnerClipRadii = nullptr) { + // When there is no blur radius, fill the path onto the destination + // surface. + aDestinationCtx->SetColor(aShadowColor); + DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget(); + RefPtr<Path> shadowPath = GetBoxShadowInsetPath( + destDrawTarget, aDestinationRect, aShadowClipRect, aInnerClipRadii); + + aDestinationCtx->SetPath(shadowPath); + aDestinationCtx->Fill(); +} + +static void CacheInsetBlur(const IntSize& aMinOuterSize, + const IntSize& aMinInnerSize, + const IntSize& aBlurRadius, + const RectCornerRadii* aCornerRadii, + const sRGBColor& aShadowColor, + BackendType aBackendType, + SourceSurface* aBoxShadow) { + bool isInsetBlur = true; + BlurCacheKey key(aMinOuterSize, aMinInnerSize, aBlurRadius, aCornerRadii, + aShadowColor, isInsetBlur, aBackendType); + IntMargin blurMargin(0, 0, 0, 0); + BlurCacheData* data = + new BlurCacheData(aBoxShadow, blurMargin, std::move(key)); + if (!gBlurCache->RegisterEntry(data)) { + delete data; + } +} + +already_AddRefed<SourceSurface> gfxAlphaBoxBlur::GetInsetBlur( + const Rect& aOuterRect, const Rect& aWhitespaceRect, bool aIsDestRect, + const sRGBColor& aShadowColor, const IntSize& aBlurRadius, + const RectCornerRadii* aInnerClipRadii, DrawTarget* aDestDrawTarget, + bool aMirrorCorners) { + if (!gBlurCache) { + gBlurCache = new BlurCache(); + } + + IntSize outerSize = IntSize::Truncate(aOuterRect.Size()); + IntSize whitespaceSize = IntSize::Truncate(aWhitespaceRect.Size()); + if (!aIsDestRect) { + BlurCacheData* cached = gBlurCache->LookupInsetBoxShadow( + outerSize, whitespaceSize, aBlurRadius, aInnerClipRadii, aShadowColor, + aDestDrawTarget->GetBackendType()); + if (cached) { + // So we don't forget the actual cached blur + RefPtr<SourceSurface> cachedBlur = cached->mBlur; + return cachedBlur.forget(); + } + } + + // If we can do a min rect, the whitespace rect will be expanded in Init to + // aOuterRect. + Rect blurRect = aIsDestRect ? aOuterRect : aWhitespaceRect; + // If mirroring corners, we only need to draw the top-left quadrant. + // Use ceil to preserve the remaining 1x1 middle area for minimized box + // shadows. + if (aMirrorCorners) { + blurRect.SizeTo(ceil(blurRect.Width() * 0.5f), + ceil(blurRect.Height() * 0.5f)); + } + IntSize zeroSpread(0, 0); + RefPtr<DrawTarget> minDrawTarget = + InitDrawTarget(aDestDrawTarget, blurRect, zeroSpread, aBlurRadius); + if (!minDrawTarget) { + return nullptr; + } + + // This is really annoying. When we create the AlphaBoxBlur, the DrawTarget + // has a translation applied to it that is the topLeft point. This is actually + // the rect we gave it plus the blur radius. The rects we give this for the + // outer and whitespace rects are based at (0, 0). We could either translate + // those rects when we don't have a destination rect or ignore the translation + // when using the dest rect. The dest rects layout gives us expect this + // translation. + if (!aIsDestRect) { + minDrawTarget->SetTransform(Matrix()); + } + + // Fill in the path between the inside white space / outer rects + // NOT the inner frame + RefPtr<Path> maskPath = GetBoxShadowInsetPath( + minDrawTarget, aOuterRect, aWhitespaceRect, aInnerClipRadii); + + ColorPattern black(DeviceColor::MaskOpaqueBlack()); + minDrawTarget->Fill(maskPath, black); + + // Blur and fill in with the color we actually wanted + RefPtr<SourceSurface> minInsetBlur = DoBlur(&aShadowColor); + if (!minInsetBlur) { + return nullptr; + } + + if (RefPtr<SourceSurface> opt = + aDestDrawTarget->OptimizeSourceSurface(minInsetBlur)) { + minInsetBlur = opt; + } + + if (!aIsDestRect) { + CacheInsetBlur(outerSize, whitespaceSize, aBlurRadius, aInnerClipRadii, + aShadowColor, aDestDrawTarget->GetBackendType(), + minInsetBlur); + } + + return minInsetBlur.forget(); +} + +/*** + * We create our minimal rect with 2 rects. + * The first is the inside whitespace rect, that is "cut out" + * from the box. This is (1). This must be the size + * of the blur radius + corner radius so we can have a big enough + * inside cut. + * + * The second (2) is one blur radius surrounding the inner + * frame of (1). This is the amount of blur space required + * to get a proper blend. + * + * B = one blur size + * W = one blur + corner radii - known as inner margin + * ___________________________________ + * | | + * | | | | + * | (2) | (1) | (2) | + * | B | W | B | + * | | | | + * | | | | + * | | | + * |________________________________| + */ +static void GetBlurMargins(const RectCornerRadii* aInnerClipRadii, + const IntSize& aBlurRadius, Margin& aOutBlurMargin, + Margin& aOutInnerMargin) { + Size cornerSize(0, 0); + if (aInnerClipRadii) { + const RectCornerRadii& corners = *aInnerClipRadii; + for (const auto i : mozilla::AllPhysicalCorners()) { + cornerSize.width = std::max(cornerSize.width, corners[i].width); + cornerSize.height = std::max(cornerSize.height, corners[i].height); + } + } + + // Only the inside whitespace size cares about the border radius size. + // Outer sizes only care about blur. + IntSize margin = IntSize::Ceil(cornerSize) + aBlurRadius; + + aOutInnerMargin.SizeTo(margin.height, margin.width, margin.height, + margin.width); + aOutBlurMargin.SizeTo(aBlurRadius.height, aBlurRadius.width, + aBlurRadius.height, aBlurRadius.width); +} + +static bool GetInsetBoxShadowRects(const Margin& aBlurMargin, + const Margin& aInnerMargin, + const Rect& aShadowClipRect, + const Rect& aDestinationRect, + Rect& aOutWhitespaceRect, + Rect& aOutOuterRect) { + // We always copy (2 * blur radius) + corner radius worth of data to the + // destination rect This covers the blend of the path + the actual blur Need + // +1 so that we copy the edges correctly as we'll copy over the min box + // shadow corners then the +1 for the edges between Note, the (x,y) + // coordinates are from the blur margin since the frame outside the whitespace + // rect is 1 blur radius extra space. + Rect insideWhiteSpace(aBlurMargin.left, aBlurMargin.top, + aInnerMargin.LeftRight() + 1, + aInnerMargin.TopBottom() + 1); + + // If the inner white space rect is larger than the shadow clip rect + // our approach does not work as we'll just copy one corner + // and cover the destination. In those cases, fallback to the destination rect + bool useDestRect = (aShadowClipRect.Width() <= aInnerMargin.LeftRight()) || + (aShadowClipRect.Height() <= aInnerMargin.TopBottom()); + + if (useDestRect) { + aOutWhitespaceRect = aShadowClipRect; + aOutOuterRect = aDestinationRect; + } else { + aOutWhitespaceRect = insideWhiteSpace; + aOutOuterRect = aOutWhitespaceRect; + aOutOuterRect.Inflate(aBlurMargin); + } + + return useDestRect; +} + +void gfxAlphaBoxBlur::BlurInsetBox( + gfxContext* aDestinationCtx, const Rect& aDestinationRect, + const Rect& aShadowClipRect, const IntSize& aBlurRadius, + const sRGBColor& aShadowColor, const RectCornerRadii* aInnerClipRadii, + const Rect& aSkipRect, const Point& aShadowOffset) { + if ((aBlurRadius.width == 0 && aBlurRadius.height == 0) || + aShadowClipRect.IsEmpty()) { + FillDestinationPath(aDestinationCtx, aDestinationRect, aShadowClipRect, + aShadowColor, aInnerClipRadii); + return; + } + + DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget(); + + Margin innerMargin; + Margin blurMargin; + GetBlurMargins(aInnerClipRadii, aBlurRadius, blurMargin, innerMargin); + + Rect whitespaceRect; + Rect outerRect; + bool useDestRect = + GetInsetBoxShadowRects(blurMargin, innerMargin, aShadowClipRect, + aDestinationRect, whitespaceRect, outerRect); + + // Check that the inset margin between the outer and whitespace rects is + // symmetric, and that all corner radii are the same, in which case the blur + // can be mirrored. + Margin checkMargin = outerRect - whitespaceRect; + bool mirrorCorners = checkMargin.left == checkMargin.right && + checkMargin.top == checkMargin.bottom && + (!aInnerClipRadii || aInnerClipRadii->AreRadiiSame()); + RefPtr<SourceSurface> minBlur = + GetInsetBlur(outerRect, whitespaceRect, useDestRect, aShadowColor, + aBlurRadius, aInnerClipRadii, destDrawTarget, mirrorCorners); + if (!minBlur) { + return; + } + + if (useDestRect) { + Rect destBlur = aDestinationRect; + destBlur.Inflate(blurMargin); + if (mirrorCorners) { + DrawMirroredBoxShadow(destDrawTarget, minBlur.get(), destBlur); + } else { + Rect srcBlur(Point(0, 0), Size(minBlur->GetSize())); + MOZ_ASSERT(RoundedOut(srcBlur).Size() == RoundedOut(destBlur).Size()); + destDrawTarget->DrawSurface(minBlur, destBlur, srcBlur); + } + } else { + Rect srcOuter(outerRect); + Rect srcInner(srcOuter); + srcInner.Deflate(blurMargin); // The outer color fill + srcInner.Deflate(innerMargin); // The inner whitespace + + // The shadow clip rect already takes into account the spread radius + Rect outerFillRect(aShadowClipRect); + outerFillRect.Inflate(blurMargin); + FillDestinationPath(aDestinationCtx, aDestinationRect, outerFillRect, + aShadowColor); + + // Inflate once for the frame around the whitespace + Rect destRect(aShadowClipRect); + destRect.Inflate(blurMargin); + + // Deflate for the blurred in white space + Rect destInnerRect(aShadowClipRect); + destInnerRect.Deflate(innerMargin); + + if (mirrorCorners) { + DrawMirroredMinBoxShadow(destDrawTarget, minBlur, destRect, destInnerRect, + srcOuter, srcInner, aSkipRect); + } else { + DrawMinBoxShadow(destDrawTarget, minBlur, destRect, destInnerRect, + srcOuter, srcInner, aSkipRect); + } + } +} diff --git a/gfx/thebes/gfxBlur.h b/gfx/thebes/gfxBlur.h new file mode 100644 index 0000000000..bd89883f18 --- /dev/null +++ b/gfx/thebes/gfxBlur.h @@ -0,0 +1,205 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_BLUR_H +#define GFX_BLUR_H + +#include "gfxTypes.h" +#include "gfxRect.h" +#include "nsSize.h" +#include "gfxPoint.h" +#include "mozilla/RefPtr.h" +#include "mozilla/gfx/Blur.h" + +class gfxContext; + +namespace mozilla { +namespace gfx { +struct sRGBColor; +struct RectCornerRadii; +class SourceSurface; +class DrawTarget; +} // namespace gfx +} // namespace mozilla + +/** + * Implementation of a triple box blur approximation of a Gaussian blur. + * + * A Gaussian blur is good for blurring because, when done independently + * in the horizontal and vertical directions, it matches the result that + * would be obtained using a different (rotated) set of axes. A triple + * box blur is a very close approximation of a Gaussian. + * + * Creates an 8-bit alpha channel context for callers to draw in, + * spreads the contents of that context, blurs the contents, and applies + * it as an alpha mask on a different existing context. + * + * A spread N makes each output pixel the maximum value of all source + * pixels within a square of side length 2N+1 centered on the output pixel. + * + * A temporary surface is created in the Init function. The caller then draws + * any desired content onto the context acquired through GetContext, and lastly + * calls Paint to apply the blurred content as an alpha mask. + */ +class gfxAlphaBoxBlur final { + typedef mozilla::gfx::sRGBColor sRGBColor; + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::RectCornerRadii RectCornerRadii; + + public: + gfxAlphaBoxBlur() = default; + + ~gfxAlphaBoxBlur(); + + /** + * Constructs a box blur and initializes the temporary surface. + * + * @param aDestinationCtx The destination to blur to. + * + * @param aRect The coordinates of the surface to create in device units. + * + * @param aBlurRadius The blur radius in pixels. This is the radius of + * the entire (triple) kernel function. Each individual box blur has + * radius approximately 1/3 this value, or diameter approximately 2/3 + * this value. This parameter should nearly always be computed using + * CalculateBlurRadius, below. + * + * @param aDirtyRect A pointer to a dirty rect, measured in device units, + * if available. This will be used for optimizing the blur operation. It + * is safe to pass nullptr here. + * + * @param aSkipRect A pointer to a rect, measured in device units, that + * represents an area where blurring is unnecessary and shouldn't be done + * for speed reasons. It is safe to pass nullptr here. + * + * @param aUseHardwareAccel Flag to state whether or not we can use hardware + * acceleration to speed up this blur. + */ + already_AddRefed<gfxContext> Init(gfxContext* aDestinationCtx, + const gfxRect& aRect, + const mozilla::gfx::IntSize& aSpreadRadius, + const mozilla::gfx::IntSize& aBlurRadius, + const gfxRect* aDirtyRect, + const gfxRect* aSkipRect, + bool aUseHardwareAccel = true); + + already_AddRefed<DrawTarget> InitDrawTarget( + const mozilla::gfx::DrawTarget* aReferenceDT, + const mozilla::gfx::Rect& aRect, + const mozilla::gfx::IntSize& aSpreadRadius, + const mozilla::gfx::IntSize& aBlurRadius, + const mozilla::gfx::Rect* aDirtyRect = nullptr, + const mozilla::gfx::Rect* aSkipRect = nullptr, + bool aUseHardwareAccel = true); + + /** + * Performs the blur and optionally colors the result if aShadowColor is not + * null. + */ + already_AddRefed<mozilla::gfx::SourceSurface> DoBlur( + const mozilla::gfx::sRGBColor* aShadowColor = nullptr, + mozilla::gfx::IntPoint* aOutTopLeft = nullptr); + + /** + * Does the actual blurring/spreading and mask applying. Users of this + * object must have drawn whatever they want to be blurred onto the internal + * gfxContext returned by GetContext before calling this. + * + * @param aDestinationCtx The graphics context on which to apply the + * blurred mask. + */ + void Paint(gfxContext* aDestinationCtx); + + /** + * Calculates a blur radius that, when used with box blur, approximates + * a Gaussian blur with the given standard deviation. The result of + * this function should be used as the aBlurRadius parameter to Init, + * above. + */ + static mozilla::gfx::IntSize CalculateBlurRadius( + const gfxPoint& aStandardDeviation); + + /** + * Blurs a coloured rectangle onto aDestinationCtx. This is equivalent + * to calling Init(), drawing a rectangle onto the returned surface + * and then calling Paint, but may let us optimize better in the + * backend. + * + * @param aDestinationCtx The destination to blur to. + * @param aRect The rectangle to blur in device pixels. + * @param aCornerRadii Corner radii for aRect, if it is a rounded + * rectangle. + * @param aBlurRadius The standard deviation of the blur. + * @param aShadowColor The color to draw the blurred shadow. + * @param aDirtyRect An area in device pixels that is dirty and + * needs to be redrawn. + * @param aSkipRect An area in device pixels to avoid blurring + * over, to prevent unnecessary work. + */ + static void BlurRectangle(gfxContext* aDestinationCtx, const gfxRect& aRect, + const RectCornerRadii* aCornerRadii, + const gfxPoint& aBlurStdDev, + const sRGBColor& aShadowColor, + const gfxRect& aDirtyRect, + const gfxRect& aSkipRect); + + static void ShutdownBlurCache(); + + /*** + * Blurs an inset box shadow according to a given path. + * This is equivalent to calling Init(), drawing the inset path, + * and calling paint. Do not call Init() if using this method. + * + * @param aDestinationCtx The destination to blur to. + * @param aDestinationRect The destination rect in device pixels + * @param aShadowClipRect The destiniation inner rect of the + * inset path in device pixels. + * @param aBlurRadius The standard deviation of the blur. + * @param aShadowColor The color of the blur. + * @param aInnerClipRadii Corner radii for the inside rect if it is a + * rounded rect. + * @param aSkipRect An area in device pixels we don't have to + * paint in. + */ + void BlurInsetBox(gfxContext* aDestinationCtx, + const mozilla::gfx::Rect& aDestinationRect, + const mozilla::gfx::Rect& aShadowClipRect, + const mozilla::gfx::IntSize& aBlurRadius, + const mozilla::gfx::sRGBColor& aShadowColor, + const RectCornerRadii* aInnerClipRadii, + const mozilla::gfx::Rect& aSkipRect, + const mozilla::gfx::Point& aShadowOffset); + + protected: + already_AddRefed<mozilla::gfx::SourceSurface> GetInsetBlur( + const mozilla::gfx::Rect& aOuterRect, + const mozilla::gfx::Rect& aWhitespaceRect, bool aIsDestRect, + const mozilla::gfx::sRGBColor& aShadowColor, + const mozilla::gfx::IntSize& aBlurRadius, + const RectCornerRadii* aInnerClipRadii, DrawTarget* aDestDrawTarget, + bool aMirrorCorners); + + /** + * The DrawTarget of the temporary alpha surface. + */ + RefPtr<DrawTarget> mDrawTarget; + + /** + * The temporary alpha surface. + */ + uint8_t* mData = nullptr; + + /** + * The object that actually does the blurring for us. + */ + mozilla::gfx::AlphaBoxBlur mBlur; + + /** + * Indicates using DrawTarget-accelerated blurs. + */ + bool mAccelerated = false; +}; + +#endif /* GFX_BLUR_H */ diff --git a/gfx/thebes/gfxColor.h b/gfx/thebes/gfxColor.h new file mode 100644 index 0000000000..eec2b0b70e --- /dev/null +++ b/gfx/thebes/gfxColor.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_COLOR_H +#define GFX_COLOR_H + +#include "mozilla/Attributes.h" // for MOZ_ALWAYS_INLINE +#include "mozilla/gfx/Types.h" // for mozilla::gfx::SurfaceFormatBit + +/** + * Fast approximate division by 255. It has the property that + * for all 0 <= n <= 255*255, GFX_DIVIDE_BY_255(n) == n/255. + * But it only uses two adds and two shifts instead of an + * integer division (which is expensive on many processors). + * + * equivalent to ((v)/255) + */ +#define GFX_DIVIDE_BY_255(v) \ + (((((unsigned)(v)) << 8) + ((unsigned)(v)) + 255) >> 16) + +/** + * Fast premultiply + * + * equivalent to (((c)*(a))/255) + */ +uint8_t MOZ_ALWAYS_INLINE gfxPreMultiply(uint8_t c, uint8_t a) { + return GFX_DIVIDE_BY_255((c) * (a)); +} + +/** + * Pack the 4 8-bit channels (A,R,G,B) + * into a 32-bit packed NON-premultiplied pixel. + */ +uint32_t MOZ_ALWAYS_INLINE gfxPackedPixelNoPreMultiply(uint8_t a, uint8_t r, + uint8_t g, uint8_t b) { + return (((a) << mozilla::gfx::SurfaceFormatBit::OS_A) | + ((r) << mozilla::gfx::SurfaceFormatBit::OS_R) | + ((g) << mozilla::gfx::SurfaceFormatBit::OS_G) | + ((b) << mozilla::gfx::SurfaceFormatBit::OS_B)); +} + +/** + * Pack the 4 8-bit channels (A,R,G,B) + * into a 32-bit packed premultiplied pixel. + */ +uint32_t MOZ_ALWAYS_INLINE gfxPackedPixel(uint8_t a, uint8_t r, uint8_t g, + uint8_t b) { + if (a == 0x00) + return 0x00000000; + else if (a == 0xFF) { + return gfxPackedPixelNoPreMultiply(a, r, g, b); + } else { + return ((a) << mozilla::gfx::SurfaceFormatBit::OS_A) | + (gfxPreMultiply(r, a) << mozilla::gfx::SurfaceFormatBit::OS_R) | + (gfxPreMultiply(g, a) << mozilla::gfx::SurfaceFormatBit::OS_G) | + (gfxPreMultiply(b, a) << mozilla::gfx::SurfaceFormatBit::OS_B); + } +} + +#endif /* _GFX_COLOR_H */ diff --git a/gfx/thebes/gfxContext.cpp b/gfx/thebes/gfxContext.cpp new file mode 100644 index 0000000000..9997f0d4e4 --- /dev/null +++ b/gfx/thebes/gfxContext.cpp @@ -0,0 +1,890 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <math.h> + +#include "mozilla/Alignment.h" + +#include "cairo.h" + +#include "gfxContext.h" + +#include "gfxMatrix.h" +#include "gfxUtils.h" +#include "gfxASurface.h" +#include "gfxPattern.h" +#include "gfxPlatform.h" + +#include "GeckoProfiler.h" +#include "gfx2DGlue.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/gfx/DrawTargetTiled.h" +#include <algorithm> +#include "TextDrawTarget.h" + +#if XP_WIN +# include "gfxWindowsPlatform.h" +# include "mozilla/gfx/DeviceManagerDx.h" +#endif + +using namespace mozilla; +using namespace mozilla::gfx; + +UserDataKey gfxContext::sDontUseAsSourceKey; + +#ifdef DEBUG +# define CURRENTSTATE_CHANGED() CurrentState().mContentChanged = true; +#else +# define CURRENTSTATE_CHANGED() +#endif + +PatternFromState::operator mozilla::gfx::Pattern&() { + gfxContext::AzureState& state = mContext->CurrentState(); + + if (state.pattern) { + return *state.pattern->GetPattern( + mContext->mDT, + state.patternTransformChanged ? &state.patternTransform : nullptr); + } + + 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"; + } + + mStateStack.SetLength(1); + CurrentState().drawTarget = mDT; + CurrentState().deviceOffset = aDeviceOffset; + mDT->SetTransform(GetDTTransform()); +} + +/* static */ +already_AddRefed<gfxContext> gfxContext::CreateOrNull( + DrawTarget* aTarget, const mozilla::gfx::Point& aDeviceOffset) { + if (!aTarget || !aTarget->IsValid()) { + gfxCriticalNote << "Invalid target in gfxContext::CreateOrNull " + << hexa(aTarget); + return nullptr; + } + + RefPtr<gfxContext> result = new gfxContext(aTarget, aDeviceOffset); + return result.forget(); +} + +/* static */ +already_AddRefed<gfxContext> gfxContext::CreatePreservingTransformOrNull( + DrawTarget* aTarget) { + if (!aTarget || !aTarget->IsValid()) { + gfxCriticalNote + << "Invalid target in gfxContext::CreatePreservingTransformOrNull " + << hexa(aTarget); + return nullptr; + } + + Matrix transform = aTarget->GetTransform(); + RefPtr<gfxContext> result = new gfxContext(aTarget); + result->SetMatrix(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(); + } + } +} + +mozilla::layout::TextDrawTarget* gfxContext::GetTextDrawer() { + if (mDT->GetBackendType() == BackendType::WEBRENDER_TEXT) { + return static_cast<mozilla::layout::TextDrawTarget*>(&*mDT); + } + return nullptr; +} + +void gfxContext::Save() { + CurrentState().transform = mTransform; + mStateStack.AppendElement(AzureState(CurrentState())); + CurrentState().pushedClips.Clear(); +#ifdef DEBUG + CurrentState().mContentChanged = false; +#endif +} + +void gfxContext::Restore() { +#ifdef DEBUG + // gfxContext::Restore is used to restore AzureState. We need to restore it + // only if it was altered. The following APIs do change the content of + // AzureState, a user should save the state before using them and restore it + // after finishing painting: + // 1. APIs to setup how to paint, such as SetColor()/SetAntialiasMode(). All + // gfxContext SetXXXX public functions belong to this category, except + // gfxContext::SetPath & gfxContext::SetMatrix. + // 2. Clip functions, such as Clip() or PopClip(). You may call PopClip() + // directly instead of using gfxContext::Save if the clip region is the + // only thing that you altered in the target context. + // 3. Function of setup transform matrix, such as Multiply() and + // SetMatrix(). Using gfxContextMatrixAutoSaveRestore is more recommended + // if transform data is the only thing that you are going to alter. + // + // You will hit the assertion message below if there is no above functions + // been used between a pair of gfxContext::Save and gfxContext::Restore. + // Considerate to remove that pair of Save/Restore if hitting that assertion. + // + // In the other hand, the following APIs do not alter the content of the + // current AzureState, therefore, there is no need to save & restore + // AzureState: + // 1. constant member functions of gfxContext. + // 2. Paint calls, such as Line()/Rectangle()/Fill(). Those APIs change the + // content of drawing buffer, which is not part of AzureState. + // 3. Path building APIs, such as SetPath()/MoveTo()/LineTo()/NewPath(). + // Surprisingly, path information is not stored in AzureState either. + // Save current AzureState before using these type of APIs does nothing but + // make performance worse. + NS_ASSERTION( + CurrentState().mContentChanged || CurrentState().pushedClips.Length() > 0, + "The context of the current AzureState is not altered after " + "Save() been called. you may consider to remove this pair of " + "gfxContext::Save/Restore."); +#endif + + for (unsigned int c = 0; c < CurrentState().pushedClips.Length(); c++) { + mDT->PopClip(); + } + + mStateStack.RemoveLastElement(); + + mDT = CurrentState().drawTarget; + + ChangeTransform(CurrentState().transform, false); +} + +// drawing +void gfxContext::NewPath() { + mPath = nullptr; + mPathBuilder = nullptr; + mPathIsRect = false; + mTransformChanged = false; +} + +void gfxContext::ClosePath() { + EnsurePathBuilder(); + mPathBuilder->Close(); +} + +already_AddRefed<Path> gfxContext::GetPath() { + EnsurePath(); + RefPtr<Path> path(mPath); + return path.forget(); +} + +void gfxContext::SetPath(Path* path) { + MOZ_ASSERT(path->GetBackendType() == mDT->GetBackendType() || + path->GetBackendType() == BackendType::RECORDING || + (mDT->GetBackendType() == BackendType::DIRECT2D1_1 && + path->GetBackendType() == BackendType::DIRECT2D) || + path->GetBackendType() == BackendType::CAPTURE); + mPath = path; + mPathBuilder = nullptr; + mPathIsRect = false; + mTransformChanged = false; +} + +void gfxContext::Fill() { Fill(PatternFromState(this)); } + +void gfxContext::Fill(const Pattern& aPattern) { + AUTO_PROFILER_LABEL("gfxContext::Fill", GRAPHICS); + 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(1.0f)); + } else { + mDT->FillRect(mRect, aPattern, DrawOptions(1.0f, op, state.aaMode)); + } + } else { + EnsurePath(); + mDT->Fill(mPath, aPattern, DrawOptions(1.0f, op, state.aaMode)); + } +} + +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(); +} + +void gfxContext::SnappedClip(const gfxRect& rect) { + Rect rec = ToRect(rect); + + 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(); + } + } + + Clip(rec); +} + +// transform stuff +void gfxContext::Multiply(const gfxMatrix& matrix) { + Multiply(ToMatrix(matrix)); +} + +// transform stuff +void gfxContext::Multiply(const Matrix& matrix) { + CURRENTSTATE_CHANGED() + ChangeTransform(matrix * mTransform); +} + +void gfxContext::SetMatrix(const gfx::Matrix& matrix) { + CURRENTSTATE_CHANGED() + ChangeTransform(matrix); +} + +void gfxContext::SetMatrixDouble(const gfxMatrix& matrix) { + SetMatrix(ToMatrix(matrix)); +} + +gfx::Matrix gfxContext::CurrentMatrix() const { return mTransform; } + +gfxMatrix gfxContext::CurrentMatrixDouble() const { + return ThebesMatrix(CurrentMatrix()); +} + +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_CHANGED() + CurrentState().aaMode = mode; +} + +AntialiasMode gfxContext::CurrentAntialiasMode() const { + return CurrentState().aaMode; +} + +void gfxContext::SetDash(const Float* dashes, int ndash, Float offset) { + CURRENTSTATE_CHANGED() + AzureState& state = CurrentState(); + + state.dashPattern.SetLength(ndash); + for (int i = 0; i < ndash; i++) { + state.dashPattern[i] = dashes[i]; + } + state.strokeOptions.mDashLength = ndash; + state.strokeOptions.mDashOffset = offset; + state.strokeOptions.mDashPattern = + ndash ? state.dashPattern.Elements() : nullptr; +} + +bool gfxContext::CurrentDash(FallibleTArray<Float>& dashes, + Float* offset) const { + const AzureState& state = CurrentState(); + int count = state.strokeOptions.mDashLength; + + if (count <= 0 || !dashes.Assign(state.dashPattern, fallible)) { + return false; + } + + *offset = state.strokeOptions.mDashOffset; + + return true; +} + +void gfxContext::SetLineWidth(Float width) { + CurrentState().strokeOptions.mLineWidth = width; +} + +Float gfxContext::CurrentLineWidth() const { + return CurrentState().strokeOptions.mLineWidth; +} + +void gfxContext::SetOp(CompositionOp aOp) { + CURRENTSTATE_CHANGED() + CurrentState().op = aOp; +} + +CompositionOp gfxContext::CurrentOp() const { return CurrentState().op; } + +void gfxContext::SetLineCap(CapStyle cap) { + CURRENTSTATE_CHANGED() + CurrentState().strokeOptions.mLineCap = cap; +} + +CapStyle gfxContext::CurrentLineCap() const { + return CurrentState().strokeOptions.mLineCap; +} + +void gfxContext::SetLineJoin(JoinStyle join) { + CURRENTSTATE_CHANGED() + CurrentState().strokeOptions.mLineJoin = join; +} + +JoinStyle gfxContext::CurrentLineJoin() const { + return CurrentState().strokeOptions.mLineJoin; +} + +void gfxContext::SetMiterLimit(Float limit) { + CURRENTSTATE_CHANGED() + CurrentState().strokeOptions.mMiterLimit = limit; +} + +Float 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.RemoveLastElement(); + mDT->PopClip(); +} + +gfxRect gfxContext::GetClipExtents(ClipExtentsSpace aSpace) const { + Rect rect = GetAzureDeviceSpaceClipBounds(); + + if (rect.IsZeroArea()) { + return gfxRect(0, 0, 0, 0); + } + + if (aSpace == eUserSpace) { + Matrix mat = mTransform; + mat.Invert(); + rect = mat.TransformBounds(rect); + } + + return ThebesRect(rect); +} + +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 sRGBColor& aColor) { + CURRENTSTATE_CHANGED() + CurrentState().pattern = nullptr; + CurrentState().color = ToDeviceColor(aColor); +} + +void gfxContext::SetDeviceColor(const DeviceColor& aColor) { + CURRENTSTATE_CHANGED() + CurrentState().pattern = nullptr; + CurrentState().color = aColor; +} + +bool gfxContext::GetDeviceColor(DeviceColor& aColorOut) { + if (CurrentState().pattern) { + return CurrentState().pattern->GetSolidColor(aColorOut); + } + + aColorOut = CurrentState().color; + return true; +} + +void gfxContext::SetPattern(gfxPattern* pattern) { + CURRENTSTATE_CHANGED() + CurrentState().patternTransformChanged = false; + CurrentState().pattern = pattern; +} + +already_AddRefed<gfxPattern> gfxContext::GetPattern() { + RefPtr<gfxPattern> pat; + + AzureState& state = CurrentState(); + if (state.pattern) { + pat = state.pattern; + } else { + pat = new gfxPattern(state.color); + } + return pat.forget(); +} + +// 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(Float alpha) { + AUTO_PROFILER_LABEL("gfxContext::Paint", GRAPHICS); + + Matrix mat = mDT->GetTransform(); + mat.Invert(); + Rect paintRect = mat.TransformBounds(Rect(Point(0, 0), Size(mDT->GetSize()))); + + mDT->FillRect(paintRect, PatternFromState(this), DrawOptions(alpha, GetOp())); +} + +void gfxContext::PushGroupForBlendBack(gfxContentType content, Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform) { + mDT->PushLayer(content == gfxContentType::COLOR, aOpacity, aMask, + aMaskTransform); +} + +void gfxContext::PushGroupAndCopyBackground(gfxContentType content, + Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform) { + IntRect clipExtents; + if (mDT->GetFormat() != SurfaceFormat::B8G8R8X8) { + gfxRect clipRect = GetClipExtents(gfxContext::eDeviceSpace); + clipRect.RoundOut(); + 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 (pushOpaqueWithCopiedBG) { + mDT->PushLayer(true, aOpacity, aMask, aMaskTransform, IntRect(), true); + } else { + mDT->PushLayer(content == gfxContentType::COLOR, aOpacity, aMask, + aMaskTransform, IntRect(), false); + } +} + +void gfxContext::PopGroupAndBlend() { mDT->PopLayer(); } + +#ifdef MOZ_DUMP_PAINTING +void gfxContext::WriteAsPNG(const char* aFile) { + gfxUtils::WriteAsPNG(mDT, aFile); +} + +void gfxContext::DumpAsDataURI() { gfxUtils::DumpAsDataURI(mDT); } + +void gfxContext::CopyAsDataURI() { gfxUtils::CopyAsDataURI(mDT); } +#endif + +void gfxContext::EnsurePath() { + if (mPathBuilder) { + mPath = mPathBuilder->Finish(); + mPathBuilder = nullptr; + } + + if (mPath) { + if (mTransformChanged) { + Matrix mat = mTransform; + mat.Invert(); + mat = mPathTransform * mat; + mPathBuilder = mPath->TransformedCopyToBuilder(mat); + mPath = mPathBuilder->Finish(); + mPathBuilder = nullptr; + + mTransformChanged = false; + } + return; + } + + EnsurePathBuilder(); + mPath = mPathBuilder->Finish(); + mPathBuilder = nullptr; +} + +void gfxContext::EnsurePathBuilder() { + if (mPathBuilder && !mTransformChanged) { + return; + } + + if (mPath) { + if (!mTransformChanged) { + mPathBuilder = mPath->CopyToBuilder(); + mPath = nullptr; + } else { + Matrix invTransform = mTransform; + invTransform.Invert(); + Matrix toNewUS = mPathTransform * invTransform; + mPathBuilder = mPath->TransformedCopyToBuilder(toNewUS); + } + return; + } + + DebugOnly<PathBuilder*> oldPath = mPathBuilder.get(); + + if (!mPathBuilder) { + mPathBuilder = mDT->CreatePathBuilder(FillRule::FILL_WINDING); + + if (mPathIsRect) { + mPathBuilder->MoveTo(mRect.TopLeft()); + mPathBuilder->LineTo(mRect.TopRight()); + mPathBuilder->LineTo(mRect.BottomRight()); + mPathBuilder->LineTo(mRect.BottomLeft()); + mPathBuilder->Close(); + } + } + + if (mTransformChanged) { + // This could be an else if since this should never happen when + // mPathBuilder is nullptr and mPath is nullptr. But this way we can + // assert if all the state is as expected. + MOZ_ASSERT(oldPath); + MOZ_ASSERT(!mPathIsRect); + + Matrix invTransform = mTransform; + invTransform.Invert(); + Matrix toNewUS = mPathTransform * invTransform; + + RefPtr<Path> path = mPathBuilder->Finish(); + if (!path) { + gfxCriticalError() + << "gfxContext::EnsurePathBuilder failed in PathBuilder::Finish"; + } + mPathBuilder = path->TransformedCopyToBuilder(toNewUS); + } + + mPathIsRect = false; +} + +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.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.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() const { + Rect rect(CurrentState().deviceOffset.x + Float(mDT->GetRect().x), + CurrentState().deviceOffset.y + Float(mDT->GetRect().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++) { + const 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; +} + +void gfxContext::SetDeviceOffset(const Point& aOffset) { + CurrentState().deviceOffset = aOffset; +} + +Matrix gfxContext::GetDTTransform() const { + Matrix mat = mTransform; + mat._31 -= CurrentState().deviceOffset.x; + mat._32 -= CurrentState().deviceOffset.y; + return mat; +} diff --git a/gfx/thebes/gfxContext.h b/gfx/thebes/gfxContext.h new file mode 100644 index 0000000000..8d1db02db1 --- /dev/null +++ b/gfx/thebes/gfxContext.h @@ -0,0 +1,690 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_CONTEXT_H +#define GFX_CONTEXT_H + +#include "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 layout { +class TextDrawTarget; +} // namespace layout +} // 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 or SetColor. + * + * 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::Float Float; + typedef mozilla::gfx::Path Path; + typedef mozilla::gfx::Pattern Pattern; + typedef mozilla::gfx::Rect Rect; + typedef mozilla::gfx::RectCornerRadii RectCornerRadii; + typedef mozilla::gfx::Size Size; + + NS_INLINE_DECL_REFCOUNTING(gfxContext) + + public: + /** + * Initialize this context from a DrawTarget. + * Strips any transform from aTarget. + * aTarget will be flushed in the gfxContext's destructor. + * If aTarget is null or invalid, nullptr is returned. The caller + * is responsible for handling this scenario as appropriate. + */ + static already_AddRefed<gfxContext> CreateOrNull( + mozilla::gfx::DrawTarget* aTarget, + const mozilla::gfx::Point& aDeviceOffset = mozilla::gfx::Point()); + + /** + * Create a new gfxContext wrapping aTarget and preserving aTarget's + * transform. Note that the transform is moved from aTarget to the resulting + * gfxContext, aTarget will no longer have its transform. + * If aTarget is null or invalid, nullptr is returned. The caller + * is responsible for handling this scenario as appropriate. + */ + static already_AddRefed<gfxContext> CreatePreservingTransformOrNull( + mozilla::gfx::DrawTarget* aTarget); + + mozilla::gfx::DrawTarget* GetDrawTarget() { return mDT; } + + /** + * Returns the DrawTarget if it's actually a TextDrawTarget. + */ + mozilla::layout::TextDrawTarget* GetTextDrawer(); + + /** + ** State + **/ + // XXX document exactly what bits are saved + void Save(); + void Restore(); + + /** + ** Paths & Drawing + **/ + + /** + * Fill the current path according to the current settings. + * + * Does not consume the current path. + */ + void Fill(); + void Fill(const Pattern& aPattern); + + /** + * Forgets the current path. + */ + void NewPath(); + + /** + * Closes the path, i.e. connects the last drawn point to the first one. + * + * Filling a path will implicitly close it. + */ + void ClosePath(); + + /** + * Returns the current path. + */ + already_AddRefed<Path> GetPath(); + + /** + * Sets the given path as the current path. + */ + void SetPath(Path* path); + + /** + * Moves the pen to a new point without drawing a line. + */ + void MoveTo(const gfxPoint& pt); + + /** + * 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. + */ + void Rectangle(const gfxRect& rect) { return Rectangle(rect, false); } + void SnappedRectangle(const gfxRect& rect) { return Rectangle(rect, true); } + + private: + void Rectangle(const gfxRect& rect, bool snapToPixels); + + public: + /** + ** Transformation Matrix manipulation + **/ + + /** + * Post-multiplies 'other' onto the current CTM, i.e. this + * matrix's transformation will take place before the previously set + * transformations. + */ + void Multiply(const gfxMatrix& other); + void Multiply(const mozilla::gfx::Matrix& other); + + /** + * Replaces the current transformation matrix with matrix. + */ + void SetMatrix(const mozilla::gfx::Matrix& matrix); + void SetMatrixDouble(const gfxMatrix& matrix); + + /** + * Returns the current transformation matrix. + */ + mozilla::gfx::Matrix CurrentMatrix() const; + gfxMatrix CurrentMatrixDouble() 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::DeviceColor& 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::DeviceColor& aColorOut); + + /** + * Returns true if color is neither opaque nor transparent (i.e. alpha is not + * 0 or 1), and false otherwise. If true, aColorOut is set on output. + */ + bool HasNonOpaqueNonTransparentColor(mozilla::gfx::DeviceColor& aColorOut) { + return GetDeviceColor(aColorOut) && 0.f < aColorOut.a && aColorOut.a < 1.f; + } + + /** + * Set a solid color in the sRGB color space to use for drawing. + * If CMS is not enabled, the color is treated as a device-space color + * and this call is identical to SetDeviceColor(). + */ + void SetColor(const mozilla::gfx::sRGBColor& aColor); + + /** + * Uses a pattern for drawing. + */ + void SetPattern(gfxPattern* pattern); + + /** + * Get the source pattern (solid color, normal pattern, surface, etc) + */ + already_AddRefed<gfxPattern> GetPattern(); + + /** + ** Painting + **/ + /** + * Paints the current source surface/pattern everywhere in the current + * clip region. + */ + void Paint(Float 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(const Float* dashes, int ndash, Float 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<Float>& dashes, Float* offset) const; + + /** + * Sets the line width that's used for line drawing. + */ + void SetLineWidth(Float width); + + /** + * Returns the currently set line width. + * + * @see SetLineWidth + */ + Float 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(Float limit); + Float 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 SnappedClip(const gfxRect& rect); // snap rect and clip to the result + void Clip(Path* aPath); + + void PopClip(); + + enum ClipExtentsSpace { + eUserSpace = 0, + eDeviceSpace = 1, + }; + + /** + * According to aSpace, this function will return the current bounds of + * the clip region in user space or device space. + */ + gfxRect GetClipExtents(ClipExtentsSpace aSpace = eUserSpace) const; + + /** + * 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; + void SetDeviceOffset(const mozilla::gfx::Point& aOffset); + +#ifdef MOZ_DUMP_PAINTING + /** + * Debug functions to encode the current surface as a PNG and export it. + */ + + /** + * Writes a binary PNG file. + */ + void WriteAsPNG(const char* aFile); + + /** + * Write as a PNG encoded Data URL to stdout. + */ + void DumpAsDataURI(); + + /** + * Copy a PNG encoded Data URL to the clipboard. + */ + void CopyAsDataURI(); +#endif + + 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::sRGBColor sRGBColor; + typedef mozilla::gfx::DeviceColor DeviceColor; + typedef mozilla::gfx::StrokeOptions StrokeOptions; + 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) +#ifdef DEBUG + , + mContentChanged(false) +#endif + { + } + + mozilla::gfx::CompositionOp op; + DeviceColor color; + RefPtr<gfxPattern> pattern; + Matrix transform; + struct PushedClip { + RefPtr<Path> path; + Rect rect; + Matrix transform; + }; + CopyableTArray<PushedClip> pushedClips; + CopyableTArray<Float> dashPattern; + StrokeOptions strokeOptions; + RefPtr<DrawTarget> drawTarget; + mozilla::gfx::AntialiasMode aaMode; + bool patternTransformChanged; + Matrix patternTransform; + DeviceColor fontSmoothingBackgroundColor; + // This is used solely for using minimal intermediate surface size. + mozilla::gfx::Point deviceOffset; +#ifdef DEBUG + // Whether the content of this AzureState changed after construction. + bool mContentChanged; +#endif + }; + + // This ensures mPath contains a valid path (in user space!) + void EnsurePath(); + // This ensures mPathBuilder contains a valid PathBuilder (in user space!) + void EnsurePathBuilder(); + CompositionOp GetOp(); + void ChangeTransform(const mozilla::gfx::Matrix& aNewMatrix, + bool aUpdatePatternTransform = true); + Rect GetAzureDeviceSpaceClipBounds() const; + Matrix GetDTTransform() const; + + bool mPathIsRect; + bool mTransformChanged; + Matrix mPathTransform; + Rect mRect; + RefPtr<PathBuilder> mPathBuilder; + RefPtr<Path> mPath; + Matrix mTransform; + nsTArray<AzureState> mStateStack; + + AzureState& CurrentState() { return mStateStack[mStateStack.Length() - 1]; } + const AzureState& CurrentState() const { + return mStateStack[mStateStack.Length() - 1]; + } + + RefPtr<DrawTarget> mDT; +}; + +/** + * Sentry helper class for functions with multiple return points that need to + * call Save() on a gfxContext and have Restore() called automatically on the + * gfxContext before they return. + */ +class gfxContextAutoSaveRestore { + public: + gfxContextAutoSaveRestore() : mContext(nullptr) {} + + explicit gfxContextAutoSaveRestore(gfxContext* aContext) + : mContext(aContext) { + mContext->Save(); + } + + ~gfxContextAutoSaveRestore() { Restore(); } + + void SetContext(gfxContext* aContext) { + NS_ASSERTION(!mContext, "Not going to call Restore() on some context!!!"); + mContext = aContext; + mContext->Save(); + } + + void EnsureSaved(gfxContext* aContext) { + MOZ_ASSERT(!mContext || mContext == aContext, "wrong context"); + if (!mContext) { + mContext = aContext; + mContext->Save(); + } + } + + void Restore() { + if (mContext) { + mContext->Restore(); + mContext = nullptr; + } + } + + private: + gfxContext* mContext; +}; + +/** + * Sentry helper class for functions with multiple return points that need to + * back up the current matrix of a context and have it automatically restored + * before they return. + */ +class gfxContextMatrixAutoSaveRestore { + public: + gfxContextMatrixAutoSaveRestore() : mContext(nullptr) {} + + explicit gfxContextMatrixAutoSaveRestore(gfxContext* aContext) + : mContext(aContext), mMatrix(aContext->CurrentMatrix()) {} + + ~gfxContextMatrixAutoSaveRestore() { + if (mContext) { + mContext->SetMatrix(mMatrix); + } + } + + void SetContext(gfxContext* aContext) { + NS_ASSERTION(!mContext, "Not going to restore the matrix on some context!"); + mContext = aContext; + mMatrix = aContext->CurrentMatrix(); + } + + void Restore() { + if (mContext) { + mContext->SetMatrix(mMatrix); + mContext = nullptr; + } + } + + const mozilla::gfx::Matrix& Matrix() { + MOZ_ASSERT(mContext, "mMatrix doesn't contain a useful matrix"); + return mMatrix; + } + + bool HasMatrix() const { return !!mContext; } + + private: + gfxContext* mContext; + mozilla::gfx::Matrix mMatrix; +}; + +class DrawTargetAutoDisableSubpixelAntialiasing { + public: + typedef mozilla::gfx::DrawTarget DrawTarget; + + DrawTargetAutoDisableSubpixelAntialiasing(DrawTarget* aDT, bool aDisable) + : mSubpixelAntialiasingEnabled(false) { + if (aDisable) { + mDT = aDT; + mSubpixelAntialiasingEnabled = mDT->GetPermitSubpixelAA(); + mDT->SetPermitSubpixelAA(false); + } + } + ~DrawTargetAutoDisableSubpixelAntialiasing() { + if (mDT) { + mDT->SetPermitSubpixelAA(mSubpixelAntialiasingEnabled); + } + } + + private: + RefPtr<DrawTarget> mDT; + bool mSubpixelAntialiasingEnabled; +}; + +/* This class lives on the stack and allows gfxContext users to easily, and + * performantly get a gfx::Pattern to use for drawing in their current context. + */ +class PatternFromState { + public: + explicit PatternFromState(gfxContext* aContext) + : mContext(aContext), mPattern(nullptr) {} + ~PatternFromState() { + if (mPattern) { + mPattern->~Pattern(); + } + } + + operator mozilla::gfx::Pattern&(); + + private: + union { + mozilla::AlignedStorage2<mozilla::gfx::ColorPattern> mColorPattern; + mozilla::AlignedStorage2<mozilla::gfx::SurfacePattern> mSurfacePattern; + }; + + gfxContext* mContext; + mozilla::gfx::Pattern* mPattern; +}; + +/* This interface should be implemented to handle exporting the clip from a + * context. + */ +class ClipExporter : public mozilla::gfx::PathSink { + public: + virtual void BeginClip(const mozilla::gfx::Matrix& aMatrix) = 0; + virtual void EndClip() = 0; +}; + +#endif /* GFX_CONTEXT_H */ diff --git a/gfx/thebes/gfxCoreTextShaper.cpp b/gfx/thebes/gfxCoreTextShaper.cpp new file mode 100644 index 0000000000..0e24337bf4 --- /dev/null +++ b/gfx/thebes/gfxCoreTextShaper.cpp @@ -0,0 +1,650 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" +#include "gfxCoreTextShaper.h" +#include "gfxMacFont.h" +#include "gfxFontUtils.h" +#include "gfxTextRun.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/UniquePtrExtensions.h" + +#include <algorithm> + +#include <dlfcn.h> + +using namespace mozilla; + +// standard font descriptors that we construct the first time they're needed +CTFontDescriptorRef gfxCoreTextShaper::sFeaturesDescriptor[kMaxFontInstances]; + +// Helper to create a CFDictionary with the right attributes for shaping our +// text, including imposing the given directionality. +CFDictionaryRef gfxCoreTextShaper::CreateAttrDict(bool aRightToLeft) { + // Because we always shape unidirectional runs, and may have applied + // directional overrides, we want to force a direction rather than + // allowing CoreText to do its own unicode-based bidi processing. + SInt16 dirOverride = kCTWritingDirectionOverride | + (aRightToLeft ? kCTWritingDirectionRightToLeft + : kCTWritingDirectionLeftToRight); + CFNumberRef dirNumber = + ::CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt16Type, &dirOverride); + CFArrayRef dirArray = ::CFArrayCreate( + kCFAllocatorDefault, (const void**)&dirNumber, 1, &kCFTypeArrayCallBacks); + ::CFRelease(dirNumber); + CFTypeRef attrs[] = {kCTFontAttributeName, kCTWritingDirectionAttributeName}; + CFTypeRef values[] = {mCTFont[0], dirArray}; + CFDictionaryRef attrDict = ::CFDictionaryCreate( + kCFAllocatorDefault, attrs, values, ArrayLength(attrs), + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + ::CFRelease(dirArray); + return attrDict; +} + +gfxCoreTextShaper::gfxCoreTextShaper(gfxMacFont* aFont) + : gfxFontShaper(aFont), + mAttributesDictLTR(nullptr), + mAttributesDictRTL(nullptr) { + for (size_t i = 0; i < kMaxFontInstances; i++) { + mCTFont[i] = nullptr; + } + // Create our default CTFontRef + mCTFont[0] = CreateCTFontWithFeatures( + aFont->GetAdjustedSize(), GetFeaturesDescriptor(kDefaultFeatures)); +} + +gfxCoreTextShaper::~gfxCoreTextShaper() { + if (mAttributesDictLTR) { + ::CFRelease(mAttributesDictLTR); + } + if (mAttributesDictRTL) { + ::CFRelease(mAttributesDictRTL); + } + for (size_t i = 0; i < kMaxFontInstances; i++) { + if (mCTFont[i]) { + ::CFRelease(mCTFont[i]); + } + } +} + +static bool IsBuggyIndicScript(unicode::Script aScript) { + return aScript == unicode::Script::BENGALI || + aScript == unicode::Script::KANNADA || + aScript == unicode::Script::ORIYA || aScript == unicode::Script::KHMER; +} + +bool gfxCoreTextShaper::ShapeText(DrawTarget* aDrawTarget, + const char16_t* aText, uint32_t aOffset, + uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, + RoundingFlags aRounding, + gfxShapedText* aShapedText) { + // Create a CFAttributedString with text and style info, so we can use + // CoreText to lay it out. + bool isRightToLeft = aShapedText->IsRightToLeft(); + const UniChar* text = reinterpret_cast<const UniChar*>(aText); + + CFStringRef stringObj = ::CFStringCreateWithCharactersNoCopy( + kCFAllocatorDefault, text, aLength, kCFAllocatorNull); + + // Figure out whether we should try to set the AAT small-caps feature: + // examine OpenType tags for the requested style, and see if 'smcp' is + // among them. + const gfxFontStyle* style = mFont->GetStyle(); + gfxFontEntry* entry = mFont->GetFontEntry(); + auto handleFeatureTag = [](const uint32_t& aTag, uint32_t& aValue, + void* aUserArg) -> void { + if (aTag == HB_TAG('s', 'm', 'c', 'p') && aValue) { + *static_cast<bool*>(aUserArg) = true; + } + }; + bool addSmallCaps = false; + MergeFontFeatures(style, entry->mFeatureSettings, false, entry->FamilyName(), + false, handleFeatureTag, &addSmallCaps); + + // Get an attributes dictionary suitable for shaping text in the + // current direction, creating it if necessary. + CFDictionaryRef attrObj = + isRightToLeft ? mAttributesDictRTL : mAttributesDictLTR; + if (!attrObj) { + attrObj = CreateAttrDict(isRightToLeft); + (isRightToLeft ? mAttributesDictRTL : mAttributesDictLTR) = attrObj; + } + + FeatureFlags featureFlags = kDefaultFeatures; + if (IsBuggyIndicScript(aScript)) { + // To work around buggy Indic AAT fonts shipped with OS X, + // we re-enable the Line Initial Smart Swashes feature that is needed + // for "split vowels" to work in at least Bengali and Kannada fonts. + // Affected fonts include Bangla MN, Bangla Sangam MN, Kannada MN, + // Kannada Sangam MN. See bugs 686225, 728557, 953231, 1145515. + // Also applies to Oriya and Khmer, see bug 1370927 and bug 1403166. + featureFlags |= kIndicFeatures; + } + if (aShapedText->DisableLigatures()) { + // For letterspacing (or maybe other situations) we need to make + // a copy of the CTFont with the ligature feature disabled. + featureFlags |= kDisableLigatures; + } + if (addSmallCaps) { + featureFlags |= kAddSmallCaps; + } + + // For the disabled-ligature, buggy-indic-font or small-caps case, replace + // the default CTFont in the attribute dictionary with a tweaked version. + CFMutableDictionaryRef mutableAttr = nullptr; + if (featureFlags != 0) { + if (!mCTFont[featureFlags]) { + mCTFont[featureFlags] = CreateCTFontWithFeatures( + mFont->GetAdjustedSize(), GetFeaturesDescriptor(featureFlags)); + } + mutableAttr = + ::CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 2, attrObj); + ::CFDictionaryReplaceValue(mutableAttr, kCTFontAttributeName, + mCTFont[featureFlags]); + attrObj = mutableAttr; + } + + // Now we can create an attributed string + CFAttributedStringRef attrStringObj = + ::CFAttributedStringCreate(kCFAllocatorDefault, stringObj, attrObj); + ::CFRelease(stringObj); + + // Create the CoreText line from our string, then we're done with it + CTLineRef line = ::CTLineCreateWithAttributedString(attrStringObj); + ::CFRelease(attrStringObj); + + // and finally retrieve the glyph data and store into the gfxTextRun + CFArrayRef glyphRuns = ::CTLineGetGlyphRuns(line); + uint32_t numRuns = ::CFArrayGetCount(glyphRuns); + + // Iterate through the glyph runs. + bool success = true; + for (uint32_t runIndex = 0; runIndex < numRuns; runIndex++) { + CTRunRef aCTRun = (CTRunRef)::CFArrayGetValueAtIndex(glyphRuns, runIndex); + CFRange range = ::CTRunGetStringRange(aCTRun); + CFDictionaryRef runAttr = ::CTRunGetAttributes(aCTRun); + if (runAttr != attrObj) { + // If Core Text manufactured a new dictionary, this may indicate + // unexpected font substitution. In that case, we fail (and fall + // back to harfbuzz shaping)... + const void* font1 = ::CFDictionaryGetValue(attrObj, kCTFontAttributeName); + const void* font2 = ::CFDictionaryGetValue(runAttr, kCTFontAttributeName); + if (font1 != font2) { + // ...except that if the fallback was only for a variation + // selector or join control that is otherwise unsupported, + // we just ignore it. + if (range.length == 1) { + char16_t ch = aText[range.location]; + if (gfxFontUtils::IsJoinControl(ch) || + gfxFontUtils::IsVarSelector(ch)) { + continue; + } + } + NS_WARNING("unexpected font fallback in Core Text"); + success = false; + break; + } + } + if (SetGlyphsFromRun(aShapedText, aOffset, aLength, aCTRun) != NS_OK) { + success = false; + break; + } + } + + if (mutableAttr) { + ::CFRelease(mutableAttr); + } + ::CFRelease(line); + + return success; +} + +#define SMALL_GLYPH_RUN \ + 128 // preallocated size of our auto arrays for per-glyph data; + // some testing indicates that 90%+ of glyph runs will fit + // without requiring a separate allocation + +nsresult gfxCoreTextShaper::SetGlyphsFromRun(gfxShapedText* aShapedText, + uint32_t aOffset, uint32_t aLength, + CTRunRef aCTRun) { + typedef gfxShapedText::CompressedGlyph CompressedGlyph; + + int32_t direction = aShapedText->IsRightToLeft() ? -1 : 1; + + int32_t numGlyphs = ::CTRunGetGlyphCount(aCTRun); + if (numGlyphs == 0) { + return NS_OK; + } + + int32_t wordLength = aLength; + + // character offsets get really confusing here, as we have to keep track of + // (a) the text in the actual textRun we're constructing + // (c) the string that was handed to CoreText, which contains the text of + // the font run + // (d) the CTRun currently being processed, which may be a sub-run of the + // CoreText line + + // get the source string range within the CTLine's text + CFRange stringRange = ::CTRunGetStringRange(aCTRun); + // skip the run if it is entirely outside the actual range of the font run + if (stringRange.location + stringRange.length <= 0 || + stringRange.location >= wordLength) { + return NS_OK; + } + + // retrieve the laid-out glyph data from the CTRun + UniquePtr<CGGlyph[]> glyphsArray; + UniquePtr<CGPoint[]> positionsArray; + UniquePtr<CFIndex[]> glyphToCharArray; + const CGGlyph* glyphs = nullptr; + const CGPoint* positions = nullptr; + const CFIndex* glyphToChar = nullptr; + + // Testing indicates that CTRunGetGlyphsPtr (almost?) always succeeds, + // and so allocating a new array and copying data with CTRunGetGlyphs + // will be extremely rare. + // If this were not the case, we could use an AutoTArray<> to + // try and avoid the heap allocation for small runs. + // It's possible that some future change to CoreText will mean that + // CTRunGetGlyphsPtr fails more often; if this happens, AutoTArray<> + // may become an attractive option. + glyphs = ::CTRunGetGlyphsPtr(aCTRun); + if (!glyphs) { + glyphsArray = MakeUniqueFallible<CGGlyph[]>(numGlyphs); + if (!glyphsArray) { + return NS_ERROR_OUT_OF_MEMORY; + } + ::CTRunGetGlyphs(aCTRun, ::CFRangeMake(0, 0), glyphsArray.get()); + glyphs = glyphsArray.get(); + } + + positions = ::CTRunGetPositionsPtr(aCTRun); + if (!positions) { + positionsArray = MakeUniqueFallible<CGPoint[]>(numGlyphs); + if (!positionsArray) { + return NS_ERROR_OUT_OF_MEMORY; + } + ::CTRunGetPositions(aCTRun, ::CFRangeMake(0, 0), positionsArray.get()); + positions = positionsArray.get(); + } + + // Remember that the glyphToChar indices relate to the CoreText line, + // not to the beginning of the textRun, the font run, + // or the stringRange of the glyph run + glyphToChar = ::CTRunGetStringIndicesPtr(aCTRun); + if (!glyphToChar) { + glyphToCharArray = MakeUniqueFallible<CFIndex[]>(numGlyphs); + if (!glyphToCharArray) { + return NS_ERROR_OUT_OF_MEMORY; + } + ::CTRunGetStringIndices(aCTRun, ::CFRangeMake(0, 0), + glyphToCharArray.get()); + glyphToChar = glyphToCharArray.get(); + } + + double runWidth = ::CTRunGetTypographicBounds(aCTRun, ::CFRangeMake(0, 0), + nullptr, nullptr, nullptr); + + AutoTArray<gfxShapedText::DetailedGlyph, 1> detailedGlyphs; + CompressedGlyph* charGlyphs = aShapedText->GetCharacterGlyphs() + aOffset; + + // CoreText gives us the glyphindex-to-charindex mapping, which relates each + // glyph to a source text character; we also need the charindex-to-glyphindex + // mapping to find the glyph for a given char. Note that some chars may not + // map to any glyph (ligature continuations), and some may map to several + // glyphs (eg Indic split vowels). We set the glyph index to NO_GLYPH for + // chars that have no associated glyph, and we record the last glyph index for + // cases where the char maps to several glyphs, so that our clumping will + // include all the glyph fragments for the character. + + // The charToGlyph array is indexed by char position within the stringRange of + // the glyph run. + + static const int32_t NO_GLYPH = -1; + AutoTArray<int32_t, SMALL_GLYPH_RUN> charToGlyphArray; + if (!charToGlyphArray.SetLength(stringRange.length, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + int32_t* charToGlyph = charToGlyphArray.Elements(); + for (int32_t offset = 0; offset < stringRange.length; ++offset) { + charToGlyph[offset] = NO_GLYPH; + } + for (int32_t i = 0; i < numGlyphs; ++i) { + int32_t loc = glyphToChar[i] - stringRange.location; + if (loc >= 0 && loc < stringRange.length) { + charToGlyph[loc] = i; + } + } + + // Find character and glyph clumps that correspond, allowing for ligatures, + // indic reordering, split glyphs, etc. + // + // The idea is that we'll find a character sequence starting at the first char + // of stringRange, and extend it until it includes the character associated + // with the first glyph; we also extend it as long as there are "holes" in the + // range of glyphs. So we will eventually have a contiguous sequence of + // characters, starting at the beginning of the range, that map to a + // contiguous sequence of glyphs, starting at the beginning of the glyph + // array. That's a clump; then we update the starting positions and repeat. + // + // NB: In the case of RTL layouts, we iterate over the stringRange in reverse. + // + + // This may find characters that fall outside the range 0:wordLength, + // so we won't necessarily use everything we find here. + + bool isRightToLeft = aShapedText->IsRightToLeft(); + int32_t glyphStart = + 0; // looking for a clump that starts at this glyph index + int32_t charStart = + isRightToLeft + ? stringRange.length - 1 + : 0; // and this char index (in the stringRange of the glyph run) + + while (glyphStart < + numGlyphs) { // keep finding groups until all glyphs are accounted for + bool inOrder = true; + int32_t charEnd = glyphToChar[glyphStart] - stringRange.location; + NS_WARNING_ASSERTION(charEnd >= 0 && charEnd < stringRange.length, + "glyph-to-char mapping points outside string range"); + // clamp charEnd to the valid range of the string + charEnd = std::max(charEnd, 0); + charEnd = std::min(charEnd, int32_t(stringRange.length)); + + int32_t glyphEnd = glyphStart; + int32_t charLimit = isRightToLeft ? -1 : stringRange.length; + do { + // This is normally executed once for each iteration of the outer loop, + // but in unusual cases where the character/glyph association is complex, + // the initial character range might correspond to a non-contiguous + // glyph range with "holes" in it. If so, we will repeat this loop to + // extend the character range until we have a contiguous glyph sequence. + NS_ASSERTION((direction > 0 && charEnd < charLimit) || + (direction < 0 && charEnd > charLimit), + "no characters left in range?"); + charEnd += direction; + while (charEnd != charLimit && charToGlyph[charEnd] == NO_GLYPH) { + charEnd += direction; + } + + // find the maximum glyph index covered by the clump so far + if (isRightToLeft) { + for (int32_t i = charStart; i > charEnd; --i) { + if (charToGlyph[i] != NO_GLYPH) { + // update extent of glyph range + glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1); + } + } + } else { + for (int32_t i = charStart; i < charEnd; ++i) { + if (charToGlyph[i] != NO_GLYPH) { + // update extent of glyph range + glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1); + } + } + } + + if (glyphEnd == glyphStart + 1) { + // for the common case of a single-glyph clump, we can skip the + // following checks + break; + } + + if (glyphEnd == glyphStart) { + // no glyphs, try to extend the clump + continue; + } + + // check whether all glyphs in the range are associated with the + // characters in our clump; if not, we have a discontinuous range, and + // should extend it unless we've reached the end of the text + bool allGlyphsAreWithinCluster = true; + int32_t prevGlyphCharIndex = charStart; + for (int32_t i = glyphStart; i < glyphEnd; ++i) { + int32_t glyphCharIndex = glyphToChar[i] - stringRange.location; + if (isRightToLeft) { + if (glyphCharIndex > charStart || glyphCharIndex <= charEnd) { + allGlyphsAreWithinCluster = false; + break; + } + if (glyphCharIndex > prevGlyphCharIndex) { + inOrder = false; + } + prevGlyphCharIndex = glyphCharIndex; + } else { + if (glyphCharIndex < charStart || glyphCharIndex >= charEnd) { + allGlyphsAreWithinCluster = false; + break; + } + if (glyphCharIndex < prevGlyphCharIndex) { + inOrder = false; + } + prevGlyphCharIndex = glyphCharIndex; + } + } + if (allGlyphsAreWithinCluster) { + break; + } + } while (charEnd != charLimit); + + NS_WARNING_ASSERTION(glyphStart < glyphEnd, + "character/glyph clump contains no glyphs!"); + if (glyphStart == glyphEnd) { + ++glyphStart; // make progress - avoid potential infinite loop + charStart = charEnd; + continue; + } + + NS_WARNING_ASSERTION(charStart != charEnd, + "character/glyph clump contains no characters!"); + if (charStart == charEnd) { + glyphStart = glyphEnd; // this is bad - we'll discard the glyph(s), + // as there's nowhere to attach them + continue; + } + + // Now charStart..charEnd is a ligature clump, corresponding to + // glyphStart..glyphEnd; Set baseCharIndex to the char we'll actually attach + // the glyphs to (1st of ligature), and endCharIndex to the limit (position + // beyond the last char), adjusting for the offset of the stringRange + // relative to the textRun. + int32_t baseCharIndex, endCharIndex; + if (isRightToLeft) { + while (charEnd >= 0 && charToGlyph[charEnd] == NO_GLYPH) { + charEnd--; + } + baseCharIndex = charEnd + stringRange.location + 1; + endCharIndex = charStart + stringRange.location + 1; + } else { + while (charEnd < stringRange.length && charToGlyph[charEnd] == NO_GLYPH) { + charEnd++; + } + baseCharIndex = charStart + stringRange.location; + endCharIndex = charEnd + stringRange.location; + } + + // Then we check if the clump falls outside our actual string range; if so, + // just go to the next. + if (endCharIndex <= 0 || baseCharIndex >= wordLength) { + glyphStart = glyphEnd; + charStart = charEnd; + continue; + } + // Ensure we won't try to go beyond the valid length of the word's text + baseCharIndex = std::max(baseCharIndex, 0); + endCharIndex = std::min(endCharIndex, wordLength); + + // Now we're ready to set the glyph info in the textRun; measure the glyph + // width of the first (perhaps only) glyph, to see if it is "Simple" + int32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit(); + double toNextGlyph; + if (glyphStart < numGlyphs - 1) { + toNextGlyph = positions[glyphStart + 1].x - positions[glyphStart].x; + } else { + toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x; + } + int32_t advance = int32_t(toNextGlyph * appUnitsPerDevUnit); + + // Check if it's a simple one-to-one mapping + int32_t glyphsInClump = glyphEnd - glyphStart; + if (glyphsInClump == 1 && + gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyphs[glyphStart]) && + gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) && + charGlyphs[baseCharIndex].IsClusterStart() && + positions[glyphStart].y == 0.0) { + charGlyphs[baseCharIndex].SetSimpleGlyph(advance, glyphs[glyphStart]); + } else { + // collect all glyphs in a list to be assigned to the first char; + // there must be at least one in the clump, and we already measured its + // advance, hence the placement of the loop-exit test and the measurement + // of the next glyph + while (true) { + gfxTextRun::DetailedGlyph* details = detailedGlyphs.AppendElement(); + details->mGlyphID = glyphs[glyphStart]; + details->mOffset.y = -positions[glyphStart].y * appUnitsPerDevUnit; + details->mAdvance = advance; + if (++glyphStart >= glyphEnd) { + break; + } + if (glyphStart < numGlyphs - 1) { + toNextGlyph = positions[glyphStart + 1].x - positions[glyphStart].x; + } else { + toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x; + } + advance = int32_t(toNextGlyph * appUnitsPerDevUnit); + } + + aShapedText->SetDetailedGlyphs(aOffset + baseCharIndex, + detailedGlyphs.Length(), + detailedGlyphs.Elements()); + + detailedGlyphs.Clear(); + } + + // the rest of the chars in the group are ligature continuations, no + // associated glyphs + while (++baseCharIndex != endCharIndex && baseCharIndex < wordLength) { + CompressedGlyph& shapedTextGlyph = charGlyphs[baseCharIndex]; + NS_ASSERTION(!shapedTextGlyph.IsSimpleGlyph(), + "overwriting a simple glyph"); + shapedTextGlyph.SetComplex(inOrder && shapedTextGlyph.IsClusterStart(), + false); + } + + glyphStart = glyphEnd; + charStart = charEnd; + } + + return NS_OK; +} + +#undef SMALL_GLYPH_RUN + +// Construct the font attribute descriptor that we'll apply by default when +// creating a CTFontRef. This will turn off line-edge swashes by default, +// because we don't know the actual line breaks when doing glyph shaping. + +// We also cache feature descriptors for shaping with disabled ligatures, and +// for buggy Indic AAT font workarounds, created on an as-needed basis. + +#define MAX_FEATURES 5 // max used by any of our Get*Descriptor functions + +CTFontDescriptorRef gfxCoreTextShaper::CreateFontFeaturesDescriptor( + const std::pair<SInt16, SInt16>* aFeatures, size_t aCount) { + MOZ_ASSERT(aCount <= MAX_FEATURES); + + CFDictionaryRef featureSettings[MAX_FEATURES]; + + for (size_t i = 0; i < aCount; i++) { + CFNumberRef type = ::CFNumberCreate( + kCFAllocatorDefault, kCFNumberSInt16Type, &aFeatures[i].first); + CFNumberRef selector = ::CFNumberCreate( + kCFAllocatorDefault, kCFNumberSInt16Type, &aFeatures[i].second); + + CFTypeRef keys[] = {kCTFontFeatureTypeIdentifierKey, + kCTFontFeatureSelectorIdentifierKey}; + CFTypeRef values[] = {type, selector}; + featureSettings[i] = ::CFDictionaryCreate( + kCFAllocatorDefault, (const void**)keys, (const void**)values, + ArrayLength(keys), &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + ::CFRelease(selector); + ::CFRelease(type); + } + + CFArrayRef featuresArray = + ::CFArrayCreate(kCFAllocatorDefault, (const void**)featureSettings, + aCount, // not ArrayLength(featureSettings), as we + // may not have used all the allocated slots + &kCFTypeArrayCallBacks); + + for (size_t i = 0; i < aCount; i++) { + ::CFRelease(featureSettings[i]); + } + + const CFTypeRef attrKeys[] = {kCTFontFeatureSettingsAttribute}; + const CFTypeRef attrValues[] = {featuresArray}; + CFDictionaryRef attributesDict = ::CFDictionaryCreate( + kCFAllocatorDefault, (const void**)attrKeys, (const void**)attrValues, + ArrayLength(attrKeys), &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + ::CFRelease(featuresArray); + + CTFontDescriptorRef descriptor = + ::CTFontDescriptorCreateWithAttributes(attributesDict); + ::CFRelease(attributesDict); + + return descriptor; +} + +CTFontDescriptorRef gfxCoreTextShaper::GetFeaturesDescriptor( + FeatureFlags aFeatureFlags) { + MOZ_ASSERT(aFeatureFlags < kMaxFontInstances); + if (!sFeaturesDescriptor[aFeatureFlags]) { + typedef std::pair<SInt16, SInt16> FeatT; + AutoTArray<FeatT, MAX_FEATURES> features; + features.AppendElement( + FeatT(kSmartSwashType, kLineFinalSwashesOffSelector)); + if ((aFeatureFlags & kIndicFeatures) == 0) { + features.AppendElement( + FeatT(kSmartSwashType, kLineInitialSwashesOffSelector)); + } + if (aFeatureFlags & kAddSmallCaps) { + features.AppendElement(FeatT(kLetterCaseType, kSmallCapsSelector)); + features.AppendElement( + FeatT(kLowerCaseType, kLowerCaseSmallCapsSelector)); + } + if (aFeatureFlags & kDisableLigatures) { + features.AppendElement( + FeatT(kLigaturesType, kCommonLigaturesOffSelector)); + } + MOZ_ASSERT(features.Length() <= MAX_FEATURES); + sFeaturesDescriptor[aFeatureFlags] = + CreateFontFeaturesDescriptor(features.Elements(), features.Length()); + } + return sFeaturesDescriptor[aFeatureFlags]; +} + +CTFontRef gfxCoreTextShaper::CreateCTFontWithFeatures( + CGFloat aSize, CTFontDescriptorRef aDescriptor) { + const gfxFontEntry* fe = mFont->GetFontEntry(); + bool isInstalledFont = !fe->IsUserFont() || fe->IsLocalUserFont(); + CGFontRef cgFont = static_cast<gfxMacFont*>(mFont)->GetCGFontRef(); + return gfxMacFont::CreateCTFontFromCGFontWithVariations( + cgFont, aSize, isInstalledFont, aDescriptor); +} + +void gfxCoreTextShaper::Shutdown() // [static] +{ + for (size_t i = 0; i < kMaxFontInstances; i++) { + if (sFeaturesDescriptor[i] != nullptr) { + ::CFRelease(sFeaturesDescriptor[i]); + sFeaturesDescriptor[i] = nullptr; + } + } +} diff --git a/gfx/thebes/gfxCoreTextShaper.h b/gfx/thebes/gfxCoreTextShaper.h new file mode 100644 index 0000000000..1385bd00bb --- /dev/null +++ b/gfx/thebes/gfxCoreTextShaper.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_CORETEXTSHAPER_H +#define GFX_CORETEXTSHAPER_H + +#include "gfxFont.h" + +#include <ApplicationServices/ApplicationServices.h> + +class gfxMacFont; + +class gfxCoreTextShaper : public gfxFontShaper { + public: + explicit gfxCoreTextShaper(gfxMacFont* aFont); + + virtual ~gfxCoreTextShaper(); + + bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, uint32_t aOffset, uint32_t aLength, + Script aScript, nsAtom* aLanguage, bool aVertical, RoundingFlags aRounding, + gfxShapedText* aShapedText) override; + + // clean up static objects that may have been cached + static void Shutdown(); + + // Flags used to track what AAT features should be enabled on the Core Text + // font instance. (Internal; only public so that we can use the + // MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS macro below.) + enum FeatureFlags : uint8_t { + kDefaultFeatures = 0x00, + // bit flags for non-default feature settings we might need + // to use, which will require separate font instances + kDisableLigatures = 0x01, + kAddSmallCaps = 0x02, + kIndicFeatures = 0x04, + + // number of font instances, indexed by OR-ing the flags above + kMaxFontInstances = 8 + }; + + protected: + CTFontRef mCTFont[kMaxFontInstances]; + + // attributes for shaping text with LTR or RTL directionality + CFDictionaryRef mAttributesDictLTR; + CFDictionaryRef mAttributesDictRTL; + + nsresult SetGlyphsFromRun(gfxShapedText* aShapedText, uint32_t aOffset, uint32_t aLength, + CTRunRef aCTRun); + + CTFontRef CreateCTFontWithFeatures(CGFloat aSize, CTFontDescriptorRef aDescriptor); + + CFDictionaryRef CreateAttrDict(bool aRightToLeft); + CFDictionaryRef CreateAttrDictWithoutDirection(); + + static CTFontDescriptorRef CreateFontFeaturesDescriptor( + const std::pair<SInt16, SInt16>* aFeatures, size_t aCount); + + static CTFontDescriptorRef GetFeaturesDescriptor(FeatureFlags aFeatureFlags); + + // cached font descriptors, created the first time they're needed + static CTFontDescriptorRef sFeaturesDescriptor[kMaxFontInstances]; +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(gfxCoreTextShaper::FeatureFlags) + +#endif /* GFX_CORETEXTSHAPER_H */ diff --git a/gfx/thebes/gfxDWriteCommon.cpp b/gfx/thebes/gfxDWriteCommon.cpp new file mode 100644 index 0000000000..3092ba3e22 --- /dev/null +++ b/gfx/thebes/gfxDWriteCommon.cpp @@ -0,0 +1,204 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxDWriteCommon.h" + +#include <unordered_map> + +#include "mozilla/Atomics.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/gfx/Logging.h" + +class gfxDWriteFontFileStream; + +static mozilla::StaticMutex sFontFileStreamsMutex; +static uint64_t sNextFontFileKey = 0; +static std::unordered_map<uint64_t, gfxDWriteFontFileStream*> sFontFileStreams; + +IDWriteFontFileLoader* gfxDWriteFontFileLoader::mInstance = nullptr; + +class gfxDWriteFontFileStream final : public IDWriteFontFileStream { + public: + /** + * Used by the FontFileLoader to create a new font stream, + * this font stream is created from data in memory. The memory + * passed may be released after object creation, it will be + * copied internally. + * + * @param aData Font data + */ + gfxDWriteFontFileStream(const uint8_t* aData, uint32_t aLength, + uint64_t aFontFileKey); + ~gfxDWriteFontFileStream(); + + // IUnknown interface + IFACEMETHOD(QueryInterface)(IID const& iid, OUT void** ppObject) { + if (iid == __uuidof(IDWriteFontFileStream)) { + *ppObject = static_cast<IDWriteFontFileStream*>(this); + return S_OK; + } else if (iid == __uuidof(IUnknown)) { + *ppObject = static_cast<IUnknown*>(this); + return S_OK; + } else { + return E_NOINTERFACE; + } + } + + IFACEMETHOD_(ULONG, AddRef)() { + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); + return ++mRefCnt; + } + + IFACEMETHOD_(ULONG, Release)() { + MOZ_ASSERT(0 != mRefCnt, "dup release"); + uint32_t count = --mRefCnt; + if (count == 0) { + // Avoid locking unless necessary. Verify the refcount hasn't changed + // while locked. Delete within the scope of the lock when zero. + mozilla::StaticMutexAutoLock lock(sFontFileStreamsMutex); + if (0 != mRefCnt) { + return mRefCnt; + } + delete this; + } + return count; + } + + // IDWriteFontFileStream methods + virtual HRESULT STDMETHODCALLTYPE + ReadFileFragment(void const** fragmentStart, UINT64 fileOffset, + UINT64 fragmentSize, OUT void** fragmentContext); + + virtual void STDMETHODCALLTYPE ReleaseFileFragment(void* fragmentContext); + + virtual HRESULT STDMETHODCALLTYPE GetFileSize(OUT UINT64* fileSize); + + virtual HRESULT STDMETHODCALLTYPE GetLastWriteTime(OUT UINT64* lastWriteTime); + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return mData.ShallowSizeOfExcludingThis(mallocSizeOf); + } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf); + } + + private: + FallibleTArray<uint8_t> mData; + mozilla::Atomic<uint32_t> mRefCnt; + uint64_t mFontFileKey; +}; + +gfxDWriteFontFileStream::gfxDWriteFontFileStream(const uint8_t* aData, + uint32_t aLength, + uint64_t aFontFileKey) + : mFontFileKey(aFontFileKey) { + // If this fails, mData will remain empty. That's OK: GetFileSize() + // will then return 0, etc., and the font just won't load. + if (!mData.AppendElements(aData, aLength, mozilla::fallible_t())) { + NS_WARNING("Failed to store data in gfxDWriteFontFileStream"); + } +} + +gfxDWriteFontFileStream::~gfxDWriteFontFileStream() { + sFontFileStreams.erase(mFontFileKey); +} + +HRESULT STDMETHODCALLTYPE +gfxDWriteFontFileStream::GetFileSize(UINT64* fileSize) { + *fileSize = mData.Length(); + return S_OK; +} + +HRESULT STDMETHODCALLTYPE +gfxDWriteFontFileStream::GetLastWriteTime(UINT64* lastWriteTime) { + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE gfxDWriteFontFileStream::ReadFileFragment( + const void** fragmentStart, UINT64 fileOffset, UINT64 fragmentSize, + void** fragmentContext) { + // We are required to do bounds checking. + if (fileOffset + fragmentSize > (UINT64)mData.Length()) { + return E_FAIL; + } + // We should be alive for the duration of this. + *fragmentStart = &mData[fileOffset]; + *fragmentContext = nullptr; + return S_OK; +} + +void STDMETHODCALLTYPE +gfxDWriteFontFileStream::ReleaseFileFragment(void* fragmentContext) {} + +HRESULT STDMETHODCALLTYPE gfxDWriteFontFileLoader::CreateStreamFromKey( + const void* fontFileReferenceKey, UINT32 fontFileReferenceKeySize, + IDWriteFontFileStream** fontFileStream) { + if (!fontFileReferenceKey || !fontFileStream) { + return E_POINTER; + } + + mozilla::StaticMutexAutoLock lock(sFontFileStreamsMutex); + uint64_t fontFileKey = *static_cast<const uint64_t*>(fontFileReferenceKey); + auto found = sFontFileStreams.find(fontFileKey); + if (found == sFontFileStreams.end()) { + *fontFileStream = nullptr; + return E_FAIL; + } + + found->second->AddRef(); + *fontFileStream = found->second; + return S_OK; +} + +/* static */ +HRESULT +gfxDWriteFontFileLoader::CreateCustomFontFile( + const uint8_t* aFontData, uint32_t aLength, IDWriteFontFile** aFontFile, + IDWriteFontFileStream** aFontFileStream) { + MOZ_ASSERT(aFontFile); + MOZ_ASSERT(aFontFileStream); + + RefPtr<IDWriteFactory> factory = mozilla::gfx::Factory::GetDWriteFactory(); + if (!factory) { + gfxCriticalError() + << "Failed to get DWrite Factory in CreateCustomFontFile."; + return E_FAIL; + } + + sFontFileStreamsMutex.Lock(); + uint64_t fontFileKey = sNextFontFileKey++; + RefPtr<gfxDWriteFontFileStream> ffsRef = + new gfxDWriteFontFileStream(aFontData, aLength, fontFileKey); + sFontFileStreams[fontFileKey] = ffsRef; + sFontFileStreamsMutex.Unlock(); + + RefPtr<IDWriteFontFile> fontFile; + HRESULT hr = factory->CreateCustomFontFileReference( + &fontFileKey, sizeof(fontFileKey), Instance(), getter_AddRefs(fontFile)); + if (FAILED(hr)) { + NS_WARNING("Failed to load font file from data!"); + return hr; + } + + fontFile.forget(aFontFile); + ffsRef.forget(aFontFileStream); + + return S_OK; +} + +size_t gfxDWriteFontFileLoader::SizeOfIncludingThis( + mozilla::MallocSizeOf mallocSizeOf) const { + size_t sizes = mallocSizeOf(this); + + // We are a singleton type that is effective owner of sFontFileStreams. + MOZ_ASSERT(this == mInstance); + for (const auto& entry : sFontFileStreams) { + gfxDWriteFontFileStream* fileStream = entry.second; + sizes += fileStream->SizeOfIncludingThis(mallocSizeOf); + } + + return sizes; +} diff --git a/gfx/thebes/gfxDWriteCommon.h b/gfx/thebes/gfxDWriteCommon.h new file mode 100644 index 0000000000..6d8f3afd40 --- /dev/null +++ b/gfx/thebes/gfxDWriteCommon.h @@ -0,0 +1,144 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_DWRITECOMMON_H +#define GFX_DWRITECOMMON_H + +// Mozilla includes +#include "mozilla/MemoryReporting.h" +#include "mozilla/FontPropertyTypes.h" +#include "nscore.h" +#include "nsCOMPtr.h" +#include "cairo-features.h" +#include "gfxFontConstants.h" +#include "nsTArray.h" +#include "gfxWindowsPlatform.h" + +#include <windows.h> +#include <dwrite.h> + +static inline DWRITE_FONT_STRETCH DWriteFontStretchFromStretch( + mozilla::FontStretch aStretch) { + if (aStretch == mozilla::FontStretch::UltraCondensed()) { + return DWRITE_FONT_STRETCH_ULTRA_CONDENSED; + } + if (aStretch == mozilla::FontStretch::ExtraCondensed()) { + return DWRITE_FONT_STRETCH_EXTRA_CONDENSED; + } + if (aStretch == mozilla::FontStretch::Condensed()) { + return DWRITE_FONT_STRETCH_CONDENSED; + } + if (aStretch == mozilla::FontStretch::SemiCondensed()) { + return DWRITE_FONT_STRETCH_SEMI_CONDENSED; + } + if (aStretch == mozilla::FontStretch::Normal()) { + return DWRITE_FONT_STRETCH_NORMAL; + } + if (aStretch == mozilla::FontStretch::SemiExpanded()) { + return DWRITE_FONT_STRETCH_SEMI_EXPANDED; + } + if (aStretch == mozilla::FontStretch::Expanded()) { + return DWRITE_FONT_STRETCH_EXPANDED; + } + if (aStretch == mozilla::FontStretch::ExtraExpanded()) { + return DWRITE_FONT_STRETCH_EXTRA_EXPANDED; + } + if (aStretch == mozilla::FontStretch::UltraExpanded()) { + return DWRITE_FONT_STRETCH_ULTRA_EXPANDED; + } + return DWRITE_FONT_STRETCH_UNDEFINED; +} + +static inline mozilla::FontStretch FontStretchFromDWriteStretch( + DWRITE_FONT_STRETCH aStretch) { + switch (aStretch) { + case DWRITE_FONT_STRETCH_ULTRA_CONDENSED: + return mozilla::FontStretch::UltraCondensed(); + case DWRITE_FONT_STRETCH_EXTRA_CONDENSED: + return mozilla::FontStretch::ExtraCondensed(); + case DWRITE_FONT_STRETCH_CONDENSED: + return mozilla::FontStretch::Condensed(); + case DWRITE_FONT_STRETCH_SEMI_CONDENSED: + return mozilla::FontStretch::SemiCondensed(); + case DWRITE_FONT_STRETCH_NORMAL: + return mozilla::FontStretch::Normal(); + case DWRITE_FONT_STRETCH_SEMI_EXPANDED: + return mozilla::FontStretch::SemiExpanded(); + case DWRITE_FONT_STRETCH_EXPANDED: + return mozilla::FontStretch::Expanded(); + case DWRITE_FONT_STRETCH_EXTRA_EXPANDED: + return mozilla::FontStretch::ExtraExpanded(); + case DWRITE_FONT_STRETCH_ULTRA_EXPANDED: + return mozilla::FontStretch::UltraExpanded(); + default: + return mozilla::FontStretch::Normal(); + } +} + +class gfxDWriteFontFileLoader : public IDWriteFontFileLoader { + public: + gfxDWriteFontFileLoader() {} + + // IUnknown interface + IFACEMETHOD(QueryInterface)(IID const& iid, OUT void** ppObject) { + if (iid == __uuidof(IDWriteFontFileLoader)) { + *ppObject = static_cast<IDWriteFontFileLoader*>(this); + return S_OK; + } else if (iid == __uuidof(IUnknown)) { + *ppObject = static_cast<IUnknown*>(this); + return S_OK; + } else { + return E_NOINTERFACE; + } + } + + IFACEMETHOD_(ULONG, AddRef)() { return 1; } + + IFACEMETHOD_(ULONG, Release)() { return 1; } + + // IDWriteFontFileLoader methods + /** + * Important! Note the key here -has- to be a pointer to a uint64_t. + */ + virtual HRESULT STDMETHODCALLTYPE CreateStreamFromKey( + void const* fontFileReferenceKey, UINT32 fontFileReferenceKeySize, + OUT IDWriteFontFileStream** fontFileStream); + + /** + * Gets the singleton loader instance. Note that when using this font + * loader, the key must be a pointer to a unint64_t. + */ + static IDWriteFontFileLoader* Instance() { + if (!mInstance) { + mInstance = new gfxDWriteFontFileLoader(); + mozilla::gfx::Factory::GetDWriteFactory()->RegisterFontFileLoader( + mInstance); + } + return mInstance; + } + + /** + * Creates a IDWriteFontFile and IDWriteFontFileStream from aFontData. + * The data from aFontData will be copied internally, so the caller + * is free to dispose of it once this method returns. + * + * @param aFontData the font data for the custom font file + * @param aLength length of the font data + * @param aFontFile out param for the created font file + * @param aFontFileStream out param for the corresponding stream + * @return HRESULT of internal calls + */ + static HRESULT CreateCustomFontFile(const uint8_t* aFontData, + uint32_t aLength, + IDWriteFontFile** aFontFile, + IDWriteFontFileStream** aFontFileStream); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + private: + static IDWriteFontFileLoader* mInstance; +}; + +#endif /* GFX_DWRITECOMMON_H */ diff --git a/gfx/thebes/gfxDWriteFontList.cpp b/gfx/thebes/gfxDWriteFontList.cpp new file mode 100644 index 0000000000..eab3b1871f --- /dev/null +++ b/gfx/thebes/gfxDWriteFontList.cpp @@ -0,0 +1,2508 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/intl/OSPreferences.h" + +#include "gfxDWriteFontList.h" +#include "gfxDWriteFonts.h" +#include "nsUnicharUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsCharSeparatedTokenizer.h" +#include "mozilla/Preferences.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/Telemetry.h" +#include "mozilla/WindowsVersion.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsAppDirectoryServiceDefs.h" + +#include "gfxGDIFontList.h" + +#include "harfbuzz/hb.h" + +#include "StandardFonts-win10.inc" + +using namespace mozilla; +using namespace mozilla::gfx; +using mozilla::intl::OSPreferences; + +#define LOG_FONTLIST(args) \ + MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug, args) +#define LOG_FONTLIST_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug) + +#define LOG_FONTINIT(args) \ + MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug, args) +#define LOG_FONTINIT_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug) + +#define LOG_CMAPDATA_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_cmapdata), LogLevel::Debug) + +static __inline void BuildKeyNameFromFontName(nsACString& aName) { + ToLowerCase(aName); +} + +//////////////////////////////////////////////////////////////////////////////// +// gfxDWriteFontFamily + +gfxDWriteFontFamily::~gfxDWriteFontFamily() {} + +static bool GetNameAsUtf8(nsACString& aName, IDWriteLocalizedStrings* aStrings, + UINT32 aIndex) { + AutoTArray<WCHAR, 32> name; + UINT32 length; + HRESULT hr = aStrings->GetStringLength(aIndex, &length); + if (FAILED(hr)) { + return false; + } + if (!name.SetLength(length + 1, fallible)) { + return false; + } + hr = aStrings->GetString(aIndex, name.Elements(), length + 1); + if (FAILED(hr)) { + return false; + } + aName.Truncate(); + AppendUTF16toUTF8( + Substring(reinterpret_cast<const char16_t*>(name.Elements()), + name.Length() - 1), + aName); + return true; +} + +static bool GetEnglishOrFirstName(nsACString& aName, + IDWriteLocalizedStrings* aStrings) { + UINT32 englishIdx = 0; + BOOL exists; + HRESULT hr = aStrings->FindLocaleName(L"en-us", &englishIdx, &exists); + if (FAILED(hr)) { + return false; + } + if (!exists) { + // Use 0 index if english is not found. + englishIdx = 0; + } + return GetNameAsUtf8(aName, aStrings, englishIdx); +} + +static HRESULT GetDirectWriteFontName(IDWriteFont* aFont, + nsACString& aFontName) { + HRESULT hr; + + RefPtr<IDWriteLocalizedStrings> names; + hr = aFont->GetFaceNames(getter_AddRefs(names)); + if (FAILED(hr)) { + return hr; + } + + if (!GetEnglishOrFirstName(aFontName, names)) { + return E_FAIL; + } + + return S_OK; +} + +#define FULLNAME_ID DWRITE_INFORMATIONAL_STRING_FULL_NAME +#define PSNAME_ID DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_NAME + +// for use in reading postscript or fullname +static HRESULT GetDirectWriteFaceName(IDWriteFont* aFont, + DWRITE_INFORMATIONAL_STRING_ID aWhichName, + nsACString& aFontName) { + HRESULT hr; + + BOOL exists; + RefPtr<IDWriteLocalizedStrings> infostrings; + hr = aFont->GetInformationalStrings(aWhichName, getter_AddRefs(infostrings), + &exists); + if (FAILED(hr) || !exists) { + return E_FAIL; + } + + if (!GetEnglishOrFirstName(aFontName, infostrings)) { + return E_FAIL; + } + + return S_OK; +} + +void gfxDWriteFontFamily::FindStyleVariations(FontInfoData* aFontInfoData) { + HRESULT hr; + if (mHasStyles) { + return; + } + mHasStyles = true; + + gfxPlatformFontList* fp = gfxPlatformFontList::PlatformFontList(); + + bool skipFaceNames = + mFaceNamesInitialized || !fp->NeedFullnamePostscriptNames(); + bool fontInfoShouldHaveFaceNames = !mFaceNamesInitialized && + fp->NeedFullnamePostscriptNames() && + aFontInfoData; + + for (UINT32 i = 0; i < mDWFamily->GetFontCount(); i++) { + RefPtr<IDWriteFont> font; + hr = mDWFamily->GetFont(i, getter_AddRefs(font)); + if (FAILED(hr)) { + // This should never happen. + NS_WARNING("Failed to get existing font from family."); + continue; + } + + if (font->GetSimulations() != DWRITE_FONT_SIMULATIONS_NONE) { + // We don't want these in the font list; we'll apply simulations + // on the fly when appropriate. + continue; + } + + // name + nsCString fullID(mName); + nsAutoCString faceName; + hr = GetDirectWriteFontName(font, faceName); + if (FAILED(hr)) { + continue; + } + fullID.Append(' '); + fullID.Append(faceName); + + // Ignore italic style's "Meiryo" because "Meiryo (Bold) Italic" has + // non-italic style glyphs as Japanese characters. However, using it + // causes serious problem if web pages wants some elements to be + // different style from others only with font-style. For example, + // <em> and <i> should be rendered as italic in the default style. + if (fullID.EqualsLiteral("Meiryo Italic") || + fullID.EqualsLiteral("Meiryo Bold Italic")) { + continue; + } + + gfxDWriteFontEntry* fe = + new gfxDWriteFontEntry(fullID, font, mIsSystemFontFamily); + fe->SetForceGDIClassic(mForceGDIClassic); + + fe->SetupVariationRanges(); + + AddFontEntry(fe); + + // postscript/fullname if needed + nsAutoCString psname, fullname; + if (fontInfoShouldHaveFaceNames) { + aFontInfoData->GetFaceNames(fe->Name(), fullname, psname); + if (!fullname.IsEmpty()) { + fp->AddFullname(fe, fullname); + } + if (!psname.IsEmpty()) { + fp->AddPostscriptName(fe, psname); + } + } else if (!skipFaceNames) { + hr = GetDirectWriteFaceName(font, PSNAME_ID, psname); + if (FAILED(hr)) { + skipFaceNames = true; + } else if (psname.Length() > 0) { + fp->AddPostscriptName(fe, psname); + } + + hr = GetDirectWriteFaceName(font, FULLNAME_ID, fullname); + if (FAILED(hr)) { + skipFaceNames = true; + } else if (fullname.Length() > 0) { + fp->AddFullname(fe, fullname); + } + } + + if (LOG_FONTLIST_ENABLED()) { + nsAutoCString weightString; + fe->Weight().ToString(weightString); + LOG_FONTLIST( + ("(fontlist) added (%s) to family (%s)" + " with style: %s weight: %s stretch: %d psname: %s fullname: %s", + fe->Name().get(), Name().get(), + (fe->IsItalic()) ? "italic" + : (fe->IsOblique() ? "oblique" : "normal"), + weightString.get(), fe->Stretch(), psname.get(), fullname.get())); + } + } + + // assume that if no error, all postscript/fullnames were initialized + if (!skipFaceNames) { + mFaceNamesInitialized = true; + } + + if (!mAvailableFonts.Length()) { + NS_WARNING("Family with no font faces in it."); + } + + if (mIsBadUnderlineFamily) { + SetBadUnderlineFonts(); + } + + CheckForSimpleFamily(); + if (mIsSimpleFamily) { + for (auto& f : mAvailableFonts) { + if (f) { + static_cast<gfxDWriteFontEntry*>(f.get())->mMayUseGDIAccess = true; + } + } + } +} + +void gfxDWriteFontFamily::ReadFaceNames(gfxPlatformFontList* aPlatformFontList, + bool aNeedFullnamePostscriptNames, + FontInfoData* aFontInfoData) { + // if all needed names have already been read, skip + if (mOtherFamilyNamesInitialized && + (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) { + return; + } + + // If we've been passed a FontInfoData, we skip the DWrite implementation + // here and fall back to the generic code which will use that info. + if (!aFontInfoData) { + // DirectWrite version of this will try to read + // postscript/fullnames via DirectWrite API + FindStyleVariations(); + } + + // fallback to looking up via name table + if (!mOtherFamilyNamesInitialized || !mFaceNamesInitialized) { + gfxFontFamily::ReadFaceNames(aPlatformFontList, + aNeedFullnamePostscriptNames, aFontInfoData); + } +} + +void gfxDWriteFontFamily::LocalizedName(nsACString& aLocalizedName) { + aLocalizedName = Name(); // just return canonical name in case of failure + + if (!mDWFamily) { + return; + } + + HRESULT hr; + nsAutoCString locale; + // We use system locale here because it's what user expects to see. + // See bug 1349454 for details. + RefPtr<OSPreferences> osprefs = OSPreferences::GetInstanceAddRefed(); + if (!osprefs) { + return; + } + osprefs->GetSystemLocale(locale); + + RefPtr<IDWriteLocalizedStrings> names; + + hr = mDWFamily->GetFamilyNames(getter_AddRefs(names)); + if (FAILED(hr)) { + return; + } + UINT32 idx = 0; + BOOL exists; + hr = + names->FindLocaleName(NS_ConvertUTF8toUTF16(locale).get(), &idx, &exists); + if (FAILED(hr)) { + return; + } + if (!exists) { + // Use english is localized is not found. + hr = names->FindLocaleName(L"en-us", &idx, &exists); + if (FAILED(hr)) { + return; + } + if (!exists) { + // Use 0 index if english is not found. + idx = 0; + } + } + AutoTArray<WCHAR, 32> famName; + UINT32 length; + + hr = names->GetStringLength(idx, &length); + if (FAILED(hr)) { + return; + } + + if (!famName.SetLength(length + 1, fallible)) { + // Eeep - running out of memory. Unlikely to end well. + return; + } + + hr = names->GetString(idx, famName.Elements(), length + 1); + if (FAILED(hr)) { + return; + } + + aLocalizedName = NS_ConvertUTF16toUTF8((const char16_t*)famName.Elements(), + famName.Length() - 1); +} + +bool gfxDWriteFontFamily::IsSymbolFontFamily() const { + // Just check the first font in the family + if (mDWFamily->GetFontCount() > 0) { + RefPtr<IDWriteFont> font; + if (SUCCEEDED(mDWFamily->GetFont(0, getter_AddRefs(font)))) { + return font->IsSymbolFont(); + } + } + return false; +} + +void gfxDWriteFontFamily::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + gfxFontFamily::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); + // TODO: + // This doesn't currently account for |mDWFamily| +} + +void gfxDWriteFontFamily::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +//////////////////////////////////////////////////////////////////////////////// +// gfxDWriteFontEntry + +gfxFontEntry* gfxDWriteFontEntry::Clone() const { + MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!"); + gfxDWriteFontEntry* fe = new gfxDWriteFontEntry(Name(), mFont); + fe->mWeightRange = mWeightRange; + fe->mStretchRange = mStretchRange; + fe->mStyleRange = mStyleRange; + return fe; +} + +gfxDWriteFontEntry::~gfxDWriteFontEntry() {} + +static bool UsingArabicOrHebrewScriptSystemLocale() { + LANGID langid = PRIMARYLANGID(::GetSystemDefaultLangID()); + switch (langid) { + case LANG_ARABIC: + case LANG_DARI: + case LANG_PASHTO: + case LANG_PERSIAN: + case LANG_SINDHI: + case LANG_UIGHUR: + case LANG_URDU: + case LANG_HEBREW: + return true; + default: + return false; + } +} + +nsresult gfxDWriteFontEntry::CopyFontTable(uint32_t aTableTag, + nsTArray<uint8_t>& aBuffer) { + gfxDWriteFontList* pFontList = gfxDWriteFontList::PlatformFontList(); + const uint32_t tagBE = NativeEndian::swapToBigEndian(aTableTag); + + // Don't use GDI table loading for symbol fonts or for + // italic fonts in Arabic-script system locales because of + // potential cmap discrepancies, see bug 629386. + // Ditto for Hebrew, bug 837498. + if (mFont && mMayUseGDIAccess && pFontList->UseGDIFontTableAccess() && + !(!IsUpright() && UsingArabicOrHebrewScriptSystemLocale()) && + !mFont->IsSymbolFont()) { + LOGFONTW logfont = {0}; + if (InitLogFont(mFont, &logfont)) { + AutoDC dc; + AutoSelectFont font(dc.GetDC(), &logfont); + if (font.IsValid()) { + uint32_t tableSize = ::GetFontData(dc.GetDC(), tagBE, 0, nullptr, 0); + if (tableSize != GDI_ERROR) { + if (aBuffer.SetLength(tableSize, fallible)) { + ::GetFontData(dc.GetDC(), tagBE, 0, aBuffer.Elements(), + aBuffer.Length()); + return NS_OK; + } + return NS_ERROR_OUT_OF_MEMORY; + } + } + } + } + + RefPtr<IDWriteFontFace> fontFace; + nsresult rv = CreateFontFace(getter_AddRefs(fontFace)); + if (NS_FAILED(rv)) { + return rv; + } + + uint8_t* tableData; + uint32_t len; + void* tableContext = nullptr; + BOOL exists; + HRESULT hr = fontFace->TryGetFontTable(tagBE, (const void**)&tableData, &len, + &tableContext, &exists); + if (FAILED(hr) || !exists) { + return NS_ERROR_FAILURE; + } + + if (aBuffer.SetLength(len, fallible)) { + memcpy(aBuffer.Elements(), tableData, len); + rv = NS_OK; + } else { + rv = NS_ERROR_OUT_OF_MEMORY; + } + + if (tableContext) { + fontFace->ReleaseFontTable(&tableContext); + } + + return rv; +} + +// Access to font tables packaged in hb_blob_t form + +// object attached to the Harfbuzz blob, used to release +// the table when the blob is destroyed +class FontTableRec { + public: + FontTableRec(IDWriteFontFace* aFontFace, void* aContext) + : mFontFace(aFontFace), mContext(aContext) { + MOZ_COUNT_CTOR(FontTableRec); + } + + ~FontTableRec() { + MOZ_COUNT_DTOR(FontTableRec); + mFontFace->ReleaseFontTable(mContext); + } + + private: + RefPtr<IDWriteFontFace> mFontFace; + void* mContext; +}; + +static void DestroyBlobFunc(void* aUserData) { + FontTableRec* ftr = static_cast<FontTableRec*>(aUserData); + delete ftr; +} + +hb_blob_t* gfxDWriteFontEntry::GetFontTable(uint32_t aTag) { + // try to avoid potentially expensive DWrite call if we haven't actually + // created the font face yet, by using the gfxFontEntry method that will + // use CopyFontTable and then cache the data + if (!mFontFace) { + return gfxFontEntry::GetFontTable(aTag); + } + + const void* data; + UINT32 size; + void* context; + BOOL exists; + HRESULT hr = mFontFace->TryGetFontTable(NativeEndian::swapToBigEndian(aTag), + &data, &size, &context, &exists); + if (SUCCEEDED(hr) && exists) { + FontTableRec* ftr = new FontTableRec(mFontFace, context); + return hb_blob_create(static_cast<const char*>(data), size, + HB_MEMORY_MODE_READONLY, ftr, DestroyBlobFunc); + } + + return nullptr; +} + +nsresult gfxDWriteFontEntry::ReadCMAP(FontInfoData* aFontInfoData) { + AUTO_PROFILER_LABEL("gfxDWriteFontEntry::ReadCMAP", GRAPHICS); + + // attempt this once, if errors occur leave a blank cmap + if (mCharacterMap || mShmemCharacterMap) { + return NS_OK; + } + + RefPtr<gfxCharacterMap> charmap; + nsresult rv; + + if (aFontInfoData && + (charmap = GetCMAPFromFontInfo(aFontInfoData, mUVSOffset))) { + rv = NS_OK; + } else { + uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p'); + charmap = new gfxCharacterMap(); + AutoTable cmapTable(this, kCMAP); + + if (cmapTable) { + uint32_t cmapLen; + const uint8_t* cmapData = reinterpret_cast<const uint8_t*>( + hb_blob_get_data(cmapTable, &cmapLen)); + rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, *charmap, mUVSOffset); + } 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(); + fontlist::FontList* sharedFontList = pfl->SharedFontList(); + if (!IsUserFont() && mShmemFace) { + mShmemFace->SetCharacterMap(sharedFontList, charmap); // async + if (!TrySetShmemCharacterMap()) { + // Temporarily retain charmap, until the shared version is + // ready for use. + mCharacterMap = charmap; + } + } else { + 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", + mName.get(), charmap->SizeOfIncludingThis(moz_malloc_size_of), + charmap->mHash, mCharacterMap == charmap ? " new" : "")); + if (LOG_CMAPDATA_ENABLED()) { + char prefix[256]; + SprintfLiteral(prefix, "(cmapdata) name: %.220s", mName.get()); + charmap->Dump(prefix, eGfxLog_cmapdata); + } + + return rv; +} + +bool gfxDWriteFontEntry::HasVariations() { + if (mHasVariationsInitialized) { + return mHasVariations; + } + mHasVariationsInitialized = true; + if (!mFontFace) { + // CreateFontFace will initialize the mFontFace field, and also + // mFontFace5 if available on the current DWrite version. + RefPtr<IDWriteFontFace> fontFace; + if (NS_FAILED(CreateFontFace(getter_AddRefs(fontFace)))) { + return false; + } + } + if (mFontFace5) { + mHasVariations = mFontFace5->HasVariations(); + } + return mHasVariations; +} + +void gfxDWriteFontEntry::GetVariationAxes( + nsTArray<gfxFontVariationAxis>& aAxes) { + if (!HasVariations()) { + return; + } + // HasVariations() will have ensured the mFontFace5 interface is available; + // so we can get an IDWriteFontResource and ask it for the axis info. + RefPtr<IDWriteFontResource> resource; + HRESULT hr = mFontFace5->GetFontResource(getter_AddRefs(resource)); + if (FAILED(hr) || !resource) { + return; + } + + uint32_t count = resource->GetFontAxisCount(); + AutoTArray<DWRITE_FONT_AXIS_VALUE, 4> defaultValues; + AutoTArray<DWRITE_FONT_AXIS_RANGE, 4> ranges; + defaultValues.SetLength(count); + ranges.SetLength(count); + resource->GetDefaultFontAxisValues(defaultValues.Elements(), count); + resource->GetFontAxisRanges(ranges.Elements(), count); + for (uint32_t i = 0; i < count; ++i) { + gfxFontVariationAxis axis; + MOZ_ASSERT(ranges[i].axisTag == defaultValues[i].axisTag); + DWRITE_FONT_AXIS_ATTRIBUTES attrs = resource->GetFontAxisAttributes(i); + if (attrs & DWRITE_FONT_AXIS_ATTRIBUTES_HIDDEN) { + continue; + } + if (!(attrs & DWRITE_FONT_AXIS_ATTRIBUTES_VARIABLE)) { + continue; + } + // Extract the 4 chars of the tag from DWrite's packed version, + // and reassemble them in the order we use for TRUETYPE_TAG. + uint32_t t = defaultValues[i].axisTag; + axis.mTag = TRUETYPE_TAG(t & 0xff, (t >> 8) & 0xff, (t >> 16) & 0xff, + (t >> 24) & 0xff); + // Try to get a human-friendly name (may not be present) + RefPtr<IDWriteLocalizedStrings> names; + resource->GetAxisNames(i, getter_AddRefs(names)); + if (names) { + GetEnglishOrFirstName(axis.mName, names); + } + axis.mMinValue = ranges[i].minValue; + axis.mMaxValue = ranges[i].maxValue; + axis.mDefaultValue = defaultValues[i].value; + aAxes.AppendElement(axis); + } +} + +void gfxDWriteFontEntry::GetVariationInstances( + nsTArray<gfxFontVariationInstance>& aInstances) { + gfxFontUtils::GetVariationData(this, nullptr, &aInstances); +} + +gfxFont* gfxDWriteFontEntry::CreateFontInstance( + const gfxFontStyle* aFontStyle) { + bool needsBold = aFontStyle->NeedsSyntheticBold(this); + DWRITE_FONT_SIMULATIONS sims = + needsBold ? DWRITE_FONT_SIMULATIONS_BOLD : DWRITE_FONT_SIMULATIONS_NONE; + ThreadSafeWeakPtr<UnscaledFontDWrite>& unscaledFontPtr = + needsBold ? mUnscaledFontBold : mUnscaledFont; + RefPtr<UnscaledFontDWrite> unscaledFont(unscaledFontPtr); + if (!unscaledFont) { + RefPtr<IDWriteFontFace> fontFace; + nsresult rv = CreateFontFace(getter_AddRefs(fontFace), nullptr, sims); + if (NS_FAILED(rv)) { + return nullptr; + } + // Only pass in the underlying IDWriteFont if the unscaled font doesn't + // reflect a data font. This signals whether or not we can safely query + // a descriptor to represent the font for various transport use-cases. + unscaledFont = + new UnscaledFontDWrite(fontFace, !mIsDataUserFont ? mFont : nullptr); + unscaledFontPtr = unscaledFont; + } + RefPtr<IDWriteFontFace> fontFace; + if (HasVariations() && !aFontStyle->variationSettings.IsEmpty()) { + nsresult rv = CreateFontFace(getter_AddRefs(fontFace), aFontStyle, sims); + if (NS_FAILED(rv)) { + return nullptr; + } + } + return new gfxDWriteFont(unscaledFont, this, aFontStyle, fontFace); +} + +nsresult gfxDWriteFontEntry::CreateFontFace( + IDWriteFontFace** aFontFace, const gfxFontStyle* aFontStyle, + DWRITE_FONT_SIMULATIONS aSimulations) { + // Convert an OpenType font tag from our uint32_t representation + // (as constructed by TRUETYPE_TAG(...)) to the order DWrite wants. + auto makeDWriteAxisTag = [](uint32_t aTag) { + return DWRITE_MAKE_FONT_AXIS_TAG((aTag >> 24) & 0xff, (aTag >> 16) & 0xff, + (aTag >> 8) & 0xff, aTag & 0xff); + }; + + // initialize mFontFace if this hasn't been done before + if (!mFontFace) { + HRESULT hr; + if (mFont) { + hr = mFont->CreateFontFace(getter_AddRefs(mFontFace)); + } else if (mFontFile) { + IDWriteFontFile* fontFile = mFontFile.get(); + hr = Factory::GetDWriteFactory()->CreateFontFace( + mFaceType, 1, &fontFile, 0, DWRITE_FONT_SIMULATIONS_NONE, + getter_AddRefs(mFontFace)); + } else { + MOZ_ASSERT_UNREACHABLE("invalid font entry"); + return NS_ERROR_FAILURE; + } + if (FAILED(hr)) { + return NS_ERROR_FAILURE; + } + // Also get the IDWriteFontFace5 interface if we're running on a + // sufficiently new DWrite version where it is available. + if (mFontFace) { + mFontFace->QueryInterface(__uuidof(IDWriteFontFace5), + (void**)getter_AddRefs(mFontFace5)); + if (!mVariationSettings.IsEmpty()) { + // If the font entry has variations specified, mFontFace5 will + // be a distinct face that has the variations applied. + RefPtr<IDWriteFontResource> resource; + HRESULT hr = mFontFace5->GetFontResource(getter_AddRefs(resource)); + if (SUCCEEDED(hr) && resource) { + AutoTArray<DWRITE_FONT_AXIS_VALUE, 4> fontAxisValues; + for (const auto& v : mVariationSettings) { + DWRITE_FONT_AXIS_VALUE axisValue = {makeDWriteAxisTag(v.mTag), + v.mValue}; + fontAxisValues.AppendElement(axisValue); + } + resource->CreateFontFace( + mFontFace->GetSimulations(), fontAxisValues.Elements(), + fontAxisValues.Length(), getter_AddRefs(mFontFace5)); + } + } + } + } + + // Do we need to modify DWrite simulations from what mFontFace has? + bool needSimulations = + (aSimulations & DWRITE_FONT_SIMULATIONS_BOLD) && + !(mFontFace->GetSimulations() & DWRITE_FONT_SIMULATIONS_BOLD); + + // If the IDWriteFontFace5 interface is available, we can try using + // IDWriteFontResource to create a new modified face. + if (mFontFace5 && (HasVariations() || needSimulations)) { + RefPtr<IDWriteFontResource> resource; + HRESULT hr = mFontFace5->GetFontResource(getter_AddRefs(resource)); + if (SUCCEEDED(hr) && resource) { + AutoTArray<DWRITE_FONT_AXIS_VALUE, 4> fontAxisValues; + + // Get the variation settings needed to instantiate the fontEntry + // for a particular fontStyle, or use default style if no aFontStyle + // was passed (e.g. instantiating a face just to read font tables). + AutoTArray<gfxFontVariation, 4> vars; + GetVariationsForStyle(vars, aFontStyle ? *aFontStyle : gfxFontStyle()); + + // Copy variation settings to DWrite's type. + if (!vars.IsEmpty()) { + for (const auto& v : vars) { + DWRITE_FONT_AXIS_VALUE axisValue = {makeDWriteAxisTag(v.mTag), + v.mValue}; + fontAxisValues.AppendElement(axisValue); + } + } + + IDWriteFontFace5* ff5; + resource->CreateFontFace(aSimulations, fontAxisValues.Elements(), + fontAxisValues.Length(), &ff5); + if (ff5) { + *aFontFace = ff5; + return NS_OK; + } + } + } + + // Do we need to add DWrite simulations to the face? + if (needSimulations) { + // if so, we need to return not mFontFace itself but a version that + // has the Bold simulation - unfortunately, old DWrite doesn't provide + // a simple API for this + UINT32 numberOfFiles = 0; + if (FAILED(mFontFace->GetFiles(&numberOfFiles, nullptr))) { + return NS_ERROR_FAILURE; + } + AutoTArray<IDWriteFontFile*, 1> files; + files.AppendElements(numberOfFiles); + if (FAILED(mFontFace->GetFiles(&numberOfFiles, files.Elements()))) { + return NS_ERROR_FAILURE; + } + HRESULT hr = Factory::GetDWriteFactory()->CreateFontFace( + mFontFace->GetType(), numberOfFiles, files.Elements(), + mFontFace->GetIndex(), aSimulations, aFontFace); + for (UINT32 i = 0; i < numberOfFiles; ++i) { + files[i]->Release(); + } + return FAILED(hr) ? NS_ERROR_FAILURE : NS_OK; + } + + // no simulation: we can just add a reference to mFontFace5 (if present) + // or mFontFace (otherwise) and return that + if (mFontFace5) { + *aFontFace = mFontFace5; + } else { + *aFontFace = mFontFace; + } + (*aFontFace)->AddRef(); + return NS_OK; +} + +bool gfxDWriteFontEntry::InitLogFont(IDWriteFont* aFont, LOGFONTW* aLogFont) { + HRESULT hr; + + BOOL isInSystemCollection; + IDWriteGdiInterop* gdi = + gfxDWriteFontList::PlatformFontList()->GetGDIInterop(); + hr = gdi->ConvertFontToLOGFONT(aFont, aLogFont, &isInSystemCollection); + // If the font is not in the system collection, GDI will be unable to + // select it and load its tables, so we return false here to indicate + // failure, and let CopyFontTable fall back to DWrite native methods. + return (SUCCEEDED(hr) && isInSystemCollection); +} + +bool gfxDWriteFontEntry::IsCJKFont() { + if (mIsCJK != UNINITIALIZED_VALUE) { + return mIsCJK; + } + + mIsCJK = false; + + const uint32_t kOS2Tag = TRUETYPE_TAG('O', 'S', '/', '2'); + hb_blob_t* blob = GetFontTable(kOS2Tag); + if (!blob) { + return mIsCJK; + } + // |blob| is an owning reference, but is not RAII-managed, so it must be + // explicitly freed using |hb_blob_destroy| before we return. (Beware of + // adding any early-return codepaths!) + + uint32_t len; + const OS2Table* os2 = + reinterpret_cast<const OS2Table*>(hb_blob_get_data(blob, &len)); + // ulCodePageRange bit definitions for the CJK codepages, + // from http://www.microsoft.com/typography/otspec/os2.htm#cpr + const uint32_t CJK_CODEPAGE_BITS = + (1 << 17) | // codepage 932 - JIS/Japan + (1 << 18) | // codepage 936 - Chinese (simplified) + (1 << 19) | // codepage 949 - Korean Wansung + (1 << 20) | // codepage 950 - Chinese (traditional) + (1 << 21); // codepage 1361 - Korean Johab + if (len >= offsetof(OS2Table, sxHeight)) { + if ((uint32_t(os2->codePageRange1) & CJK_CODEPAGE_BITS) != 0) { + mIsCJK = true; + } + } + hb_blob_destroy(blob); + + return mIsCJK; +} + +void gfxDWriteFontEntry::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + gfxFontEntry::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); + // TODO: + // This doesn't currently account for the |mFont| and |mFontFile| members +} + +void gfxDWriteFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +//////////////////////////////////////////////////////////////////////////////// +// gfxDWriteFontList + +gfxDWriteFontList::gfxDWriteFontList() : mForceGDIClassicMaxFontSize(0.0) { + CheckFamilyList(kBaseFonts, ArrayLength(kBaseFonts)); + CheckFamilyList(kLangPackFonts, ArrayLength(kLangPackFonts)); +} + +// bug 602792 - CJK systems default to large CJK fonts which cause excessive +// I/O strain during cold startup due to dwrite caching bugs. Default to +// Arial to avoid this. + +FontFamily gfxDWriteFontList::GetDefaultFontForPlatform( + const gfxFontStyle* aStyle, nsAtom* aLanguage) { + // try Arial first + FontFamily ff; + ff = FindFamily("Arial"_ns); + if (!ff.IsNull()) { + return ff; + } + + // otherwise, use local default + NONCLIENTMETRICSW ncm; + ncm.cbSize = sizeof(ncm); + BOOL status = + ::SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0); + + if (status) { + ff = FindFamily(NS_ConvertUTF16toUTF8(ncm.lfMessageFont.lfFaceName)); + } + + return ff; +} + +gfxFontEntry* gfxDWriteFontList::LookupLocalFont( + const nsACString& aFontName, WeightRange aWeightForEntry, + StretchRange aStretchForEntry, SlantStyleRange aStyleForEntry) { + if (SharedFontList()) { + return LookupInSharedFaceNameList(aFontName, aWeightForEntry, + aStretchForEntry, aStyleForEntry); + } + + gfxFontEntry* lookup; + + lookup = LookupInFaceNameLists(aFontName); + if (!lookup) { + return nullptr; + } + + gfxDWriteFontEntry* dwriteLookup = static_cast<gfxDWriteFontEntry*>(lookup); + gfxDWriteFontEntry* fe = + new gfxDWriteFontEntry(lookup->Name(), dwriteLookup->mFont, + aWeightForEntry, aStretchForEntry, aStyleForEntry); + fe->SetForceGDIClassic(dwriteLookup->GetForceGDIClassic()); + return fe; +} + +gfxFontEntry* gfxDWriteFontList::MakePlatformFont( + const nsACString& aFontName, WeightRange aWeightForEntry, + StretchRange aStretchForEntry, SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, uint32_t aLength) { + RefPtr<IDWriteFontFileStream> fontFileStream; + RefPtr<IDWriteFontFile> fontFile; + HRESULT hr = gfxDWriteFontFileLoader::CreateCustomFontFile( + aFontData, aLength, getter_AddRefs(fontFile), + getter_AddRefs(fontFileStream)); + free((void*)aFontData); + NS_ASSERTION(SUCCEEDED(hr), "Failed to create font file reference"); + if (FAILED(hr)) { + return nullptr; + } + + nsAutoString uniqueName; + nsresult rv = gfxFontUtils::MakeUniqueUserFontName(uniqueName); + NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to make unique user font name"); + if (NS_FAILED(rv)) { + return nullptr; + } + + BOOL isSupported; + DWRITE_FONT_FILE_TYPE fileType; + UINT32 numFaces; + + auto entry = MakeUnique<gfxDWriteFontEntry>( + NS_ConvertUTF16toUTF8(uniqueName), fontFile, fontFileStream, + aWeightForEntry, aStretchForEntry, aStyleForEntry); + + hr = fontFile->Analyze(&isSupported, &fileType, &entry->mFaceType, &numFaces); + NS_ASSERTION(SUCCEEDED(hr), "IDWriteFontFile::Analyze failed"); + if (FAILED(hr)) { + return nullptr; + } + NS_ASSERTION(isSupported, "Unsupported font file"); + if (!isSupported) { + return nullptr; + } + NS_ASSERTION(numFaces == 1, "Font file does not contain exactly 1 face"); + if (numFaces != 1) { + // We don't know how to deal with 0 faces either. + return nullptr; + } + + return entry.release(); +} + +static void GetPostScriptNameFromNameTable(IDWriteFontFace* aFace, + nsCString& aName) { + const auto kNAME = + NativeEndian::swapToBigEndian(TRUETYPE_TAG('n', 'a', 'm', 'e')); + const char* data; + UINT32 size; + void* context; + BOOL exists; + if (SUCCEEDED(aFace->TryGetFontTable(kNAME, (const void**)&data, &size, + &context, &exists)) && + exists) { + if (NS_FAILED(gfxFontUtils::ReadCanonicalName( + data, size, gfxFontUtils::NAME_ID_POSTSCRIPT, aName))) { + aName.Truncate(0); + } + aFace->ReleaseFontTable(context); + } +} + +gfxFontEntry* gfxDWriteFontList::CreateFontEntry( + fontlist::Face* aFace, const fontlist::Family* aFamily) { + IDWriteFontCollection* collection = +#ifdef MOZ_BUNDLED_FONTS + aFamily->IsBundled() ? mBundledFonts : mSystemFonts; +#else + mSystemFonts; +#endif + RefPtr<IDWriteFontFamily> family; + bool foundExpectedFamily = false; + const nsCString& familyName = + aFamily->DisplayName().AsString(SharedFontList()); + if (aFamily->Index() < collection->GetFontFamilyCount()) { + HRESULT hr = + collection->GetFontFamily(aFamily->Index(), getter_AddRefs(family)); + // Check that the family name is what we expected; if not, fall back to + // search by name. It's sad we have to do this, but it is possible for + // Windows to have given different versions of the system font collection + // to the parent and child processes. + if (SUCCEEDED(hr) && family) { + RefPtr<IDWriteLocalizedStrings> names; + hr = family->GetFamilyNames(getter_AddRefs(names)); + if (SUCCEEDED(hr) && names) { + nsAutoCString name; + if (GetEnglishOrFirstName(name, names)) { + foundExpectedFamily = name.Equals(familyName); + } + } + } + } + if (!foundExpectedFamily) { + // Try to get family by name instead of index (to deal with the case of + // collection mismatch). + UINT32 index; + BOOL exists; + NS_ConvertUTF8toUTF16 name16(familyName); + HRESULT hr = collection->FindFamilyName( + reinterpret_cast<const WCHAR*>(name16.BeginReading()), &index, &exists); + if (FAILED(hr) || !exists || index == UINT_MAX || + FAILED(collection->GetFontFamily(index, getter_AddRefs(family))) || + !family) { + return nullptr; + } + } + + // Retrieve the required face by index within the family. + RefPtr<IDWriteFont> font; + if (FAILED(family->GetFont(aFace->mIndex, getter_AddRefs(font))) || !font) { + return nullptr; + } + + // Retrieve the psName from the font, so we can check we've found the + // expected face. + nsAutoCString psName; + if (FAILED(GetDirectWriteFaceName(font, PSNAME_ID, psName))) { + RefPtr<IDWriteFontFace> dwFontFace; + if (SUCCEEDED(font->CreateFontFace(getter_AddRefs(dwFontFace)))) { + GetPostScriptNameFromNameTable(dwFontFace, psName); + } + } + + // If it doesn't match, DirectWrite must have shuffled the order of faces + // returned for the family; search by name as a fallback. + nsCString faceName = aFace->mDescriptor.AsString(SharedFontList()); + if (psName != faceName) { + gfxWarning() << "Face name mismatch for index " << aFace->mIndex + << " in family " << familyName.get() << ": expected " + << faceName.get() << ", found " << psName.get(); + for (uint32_t i = 0; i < family->GetFontCount(); ++i) { + if (i == aFace->mIndex) { + continue; // this was the face we already tried + } + if (FAILED(family->GetFont(i, getter_AddRefs(font))) || !font) { + return nullptr; // this font family is broken! + } + if (FAILED(GetDirectWriteFaceName(font, PSNAME_ID, psName))) { + RefPtr<IDWriteFontFace> dwFontFace; + if (SUCCEEDED(font->CreateFontFace(getter_AddRefs(dwFontFace)))) { + GetPostScriptNameFromNameTable(dwFontFace, psName); + } + } + if (psName == faceName) { + break; + } + } + } + if (psName != faceName) { + return nullptr; + } + + auto fe = new gfxDWriteFontEntry(faceName, font, !aFamily->IsBundled()); + fe->InitializeFrom(aFace, aFamily); + fe->mForceGDIClassic = aFamily->IsForceClassic(); + fe->mMayUseGDIAccess = aFamily->IsSimple(); + return fe; +} + +FontVisibility gfxDWriteFontList::GetVisibilityForFamily( + const nsACString& aName) const { + if (FamilyInList(aName, kBaseFonts, ArrayLength(kBaseFonts))) { + return FontVisibility::Base; + } + if (FamilyInList(aName, kLangPackFonts, ArrayLength(kLangPackFonts))) { + return FontVisibility::LangPack; + } + return FontVisibility::User; +} + +void gfxDWriteFontList::AppendFamiliesFromCollection( + IDWriteFontCollection* aCollection, + nsTArray<fontlist::Family::InitData>& aFamilies, + const nsTArray<nsCString>* aForceClassicFams) { + auto allFacesUltraBold = [](IDWriteFontFamily* aFamily) -> bool { + for (UINT32 i = 0; i < aFamily->GetFontCount(); i++) { + RefPtr<IDWriteFont> font; + HRESULT hr = aFamily->GetFont(i, getter_AddRefs(font)); + if (FAILED(hr)) { + NS_WARNING("Failed to get existing font from family."); + continue; + } + nsAutoCString faceName; + hr = GetDirectWriteFontName(font, faceName); + if (FAILED(hr)) { + continue; + } + if (faceName.Find("Ultra Bold"_ns) == kNotFound) { + return false; + } + } + return true; + }; + + nsAutoCString locale; + OSPreferences::GetInstance()->GetSystemLocale(locale); + ToLowerCase(locale); + NS_ConvertUTF8toUTF16 loc16(locale); + + for (unsigned i = 0; i < aCollection->GetFontFamilyCount(); ++i) { + RefPtr<IDWriteFontFamily> family; + aCollection->GetFontFamily(i, getter_AddRefs(family)); + RefPtr<IDWriteLocalizedStrings> localizedNames; + HRESULT hr = family->GetFamilyNames(getter_AddRefs(localizedNames)); + if (FAILED(hr)) { + continue; + } + + auto addFamily = [&](const nsACString& name, bool altLocale = false) { + nsAutoCString key; + key = name; + BuildKeyNameFromFontName(key); + bool bad = mBadUnderlineFamilyNames.ContainsSorted(key); + bool classic = + aForceClassicFams && aForceClassicFams->ContainsSorted(key); + FontVisibility visibility; + // Special case: hide the "Gill Sans" family that contains only UltraBold + // faces, as this leads to breakage on sites with CSS that targeted the + // Gill Sans family as found on macOS. (Bug 551313, bug 1632738) + // TODO (jfkthame): the ultrabold faces from Gill Sans should be treated + // as belonging to the Gill Sans MT family. + if (key.EqualsLiteral("gill sans") && allFacesUltraBold(family)) { + visibility = FontVisibility::Hidden; + } else { + visibility = aCollection == mSystemFonts ? GetVisibilityForFamily(name) + : FontVisibility::Base; + } + aFamilies.AppendElement(fontlist::Family::InitData( + key, name, i, visibility, aCollection != mSystemFonts, bad, classic, + altLocale)); + }; + + unsigned count = localizedNames->GetCount(); + if (count == 1) { + // This is the common case: the great majority of fonts only provide an + // en-US family name. + nsAutoCString name; + if (!GetNameAsUtf8(name, localizedNames, 0)) { + continue; + } + addFamily(name); + } else { + AutoTArray<nsCString, 4> names; + int sysLocIndex = -1; + for (unsigned index = 0; index < count; ++index) { + nsAutoCString name; + if (!GetNameAsUtf8(name, localizedNames, index)) { + continue; + } + if (!names.Contains(name)) { + if (sysLocIndex == -1) { + WCHAR buf[32]; + if (FAILED(localizedNames->GetLocaleName(index, buf, 32))) { + continue; + } + if (loc16.Equals(buf)) { + sysLocIndex = names.Length(); + } + } + names.AppendElement(name); + } + } + // If we didn't find a name that matched the system locale, use the + // first (which is most often en-US). + if (sysLocIndex == -1) { + sysLocIndex = 0; + } + // Hack to work around EPSON fonts with bad names (tagged as MacRoman + // but actually contain MacJapanese data): if we've chosen the first + // name, *and* it is non-ASCII, *and* there is an alternative present, + // use the next option instead as being more likely to be valid. + if (sysLocIndex == 0 && names.Length() > 1 && !IsAscii(names[0])) { + sysLocIndex = 1; + } + for (unsigned index = 0; index < names.Length(); ++index) { + addFamily(names[index], index != sysLocIndex); + } + } + } +} + +void gfxDWriteFontList::GetFacesInitDataForFamily( + const fontlist::Family* aFamily, nsTArray<fontlist::Face::InitData>& aFaces, + bool aLoadCmaps) const { + IDWriteFontCollection* collection = +#ifdef MOZ_BUNDLED_FONTS + aFamily->IsBundled() ? mBundledFonts : mSystemFonts; +#else + mSystemFonts; +#endif + if (!collection) { + return; + } + RefPtr<IDWriteFontFamily> family; + collection->GetFontFamily(aFamily->Index(), getter_AddRefs(family)); + for (unsigned i = 0; i < family->GetFontCount(); ++i) { + RefPtr<IDWriteFont> dwFont; + family->GetFont(i, getter_AddRefs(dwFont)); + if (!dwFont || dwFont->GetSimulations() != DWRITE_FONT_SIMULATIONS_NONE) { + continue; + } + DWRITE_FONT_STYLE dwstyle = dwFont->GetStyle(); + // Ignore italic styles of Meiryo because "Meiryo (Bold) Italic" has + // non-italic style glyphs as Japanese characters. However, using it + // causes serious problem if web pages wants some elements to be + // different style from others only with font-style. For example, + // <em> and <i> should be rendered as italic in the default style. + if (dwstyle != DWRITE_FONT_STYLE_NORMAL && + aFamily->Key().AsString(SharedFontList()).EqualsLiteral("meiryo")) { + continue; + } + WeightRange weight(FontWeight(dwFont->GetWeight())); + StretchRange stretch(FontStretchFromDWriteStretch(dwFont->GetStretch())); + // Try to read PSName as a unique face identifier; if this fails we'll get + // it directly from the 'name' table, and if that also fails we consider + // the face unusable. + MOZ_SEH_TRY { + nsAutoCString name; + RefPtr<gfxCharacterMap> charmap; + if (FAILED(GetDirectWriteFaceName(dwFont, PSNAME_ID, name)) || + aLoadCmaps) { + RefPtr<IDWriteFontFace> dwFontFace; + if (SUCCEEDED(dwFont->CreateFontFace(getter_AddRefs(dwFontFace)))) { + if (name.IsEmpty()) { + GetPostScriptNameFromNameTable(dwFontFace, name); + } + const auto kCMAP = + NativeEndian::swapToBigEndian(TRUETYPE_TAG('c', 'm', 'a', 'p')); + const char* data; + UINT32 size; + void* context; + BOOL exists; + if (aLoadCmaps) { + if (SUCCEEDED(dwFontFace->TryGetFontTable( + kCMAP, (const void**)&data, &size, &context, &exists)) && + exists) { + charmap = new gfxCharacterMap(); + uint32_t offset; + gfxFontUtils::ReadCMAP((const uint8_t*)data, size, *charmap, + offset); + dwFontFace->ReleaseFontTable(context); + } + } + } + } + if (name.IsEmpty()) { + gfxWarning() << "Failed to get name for face " << i << " in family " + << aFamily->Key().AsString(SharedFontList()).get(); + continue; + } + SlantStyleRange slant( + dwstyle == DWRITE_FONT_STYLE_NORMAL ? FontSlantStyle::Normal() + : dwstyle == DWRITE_FONT_STYLE_ITALIC ? FontSlantStyle::Italic() + : FontSlantStyle::Oblique()); + aFaces.AppendElement(fontlist::Face::InitData{ + name, uint16_t(i), false, weight, stretch, slant, charmap}); + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + // Exception (e.g. disk i/o error) occurred when DirectWrite tried to use + // the font resource. We'll just skip the bad face. + gfxCriticalNote << "Exception occurred reading faces for " + << aFamily->Key().AsString(SharedFontList()).get(); + } + } +} + +bool gfxDWriteFontList::ReadFaceNames(fontlist::Family* aFamily, + fontlist::Face* aFace, nsCString& aPSName, + nsCString& aFullName) { + IDWriteFontCollection* collection = +#ifdef MOZ_BUNDLED_FONTS + aFamily->IsBundled() ? mBundledFonts : mSystemFonts; +#else + mSystemFonts; +#endif + RefPtr<IDWriteFontFamily> family; + if (FAILED(collection->GetFontFamily(aFamily->Index(), + getter_AddRefs(family)))) { + MOZ_ASSERT_UNREACHABLE("failed to get font-family"); + return false; + } + RefPtr<IDWriteFont> dwFont; + if (FAILED(family->GetFont(aFace->mIndex, getter_AddRefs(dwFont)))) { + MOZ_ASSERT_UNREACHABLE("failed to get font from family"); + return false; + } + HRESULT ps = GetDirectWriteFaceName(dwFont, PSNAME_ID, aPSName); + HRESULT full = GetDirectWriteFaceName(dwFont, FULLNAME_ID, aFullName); + if (FAILED(ps) || FAILED(full) || aPSName.IsEmpty() || aFullName.IsEmpty()) { + // We'll return true if either name was found, false if both fail. + // Note that on older Win7 systems, GetDirectWriteFaceName may "succeed" + // but return an empty string, so we have to check for non-empty strings + // to be sure we actually got a usable name. + + // Initialize result to true if either name was already found. + bool result = (SUCCEEDED(ps) && !aPSName.IsEmpty()) || + (SUCCEEDED(full) && !aFullName.IsEmpty()); + RefPtr<IDWriteFontFace> dwFontFace; + if (FAILED(dwFont->CreateFontFace(getter_AddRefs(dwFontFace)))) { + NS_WARNING("failed to create font face"); + return result; + } + void* context; + const char* data; + UINT32 size; + BOOL exists; + if (FAILED(dwFontFace->TryGetFontTable( + NativeEndian::swapToBigEndian(TRUETYPE_TAG('n', 'a', 'm', 'e')), + (const void**)&data, &size, &context, &exists)) || + !exists) { + NS_WARNING("failed to get name table"); + return result; + } + MOZ_SEH_TRY { + // Try to read the name table entries, and ensure result is true if either + // one succeeds. + if (FAILED(ps) || aPSName.IsEmpty()) { + if (NS_SUCCEEDED(gfxFontUtils::ReadCanonicalName( + data, size, gfxFontUtils::NAME_ID_POSTSCRIPT, aPSName))) { + result = true; + } else { + NS_WARNING("failed to read psname"); + } + } + if (FAILED(full) || aFullName.IsEmpty()) { + if (NS_SUCCEEDED(gfxFontUtils::ReadCanonicalName( + data, size, gfxFontUtils::NAME_ID_FULL, aFullName))) { + result = true; + } else { + NS_WARNING("failed to read fullname"); + } + } + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + gfxCriticalNote << "Exception occurred reading face names for " + << aFamily->Key().AsString(SharedFontList()).get(); + } + if (dwFontFace && context) { + dwFontFace->ReleaseFontTable(context); + } + return result; + } + return true; +} + +void gfxDWriteFontList::ReadFaceNamesForFamily( + fontlist::Family* aFamily, bool aNeedFullnamePostscriptNames) { + if (!aFamily->IsInitialized()) { + if (!InitializeFamily(aFamily)) { + return; + } + } + IDWriteFontCollection* collection = +#ifdef MOZ_BUNDLED_FONTS + aFamily->IsBundled() ? mBundledFonts : mSystemFonts; +#else + mSystemFonts; +#endif + RefPtr<IDWriteFontFamily> family; + if (FAILED(collection->GetFontFamily(aFamily->Index(), + getter_AddRefs(family)))) { + return; + } + fontlist::FontList* list = SharedFontList(); + const fontlist::Pointer* facePtrs = aFamily->Faces(list); + nsAutoCString familyName(aFamily->DisplayName().AsString(list)); + nsAutoCString key(aFamily->Key().AsString(list)); + + // Read PS-names and fullnames of the faces, and any alternate family names + // (either localizations or legacy subfamily names) + for (unsigned i = 0; i < aFamily->NumFaces(); ++i) { + auto face = static_cast<fontlist::Face*>(facePtrs[i].ToPtr(list)); + if (!face) { + continue; + } + RefPtr<IDWriteFont> dwFont; + if (FAILED(family->GetFont(face->mIndex, getter_AddRefs(dwFont)))) { + continue; + } + RefPtr<IDWriteFontFace> dwFontFace; + if (FAILED(dwFont->CreateFontFace(getter_AddRefs(dwFontFace)))) { + continue; + } + + const char* data; + UINT32 size; + void* context; + BOOL exists; + if (FAILED(dwFontFace->TryGetFontTable( + NativeEndian::swapToBigEndian(TRUETYPE_TAG('n', 'a', 'm', 'e')), + (const void**)&data, &size, &context, &exists)) || + !exists) { + continue; + } + + MOZ_SEH_TRY { + AutoTArray<nsCString, 4> otherFamilyNames; + gfxFontUtils::ReadOtherFamilyNamesForFace(familyName, data, size, + otherFamilyNames, false); + for (const auto& alias : otherFamilyNames) { + nsAutoCString key(alias); + ToLowerCase(key); + auto aliasData = mAliasTable.LookupOrAdd(key); + aliasData->InitFromFamily(aFamily, familyName); + aliasData->mFaces.AppendElement(facePtrs[i]); + } + + nsAutoCString psname, fullname; + if (NS_SUCCEEDED(gfxFontUtils::ReadCanonicalName( + data, size, gfxFontUtils::NAME_ID_POSTSCRIPT, psname))) { + ToLowerCase(psname); + mLocalNameTable.Put(psname, fontlist::LocalFaceRec::InitData(key, i)); + } + if (NS_SUCCEEDED(gfxFontUtils::ReadCanonicalName( + data, size, gfxFontUtils::NAME_ID_FULL, fullname))) { + ToLowerCase(fullname); + if (fullname != psname) { + mLocalNameTable.Put(fullname, + fontlist::LocalFaceRec::InitData(key, i)); + } + } + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + gfxCriticalNote << "Exception occurred reading names for " + << familyName.get(); + } + + dwFontFace->ReleaseFontTable(context); + } +} + +enum DWriteInitError { + errGDIInterop = 1, + errSystemFontCollection = 2, + errNoFonts = 3 +}; + +void gfxDWriteFontList::InitSharedFontListForPlatform() { + mGDIFontTableAccess = Preferences::GetBool( + "gfx.font_rendering.directwrite.use_gdi_table_loading", false); + mForceGDIClassicMaxFontSize = Preferences::GetInt( + "gfx.font_rendering.cleartype_params.force_gdi_classic_max_size", + mForceGDIClassicMaxFontSize); + + mFontSubstitutes.Clear(); + mNonExistingFonts.Clear(); + + RefPtr<IDWriteFactory> factory = Factory::GetDWriteFactory(); + HRESULT hr = factory->GetGdiInterop(getter_AddRefs(mGDIInterop)); + if (FAILED(hr)) { + Telemetry::Accumulate(Telemetry::DWRITEFONT_INIT_PROBLEM, + uint32_t(errGDIInterop)); + mSharedFontList.reset(nullptr); + return; + } + + mSystemFonts = Factory::GetDWriteSystemFonts(true); + NS_ASSERTION(mSystemFonts != nullptr, "GetSystemFontCollection failed!"); + if (!mSystemFonts) { + Telemetry::Accumulate(Telemetry::DWRITEFONT_INIT_PROBLEM, + uint32_t(errSystemFontCollection)); + mSharedFontList.reset(nullptr); + return; + } +#ifdef MOZ_BUNDLED_FONTS + // If the bundled-fonts pref is < 0 (auto), we skip the bundled fonts on + // Windows 8.1 or later, where Segoe UI Emoji is available. + if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() > 0 || + (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() < 0 && + !IsWin8Point1OrLater())) { + mBundledFonts = CreateBundledFontsCollection(factory); + } +#endif + + if (XRE_IsParentProcess()) { + nsAutoCString classicFamilies; + AutoTArray<nsCString, 16> forceClassicFams; + nsresult rv = Preferences::GetCString( + "gfx.font_rendering.cleartype_params.force_gdi_classic_for_families", + classicFamilies); + if (NS_SUCCEEDED(rv)) { + for (auto name : + nsCCharSeparatedTokenizer(classicFamilies, ',').ToRange()) { + BuildKeyNameFromFontName(name); + forceClassicFams.AppendElement(name); + } + forceClassicFams.Sort(); + } + nsTArray<fontlist::Family::InitData> families; + AppendFamiliesFromCollection(mSystemFonts, families, &forceClassicFams); +#ifdef MOZ_BUNDLED_FONTS + if (mBundledFonts) { + AppendFamiliesFromCollection(mBundledFonts, families); + } +#endif + SharedFontList()->SetFamilyNames(families); + GetPrefsAndStartLoader(); + } + + if (!SharedFontList()->Initialized()) { + return; + } + + GetDirectWriteSubstitutes(); + GetFontSubstitutes(); +} + +nsresult gfxDWriteFontList::InitFontListForPlatform() { + LARGE_INTEGER frequency; // ticks per second + LARGE_INTEGER t1, t2, t3, t4, t5; // ticks + double elapsedTime, upTime; + char nowTime[256], nowDate[256]; + + if (LOG_FONTINIT_ENABLED()) { + GetTimeFormatA(LOCALE_INVARIANT, TIME_FORCE24HOURFORMAT, nullptr, nullptr, + nowTime, 256); + GetDateFormatA(LOCALE_INVARIANT, 0, nullptr, nullptr, nowDate, 256); + upTime = (double)GetTickCount(); + } + QueryPerformanceFrequency(&frequency); + QueryPerformanceCounter(&t1); // start + + HRESULT hr; + mGDIFontTableAccess = Preferences::GetBool( + "gfx.font_rendering.directwrite.use_gdi_table_loading", false); + + mFontSubstitutes.Clear(); + mNonExistingFonts.Clear(); + + RefPtr<IDWriteFactory> factory = Factory::GetDWriteFactory(); + + hr = factory->GetGdiInterop(getter_AddRefs(mGDIInterop)); + if (FAILED(hr)) { + Telemetry::Accumulate(Telemetry::DWRITEFONT_INIT_PROBLEM, + uint32_t(errGDIInterop)); + return NS_ERROR_FAILURE; + } + + QueryPerformanceCounter(&t2); // base-class/interop initialization + + mSystemFonts = Factory::GetDWriteSystemFonts(true); + NS_ASSERTION(mSystemFonts != nullptr, "GetSystemFontCollection failed!"); + + if (!mSystemFonts) { + Telemetry::Accumulate(Telemetry::DWRITEFONT_INIT_PROBLEM, + uint32_t(errSystemFontCollection)); + return NS_ERROR_FAILURE; + } + +#ifdef MOZ_BUNDLED_FONTS + // Get bundled fonts before the system collection, so that in the case of + // duplicate names, we have recorded the family as bundled (and therefore + // available regardless of visibility settings). + // If the bundled-fonts pref is < 0 (auto), we skip the bundled fonts on + // Windows 8.1 or later, where Segoe UI Emoji is available. + if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() > 0 || + (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() < 0 && + !IsWin8Point1OrLater())) { + mBundledFonts = CreateBundledFontsCollection(factory); + } + if (mBundledFonts) { + GetFontsFromCollection(mBundledFonts); + } +#endif + const uint32_t kBundledCount = mFontFamilies.Count(); + + QueryPerformanceCounter(&t3); // system font collection + + GetFontsFromCollection(mSystemFonts); + + // if no fonts found, something is out of whack, bail and use GDI backend + NS_ASSERTION(mFontFamilies.Count() > kBundledCount, + "no fonts found in the system fontlist -- holy crap batman!"); + if (mFontFamilies.Count() == kBundledCount) { + Telemetry::Accumulate(Telemetry::DWRITEFONT_INIT_PROBLEM, + uint32_t(errNoFonts)); + return NS_ERROR_FAILURE; + } + + QueryPerformanceCounter(&t4); // iterate over system fonts + + mOtherFamilyNamesInitialized = true; + GetFontSubstitutes(); + + // bug 642093 - DirectWrite does not support old bitmap (.fon) + // font files, but a few of these such as "Courier" and "MS Sans Serif" + // are frequently specified in shoddy CSS, without appropriate fallbacks. + // By mapping these to TrueType equivalents, we provide better consistency + // with both pre-DW systems and with IE9, which appears to do the same. + GetDirectWriteSubstitutes(); + + // bug 551313 - DirectWrite creates a Gill Sans family out of + // poorly named members of the Gill Sans MT family containing + // only Ultra Bold weights. This causes big problems for pages + // using Gill Sans which is usually only available on OSX + + nsAutoCString nameGillSans("Gill Sans"); + nsAutoCString nameGillSansMT("Gill Sans MT"); + BuildKeyNameFromFontName(nameGillSans); + BuildKeyNameFromFontName(nameGillSansMT); + + gfxFontFamily* gillSansFamily = mFontFamilies.GetWeak(nameGillSans); + gfxFontFamily* gillSansMTFamily = mFontFamilies.GetWeak(nameGillSansMT); + + if (gillSansFamily && gillSansMTFamily) { + gillSansFamily->FindStyleVariations(); + nsTArray<RefPtr<gfxFontEntry> >& faces = gillSansFamily->GetFontList(); + uint32_t i; + + bool allUltraBold = true; + for (i = 0; i < faces.Length(); i++) { + // does the face have 'Ultra Bold' in the name? + if (faces[i]->Name().Find("Ultra Bold"_ns) == -1) { + allUltraBold = false; + break; + } + } + + // if all the Gill Sans faces are Ultra Bold ==> move faces + // for Gill Sans into Gill Sans MT family + if (allUltraBold) { + // add faces to Gill Sans MT + for (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]; + nsAutoCString weightString; + fe->Weight().ToString(weightString); + LOG_FONTLIST( + ("(fontlist) moved (%s) to family (%s)" + " with style: %s weight: %s stretch: %d", + fe->Name().get(), gillSansMTFamily->Name().get(), + (fe->IsItalic()) ? "italic" + : (fe->IsOblique() ? "oblique" : "normal"), + weightString.get(), fe->Stretch())); + } + } + + // remove Gills Sans + mFontFamilies.Remove(nameGillSans); + } + } + + nsAutoCString classicFamilies; + nsresult rv = Preferences::GetCString( + "gfx.font_rendering.cleartype_params.force_gdi_classic_for_families", + classicFamilies); + if (NS_SUCCEEDED(rv)) { + nsCCharSeparatedTokenizer tokenizer(classicFamilies, ','); + while (tokenizer.hasMoreTokens()) { + nsAutoCString name(tokenizer.nextToken()); + BuildKeyNameFromFontName(name); + gfxFontFamily* family = mFontFamilies.GetWeak(name); + if (family) { + static_cast<gfxDWriteFontFamily*>(family)->SetForceGDIClassic(true); + } + } + } + mForceGDIClassicMaxFontSize = Preferences::GetInt( + "gfx.font_rendering.cleartype_params.force_gdi_classic_max_size", + mForceGDIClassicMaxFontSize); + + GetPrefsAndStartLoader(); + + QueryPerformanceCounter(&t5); // misc initialization + + if (LOG_FONTINIT_ENABLED()) { + // determine dwrite version + nsAutoString dwriteVers; + gfxWindowsPlatform::GetDLLVersion(L"dwrite.dll", dwriteVers); + LOG_FONTINIT(("(fontinit) Start: %s %s\n", nowDate, nowTime)); + LOG_FONTINIT(("(fontinit) Uptime: %9.3f s\n", upTime / 1000)); + LOG_FONTINIT(("(fontinit) dwrite version: %s\n", + NS_ConvertUTF16toUTF8(dwriteVers).get())); + } + + elapsedTime = (t5.QuadPart - t1.QuadPart) * 1000.0 / frequency.QuadPart; + Telemetry::Accumulate(Telemetry::DWRITEFONT_DELAYEDINITFONTLIST_TOTAL, + elapsedTime); + Telemetry::Accumulate(Telemetry::DWRITEFONT_DELAYEDINITFONTLIST_COUNT, + mSystemFonts->GetFontFamilyCount()); + LOG_FONTINIT(( + "(fontinit) Total time in InitFontList: %9.3f ms (families: %d, %s)\n", + elapsedTime, mSystemFonts->GetFontFamilyCount(), + (mGDIFontTableAccess ? "gdi table access" : "dwrite table access"))); + + elapsedTime = (t2.QuadPart - t1.QuadPart) * 1000.0 / frequency.QuadPart; + LOG_FONTINIT( + ("(fontinit) --- base/interop obj initialization init: %9.3f ms\n", + elapsedTime)); + + elapsedTime = (t3.QuadPart - t2.QuadPart) * 1000.0 / frequency.QuadPart; + Telemetry::Accumulate(Telemetry::DWRITEFONT_DELAYEDINITFONTLIST_COLLECT, + elapsedTime); + LOG_FONTINIT( + ("(fontinit) --- GetSystemFontCollection: %9.3f ms\n", elapsedTime)); + + elapsedTime = (t4.QuadPart - t3.QuadPart) * 1000.0 / frequency.QuadPart; + LOG_FONTINIT( + ("(fontinit) --- iterate over families: %9.3f ms\n", elapsedTime)); + + elapsedTime = (t5.QuadPart - t4.QuadPart) * 1000.0 / frequency.QuadPart; + LOG_FONTINIT( + ("(fontinit) --- misc initialization: %9.3f ms\n", elapsedTime)); + + return NS_OK; +} + +void gfxDWriteFontList::GetFontsFromCollection( + IDWriteFontCollection* aCollection) { + for (UINT32 i = 0; i < aCollection->GetFontFamilyCount(); i++) { + RefPtr<IDWriteFontFamily> family; + aCollection->GetFontFamily(i, getter_AddRefs(family)); + + RefPtr<IDWriteLocalizedStrings> names; + HRESULT hr = family->GetFamilyNames(getter_AddRefs(names)); + if (FAILED(hr)) { + continue; + } + + nsAutoCString name; + if (!GetEnglishOrFirstName(name, names)) { + continue; + } + nsAutoCString familyName( + name); // keep a copy before we lowercase it as a key + + BuildKeyNameFromFontName(name); + + RefPtr<gfxFontFamily> fam; + + if (mFontFamilies.GetWeak(name)) { + continue; + } + + FontVisibility visibility = aCollection == mSystemFonts + ? GetVisibilityForFamily(familyName) + : FontVisibility::Base; + + fam = new gfxDWriteFontFamily(familyName, visibility, family, + aCollection == mSystemFonts); + if (!fam) { + continue; + } + + if (mBadUnderlineFamilyNames.ContainsSorted(name)) { + fam->SetBadUnderlineFamily(); + } + mFontFamilies.Put(name, RefPtr{fam}); + + // now add other family name localizations, if present + uint32_t nameCount = names->GetCount(); + uint32_t nameIndex; + + if (nameCount > 1) { + UINT32 englishIdx = 0; + BOOL exists; + // if this fails/doesn't exist, we'll have used name index 0, + // so that's the one we'll want to skip here + names->FindLocaleName(L"en-us", &englishIdx, &exists); + + for (nameIndex = 0; nameIndex < nameCount; nameIndex++) { + UINT32 nameLen; + AutoTArray<WCHAR, 32> localizedName; + + // only add other names + if (nameIndex == englishIdx) { + continue; + } + + hr = names->GetStringLength(nameIndex, &nameLen); + if (FAILED(hr)) { + continue; + } + + if (!localizedName.SetLength(nameLen + 1, fallible)) { + continue; + } + + hr = names->GetString(nameIndex, localizedName.Elements(), nameLen + 1); + if (FAILED(hr)) { + continue; + } + + NS_ConvertUTF16toUTF8 locName(localizedName.Elements()); + + if (!familyName.Equals(locName)) { + AddOtherFamilyName(fam, locName); + } + } + } + + // at this point, all family names have been read in + fam->SetOtherFamilyNamesInitialized(); + } +} + +static void RemoveCharsetFromFontSubstitute(nsACString& aName) { + int32_t comma = aName.FindChar(','); + if (comma >= 0) aName.Truncate(comma); +} + +#define MAX_VALUE_NAME 512 +#define MAX_VALUE_DATA 512 + +nsresult gfxDWriteFontList::GetFontSubstitutes() { + HKEY hKey; + DWORD i, rv, lenAlias, lenActual, valueType; + WCHAR aliasName[MAX_VALUE_NAME]; + WCHAR actualName[MAX_VALUE_DATA]; + + if (RegOpenKeyExW( + HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\FontSubstitutes", + 0, KEY_READ, &hKey) != ERROR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + for (i = 0, rv = ERROR_SUCCESS; rv != ERROR_NO_MORE_ITEMS; i++) { + aliasName[0] = 0; + lenAlias = ArrayLength(aliasName); + actualName[0] = 0; + lenActual = sizeof(actualName); + rv = RegEnumValueW(hKey, i, aliasName, &lenAlias, nullptr, &valueType, + (LPBYTE)actualName, &lenActual); + + if (rv != ERROR_SUCCESS || valueType != REG_SZ || lenAlias == 0) { + continue; + } + + if (aliasName[0] == WCHAR('@')) { + continue; + } + + NS_ConvertUTF16toUTF8 substituteName((char16_t*)aliasName); + NS_ConvertUTF16toUTF8 actualFontName((char16_t*)actualName); + RemoveCharsetFromFontSubstitute(substituteName); + BuildKeyNameFromFontName(substituteName); + RemoveCharsetFromFontSubstitute(actualFontName); + BuildKeyNameFromFontName(actualFontName); + if (SharedFontList()) { + // Font substitutions are recorded for the canonical family names; we + // don't need FindFamily to consider localized aliases when searching. + if (SharedFontList()->FindFamily(substituteName, + /*aPrimaryNameOnly*/ true)) { + continue; + } + if (SharedFontList()->FindFamily(actualFontName, + /*aPrimaryNameOnly*/ true)) { + mSubstitutions.Put(substituteName, new nsCString(actualFontName)); + } else if (mSubstitutions.Get(actualFontName)) { + mSubstitutions.Put(substituteName, + new nsCString(*mSubstitutions.Get(actualFontName))); + } else { + mNonExistingFonts.AppendElement(substituteName); + } + } else { + gfxFontFamily* ff; + if (!actualFontName.IsEmpty() && + (ff = mFontFamilies.GetWeak(actualFontName))) { + mFontSubstitutes.Put(substituteName, RefPtr{ff}); + } else { + mNonExistingFonts.AppendElement(substituteName); + } + } + } + return NS_OK; +} + +struct FontSubstitution { + const char* aliasName; + const char* actualName; +}; + +static const FontSubstitution sDirectWriteSubs[] = { + {"MS Sans Serif", "Microsoft Sans Serif"}, + {"MS Serif", "Times New Roman"}, + {"Courier", "Courier New"}, + {"Small Fonts", "Arial"}, + {"Roman", "Times New Roman"}, + {"Script", "Mistral"}}; + +void gfxDWriteFontList::GetDirectWriteSubstitutes() { + for (uint32_t i = 0; i < ArrayLength(sDirectWriteSubs); ++i) { + const FontSubstitution& sub(sDirectWriteSubs[i]); + nsAutoCString substituteName(sub.aliasName); + BuildKeyNameFromFontName(substituteName); + if (SharedFontList()) { + // We don't need FindFamily to consider localized aliases when searching + // for the DirectWrite substitutes, we know the canonical names. + if (SharedFontList()->FindFamily(substituteName, + /*aPrimaryNameOnly*/ true)) { + continue; + } + nsAutoCString actualFontName(sub.actualName); + BuildKeyNameFromFontName(actualFontName); + if (SharedFontList()->FindFamily(actualFontName, + /*aPrimaryNameOnly*/ true)) { + mSubstitutions.Put(substituteName, new nsCString(actualFontName)); + } else { + mNonExistingFonts.AppendElement(substituteName); + } + } else { + if (nullptr != mFontFamilies.GetWeak(substituteName)) { + // don't do the substitution if user actually has a usable font + // with this name installed + continue; + } + nsAutoCString actualFontName(sub.actualName); + BuildKeyNameFromFontName(actualFontName); + gfxFontFamily* ff; + if (nullptr != (ff = mFontFamilies.GetWeak(actualFontName))) { + mFontSubstitutes.Put(substituteName, RefPtr{ff}); + } else { + mNonExistingFonts.AppendElement(substituteName); + } + } + } +} + +bool gfxDWriteFontList::FindAndAddFamilies( + StyleGenericFontFamily aGeneric, const nsACString& aFamily, + nsTArray<FamilyAndGeneric>* aOutput, FindFamiliesFlags aFlags, + gfxFontStyle* aStyle, nsAtom* aLanguage, gfxFloat aDevToCssSize) { + nsAutoCString keyName(aFamily); + BuildKeyNameFromFontName(keyName); + + if (SharedFontList()) { + nsACString* subst = mSubstitutions.Get(keyName); + if (subst) { + keyName = *subst; + } + } else { + gfxFontFamily* ff = mFontSubstitutes.GetWeak(keyName); + if (ff) { + aOutput->AppendElement(FamilyAndGeneric(ff, aGeneric)); + return true; + } + } + + if (mNonExistingFonts.Contains(keyName)) { + return false; + } + + return gfxPlatformFontList::FindAndAddFamilies( + aGeneric, keyName, aOutput, aFlags, aStyle, aLanguage, aDevToCssSize); +} + +void gfxDWriteFontList::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + gfxPlatformFontList::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); + + // We are a singleton, so include the font loader singleton's memory. + MOZ_ASSERT(static_cast<const gfxPlatformFontList*>(this) == + gfxPlatformFontList::PlatformFontList()); + gfxDWriteFontFileLoader* loader = static_cast<gfxDWriteFontFileLoader*>( + gfxDWriteFontFileLoader::Instance()); + aSizes->mLoaderSize += loader->SizeOfIncludingThis(aMallocSizeOf); + + aSizes->mFontListSize += + SizeOfFontFamilyTableExcludingThis(mFontSubstitutes, aMallocSizeOf); + + aSizes->mFontListSize += + mNonExistingFonts.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (uint32_t i = 0; i < mNonExistingFonts.Length(); ++i) { + aSizes->mFontListSize += + mNonExistingFonts[i].SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } +} + +void gfxDWriteFontList::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +static HRESULT GetFamilyName(IDWriteFont* aFont, nsCString& aFamilyName) { + HRESULT hr; + RefPtr<IDWriteFontFamily> family; + + // clean out previous value + aFamilyName.Truncate(); + + hr = aFont->GetFontFamily(getter_AddRefs(family)); + if (FAILED(hr)) { + return hr; + } + + RefPtr<IDWriteLocalizedStrings> familyNames; + + hr = family->GetFamilyNames(getter_AddRefs(familyNames)); + if (FAILED(hr)) { + return hr; + } + + if (!GetEnglishOrFirstName(aFamilyName, familyNames)) { + return E_FAIL; + } + + return S_OK; +} + +// bug 705594 - the method below doesn't actually do any "drawing", it's only +// used to invoke the DirectWrite layout engine to determine the fallback font +// for a given character. + +IFACEMETHODIMP DWriteFontFallbackRenderer::DrawGlyphRun( + void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, + DWRITE_MEASURING_MODE measuringMode, DWRITE_GLYPH_RUN const* glyphRun, + DWRITE_GLYPH_RUN_DESCRIPTION const* glyphRunDescription, + IUnknown* clientDrawingEffect) { + if (!mSystemFonts) { + return E_FAIL; + } + + HRESULT hr = S_OK; + + RefPtr<IDWriteFont> font; + hr = mSystemFonts->GetFontFromFontFace(glyphRun->fontFace, + getter_AddRefs(font)); + if (FAILED(hr)) { + return hr; + } + + // copy the family name + hr = GetFamilyName(font, mFamilyName); + if (FAILED(hr)) { + return hr; + } + + // Arial is used as the default fallback font + // so if it matches ==> no font found + if (mFamilyName.EqualsLiteral("Arial")) { + mFamilyName.Truncate(); + return E_FAIL; + } + return hr; +} + +gfxFontEntry* gfxDWriteFontList::PlatformGlobalFontFallback( + const uint32_t aCh, Script aRunScript, const gfxFontStyle* aMatchStyle, + FontFamily& aMatchedFamily) { + HRESULT hr; + + RefPtr<IDWriteFactory> dwFactory = Factory::GetDWriteFactory(); + if (!dwFactory) { + return nullptr; + } + + // initialize fallback renderer + if (!mFallbackRenderer) { + mFallbackRenderer = new DWriteFontFallbackRenderer(dwFactory); + } + + // initialize text format + if (!mFallbackFormat) { + hr = dwFactory->CreateTextFormat( + L"Arial", nullptr, DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL, + DWRITE_FONT_STRETCH_NORMAL, 72.0f, L"en-us", + getter_AddRefs(mFallbackFormat)); + if (FAILED(hr)) { + return nullptr; + } + } + + // set up string with fallback character + wchar_t str[16]; + uint32_t strLen; + + if (IS_IN_BMP(aCh)) { + str[0] = static_cast<wchar_t>(aCh); + str[1] = 0; + strLen = 1; + } else { + str[0] = static_cast<wchar_t>(H_SURROGATE(aCh)); + str[1] = static_cast<wchar_t>(L_SURROGATE(aCh)); + str[2] = 0; + strLen = 2; + } + + // set up layout + RefPtr<IDWriteTextLayout> fallbackLayout; + + hr = dwFactory->CreateTextLayout(str, strLen, mFallbackFormat, 200.0f, 200.0f, + getter_AddRefs(fallbackLayout)); + if (FAILED(hr)) { + return nullptr; + } + + // call the draw method to invoke the DirectWrite layout functions + // which determine the fallback font + hr = fallbackLayout->Draw(nullptr, mFallbackRenderer, 50.0f, 50.0f); + if (FAILED(hr)) { + return nullptr; + } + + FontFamily family = FindFamily(mFallbackRenderer->FallbackFamilyName()); + if (!family.IsNull()) { + gfxFontEntry* fontEntry = nullptr; + if (family.mIsShared) { + auto face = + family.mShared->FindFaceForStyle(SharedFontList(), *aMatchStyle); + if (face) { + fontEntry = GetOrCreateFontEntry(face, family.mShared); + } + } else { + fontEntry = family.mUnshared->FindFontForStyle(*aMatchStyle); + } + if (fontEntry && fontEntry->HasCharacter(aCh)) { + aMatchedFamily = family; + return fontEntry; + } + Telemetry::Accumulate(Telemetry::BAD_FALLBACK_FONT, true); + } + + return nullptr; +} + +// used to load system-wide font info on off-main thread +class DirectWriteFontInfo : public FontInfoData { + public: + DirectWriteFontInfo(bool aLoadOtherNames, bool aLoadFaceNames, + bool aLoadCmaps, IDWriteFontCollection* aSystemFonts +#ifdef MOZ_BUNDLED_FONTS + , + IDWriteFontCollection* aBundledFonts +#endif + ) + : FontInfoData(aLoadOtherNames, aLoadFaceNames, aLoadCmaps), + mSystemFonts(aSystemFonts) +#ifdef MOZ_BUNDLED_FONTS + , + mBundledFonts(aBundledFonts) +#endif + { + } + + virtual ~DirectWriteFontInfo() = default; + + // loads font data for all members of a given family + virtual void LoadFontFamilyData(const nsACString& aFamilyName); + + private: + RefPtr<IDWriteFontCollection> mSystemFonts; +#ifdef MOZ_BUNDLED_FONTS + RefPtr<IDWriteFontCollection> mBundledFonts; +#endif +}; + +void DirectWriteFontInfo::LoadFontFamilyData(const nsACString& aFamilyName) { + // lookup the family + NS_ConvertUTF8toUTF16 famName(aFamilyName); + + HRESULT hr; + BOOL exists = false; + + uint32_t index; + RefPtr<IDWriteFontFamily> family; + hr = mSystemFonts->FindFamilyName((const wchar_t*)famName.get(), &index, + &exists); + if (SUCCEEDED(hr) && exists) { + mSystemFonts->GetFontFamily(index, getter_AddRefs(family)); + if (!family) { + return; + } + } + +#ifdef MOZ_BUNDLED_FONTS + if (!family && mBundledFonts) { + hr = mBundledFonts->FindFamilyName((const wchar_t*)famName.get(), &index, + &exists); + if (SUCCEEDED(hr) && exists) { + mBundledFonts->GetFontFamily(index, getter_AddRefs(family)); + } + } +#endif + + if (!family) { + return; + } + + // later versions of DirectWrite support querying the fullname/psname + bool loadFaceNamesUsingDirectWrite = mLoadFaceNames; + + for (uint32_t i = 0; i < family->GetFontCount(); i++) { + // get the font + RefPtr<IDWriteFont> dwFont; + hr = family->GetFont(i, getter_AddRefs(dwFont)); + if (FAILED(hr)) { + // This should never happen. + NS_WARNING("Failed to get existing font from family."); + continue; + } + + if (dwFont->GetSimulations() != DWRITE_FONT_SIMULATIONS_NONE) { + // We don't want these in the font list; we'll apply simulations + // on the fly when appropriate. + continue; + } + + mLoadStats.fonts++; + + // get the name of the face + nsCString fullID(aFamilyName); + nsAutoCString fontName; + hr = GetDirectWriteFontName(dwFont, fontName); + if (FAILED(hr)) { + continue; + } + fullID.Append(' '); + fullID.Append(fontName); + + FontFaceData fontData; + bool haveData = true; + RefPtr<IDWriteFontFace> dwFontFace; + + if (mLoadFaceNames) { + // try to load using DirectWrite first + if (loadFaceNamesUsingDirectWrite) { + hr = + GetDirectWriteFaceName(dwFont, PSNAME_ID, fontData.mPostscriptName); + if (FAILED(hr)) { + loadFaceNamesUsingDirectWrite = false; + } + hr = GetDirectWriteFaceName(dwFont, FULLNAME_ID, fontData.mFullName); + if (FAILED(hr)) { + loadFaceNamesUsingDirectWrite = false; + } + } + + // if DirectWrite read fails, load directly from name table + if (!loadFaceNamesUsingDirectWrite) { + hr = dwFont->CreateFontFace(getter_AddRefs(dwFontFace)); + if (SUCCEEDED(hr)) { + uint32_t kNAME = + NativeEndian::swapToBigEndian(TRUETYPE_TAG('n', 'a', 'm', 'e')); + const char* nameData; + BOOL exists; + void* ctx; + uint32_t nameSize; + + hr = dwFontFace->TryGetFontTable(kNAME, (const void**)&nameData, + &nameSize, &ctx, &exists); + if (SUCCEEDED(hr) && nameData && nameSize > 0) { + MOZ_SEH_TRY { + gfxFontUtils::ReadCanonicalName(nameData, nameSize, + gfxFontUtils::NAME_ID_FULL, + fontData.mFullName); + gfxFontUtils::ReadCanonicalName(nameData, nameSize, + gfxFontUtils::NAME_ID_POSTSCRIPT, + fontData.mPostscriptName); + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + gfxCriticalNote << "Exception occurred reading names for " + << PromiseFlatCString(aFamilyName).get(); + } + dwFontFace->ReleaseFontTable(ctx); + } + } + } + + haveData = + !fontData.mPostscriptName.IsEmpty() || !fontData.mFullName.IsEmpty(); + if (haveData) { + mLoadStats.facenames++; + } + } + + // cmaps + if (mLoadCmaps) { + if (!dwFontFace) { + hr = dwFont->CreateFontFace(getter_AddRefs(dwFontFace)); + if (!SUCCEEDED(hr)) { + continue; + } + } + + uint32_t kCMAP = + NativeEndian::swapToBigEndian(TRUETYPE_TAG('c', 'm', 'a', 'p')); + const uint8_t* cmapData; + BOOL exists; + void* ctx; + uint32_t cmapSize; + + hr = dwFontFace->TryGetFontTable(kCMAP, (const void**)&cmapData, + &cmapSize, &ctx, &exists); + + if (SUCCEEDED(hr) && exists) { + bool cmapLoaded = false; + RefPtr<gfxCharacterMap> charmap = new gfxCharacterMap(); + uint32_t offset; + MOZ_SEH_TRY { + if (cmapData && cmapSize > 0 && + NS_SUCCEEDED(gfxFontUtils::ReadCMAP(cmapData, cmapSize, *charmap, + offset))) { + fontData.mCharacterMap = charmap; + fontData.mUVSOffset = offset; + cmapLoaded = true; + mLoadStats.cmaps++; + } + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + gfxCriticalNote << "Exception occurred reading cmaps for " + << PromiseFlatCString(aFamilyName).get(); + } + dwFontFace->ReleaseFontTable(ctx); + haveData = haveData || cmapLoaded; + } + } + + // if have data, load + if (haveData) { + mFontFaceData.Put(fullID, fontData); + } + } +} + +already_AddRefed<FontInfoData> gfxDWriteFontList::CreateFontInfoData() { + bool loadCmaps = !UsesSystemFallback() || + gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback(); + + RefPtr<DirectWriteFontInfo> fi = new DirectWriteFontInfo( + false, NeedFullnamePostscriptNames(), loadCmaps, mSystemFonts +#ifdef MOZ_BUNDLED_FONTS + , + mBundledFonts +#endif + ); + + return fi.forget(); +} + +gfxFontFamily* gfxDWriteFontList::CreateFontFamily( + const nsACString& aName, FontVisibility aVisibility) const { + return new gfxDWriteFontFamily(aName, aVisibility, nullptr); +} + +#ifdef MOZ_BUNDLED_FONTS + +# define IMPL_QI_FOR_DWRITE(_interface) \ + public: \ + IFACEMETHOD(QueryInterface)(IID const& riid, void** ppvObject) { \ + if (__uuidof(_interface) == riid) { \ + *ppvObject = this; \ + } else if (__uuidof(IUnknown) == riid) { \ + *ppvObject = this; \ + } else { \ + *ppvObject = nullptr; \ + return E_NOINTERFACE; \ + } \ + this->AddRef(); \ + return S_OK; \ + } + +class BundledFontFileEnumerator : public IDWriteFontFileEnumerator { + IMPL_QI_FOR_DWRITE(IDWriteFontFileEnumerator) + + NS_INLINE_DECL_REFCOUNTING(BundledFontFileEnumerator) + + public: + BundledFontFileEnumerator(IDWriteFactory* aFactory, nsIFile* aFontDir); + + IFACEMETHODIMP MoveNext(BOOL* hasCurrentFile); + + IFACEMETHODIMP GetCurrentFontFile(IDWriteFontFile** fontFile); + + private: + BundledFontFileEnumerator() = delete; + BundledFontFileEnumerator(const BundledFontFileEnumerator&) = delete; + BundledFontFileEnumerator& operator=(const BundledFontFileEnumerator&) = + delete; + virtual ~BundledFontFileEnumerator() = default; + + RefPtr<IDWriteFactory> mFactory; + + nsCOMPtr<nsIFile> mFontDir; + nsCOMPtr<nsIDirectoryEnumerator> mEntries; + nsCOMPtr<nsISupports> mCurrent; +}; + +BundledFontFileEnumerator::BundledFontFileEnumerator(IDWriteFactory* aFactory, + nsIFile* aFontDir) + : mFactory(aFactory), mFontDir(aFontDir) { + mFontDir->GetDirectoryEntries(getter_AddRefs(mEntries)); +} + +IFACEMETHODIMP +BundledFontFileEnumerator::MoveNext(BOOL* aHasCurrentFile) { + bool hasMore = false; + if (mEntries) { + if (NS_SUCCEEDED(mEntries->HasMoreElements(&hasMore)) && hasMore) { + if (NS_SUCCEEDED(mEntries->GetNext(getter_AddRefs(mCurrent)))) { + hasMore = true; + } + } + } + *aHasCurrentFile = hasMore; + return S_OK; +} + +IFACEMETHODIMP +BundledFontFileEnumerator::GetCurrentFontFile(IDWriteFontFile** aFontFile) { + nsCOMPtr<nsIFile> file = do_QueryInterface(mCurrent); + if (!file) { + return E_FAIL; + } + nsString path; + if (NS_FAILED(file->GetPath(path))) { + return E_FAIL; + } + return mFactory->CreateFontFileReference((const WCHAR*)path.get(), nullptr, + aFontFile); +} + +class BundledFontLoader : public IDWriteFontCollectionLoader { + IMPL_QI_FOR_DWRITE(IDWriteFontCollectionLoader) + + NS_INLINE_DECL_REFCOUNTING(BundledFontLoader) + + public: + BundledFontLoader() {} + + IFACEMETHODIMP CreateEnumeratorFromKey( + IDWriteFactory* aFactory, const void* aCollectionKey, + UINT32 aCollectionKeySize, + IDWriteFontFileEnumerator** aFontFileEnumerator); + + private: + BundledFontLoader(const BundledFontLoader&) = delete; + BundledFontLoader& operator=(const BundledFontLoader&) = delete; + virtual ~BundledFontLoader() = default; +}; + +IFACEMETHODIMP +BundledFontLoader::CreateEnumeratorFromKey( + IDWriteFactory* aFactory, const void* aCollectionKey, + UINT32 aCollectionKeySize, + IDWriteFontFileEnumerator** aFontFileEnumerator) { + nsIFile* fontDir = *(nsIFile**)aCollectionKey; + *aFontFileEnumerator = new BundledFontFileEnumerator(aFactory, fontDir); + NS_ADDREF(*aFontFileEnumerator); + return S_OK; +} + +already_AddRefed<IDWriteFontCollection> +gfxDWriteFontList::CreateBundledFontsCollection(IDWriteFactory* aFactory) { + nsCOMPtr<nsIFile> localDir; + nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(localDir)); + if (NS_FAILED(rv)) { + return nullptr; + } + if (NS_FAILED(localDir->Append(u"fonts"_ns))) { + return nullptr; + } + bool isDir; + if (NS_FAILED(localDir->IsDirectory(&isDir)) || !isDir) { + return nullptr; + } + + RefPtr<BundledFontLoader> loader = new BundledFontLoader(); + if (FAILED(aFactory->RegisterFontCollectionLoader(loader))) { + return nullptr; + } + + const void* key = localDir.get(); + RefPtr<IDWriteFontCollection> collection; + HRESULT hr = aFactory->CreateCustomFontCollection(loader, &key, sizeof(key), + getter_AddRefs(collection)); + + aFactory->UnregisterFontCollectionLoader(loader); + + if (FAILED(hr)) { + return nullptr; + } else { + return collection.forget(); + } +} + +#endif diff --git a/gfx/thebes/gfxDWriteFontList.h b/gfx/thebes/gfxDWriteFontList.h new file mode 100644 index 0000000000..e6422aecdd --- /dev/null +++ b/gfx/thebes/gfxDWriteFontList.h @@ -0,0 +1,490 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_DWRITEFONTLIST_H +#define GFX_DWRITEFONTLIST_H + +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/MemoryReporting.h" +#include "gfxDWriteCommon.h" +#include "dwrite_3.h" + +// Currently, we build with WINVER=0x601 (Win7), which means newer +// declarations in dwrite_3.h will not be visible. Also, we don't +// yet have the Fall Creators Update SDK available on build machines, +// so even with updated WINVER, some of the interfaces we need would +// not be present. +// To work around this, until the build environment is updated, +// we #include an extra header that contains copies of the relevant +// classes/interfaces we need. +#if !defined(__MINGW32__) && WINVER < 0x0A00 +# include "mozilla/gfx/dw-extra.h" +#endif + +#include "gfxFont.h" +#include "gfxUserFontSet.h" +#include "cairo-win32.h" + +#include "gfxPlatformFontList.h" +#include "gfxPlatform.h" +#include <algorithm> + +#include "mozilla/gfx/UnscaledFontDWrite.h" + +/** + * \brief Class representing directwrite font family. + * + * gfxDWriteFontFamily is a class that describes one of the font families on + * the user's system. It holds each gfxDWriteFontEntry (maps more directly to + * a font face) which holds font type, charset info and character map info. + */ +class gfxDWriteFontFamily final : public gfxFontFamily { + public: + typedef mozilla::FontStretch FontStretch; + typedef mozilla::FontSlantStyle FontSlantStyle; + typedef mozilla::FontWeight FontWeight; + + /** + * Constructs a new DWriteFont Family. + * + * \param aName Name identifying the family + * \param aFamily IDWriteFontFamily object representing the directwrite + * family object. + */ + gfxDWriteFontFamily(const nsACString& aName, FontVisibility aVisibility, + IDWriteFontFamily* aFamily, + bool aIsSystemFontFamily = false) + : gfxFontFamily(aName, aVisibility), + mDWFamily(aFamily), + mIsSystemFontFamily(aIsSystemFontFamily), + mForceGDIClassic(false) {} + virtual ~gfxDWriteFontFamily(); + + void FindStyleVariations(FontInfoData* aFontInfoData = nullptr) final; + + void LocalizedName(nsACString& aLocalizedName) final; + + void ReadFaceNames(gfxPlatformFontList* aPlatformFontList, + bool aNeedFullnamePostscriptNames, + FontInfoData* aFontInfoData = nullptr) final; + + void SetForceGDIClassic(bool aForce) { mForceGDIClassic = aForce; } + + void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const final; + void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const final; + + bool FilterForFontList(nsAtom* aLangGroup, + const nsACString& aGeneric) const final { + return !IsSymbolFontFamily(); + } + + protected: + // helper for FilterForFontList + bool IsSymbolFontFamily() const; + + /** This font family's directwrite fontfamily object */ + RefPtr<IDWriteFontFamily> mDWFamily; + bool mIsSystemFontFamily; + bool mForceGDIClassic; +}; + +/** + * \brief Class representing DirectWrite FontEntry (a unique font style/family) + */ +class gfxDWriteFontEntry final : public gfxFontEntry { + public: + /** + * Constructs a font entry. + * + * \param aFaceName The name of the corresponding font face. + * \param aFont DirectWrite font object + */ + gfxDWriteFontEntry(const nsACString& aFaceName, IDWriteFont* aFont, + bool aIsSystemFont = false) + : gfxFontEntry(aFaceName), + mFont(aFont), + mFontFile(nullptr), + mIsSystemFont(aIsSystemFont), + mForceGDIClassic(false), + mHasVariations(false), + mHasVariationsInitialized(false) { + DWRITE_FONT_STYLE dwriteStyle = aFont->GetStyle(); + FontSlantStyle style = (dwriteStyle == DWRITE_FONT_STYLE_ITALIC + ? FontSlantStyle::Italic() + : (dwriteStyle == DWRITE_FONT_STYLE_OBLIQUE + ? FontSlantStyle::Oblique() + : FontSlantStyle::Normal())); + mStyleRange = SlantStyleRange(style); + + mStretchRange = + StretchRange(FontStretchFromDWriteStretch(aFont->GetStretch())); + + int weight = NS_ROUNDUP(aFont->GetWeight() - 50, 100); + weight = mozilla::Clamp(weight, 100, 900); + mWeightRange = WeightRange(FontWeight(weight)); + + mIsCJK = UNINITIALIZED_VALUE; + } + + /** + * Constructs a font entry using a font. But with custom font values. + * This is used for creating correct font entries for @font-face with local + * font source. + * + * \param aFaceName The name of the corresponding font face. + * \param aFont DirectWrite font object + * \param aWeight Weight of the font + * \param aStretch Stretch of the font + * \param aStyle italic or oblique of font + */ + gfxDWriteFontEntry(const nsACString& aFaceName, IDWriteFont* aFont, + WeightRange aWeight, StretchRange aStretch, + SlantStyleRange aStyle) + : gfxFontEntry(aFaceName), + mFont(aFont), + mFontFile(nullptr), + mIsSystemFont(false), + mForceGDIClassic(false), + mHasVariations(false), + mHasVariationsInitialized(false) { + mWeightRange = aWeight; + mStretchRange = aStretch; + mStyleRange = aStyle; + mIsLocalUserFont = true; + mIsCJK = UNINITIALIZED_VALUE; + } + + /** + * Constructs a font entry using a font file. + * + * \param aFaceName The name of the corresponding font face. + * \param aFontFile DirectWrite fontfile object + * \param aFontFileStream DirectWrite fontfile stream object + * \param aWeight Weight of the font + * \param aStretch Stretch of the font + * \param aStyle italic or oblique of font + */ + gfxDWriteFontEntry(const nsACString& aFaceName, IDWriteFontFile* aFontFile, + IDWriteFontFileStream* aFontFileStream, + WeightRange aWeight, StretchRange aStretch, + SlantStyleRange aStyle) + : gfxFontEntry(aFaceName), + mFont(nullptr), + mFontFile(aFontFile), + mFontFileStream(aFontFileStream), + mIsSystemFont(false), + mForceGDIClassic(false), + mHasVariations(false), + mHasVariationsInitialized(false) { + mWeightRange = aWeight; + mStretchRange = aStretch; + mStyleRange = aStyle; + mIsDataUserFont = true; + mIsCJK = UNINITIALIZED_VALUE; + } + + gfxFontEntry* Clone() const override; + + virtual ~gfxDWriteFontEntry(); + + hb_blob_t* GetFontTable(uint32_t aTableTag) override; + + nsresult ReadCMAP(FontInfoData* aFontInfoData = nullptr); + + bool IsCJKFont(); + + bool HasVariations() override; + void GetVariationAxes(nsTArray<gfxFontVariationAxis>& aAxes) override; + void GetVariationInstances( + nsTArray<gfxFontVariationInstance>& aInstances) override; + + void SetForceGDIClassic(bool aForce) { mForceGDIClassic = aForce; } + bool GetForceGDIClassic() { return mForceGDIClassic; } + + 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; + friend class gfxDWriteFontFamily; + + virtual nsresult CopyFontTable(uint32_t aTableTag, + nsTArray<uint8_t>& aBuffer) override; + + virtual gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle); + + nsresult CreateFontFace( + IDWriteFontFace** aFontFace, const gfxFontStyle* aFontStyle = nullptr, + DWRITE_FONT_SIMULATIONS aSimulations = DWRITE_FONT_SIMULATIONS_NONE); + + static bool InitLogFont(IDWriteFont* aFont, LOGFONTW* aLogFont); + + /** + * A fontentry only needs to have either of these. If it has both only + * the IDWriteFont will be used. + */ + RefPtr<IDWriteFont> mFont; + RefPtr<IDWriteFontFile> mFontFile; + + // For custom fonts, we hold a reference to the IDWriteFontFileStream for + // for the IDWriteFontFile, so that the data is available. + RefPtr<IDWriteFontFileStream> mFontFileStream; + + // font face corresponding to the mFont/mFontFile *without* any DWrite + // style simulations applied + RefPtr<IDWriteFontFace> mFontFace; + // Extended fontface interface if supported, else null + RefPtr<IDWriteFontFace5> mFontFace5; + + DWRITE_FONT_FACE_TYPE mFaceType; + + int8_t mIsCJK; + bool mIsSystemFont; + bool mForceGDIClassic; + bool mHasVariations; + bool mHasVariationsInitialized; + + // Set to true only if the font belongs to a "simple" family where the + // faces can be reliably identified via a GDI LOGFONT structure. + bool mMayUseGDIAccess = false; + + mozilla::ThreadSafeWeakPtr<mozilla::gfx::UnscaledFontDWrite> mUnscaledFont; + mozilla::ThreadSafeWeakPtr<mozilla::gfx::UnscaledFontDWrite> + mUnscaledFontBold; +}; + +// custom text renderer used to determine the fallback font for a given char +class DWriteFontFallbackRenderer final : public IDWriteTextRenderer { + public: + explicit DWriteFontFallbackRenderer(IDWriteFactory* aFactory) : mRefCount(0) { + HRESULT hr = 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 nsCString& FallbackFamilyName() { return mFamilyName; } + + protected: + long mRefCount; + RefPtr<IDWriteFontCollection> mSystemFonts; + nsCString mFamilyName; +}; + +class gfxDWriteFontList final : public gfxPlatformFontList { + public: + gfxDWriteFontList(); + + static gfxDWriteFontList* PlatformFontList() { + return static_cast<gfxDWriteFontList*>(sPlatformFontList); + } + + // initialize font lists + nsresult InitFontListForPlatform() override; + void InitSharedFontListForPlatform() override; + + FontVisibility GetVisibilityForFamily(const nsACString& aName) const; + + gfxFontFamily* CreateFontFamily(const nsACString& aName, + FontVisibility aVisibility) const override; + + gfxFontEntry* CreateFontEntry( + mozilla::fontlist::Face* aFace, + const mozilla::fontlist::Family* aFamily) override; + + void ReadFaceNamesForFamily(mozilla::fontlist::Family* aFamily, + bool aNeedFullnamePostscriptNames) override; + + bool ReadFaceNames(mozilla::fontlist::Family* aFamily, + mozilla::fontlist::Face* aFace, nsCString& aPSName, + nsCString& aFullName) override; + + void GetFacesInitDataForFamily( + const mozilla::fontlist::Family* aFamily, + nsTArray<mozilla::fontlist::Face::InitData>& aFaces, + bool aLoadCmaps) const override; + + gfxFontEntry* LookupLocalFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) override; + + gfxFontEntry* MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, + uint32_t aLength) override; + + IDWriteGdiInterop* GetGDIInterop() { return mGDIInterop; } + bool UseGDIFontTableAccess() { return mGDIFontTableAccess; } + + bool FindAndAddFamilies(mozilla::StyleGenericFontFamily aGeneric, + const nsACString& aFamily, + nsTArray<FamilyAndGeneric>* aOutput, + FindFamiliesFlags aFlags, + gfxFontStyle* aStyle = nullptr, + nsAtom* aLanguage = 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: + FontFamily GetDefaultFontForPlatform(const gfxFontStyle* aStyle, + nsAtom* aLanguage = nullptr) 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, + FontFamily& aMatchedFamily) override; + + private: + friend class gfxDWriteFontFamily; + + nsresult GetFontSubstitutes(); + + void GetDirectWriteSubstitutes(); + + virtual bool UsesSystemFallback() { return true; } + + void GetFontsFromCollection(IDWriteFontCollection* aCollection); + + void AppendFamiliesFromCollection( + IDWriteFontCollection* aCollection, + nsTArray<mozilla::fontlist::Family::InitData>& aFamilies, + const nsTArray<nsCString>* aForceClassicFams = nullptr); + +#ifdef MOZ_BUNDLED_FONTS + already_AddRefed<IDWriteFontCollection> CreateBundledFontsCollection( + IDWriteFactory* aFactory); +#endif + + /** + * Fonts listed in the registry as substitutes but for which no actual + * font family is found. + */ + nsTArray<nsCString> mNonExistingFonts; + + /** + * Table of font substitutes, we grab this from the registry to get + * alternative font names. + */ + FontFamilyTable mFontSubstitutes; + nsClassHashtable<nsCStringHashKey, nsCString> mSubstitutions; + + virtual already_AddRefed<FontInfoData> CreateFontInfoData(); + + gfxFloat mForceGDIClassicMaxFontSize; + + // whether to use GDI font table access routines + bool mGDIFontTableAccess; + RefPtr<IDWriteGdiInterop> mGDIInterop; + + RefPtr<DWriteFontFallbackRenderer> mFallbackRenderer; + RefPtr<IDWriteTextFormat> mFallbackFormat; + + RefPtr<IDWriteFontCollection> mSystemFonts; +#ifdef MOZ_BUNDLED_FONTS + RefPtr<IDWriteFontCollection> mBundledFonts; +#endif +}; + +#endif /* GFX_DWRITEFONTLIST_H */ diff --git a/gfx/thebes/gfxDWriteFonts.cpp b/gfx/thebes/gfxDWriteFonts.cpp new file mode 100644 index 0000000000..f043733d33 --- /dev/null +++ b/gfx/thebes/gfxDWriteFonts.cpp @@ -0,0 +1,643 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxDWriteFonts.h" + +#include <algorithm> +#include "gfxDWriteFontList.h" +#include "gfxContext.h" +#include "gfxTextRun.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/gfx/gfxVars.h" + +#include "harfbuzz/hb.h" +#include "mozilla/FontPropertyTypes.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +// Code to determine whether Windows is set to use ClearType font smoothing; +// based on private functions in cairo-win32-font.c + +#ifndef SPI_GETFONTSMOOTHINGTYPE +# define SPI_GETFONTSMOOTHINGTYPE 0x200a +#endif +#ifndef FE_FONTSMOOTHINGCLEARTYPE +# define FE_FONTSMOOTHINGCLEARTYPE 2 +#endif + +// Cleartype can be dynamically enabled/disabled, so we have to allow for +// dynamically updating it. +static BYTE GetSystemTextQuality() { + BOOL font_smoothing; + UINT smoothing_type; + + if (!SystemParametersInfo(SPI_GETFONTSMOOTHING, 0, &font_smoothing, 0)) { + return DEFAULT_QUALITY; + } + + if (font_smoothing) { + if (!SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE, 0, &smoothing_type, + 0)) { + return DEFAULT_QUALITY; + } + + if (smoothing_type == FE_FONTSMOOTHINGCLEARTYPE) { + return CLEARTYPE_QUALITY; + } + + return ANTIALIASED_QUALITY; + } + + return DEFAULT_QUALITY; +} + +#ifndef SPI_GETFONTSMOOTHINGCONTRAST +# define SPI_GETFONTSMOOTHINGCONTRAST 0x200c +#endif + +// "Retrieves a contrast value that is used in ClearType smoothing. Valid +// contrast values are from 1000 to 2200. The default value is 1400." +static FLOAT GetSystemGDIGamma() { + static FLOAT sGDIGamma = 0.0f; + if (!sGDIGamma) { + UINT value = 0; + if (!SystemParametersInfo(SPI_GETFONTSMOOTHINGCONTRAST, 0, &value, 0) || + value < 1000 || value > 2200) { + value = 1400; + } + sGDIGamma = value / 1000.0f; + } + return sGDIGamma; +} + +//////////////////////////////////////////////////////////////////////////////// +// gfxDWriteFont +gfxDWriteFont::gfxDWriteFont(const RefPtr<UnscaledFontDWrite>& aUnscaledFont, + gfxFontEntry* aFontEntry, + const gfxFontStyle* aFontStyle, + RefPtr<IDWriteFontFace> aFontFace, + AntialiasOption anAAOption) + : gfxFont(aUnscaledFont, aFontEntry, aFontStyle, anAAOption), + mFontFace(aFontFace ? aFontFace : aUnscaledFont->GetFontFace()), + mMetrics(nullptr), + mUseSubpixelPositions(false), + mAllowManualShowGlyphs(true), + mAzureScaledFontUsedClearType(false) { + // If the IDWriteFontFace1 interface is available, we can use that for + // faster glyph width retrieval. + mFontFace->QueryInterface(__uuidof(IDWriteFontFace1), + (void**)getter_AddRefs(mFontFace1)); + + ComputeMetrics(anAAOption); +} + +gfxDWriteFont::~gfxDWriteFont() { delete mMetrics; } + +void gfxDWriteFont::UpdateSystemTextQuality() { + BYTE newQuality = GetSystemTextQuality(); + if (gfxVars::SystemTextQuality() != newQuality) { + gfxVars::SetSystemTextQuality(newQuality); + } +} + +void gfxDWriteFont::SystemTextQualityChanged() { + // If ClearType status has changed, update our value, + Factory::SetSystemTextQuality(gfxVars::SystemTextQuality()); + // flush cached stuff that depended on the old setting, and force + // reflow everywhere to ensure we are using correct glyph metrics. + gfxPlatform::FlushFontAndWordCaches(); + gfxPlatform::ForceGlobalReflow(); +} + +UniquePtr<gfxFont> gfxDWriteFont::CopyWithAntialiasOption( + AntialiasOption anAAOption) { + auto entry = static_cast<gfxDWriteFontEntry*>(mFontEntry.get()); + RefPtr<UnscaledFontDWrite> unscaledFont = + static_cast<UnscaledFontDWrite*>(mUnscaledFont.get()); + return MakeUnique<gfxDWriteFont>(unscaledFont, entry, &mStyle, mFontFace, + anAAOption); +} + +const gfxFont::Metrics& gfxDWriteFont::GetHorizontalMetrics() { + return *mMetrics; +} + +bool gfxDWriteFont::GetFakeMetricsForArialBlack( + DWRITE_FONT_METRICS* aFontMetrics) { + gfxFontStyle style(mStyle); + style.weight = FontWeight(700); + + gfxFontEntry* fe = gfxPlatformFontList::PlatformFontList()->FindFontForFamily( + "Arial"_ns, &style); + if (!fe || fe == mFontEntry) { + return false; + } + + RefPtr<gfxFont> font = fe->FindOrMakeFont(&style); + gfxDWriteFont* dwFont = static_cast<gfxDWriteFont*>(font.get()); + dwFont->mFontFace->GetMetrics(aFontMetrics); + + return true; +} + +void gfxDWriteFont::ComputeMetrics(AntialiasOption anAAOption) { + DWRITE_FONT_METRICS fontMetrics; + if (!(mFontEntry->Weight().Min() == FontWeight(900) && + mFontEntry->Weight().Max() == FontWeight(900) && + !mFontEntry->IsUserFont() && + mFontEntry->Name().EqualsLiteral("Arial Black") && + GetFakeMetricsForArialBlack(&fontMetrics))) { + mFontFace->GetMetrics(&fontMetrics); + } + + if (mStyle.sizeAdjust >= 0.0) { + gfxFloat aspect = + (gfxFloat)fontMetrics.xHeight / fontMetrics.designUnitsPerEm; + mAdjustedSize = mStyle.GetAdjustedSize(aspect); + } else { + mAdjustedSize = mStyle.size; + } + + // Note that GetMeasuringMode depends on mAdjustedSize + if ((anAAOption == gfxFont::kAntialiasDefault && UsingClearType() && + GetMeasuringMode() == DWRITE_MEASURING_MODE_NATURAL) || + anAAOption == gfxFont::kAntialiasSubpixel) { + mUseSubpixelPositions = true; + // note that this may be reset to FALSE if we determine that a bitmap + // strike is going to be used + } + + gfxDWriteFontEntry* fe = static_cast<gfxDWriteFontEntry*>(mFontEntry.get()); + if (fe->IsCJKFont() && HasBitmapStrikeForSize(NS_lround(mAdjustedSize))) { + mAdjustedSize = NS_lround(mAdjustedSize); + mUseSubpixelPositions = false; + // if we have bitmaps, we need to tell Cairo NOT to use subpixel AA, + // to avoid the manual-subpixel codepath in cairo-d2d-surface.cpp + // which fails to render bitmap glyphs (see bug 626299). + // This option will be passed to the cairo_dwrite_scaled_font_t + // after creation. + mAllowManualShowGlyphs = false; + } + + mMetrics = new gfxFont::Metrics; + ::memset(mMetrics, 0, sizeof(*mMetrics)); + + mFUnitsConvFactor = float(mAdjustedSize / fontMetrics.designUnitsPerEm); + + mMetrics->xHeight = fontMetrics.xHeight * mFUnitsConvFactor; + mMetrics->capHeight = fontMetrics.capHeight * mFUnitsConvFactor; + + mMetrics->maxAscent = round(fontMetrics.ascent * mFUnitsConvFactor); + mMetrics->maxDescent = round(fontMetrics.descent * mFUnitsConvFactor); + mMetrics->maxHeight = mMetrics->maxAscent + mMetrics->maxDescent; + + mMetrics->emHeight = mAdjustedSize; + mMetrics->emAscent = + mMetrics->emHeight * mMetrics->maxAscent / mMetrics->maxHeight; + mMetrics->emDescent = mMetrics->emHeight - mMetrics->emAscent; + + mMetrics->maxAdvance = mAdjustedSize; + + // try to get the true maxAdvance value from 'hhea' + gfxFontEntry::AutoTable hheaTable(GetFontEntry(), + TRUETYPE_TAG('h', 'h', 'e', 'a')); + if (hheaTable) { + uint32_t len; + const MetricsHeader* hhea = reinterpret_cast<const MetricsHeader*>( + hb_blob_get_data(hheaTable, &len)); + if (len >= sizeof(MetricsHeader)) { + mMetrics->maxAdvance = + uint16_t(hhea->advanceWidthMax) * mFUnitsConvFactor; + } + } + + mMetrics->internalLeading = + std::max(mMetrics->maxHeight - mMetrics->emHeight, 0.0); + mMetrics->externalLeading = ceil(fontMetrics.lineGap * mFUnitsConvFactor); + + UINT32 ucs = L' '; + UINT16 glyph; + if (SUCCEEDED(mFontFace->GetGlyphIndices(&ucs, 1, &glyph)) && glyph != 0) { + mSpaceGlyph = glyph; + mMetrics->spaceWidth = MeasureGlyphWidth(glyph); + } else { + mMetrics->spaceWidth = 0; + } + + // try to get aveCharWidth from the OS/2 table, fall back to measuring 'x' + // if the table is not available or if using hinted/pixel-snapped widths + if (mUseSubpixelPositions) { + mMetrics->aveCharWidth = 0; + gfxFontEntry::AutoTable os2Table(GetFontEntry(), + TRUETYPE_TAG('O', 'S', '/', '2')); + if (os2Table) { + uint32_t len; + const OS2Table* os2 = + reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len)); + if (len >= 4) { + // Not checking against sizeof(mozilla::OS2Table) here because older + // versions of the table have different sizes; we only need the first + // two 16-bit fields here. + mMetrics->aveCharWidth = + int16_t(os2->xAvgCharWidth) * mFUnitsConvFactor; + } + } + } + + if (mMetrics->aveCharWidth < 1) { + ucs = L'x'; + if (SUCCEEDED(mFontFace->GetGlyphIndices(&ucs, 1, &glyph)) && glyph != 0) { + 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->GetGlyphIndices(&ucs, 1, &glyph)) && glyph != 0) { + mMetrics->zeroWidth = MeasureGlyphWidth(glyph); + } else { + mMetrics->zeroWidth = -1.0; // indicates not found + } + + 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 zeroWidth: %f\n", + mMetrics->spaceWidth, mMetrics->aveCharWidth, mMetrics->zeroWidth); + printf(" xHeight: %f capHeight: %f\n", mMetrics->xHeight, mMetrics->capHeight); + printf(" uOff: %f uSize: %f stOff: %f stSize: %f\n", + mMetrics->underlineOffset, mMetrics->underlineSize, mMetrics->strikeoutOffset, mMetrics->strikeoutSize); +#endif +} + +using namespace mozilla; // for AutoSwap_* types + +struct EBLCHeader { + AutoSwap_PRUint32 version; + AutoSwap_PRUint32 numSizes; +}; + +struct SbitLineMetrics { + int8_t ascender; + int8_t descender; + uint8_t widthMax; + int8_t caretSlopeNumerator; + int8_t caretSlopeDenominator; + int8_t caretOffset; + int8_t minOriginSB; + int8_t minAdvanceSB; + int8_t maxBeforeBL; + int8_t minAfterBL; + int8_t pad1; + int8_t pad2; +}; + +struct BitmapSizeTable { + AutoSwap_PRUint32 indexSubTableArrayOffset; + AutoSwap_PRUint32 indexTablesSize; + AutoSwap_PRUint32 numberOfIndexSubTables; + AutoSwap_PRUint32 colorRef; + SbitLineMetrics hori; + SbitLineMetrics vert; + AutoSwap_PRUint16 startGlyphIndex; + AutoSwap_PRUint16 endGlyphIndex; + uint8_t ppemX; + uint8_t ppemY; + uint8_t bitDepth; + uint8_t flags; +}; + +typedef EBLCHeader EBSCHeader; + +struct BitmapScaleTable { + SbitLineMetrics hori; + SbitLineMetrics vert; + uint8_t ppemX; + uint8_t ppemY; + uint8_t substitutePpemX; + uint8_t substitutePpemY; +}; + +bool gfxDWriteFont::HasBitmapStrikeForSize(uint32_t aSize) { + uint8_t* tableData; + uint32_t len; + void* tableContext; + BOOL exists; + HRESULT hr = mFontFace->TryGetFontTable( + DWRITE_MAKE_OPENTYPE_TAG('E', 'B', 'L', 'C'), (const void**)&tableData, + &len, &tableContext, &exists); + if (FAILED(hr)) { + return false; + } + + bool hasStrike = false; + // not really a loop, but this lets us use 'break' to skip out of the block + // as soon as we know the answer, and skips it altogether if the table is + // not present + while (exists) { + if (len < sizeof(EBLCHeader)) { + break; + } + const EBLCHeader* hdr = reinterpret_cast<const EBLCHeader*>(tableData); + if (hdr->version != 0x00020000) { + break; + } + uint32_t numSizes = hdr->numSizes; + if (numSizes > 0xffff) { // sanity-check, prevent overflow below + break; + } + if (len < sizeof(EBLCHeader) + numSizes * sizeof(BitmapSizeTable)) { + break; + } + const BitmapSizeTable* sizeTable = + reinterpret_cast<const BitmapSizeTable*>(hdr + 1); + for (uint32_t i = 0; i < numSizes; ++i, ++sizeTable) { + if (sizeTable->ppemX == aSize && sizeTable->ppemY == aSize) { + // we ignore a strike that contains fewer than 4 glyphs, + // as that probably indicates a font such as Courier New + // that provides bitmaps ONLY for the "shading" characters + // U+2591..2593 + hasStrike = (uint16_t(sizeTable->endGlyphIndex) >= + uint16_t(sizeTable->startGlyphIndex) + 3); + break; + } + } + // if we reach here, we didn't find a strike; unconditionally break + // out of the while-loop block + break; + } + mFontFace->ReleaseFontTable(tableContext); + + if (hasStrike) { + return true; + } + + // if we didn't find a real strike, check if the font calls for scaling + // another bitmap to this size + hr = mFontFace->TryGetFontTable(DWRITE_MAKE_OPENTYPE_TAG('E', 'B', 'S', 'C'), + (const void**)&tableData, &len, &tableContext, + &exists); + if (FAILED(hr)) { + return false; + } + + while (exists) { + if (len < sizeof(EBSCHeader)) { + break; + } + const EBSCHeader* hdr = reinterpret_cast<const EBSCHeader*>(tableData); + if (hdr->version != 0x00020000) { + break; + } + uint32_t numSizes = hdr->numSizes; + if (numSizes > 0xffff) { + break; + } + if (len < sizeof(EBSCHeader) + numSizes * sizeof(BitmapScaleTable)) { + break; + } + const BitmapScaleTable* scaleTable = + reinterpret_cast<const BitmapScaleTable*>(hdr + 1); + for (uint32_t i = 0; i < numSizes; ++i, ++scaleTable) { + if (scaleTable->ppemX == aSize && scaleTable->ppemY == aSize) { + hasStrike = true; + break; + } + } + break; + } + mFontFace->ReleaseFontTable(tableContext); + + return hasStrike; +} + +bool gfxDWriteFont::IsValid() const { return mFontFace != nullptr; } + +IDWriteFontFace* gfxDWriteFont::GetFontFace() { return mFontFace.get(); } + +gfxFont::RunMetrics gfxDWriteFont::Measure(const gfxTextRun* aTextRun, + uint32_t aStart, uint32_t aEnd, + BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, + Spacing* aSpacing, + gfx::ShapedTextFlags aOrientation) { + gfxFont::RunMetrics metrics = + gfxFont::Measure(aTextRun, aStart, aEnd, aBoundingBoxType, aRefDrawTarget, + aSpacing, aOrientation); + + // if aBoundingBoxType is LOOSE_INK_EXTENTS + // and the underlying cairo font may be antialiased, + // we can't trust Windows to have considered all the pixels + // so we need to add "padding" to the bounds. + // (see bugs 475968, 439831, compare also bug 445087) + if (aBoundingBoxType == LOOSE_INK_EXTENTS && + mAntialiasOption != kAntialiasNone && + GetMeasuringMode() == DWRITE_MEASURING_MODE_GDI_CLASSIC && + metrics.mBoundingBox.Width() > 0) { + metrics.mBoundingBox.MoveByX(-aTextRun->GetAppUnitsPerDevUnit()); + metrics.mBoundingBox.SetWidth(metrics.mBoundingBox.Width() + + aTextRun->GetAppUnitsPerDevUnit() * 3); + } + + return metrics; +} + +bool gfxDWriteFont::ProvidesGlyphWidths() const { + return !mUseSubpixelPositions || + (mFontFace->GetSimulations() & DWRITE_FONT_SIMULATIONS_BOLD) || + (((gfxDWriteFontEntry*)(GetFontEntry()))->HasVariations() && + !mStyle.variationSettings.IsEmpty()); +} + +int32_t gfxDWriteFont::GetGlyphWidth(uint16_t aGID) { + if (!mGlyphWidths) { + mGlyphWidths = MakeUnique<nsDataHashtable<nsUint32HashKey, int32_t>>(128); + } + + int32_t width = -1; + if (mGlyphWidths->Get(aGID, &width)) { + return width; + } + + width = NS_lround(MeasureGlyphWidth(aGID) * 65536.0); + mGlyphWidths->Put(aGID, width); + return width; +} + +bool gfxDWriteFont::GetForceGDIClassic() const { + return static_cast<gfxDWriteFontEntry*>(mFontEntry.get()) + ->GetForceGDIClassic() && + cairo_dwrite_get_cleartype_rendering_mode() < 0 && + GetAdjustedSize() <= gfxDWriteFontList::PlatformFontList() + ->GetForceGDIClassicMaxFontSize(); +} + +DWRITE_MEASURING_MODE +gfxDWriteFont::GetMeasuringMode() const { + return GetForceGDIClassic() + ? DWRITE_MEASURING_MODE_GDI_CLASSIC + : gfxWindowsPlatform::GetPlatform()->DWriteMeasuringMode(); +} + +gfxFloat gfxDWriteFont::MeasureGlyphWidth(uint16_t aGlyph) { + MOZ_SEH_TRY { + HRESULT hr; + if (mFontFace1) { + int32_t advance; + if (mUseSubpixelPositions) { + hr = mFontFace1->GetDesignGlyphAdvances(1, &aGlyph, &advance, FALSE); + if (SUCCEEDED(hr)) { + return advance * mFUnitsConvFactor; + } + } else { + hr = mFontFace1->GetGdiCompatibleGlyphAdvances( + FLOAT(mAdjustedSize), 1.0f, nullptr, + GetMeasuringMode() == DWRITE_MEASURING_MODE_GDI_NATURAL, FALSE, 1, + &aGlyph, &advance); + if (SUCCEEDED(hr)) { + return NS_lround(advance * mFUnitsConvFactor); + } + } + } else { + DWRITE_GLYPH_METRICS metrics; + if (mUseSubpixelPositions) { + hr = mFontFace->GetDesignGlyphMetrics(&aGlyph, 1, &metrics, FALSE); + if (SUCCEEDED(hr)) { + return metrics.advanceWidth * mFUnitsConvFactor; + } + } else { + hr = mFontFace->GetGdiCompatibleGlyphMetrics( + FLOAT(mAdjustedSize), 1.0f, nullptr, + GetMeasuringMode() == DWRITE_MEASURING_MODE_GDI_NATURAL, &aGlyph, 1, + &metrics, FALSE); + if (SUCCEEDED(hr)) { + return NS_lround(metrics.advanceWidth * mFUnitsConvFactor); + } + } + } + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + // Exception (e.g. disk i/o error) occurred when DirectWrite tried to use + // the font resource; possibly a failing drive or similar hardware issue. + // Mark the font as invalid, and wipe the fontEntry's charmap so that font + // selection will skip it; we'll use a fallback font instead. + mIsValid = false; + GetFontEntry()->mCharacterMap = new gfxCharacterMap(); + GetFontEntry()->mShmemCharacterMap = nullptr; + gfxCriticalError() << "Exception occurred measuring glyph width for " + << GetFontEntry()->Name().get(); + } + return 0.0; +} + +bool gfxDWriteFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, + bool aTight) { + DWRITE_GLYPH_METRICS m; + HRESULT hr = mFontFace->GetDesignGlyphMetrics(&aGID, 1, &m, FALSE); + if (FAILED(hr)) { + return false; + } + gfxRect bounds(m.leftSideBearing, m.topSideBearing - m.verticalOriginY, + m.advanceWidth - m.leftSideBearing - m.rightSideBearing, + m.advanceHeight - m.topSideBearing - m.bottomSideBearing); + bounds.Scale(mFUnitsConvFactor); + // GetDesignGlyphMetrics returns 'ideal' glyph metrics, we need to pad to + // account for antialiasing. + if (!aTight && !aBounds->IsEmpty()) { + bounds.Inflate(1.0, 0.0); + } + *aBounds = bounds; + return true; +} + +void gfxDWriteFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const { + gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); + aSizes->mFontInstances += aMallocSizeOf(mMetrics); + if (mGlyphWidths) { + aSizes->mFontInstances += + mGlyphWidths->ShallowSizeOfIncludingThis(aMallocSizeOf); + } +} + +void gfxDWriteFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const { + aSizes->mFontInstances += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +already_AddRefed<ScaledFont> gfxDWriteFont::GetScaledFont( + mozilla::gfx::DrawTarget* aTarget) { + if (mAzureScaledFontUsedClearType != UsingClearType()) { + mAzureScaledFont = nullptr; + } + if (!mAzureScaledFont) { + gfxDWriteFontEntry* fe = static_cast<gfxDWriteFontEntry*>(mFontEntry.get()); + bool forceGDI = GetForceGDIClassic(); + + // params may be null, if initialization failed + IDWriteRenderingParams* params = + gfxWindowsPlatform::GetPlatform()->GetRenderingParams( + UsingClearType() + ? (forceGDI ? gfxWindowsPlatform::TEXT_RENDERING_GDI_CLASSIC + : gfxWindowsPlatform::TEXT_RENDERING_NORMAL) + : gfxWindowsPlatform::TEXT_RENDERING_NO_CLEARTYPE); + + DWRITE_RENDERING_MODE renderingMode = + params ? params->GetRenderingMode() : DWRITE_RENDERING_MODE_DEFAULT; + FLOAT gamma = params ? params->GetGamma() : 2.2; + FLOAT contrast = params ? params->GetEnhancedContrast() : 1.0; + FLOAT clearTypeLevel = params ? params->GetClearTypeLevel() : 1.0; + if (forceGDI || renderingMode == DWRITE_RENDERING_MODE_GDI_CLASSIC) { + renderingMode = DWRITE_RENDERING_MODE_GDI_CLASSIC; + gamma = GetSystemGDIGamma(); + contrast = 0.0f; + } + + bool useEmbeddedBitmap = + (renderingMode == DWRITE_RENDERING_MODE_DEFAULT || + renderingMode == DWRITE_RENDERING_MODE_GDI_CLASSIC) && + fe->IsCJKFont() && HasBitmapStrikeForSize(NS_lround(mAdjustedSize)); + + const gfxFontStyle* fontStyle = GetStyle(); + mAzureScaledFont = Factory::CreateScaledFontForDWriteFont( + mFontFace, fontStyle, GetUnscaledFont(), GetAdjustedSize(), + useEmbeddedBitmap, (int)renderingMode, params, gamma, contrast, + clearTypeLevel); + if (!mAzureScaledFont) { + return nullptr; + } + InitializeScaledFont(); + mAzureScaledFontUsedClearType = UsingClearType(); + } + + RefPtr<ScaledFont> scaledFont(mAzureScaledFont); + return scaledFont.forget(); +} + +bool gfxDWriteFont::ShouldRoundXOffset(cairo_t* aCairo) const { + // show_glyphs is implemented on the font and so is used for all Cairo + // surface types; however, it may pixel-snap depending on the dwrite + // rendering mode + return GetForceGDIClassic() || + gfxWindowsPlatform::GetPlatform()->DWriteMeasuringMode() != + DWRITE_MEASURING_MODE_NATURAL; +} diff --git a/gfx/thebes/gfxDWriteFonts.h b/gfx/thebes/gfxDWriteFonts.h new file mode 100644 index 0000000000..40f565e962 --- /dev/null +++ b/gfx/thebes/gfxDWriteFonts.h @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_WINDOWSDWRITEFONTS_H +#define GFX_WINDOWSDWRITEFONTS_H + +#include "mozilla/MemoryReporting.h" +#include "mozilla/UniquePtr.h" +#include <dwrite_1.h> + +#include "gfxFont.h" +#include "gfxUserFontSet.h" +#include "nsDataHashtable.h" +#include "nsHashKeys.h" + +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/UnscaledFontDWrite.h" + +/** + * \brief Class representing a font face for a font entry. + */ +class gfxDWriteFont final : public gfxFont { + public: + gfxDWriteFont(const RefPtr<mozilla::gfx::UnscaledFontDWrite>& aUnscaledFont, + gfxFontEntry* aFontEntry, const gfxFontStyle* aFontStyle, + RefPtr<IDWriteFontFace> aFontFace = nullptr, + AntialiasOption = kAntialiasDefault); + ~gfxDWriteFont(); + + static void UpdateSystemTextQuality(); + static void SystemTextQualityChanged(); + + mozilla::UniquePtr<gfxFont> CopyWithAntialiasOption( + AntialiasOption anAAOption) override; + + bool AllowSubpixelAA() override { return mAllowManualShowGlyphs; } + + bool IsValid() const; + + IDWriteFontFace* GetFontFace(); + + /* override Measure to add padding for antialiasing */ + RunMetrics Measure(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd, + BoundingBoxType aBoundingBoxType, + DrawTarget* aDrawTargetForTightBoundingBox, + Spacing* aSpacing, + mozilla::gfx::ShapedTextFlags aOrientation) override; + + bool ProvidesGlyphWidths() const override; + + int32_t GetGlyphWidth(uint16_t aGID) override; + + bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override; + + void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const override; + void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const override; + + FontType GetType() const override { return FONT_TYPE_DWRITE; } + + already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont( + mozilla::gfx::DrawTarget* aTarget) override; + + bool ShouldRoundXOffset(cairo_t* aCairo) const override; + + protected: + const Metrics& GetHorizontalMetrics() override; + + bool GetFakeMetricsForArialBlack(DWRITE_FONT_METRICS* aFontMetrics); + + void ComputeMetrics(AntialiasOption anAAOption); + + bool HasBitmapStrikeForSize(uint32_t aSize); + + gfxFloat MeasureGlyphWidth(uint16_t aGlyph); + + DWRITE_MEASURING_MODE GetMeasuringMode() const; + bool GetForceGDIClassic() const; + + RefPtr<IDWriteFontFace> mFontFace; + RefPtr<IDWriteFontFace1> mFontFace1; // may be unavailable on older DWrite + + Metrics* mMetrics; + + // cache of glyph widths in 16.16 fixed-point pixels + mozilla::UniquePtr<nsDataHashtable<nsUint32HashKey, int32_t>> mGlyphWidths; + + bool mUseSubpixelPositions; + bool mAllowManualShowGlyphs; + + // Used to record the sUseClearType setting at the time mAzureScaledFont + // was set up, so we can tell if it's stale and needs to be re-created. + bool mAzureScaledFontUsedClearType; + + bool UsingClearType() { + return mozilla::gfx::gfxVars::SystemTextQuality() == CLEARTYPE_QUALITY; + } +}; + +#endif diff --git a/gfx/thebes/gfxDrawable.cpp b/gfx/thebes/gfxDrawable.cpp new file mode 100644 index 0000000000..23baa1cc2a --- /dev/null +++ b/gfx/thebes/gfxDrawable.cpp @@ -0,0 +1,216 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxDrawable.h" +#include "gfxASurface.h" +#include "gfxContext.h" +#include "gfxPlatform.h" +#include "gfx2DGlue.h" +#ifdef MOZ_X11 +# include "cairo.h" +# include "gfxXlibSurface.h" +#endif +#include "mozilla/gfx/Logging.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +gfxSurfaceDrawable::gfxSurfaceDrawable(SourceSurface* aSurface, + const IntSize aSize, + const gfxMatrix aTransform) + : gfxDrawable(aSize), mSourceSurface(aSurface), mTransform(aTransform) { + if (!mSourceSurface) { + gfxWarning() << "Creating gfxSurfaceDrawable with null SourceSurface"; + } +} + +bool gfxSurfaceDrawable::DrawWithSamplingRect( + DrawTarget* aDrawTarget, CompositionOp aOp, AntialiasMode aAntialiasMode, + const gfxRect& aFillRect, const gfxRect& aSamplingRect, + ExtendMode aExtendMode, const SamplingFilter aSamplingFilter, + gfxFloat aOpacity) { + if (!mSourceSurface) { + return true; + } + + // When drawing with CLAMP we can expand the sampling rect to the nearest + // pixel without changing the result. + IntRect intRect = + IntRect::RoundOut(aSamplingRect.X(), aSamplingRect.Y(), + aSamplingRect.Width(), aSamplingRect.Height()); + + IntSize size = mSourceSurface->GetSize(); + if (!IntRect(IntPoint(), size).Contains(intRect)) { + return false; + } + + DrawInternal(aDrawTarget, aOp, aAntialiasMode, aFillRect, intRect, + ExtendMode::CLAMP, aSamplingFilter, aOpacity, gfxMatrix()); + return true; +} + +bool gfxSurfaceDrawable::Draw(gfxContext* aContext, const gfxRect& aFillRect, + ExtendMode aExtendMode, + const SamplingFilter aSamplingFilter, + gfxFloat aOpacity, const gfxMatrix& aTransform) + +{ + if (!mSourceSurface) { + return true; + } + + DrawInternal(aContext->GetDrawTarget(), aContext->CurrentOp(), + aContext->CurrentAntialiasMode(), aFillRect, IntRect(), + aExtendMode, aSamplingFilter, aOpacity, aTransform); + return true; +} + +void gfxSurfaceDrawable::DrawInternal( + DrawTarget* aDrawTarget, CompositionOp aOp, AntialiasMode aAntialiasMode, + const gfxRect& aFillRect, const IntRect& aSamplingRect, + ExtendMode aExtendMode, const SamplingFilter aSamplingFilter, + gfxFloat aOpacity, const gfxMatrix& aTransform) { + Matrix patternTransform = ToMatrix(aTransform * mTransform); + patternTransform.Invert(); + + SurfacePattern pattern(mSourceSurface, aExtendMode, patternTransform, + aSamplingFilter, aSamplingRect); + + Rect fillRect = ToRect(aFillRect); + + if (aOp == CompositionOp::OP_SOURCE && aOpacity == 1.0) { + // Emulate cairo operator source which is bound by mask! + aDrawTarget->ClearRect(fillRect); + aDrawTarget->FillRect(fillRect, pattern); + } else { + aDrawTarget->FillRect(fillRect, pattern, + DrawOptions(aOpacity, aOp, aAntialiasMode)); + } +} + +gfxCallbackDrawable::gfxCallbackDrawable(gfxDrawingCallback* aCallback, + const IntSize aSize) + : gfxDrawable(aSize), mCallback(aCallback) {} + +already_AddRefed<gfxSurfaceDrawable> gfxCallbackDrawable::MakeSurfaceDrawable( + gfxContext* aContext, const SamplingFilter aSamplingFilter) { + SurfaceFormat format = gfxPlatform::GetPlatform()->Optimal2DFormatForContent( + gfxContentType::COLOR_ALPHA); + if (!aContext->GetDrawTarget()->CanCreateSimilarDrawTarget(mSize, format)) { + return nullptr; + } + RefPtr<DrawTarget> dt = + aContext->GetDrawTarget()->CreateSimilarDrawTarget(mSize, format); + + if (!dt || !dt->IsValid()) return nullptr; + + RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(dt); + MOZ_ASSERT(ctx); // already checked for target above + Draw(ctx, gfxRect(0, 0, mSize.width, mSize.height), ExtendMode::CLAMP, + aSamplingFilter); + + RefPtr<SourceSurface> surface = dt->Snapshot(); + if (surface) { + RefPtr<gfxSurfaceDrawable> drawable = + new gfxSurfaceDrawable(surface, mSize); + return drawable.forget(); + } + return nullptr; +} + +static bool IsRepeatingExtendMode(ExtendMode aExtendMode) { + switch (aExtendMode) { + case ExtendMode::REPEAT: + case ExtendMode::REPEAT_X: + case ExtendMode::REPEAT_Y: + return true; + default: + return false; + } +} + +bool gfxCallbackDrawable::Draw(gfxContext* aContext, const gfxRect& aFillRect, + ExtendMode aExtendMode, + const SamplingFilter aSamplingFilter, + gfxFloat aOpacity, const gfxMatrix& aTransform) { + if ((IsRepeatingExtendMode(aExtendMode) || aOpacity != 1.0 || + aContext->CurrentOp() != CompositionOp::OP_OVER) && + !mSurfaceDrawable) { + mSurfaceDrawable = MakeSurfaceDrawable(aContext, aSamplingFilter); + } + + if (mSurfaceDrawable) + return mSurfaceDrawable->Draw(aContext, aFillRect, aExtendMode, + aSamplingFilter, aOpacity, aTransform); + + if (mCallback) + return (*mCallback)(aContext, aFillRect, aSamplingFilter, aTransform); + + return false; +} + +gfxPatternDrawable::gfxPatternDrawable(gfxPattern* aPattern, + const IntSize aSize) + : gfxDrawable(aSize), mPattern(aPattern) {} + +gfxPatternDrawable::~gfxPatternDrawable() = default; + +class DrawingCallbackFromDrawable : public gfxDrawingCallback { + public: + explicit DrawingCallbackFromDrawable(gfxDrawable* aDrawable) + : mDrawable(aDrawable) { + NS_ASSERTION(aDrawable, "aDrawable is null!"); + } + + virtual ~DrawingCallbackFromDrawable() = default; + + bool operator()(gfxContext* aContext, const gfxRect& aFillRect, + const SamplingFilter aSamplingFilter, + const gfxMatrix& aTransform = gfxMatrix()) override { + return mDrawable->Draw(aContext, aFillRect, ExtendMode::CLAMP, + aSamplingFilter, 1.0, aTransform); + } + + private: + RefPtr<gfxDrawable> mDrawable; +}; + +already_AddRefed<gfxCallbackDrawable> +gfxPatternDrawable::MakeCallbackDrawable() { + RefPtr<gfxDrawingCallback> callback = new DrawingCallbackFromDrawable(this); + RefPtr<gfxCallbackDrawable> callbackDrawable = + new gfxCallbackDrawable(callback, mSize); + return callbackDrawable.forget(); +} + +bool gfxPatternDrawable::Draw(gfxContext* aContext, const gfxRect& aFillRect, + ExtendMode aExtendMode, + const SamplingFilter aSamplingFilter, + gfxFloat aOpacity, const gfxMatrix& aTransform) { + DrawTarget& aDrawTarget = *aContext->GetDrawTarget(); + + if (!mPattern) return false; + + if (IsRepeatingExtendMode(aExtendMode)) { + // We can't use mPattern directly: We want our repeated tiles to have + // the size mSize, which might not be the case in mPattern. + // So we need to draw mPattern into a surface of size mSize, create + // a pattern from the surface and draw that pattern. + // gfxCallbackDrawable and gfxSurfaceDrawable already know how to do + // those things, so we use them here. Drawing mPattern into the surface + // will happen through this Draw() method with aRepeat = false. + RefPtr<gfxCallbackDrawable> callbackDrawable = MakeCallbackDrawable(); + return callbackDrawable->Draw(aContext, aFillRect, aExtendMode, + aSamplingFilter, aOpacity, aTransform); + } + + gfxMatrix oldMatrix = mPattern->GetMatrix(); + mPattern->SetMatrix(aTransform * oldMatrix); + DrawOptions drawOptions(aOpacity); + aDrawTarget.FillRect(ToRect(aFillRect), *mPattern->GetPattern(&aDrawTarget), + drawOptions); + mPattern->SetMatrix(oldMatrix); + return true; +} diff --git a/gfx/thebes/gfxDrawable.h b/gfx/thebes/gfxDrawable.h new file mode 100644 index 0000000000..b0d47e3691 --- /dev/null +++ b/gfx/thebes/gfxDrawable.h @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_DRAWABLE_H +#define GFX_DRAWABLE_H + +#include "gfxRect.h" +#include "gfxMatrix.h" +#include "gfxTypes.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Types.h" +#include "nsISupportsImpl.h" + +class gfxContext; +class gfxPattern; + +/** + * gfxDrawable + * An Interface representing something that has an intrinsic size and can draw + * itself repeatedly. + */ +class gfxDrawable { + NS_INLINE_DECL_REFCOUNTING(gfxDrawable) + public: + typedef mozilla::gfx::AntialiasMode AntialiasMode; + typedef mozilla::gfx::CompositionOp CompositionOp; + typedef mozilla::gfx::DrawTarget DrawTarget; + + explicit gfxDrawable(const mozilla::gfx::IntSize aSize) : mSize(aSize) {} + + /** + * Draw into aContext filling aFillRect, possibly repeating, using + * aSamplingFilter. aTransform is a userspace to "image"space matrix. For + * example, if Draw draws using a gfxPattern, this is the matrix that should + * be set on the pattern prior to rendering it. + * @return whether drawing was successful + */ + virtual bool Draw(gfxContext* aContext, const gfxRect& aFillRect, + mozilla::gfx::ExtendMode aExtendMode, + const mozilla::gfx::SamplingFilter aSamplingFilter, + gfxFloat aOpacity = 1.0, + const gfxMatrix& aTransform = gfxMatrix()) = 0; + + virtual bool DrawWithSamplingRect( + DrawTarget* aDrawTarget, CompositionOp aOp, AntialiasMode aAntialiasMode, + const gfxRect& aFillRect, const gfxRect& aSamplingRect, + mozilla::gfx::ExtendMode aExtendMode, + const mozilla::gfx::SamplingFilter aSamplingFilter, + gfxFloat aOpacity = 1.0) { + return false; + } + + virtual mozilla::gfx::IntSize Size() { return mSize; } + + protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~gfxDrawable() = default; + + const mozilla::gfx::IntSize mSize; +}; + +/** + * gfxSurfaceDrawable + * A convenience implementation of gfxDrawable for surfaces. + */ +class gfxSurfaceDrawable : public gfxDrawable { + public: + gfxSurfaceDrawable(mozilla::gfx::SourceSurface* aSurface, + const mozilla::gfx::IntSize aSize, + const gfxMatrix aTransform = gfxMatrix()); + virtual ~gfxSurfaceDrawable() = default; + + virtual bool Draw(gfxContext* aContext, const gfxRect& aFillRect, + mozilla::gfx::ExtendMode aExtendMode, + const mozilla::gfx::SamplingFilter aSamplingFilter, + gfxFloat aOpacity = 1.0, + const gfxMatrix& aTransform = gfxMatrix()) override; + + virtual bool DrawWithSamplingRect( + DrawTarget* aDrawTarget, CompositionOp aOp, AntialiasMode aAntialiasMode, + const gfxRect& aFillRect, const gfxRect& aSamplingRect, + mozilla::gfx::ExtendMode aExtendMode, + const mozilla::gfx::SamplingFilter aSamplingFilter, + gfxFloat aOpacity = 1.0) override; + + protected: + void DrawInternal(DrawTarget* aDrawTarget, CompositionOp aOp, + AntialiasMode aAntialiasMode, const gfxRect& aFillRect, + const mozilla::gfx::IntRect& aSamplingRect, + mozilla::gfx::ExtendMode aExtendMode, + const mozilla::gfx::SamplingFilter aSamplingFilter, + gfxFloat aOpacity, + const gfxMatrix& aTransform = gfxMatrix()); + + RefPtr<mozilla::gfx::SourceSurface> mSourceSurface; + const gfxMatrix mTransform; +}; + +/** + * gfxDrawingCallback + * A simple drawing functor. + */ +class gfxDrawingCallback { + NS_INLINE_DECL_REFCOUNTING(gfxDrawingCallback) + protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~gfxDrawingCallback() = default; + + public: + /** + * Draw into aContext filling aFillRect using aSamplingFilter. + * aTransform is a userspace to "image"space matrix. For example, if Draw + * draws using a gfxPattern, this is the matrix that should be set on the + * pattern prior to rendering it. + * @return whether drawing was successful + */ + virtual bool operator()(gfxContext* aContext, const gfxRect& aFillRect, + const mozilla::gfx::SamplingFilter aSamplingFilter, + const gfxMatrix& aTransform = gfxMatrix()) = 0; +}; + +/** + * gfxCallbackDrawable + * A convenience implementation of gfxDrawable for callbacks. + */ +class gfxCallbackDrawable : public gfxDrawable { + public: + gfxCallbackDrawable(gfxDrawingCallback* aCallback, + const mozilla::gfx::IntSize aSize); + virtual ~gfxCallbackDrawable() = default; + + virtual bool Draw(gfxContext* aContext, const gfxRect& aFillRect, + mozilla::gfx::ExtendMode aExtendMode, + const mozilla::gfx::SamplingFilter aSamplingFilter, + gfxFloat aOpacity = 1.0, + const gfxMatrix& aTransform = gfxMatrix()) override; + + protected: + already_AddRefed<gfxSurfaceDrawable> MakeSurfaceDrawable( + gfxContext* aContext, mozilla::gfx::SamplingFilter aSamplingFilter = + mozilla::gfx::SamplingFilter::LINEAR); + + RefPtr<gfxDrawingCallback> mCallback; + RefPtr<gfxSurfaceDrawable> mSurfaceDrawable; +}; + +/** + * gfxPatternDrawable + * A convenience implementation of gfxDrawable for patterns. + */ +class gfxPatternDrawable : public gfxDrawable { + public: + gfxPatternDrawable(gfxPattern* aPattern, const mozilla::gfx::IntSize aSize); + virtual ~gfxPatternDrawable(); + + virtual bool Draw(gfxContext* aContext, const gfxRect& aFillRect, + mozilla::gfx::ExtendMode aExtendMode, + const mozilla::gfx::SamplingFilter aSamplingFilter, + gfxFloat aOpacity = 1.0, + const gfxMatrix& aTransform = gfxMatrix()) override; + + protected: + already_AddRefed<gfxCallbackDrawable> MakeCallbackDrawable(); + + RefPtr<gfxPattern> mPattern; +}; + +#endif /* GFX_DRAWABLE_H */ diff --git a/gfx/thebes/gfxEnv.h b/gfx/thebes/gfxEnv.h new file mode 100644 index 0000000000..afb5a467ab --- /dev/null +++ b/gfx/thebes/gfxEnv.h @@ -0,0 +1,133 @@ +/* -*- 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 GLContextProviderEGL + DECL_GFX_ENV("MOZ_GL_SPEW", GlSpew); + + // + DECL_GFX_ENV("MOZ_GPU_SWITCHING_SPEW", GpuSwitchingSpew); + + // 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); + + // Skip final window composition + DECL_GFX_ENV("MOZ_SKIPCOMPOSITION", SkipComposition); + + // Skip rasterizing painted layer contents + DECL_GFX_ENV("MOZ_SKIPRASTERIZATION", SkipRasterization); + + // 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() = default; + ~gfxEnv() = default; + + 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 0000000000..ad5fc09bbc --- /dev/null +++ b/gfx/thebes/gfxFT2FontBase.cpp @@ -0,0 +1,735 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxFT2FontBase.h" +#include "gfxFT2Utils.h" +#include "harfbuzz/hb.h" +#include "mozilla/Likely.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "gfxFontConstants.h" +#include "gfxFontUtils.h" +#include <algorithm> +#include <dlfcn.h> + +#include FT_TRUETYPE_TAGS_H +#include FT_TRUETYPE_TABLES_H +#include FT_ADVANCES_H +#include FT_MULTIPLE_MASTERS_H + +#ifndef FT_LOAD_COLOR +# define FT_LOAD_COLOR (1L << 20) +#endif +#ifndef FT_FACE_FLAG_COLOR +# define FT_FACE_FLAG_COLOR (1L << 14) +#endif + +using namespace mozilla; +using namespace mozilla::gfx; + +gfxFT2FontBase::gfxFT2FontBase( + const RefPtr<UnscaledFontFreeType>& aUnscaledFont, + RefPtr<mozilla::gfx::SharedFTFace>&& aFTFace, gfxFontEntry* aFontEntry, + const gfxFontStyle* aFontStyle, int aLoadFlags, bool aEmbolden) + : gfxFont(aUnscaledFont, aFontEntry, aFontStyle, kAntialiasDefault), + mFTFace(std::move(aFTFace)), + mFTLoadFlags(aLoadFlags | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH | + FT_LOAD_COLOR), + mEmbolden(aEmbolden), + mFTSize(1.0) {} + +gfxFT2FontBase::~gfxFT2FontBase() { mFTFace->ForgetLockOwner(this); } + +FT_Face gfxFT2FontBase::LockFTFace() { + if (!mFTFace->Lock(this)) { + FT_Set_Transform(mFTFace->GetFace(), nullptr, nullptr); + + FT_F26Dot6 charSize = NS_lround(mFTSize * 64.0); + FT_Set_Char_Size(mFTFace->GetFace(), charSize, charSize, 0, 0); + } + return mFTFace->GetFace(); +} + +void gfxFT2FontBase::UnlockFTFace() { mFTFace->Unlock(); } + +gfxFT2FontEntryBase::CmapCacheSlot* gfxFT2FontEntryBase::GetCmapCacheSlot( + uint32_t aCharCode) { + // This cache algorithm and size is based on what is done in + // cairo_scaled_font_text_to_glyphs and pango_fc_font_real_get_glyph. I + // think the concept is that adjacent characters probably come mostly from + // one Unicode block. This assumption is probably not so valid with + // scripts with large character sets as used for East Asian languages. + if (!mCmapCache) { + mCmapCache = mozilla::MakeUnique<CmapCacheSlot[]>(kNumCmapCacheSlots); + + // Invalidate slot 0 by setting its char code to something that would + // never end up in slot 0. All other slots are already invalid + // because they have mCharCode = 0 and a glyph for char code 0 will + // always be in the slot 0. + mCmapCache[0].mCharCode = 1; + } + return &mCmapCache[aCharCode % kNumCmapCacheSlots]; +} + +static FT_ULong GetTableSizeFromFTFace(SharedFTFace* aFace, + uint32_t aTableTag) { + if (!aFace) { + return 0; + } + FT_ULong len = 0; + if (FT_Load_Sfnt_Table(aFace->GetFace(), aTableTag, 0, nullptr, &len) != 0) { + return 0; + } + return len; +} + +bool gfxFT2FontEntryBase::FaceHasTable(SharedFTFace* aFace, + uint32_t aTableTag) { + return GetTableSizeFromFTFace(aFace, aTableTag) > 0; +} + +nsresult gfxFT2FontEntryBase::CopyFaceTable(SharedFTFace* aFace, + uint32_t aTableTag, + nsTArray<uint8_t>& aBuffer) { + FT_ULong length = GetTableSizeFromFTFace(aFace, aTableTag); + if (!length) { + return NS_ERROR_NOT_AVAILABLE; + } + if (!aBuffer.SetLength(length, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + if (FT_Load_Sfnt_Table(aFace->GetFace(), aTableTag, 0, aBuffer.Elements(), + &length) != 0) { + aBuffer.Clear(); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +uint32_t 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 font entry. + auto* slot = static_cast<gfxFT2FontEntryBase*>(mFontEntry.get()) + ->GetCmapCacheSlot(aCharCode); + if (slot->mCharCode != aCharCode) { + slot->mCharCode = aCharCode; + slot->mGlyphIndex = gfxFT2LockedFace(this).GetGlyph(aCharCode); + } + return slot->mGlyphIndex; +} + +// aScale is intended for a 16.16 x/y_scale of an FT_Size_Metrics +static inline FT_Long ScaleRoundDesignUnits(FT_Short aDesignMetric, + FT_Fixed aScale) { + FT_Long fixed26dot6 = FT_MulFix(aDesignMetric, aScale); + return ROUND_26_6_TO_INT(fixed26dot6); +} + +// Snap a line to pixels while keeping the center and size of the line as +// close to the original position as possible. +// +// Pango does similar snapping for underline and strikethrough when fonts are +// hinted, but nsCSSRendering::GetTextDecorationRectInternal always snaps the +// top and size of lines. Optimizing the distance between the line and +// baseline is probably good for the gap between text and underline, but +// optimizing the center of the line is better for positioning strikethough. +static void SnapLineToPixels(gfxFloat& aOffset, gfxFloat& aSize) { + gfxFloat snappedSize = std::max(floor(aSize + 0.5), 1.0); + // Correct offset for change in size + gfxFloat offset = aOffset - 0.5 * (aSize - snappedSize); + // Snap offset + aOffset = floor(offset + 0.5); + aSize = snappedSize; +} + +static inline gfxRect ScaleGlyphBounds(const IntRect& aBounds, + gfxFloat aScale) { + return gfxRect(FLOAT_FROM_26_6(aBounds.x) * aScale, + FLOAT_FROM_26_6(aBounds.y) * aScale, + FLOAT_FROM_26_6(aBounds.width) * aScale, + FLOAT_FROM_26_6(aBounds.height) * aScale); +} + +/** + * Get extents for a simple character representable by a single glyph. + * The return value is the glyph id of that glyph or zero if no such glyph + * exists. aWidth/aBounds is only set when this returns a non-zero glyph id. + * This is just for use during initialization, and doesn't use the width cache. + */ +uint32_t gfxFT2FontBase::GetCharExtents(char aChar, gfxFloat* aWidth, + gfxRect* aBounds) { + FT_UInt gid = GetGlyph(aChar); + int32_t width; + IntRect bounds; + if (gid && GetFTGlyphExtents(gid, aWidth ? &width : nullptr, + aBounds ? &bounds : nullptr)) { + if (aWidth) { + *aWidth = FLOAT_FROM_16_16(width); + } + if (aBounds) { + *aBounds = ScaleGlyphBounds(bounds, GetAdjustedSize() / mFTSize); + } + return gid; + } else { + return 0; + } +} + +/** + * Find the closest available fixed strike size, if applicable, to the + * desired font size. + */ +static double FindClosestSize(FT_Face aFace, double aSize) { + // FT size selection does not actually support sizes smaller than 1 and will + // clamp this internally, regardless of what is requested. Do the clamp here + // instead so that glyph extents/font matrix scaling will compensate it, as + // Cairo normally would. + if (aSize < 1.0) { + aSize = 1.0; + } + if (FT_IS_SCALABLE(aFace)) { + return aSize; + } + double bestDist = DBL_MAX; + FT_Int bestSize = -1; + for (FT_Int i = 0; i < aFace->num_fixed_sizes; i++) { + double dist = aFace->available_sizes[i].y_ppem / 64.0 - aSize; + // If the previous best is smaller than the desired size, prefer + // a bigger size. Otherwise, just choose whatever size is closest. + if (bestDist < 0 ? dist >= bestDist : fabs(dist) <= bestDist) { + bestDist = dist; + bestSize = i; + } + } + if (bestSize < 0) { + return aSize; + } + return aFace->available_sizes[bestSize].y_ppem / 64.0; +} + +void gfxFT2FontBase::InitMetrics() { + mFUnitsConvFactor = 0.0; + + if (MOZ_UNLIKELY(GetStyle()->size <= 0.0) || + MOZ_UNLIKELY(GetStyle()->sizeAdjust == 0.0)) { + memset(&mMetrics, 0, sizeof(mMetrics)); // zero initialize + mSpaceGlyph = GetGlyph(' '); + return; + } + + // Cairo metrics are normalized to em-space, so that whatever fixed size + // might actually be chosen is factored out. They are then later scaled by + // the font matrix to the target adjusted size. Stash the chosen closest + // size here for later scaling of the metrics. + mFTSize = FindClosestSize(mFTFace->GetFace(), GetAdjustedSize()); + + // Explicitly lock the face so we can release it early before calling + // back into Cairo below. + FT_Face face = LockFTFace(); + + if (MOZ_UNLIKELY(!face)) { + // No face. This unfortunate situation might happen if the font + // file is (re)moved at the wrong time. + const gfxFloat emHeight = GetAdjustedSize(); + mMetrics.emHeight = emHeight; + mMetrics.maxAscent = mMetrics.emAscent = 0.8 * emHeight; + mMetrics.maxDescent = mMetrics.emDescent = 0.2 * emHeight; + mMetrics.maxHeight = emHeight; + mMetrics.internalLeading = 0.0; + mMetrics.externalLeading = 0.2 * emHeight; + const gfxFloat spaceWidth = 0.5 * emHeight; + mMetrics.spaceWidth = spaceWidth; + mMetrics.maxAdvance = spaceWidth; + mMetrics.aveCharWidth = spaceWidth; + mMetrics.zeroWidth = spaceWidth; + const gfxFloat xHeight = 0.5 * emHeight; + mMetrics.xHeight = xHeight; + mMetrics.capHeight = mMetrics.maxAscent; + const gfxFloat underlineSize = emHeight / 14.0; + mMetrics.underlineSize = underlineSize; + mMetrics.underlineOffset = -underlineSize; + mMetrics.strikeoutOffset = 0.25 * emHeight; + mMetrics.strikeoutSize = underlineSize; + + SanitizeMetrics(&mMetrics, false); + return; + } + + const FT_Size_Metrics& ftMetrics = face->size->metrics; + + mMetrics.maxAscent = FLOAT_FROM_26_6(ftMetrics.ascender); + mMetrics.maxDescent = -FLOAT_FROM_26_6(ftMetrics.descender); + mMetrics.maxAdvance = FLOAT_FROM_26_6(ftMetrics.max_advance); + gfxFloat lineHeight = FLOAT_FROM_26_6(ftMetrics.height); + + gfxFloat emHeight; + // Scale for vertical design metric conversion: pixels per design unit. + // If this remains at 0.0, we can't use metrics from OS/2 etc. + gfxFloat yScale = 0.0; + if (FT_IS_SCALABLE(face)) { + // Prefer FT_Size_Metrics::x_scale to x_ppem as x_ppem does not + // have subpixel accuracy. + // + // FT_Size_Metrics::y_scale is in 16.16 fixed point format. Its + // (fractional) value is a factor that converts vertical metrics from + // design units to units of 1/64 pixels, so that the result may be + // interpreted as pixels in 26.6 fixed point format. + mFUnitsConvFactor = FLOAT_FROM_26_6(FLOAT_FROM_16_16(ftMetrics.x_scale)); + yScale = FLOAT_FROM_26_6(FLOAT_FROM_16_16(ftMetrics.y_scale)); + emHeight = face->units_per_EM * yScale; + } else { // Not scalable. + emHeight = ftMetrics.y_ppem; + // FT_Face doc says units_per_EM and a bunch of following fields + // are "only relevant to scalable outlines". If it's an sfnt, + // we can get units_per_EM from the 'head' table instead; otherwise, + // we don't have a unitsPerEm value so we can't compute/use yScale or + // mFUnitsConvFactor (x scale). + const TT_Header* head = + static_cast<TT_Header*>(FT_Get_Sfnt_Table(face, ft_sfnt_head)); + if (head) { + // Bug 1267909 - Even if the font is not explicitly scalable, + // if the face has color bitmaps, it should be treated as scalable + // and scaled to the desired size. Metrics based on y_ppem need + // to be rescaled for the adjusted size. This makes metrics agree + // with the scales we pass to Cairo for Fontconfig fonts. + if (face->face_flags & FT_FACE_FLAG_COLOR) { + emHeight = GetAdjustedSize(); + gfxFloat adjustScale = emHeight / ftMetrics.y_ppem; + mMetrics.maxAscent *= adjustScale; + mMetrics.maxDescent *= adjustScale; + mMetrics.maxAdvance *= adjustScale; + lineHeight *= adjustScale; + } + gfxFloat emUnit = head->Units_Per_EM; + mFUnitsConvFactor = ftMetrics.x_ppem / emUnit; + yScale = emHeight / emUnit; + } + } + + TT_OS2* os2 = static_cast<TT_OS2*>(FT_Get_Sfnt_Table(face, ft_sfnt_os2)); + + if (os2 && os2->sTypoAscender && yScale > 0.0) { + mMetrics.emAscent = os2->sTypoAscender * yScale; + mMetrics.emDescent = -os2->sTypoDescender * yScale; + FT_Short typoHeight = + os2->sTypoAscender - os2->sTypoDescender + os2->sTypoLineGap; + lineHeight = typoHeight * yScale; + + // If the OS/2 fsSelection USE_TYPO_METRICS bit is set, + // set maxAscent/Descent from the sTypo* fields instead of hhea. + const uint16_t kUseTypoMetricsMask = 1 << 7; + if ((os2->fsSelection & kUseTypoMetricsMask) || + // maxAscent/maxDescent get used for frame heights, and some fonts + // don't have the HHEA table ascent/descent set (bug 279032). + (mMetrics.maxAscent == 0.0 && mMetrics.maxDescent == 0.0)) { + // We use NS_round here to parallel the pixel-rounded values that + // freetype gives us for ftMetrics.ascender/descender. + mMetrics.maxAscent = NS_round(mMetrics.emAscent); + mMetrics.maxDescent = NS_round(mMetrics.emDescent); + } + } else { + mMetrics.emAscent = mMetrics.maxAscent; + mMetrics.emDescent = mMetrics.maxDescent; + } + + // gfxFont::Metrics::underlineOffset is the position of the top of the + // underline. + // + // FT_FaceRec documentation describes underline_position as "the + // center of the underlining stem". This was the original definition + // of the PostScript metric, but in the PostScript table of OpenType + // fonts the metric is "the top of the underline" + // (http://www.microsoft.com/typography/otspec/post.htm), and FreeType + // (up to version 2.3.7) doesn't make any adjustment. + // + // Therefore get the underline position directly from the table + // ourselves when this table exists. Use FreeType's metrics for + // other (including older PostScript) fonts. + if (face->underline_position && face->underline_thickness && yScale > 0.0) { + mMetrics.underlineSize = face->underline_thickness * yScale; + TT_Postscript* post = + static_cast<TT_Postscript*>(FT_Get_Sfnt_Table(face, ft_sfnt_post)); + if (post && post->underlinePosition) { + mMetrics.underlineOffset = post->underlinePosition * yScale; + } else { + mMetrics.underlineOffset = + face->underline_position * yScale + 0.5 * mMetrics.underlineSize; + } + } else { // No underline info. + // Imitate Pango. + mMetrics.underlineSize = emHeight / 14.0; + mMetrics.underlineOffset = -mMetrics.underlineSize; + } + + if (os2 && os2->yStrikeoutSize && os2->yStrikeoutPosition && yScale > 0.0) { + mMetrics.strikeoutSize = os2->yStrikeoutSize * yScale; + mMetrics.strikeoutOffset = os2->yStrikeoutPosition * yScale; + } else { // No strikeout info. + mMetrics.strikeoutSize = mMetrics.underlineSize; + // Use OpenType spec's suggested position for Roman font. + mMetrics.strikeoutOffset = + emHeight * 409.0 / 2048.0 + 0.5 * mMetrics.strikeoutSize; + } + SnapLineToPixels(mMetrics.strikeoutOffset, mMetrics.strikeoutSize); + + if (os2 && os2->sxHeight && yScale > 0.0) { + mMetrics.xHeight = os2->sxHeight * yScale; + } else { + // CSS 2.1, section 4.3.2 Lengths: "In the cases where it is + // impossible or impractical to determine the x-height, a value of + // 0.5em should be used." + mMetrics.xHeight = 0.5 * emHeight; + } + + // aveCharWidth is used for the width of text input elements so be + // liberal rather than conservative in the estimate. + if (os2 && os2->xAvgCharWidth) { + // Round to pixels as this is compared with maxAdvance to guess + // whether this is a fixed width font. + mMetrics.aveCharWidth = + ScaleRoundDesignUnits(os2->xAvgCharWidth, ftMetrics.x_scale); + } else { + mMetrics.aveCharWidth = 0.0; // updated below + } + + if (os2 && os2->sCapHeight && yScale > 0.0) { + mMetrics.capHeight = os2->sCapHeight * yScale; + } else { + mMetrics.capHeight = mMetrics.maxAscent; + } + + // Release the face lock to safely load glyphs with GetCharExtents if + // necessary without recursively locking. + UnlockFTFace(); + + gfxFloat width; + mSpaceGlyph = GetCharExtents(' ', &width); + if (mSpaceGlyph) { + mMetrics.spaceWidth = width; + } else { + mMetrics.spaceWidth = mMetrics.maxAdvance; // guess + } + + if (GetCharExtents('0', &width)) { + mMetrics.zeroWidth = width; + } else { + mMetrics.zeroWidth = -1.0; // indicates not found + } + + // 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. + gfxFloat xWidth; + gfxRect xBounds; + if (GetCharExtents('x', &xWidth, &xBounds) && xBounds.y < 0.0) { + mMetrics.xHeight = -xBounds.y; + mMetrics.aveCharWidth = std::max(mMetrics.aveCharWidth, xWidth); + } + + if (GetCharExtents('H', nullptr, &xBounds) && xBounds.y < 0.0) { + mMetrics.capHeight = -xBounds.y; + } + + mMetrics.aveCharWidth = std::max(mMetrics.aveCharWidth, mMetrics.zeroWidth); + if (mMetrics.aveCharWidth == 0.0) { + mMetrics.aveCharWidth = mMetrics.spaceWidth; + } + // Apparently hinting can mean that max_advance is not always accurate. + mMetrics.maxAdvance = std::max(mMetrics.maxAdvance, mMetrics.aveCharWidth); + + mMetrics.maxHeight = mMetrics.maxAscent + mMetrics.maxDescent; + + // Make the line height an integer number of pixels so that lines will be + // equally spaced (rather than just being snapped to pixels, some up and + // some down). Layout calculates line height from the emHeight + + // internalLeading + externalLeading, but first each of these is rounded + // to layout units. To ensure that the result is an integer number of + // pixels, round each of the components to pixels. + mMetrics.emHeight = floor(emHeight + 0.5); + + // maxHeight will normally be an integer, but round anyway in case + // FreeType is configured differently. + mMetrics.internalLeading = + floor(mMetrics.maxHeight - mMetrics.emHeight + 0.5); + + // Text input boxes currently don't work well with lineHeight + // significantly less than maxHeight (with Verdana, for example). + lineHeight = floor(std::max(lineHeight, mMetrics.maxHeight) + 0.5); + mMetrics.externalLeading = + lineHeight - mMetrics.internalLeading - mMetrics.emHeight; + + // Ensure emAscent + emDescent == emHeight + gfxFloat sum = mMetrics.emAscent + mMetrics.emDescent; + mMetrics.emAscent = + sum > 0.0 ? mMetrics.emAscent * mMetrics.emHeight / sum : 0.0; + mMetrics.emDescent = mMetrics.emHeight - mMetrics.emAscent; + + SanitizeMetrics(&mMetrics, false); + +#if 0 + // printf("font name: %s %f\n", NS_ConvertUTF16toUTF8(GetName()).get(), GetStyle()->size); + // printf ("pango font %s\n", pango_font_description_to_string (pango_font_describe (font))); + + fprintf (stderr, "Font: %s\n", 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 +} + +const gfxFont::Metrics& gfxFT2FontBase::GetHorizontalMetrics() { + return mMetrics; +} + +uint32_t gfxFT2FontBase::GetGlyph(uint32_t unicode, + uint32_t variation_selector) { + if (variation_selector) { + uint32_t id = + gfxFT2LockedFace(this).GetUVSGlyph(unicode, variation_selector); + if (id) { + return id; + } + unicode = gfxFontUtils::GetUVSFallback(unicode, variation_selector); + if (unicode) { + return GetGlyph(unicode); + } + return 0; + } + + return GetGlyph(unicode); +} + +bool gfxFT2FontBase::ShouldRoundXOffset(cairo_t* aCairo) const { + // Force rounding if outputting to a Cairo context or if requested by pref to + // disable subpixel positioning. Otherwise, allow subpixel positioning (no + // rounding) if rendering a scalable outline font with anti-aliasing. + // Monochrome rendering or some bitmap fonts can become too distorted with + // subpixel positioning, so force rounding in those cases. Also be careful not + // to use subpixel positioning if the user requests full hinting via + // Fontconfig, which we detect by checking that neither hinting was disabled + // nor light hinting was requested. Allow pref to force subpixel positioning + // on even if full hinting was requested. + return MOZ_UNLIKELY( + StaticPrefs:: + gfx_text_subpixel_position_force_disabled_AtStartup()) || + aCairo != nullptr || !mFTFace || !FT_IS_SCALABLE(mFTFace->GetFace()) || + (mFTLoadFlags & FT_LOAD_MONOCHROME) || + !((mFTLoadFlags & FT_LOAD_NO_HINTING) || + FT_LOAD_TARGET_MODE(mFTLoadFlags) == FT_RENDER_MODE_LIGHT || + MOZ_UNLIKELY( + StaticPrefs:: + gfx_text_subpixel_position_force_enabled_AtStartup())); +} + +FT_Vector gfxFT2FontBase::GetEmboldenStrength(FT_Face aFace) { + FT_Vector strength = {0, 0}; + if (!mEmbolden) { + return strength; + } + + // If it's an outline glyph, we'll be using mozilla_glyphslot_embolden_less + // (see gfx/wr/webrender/src/platform/unix/font.rs), so we need to match its + // emboldening strength here. + if (aFace->glyph->format == FT_GLYPH_FORMAT_OUTLINE) { + strength.x = + FT_MulFix(aFace->units_per_EM, aFace->size->metrics.y_scale) / 48; + strength.y = strength.x; + return strength; + } + + // This is the embolden "strength" used by FT_GlyphSlot_Embolden. + strength.x = + FT_MulFix(aFace->units_per_EM, aFace->size->metrics.y_scale) / 24; + strength.y = strength.x; + if (aFace->glyph->format == FT_GLYPH_FORMAT_BITMAP) { + strength.x &= -64; + if (!strength.x) { + strength.x = 64; + } + strength.y &= -64; + } + return strength; +} + +bool gfxFT2FontBase::GetFTGlyphExtents(uint16_t aGID, int32_t* aAdvance, + IntRect* aBounds) { + gfxFT2LockedFace face(this); + MOZ_ASSERT(face.get()); + if (!face.get()) { + // Failed to get the FT_Face? Give up already. + NS_WARNING("failed to get FT_Face!"); + return false; + } + + FT_Int32 flags = mFTLoadFlags; + if (!aBounds) { + flags |= FT_LOAD_ADVANCE_ONLY; + } + if (Factory::LoadFTGlyph(face.get(), aGID, flags) != FT_Err_Ok) { + // FT_Face was somehow broken/invalid? Don't try to access glyph slot. + // This probably shouldn't happen, but does: see bug 1440938. + NS_WARNING("failed to load glyph!"); + return false; + } + + bool hintMetrics = ShouldHintMetrics(); + + // Normalize out the loaded FT glyph size and then scale to the actually + // desired size, in case these two sizes differ. + gfxFloat extentsScale = GetAdjustedSize() / mFTSize; + + FT_Vector bold = GetEmboldenStrength(face.get()); + + // Due to freetype bug 52683 we MUST use the linearHoriAdvance field when + // dealing with a variation font; also use it for scalable fonts when not + // applying hinting. Otherwise, prefer hinted width from glyph->advance.x. + if (aAdvance) { + FT_Fixed advance; + if (!ShouldRoundXOffset(nullptr) || FT_HAS_MULTIPLE_MASTERS(face.get())) { + advance = face.get()->glyph->linearHoriAdvance; + } else { + advance = face.get()->glyph->advance.x << 10; // convert 26.6 to 16.16 + } + if (advance) { + advance += bold.x << 10; // convert 26.6 to 16.16 + } + // Hinting was requested, but FT did not apply any hinting to the metrics. + // Round the advance here to approximate hinting as Cairo does. This must + // happen BEFORE we apply the glyph extents scale, just like FT hinting + // would. + if (hintMetrics && (mFTLoadFlags & FT_LOAD_NO_HINTING)) { + advance = (advance + 0x8000) & 0xffff0000u; + } + *aAdvance = NS_lround(advance * extentsScale); + } + + if (aBounds) { + const FT_Glyph_Metrics& metrics = face.get()->glyph->metrics; + FT_F26Dot6 x = metrics.horiBearingX; + FT_F26Dot6 y = -metrics.horiBearingY; + FT_F26Dot6 x2 = x + metrics.width; + FT_F26Dot6 y2 = y + metrics.height; + // Synthetic bold moves the glyph top and right boundaries. + y -= bold.y; + x2 += bold.x; + if (hintMetrics && (mFTLoadFlags & FT_LOAD_NO_HINTING)) { + x &= -64; + y &= -64; + x2 = (x2 + 63) & -64; + y2 = (y2 + 63) & -64; + } + *aBounds = IntRect(x, y, x2 - x, y2 - y); + } + return true; +} + +/** + * Get the cached glyph metrics for the glyph id if available. Otherwise, query + * FreeType for the glyph extents and initialize the glyph metrics. + */ +const gfxFT2FontBase::GlyphMetrics& gfxFT2FontBase::GetCachedGlyphMetrics( + uint16_t aGID, IntRect* aBounds) { + if (!mGlyphMetrics) { + mGlyphMetrics = + mozilla::MakeUnique<nsDataHashtable<nsUint32HashKey, GlyphMetrics>>( + 128); + } + + if (const GlyphMetrics* metrics = mGlyphMetrics->GetValue(aGID)) { + return *metrics; + } + + GlyphMetrics& metrics = mGlyphMetrics->GetOrInsert(aGID); + IntRect bounds; + if (GetFTGlyphExtents(aGID, &metrics.mAdvance, &bounds)) { + metrics.SetBounds(bounds); + if (aBounds) { + *aBounds = bounds; + } + } + return metrics; +} + +int32_t gfxFT2FontBase::GetGlyphWidth(uint16_t aGID) { + return GetCachedGlyphMetrics(aGID).mAdvance; +} + +bool gfxFT2FontBase::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, + bool aTight) { + IntRect bounds; + const GlyphMetrics& metrics = GetCachedGlyphMetrics(aGID, &bounds); + if (!metrics.HasValidBounds()) { + return false; + } + // Check if there are cached bounds and use those if available. Otherwise, + // fall back to directly querying the glyph extents. + if (metrics.HasCachedBounds()) { + bounds = metrics.GetBounds(); + } else if (bounds.IsEmpty() && !GetFTGlyphExtents(aGID, nullptr, &bounds)) { + return false; + } + // The bounds are stored unscaled, so must be scaled to the adjusted size. + *aBounds = ScaleGlyphBounds(bounds, GetAdjustedSize() / mFTSize); + return true; +} + +// For variation fonts, figure out the variation coordinates to be applied +// for each axis, in freetype's order (which may not match the order of +// axes in mStyle.variationSettings, so we need to search by axis tag). +/*static*/ +void gfxFT2FontBase::SetupVarCoords( + FT_MM_Var* aMMVar, const nsTArray<gfxFontVariation>& aVariations, + FT_Face aFTFace) { + if (!aMMVar) { + return; + } + + nsTArray<FT_Fixed> coords; + for (unsigned i = 0; i < aMMVar->num_axis; ++i) { + coords.AppendElement(aMMVar->axis[i].def); + for (const auto& v : aVariations) { + if (aMMVar->axis[i].tag == v.mTag) { + FT_Fixed val = v.mValue * 0x10000; + val = std::min(val, aMMVar->axis[i].maximum); + val = std::max(val, aMMVar->axis[i].minimum); + coords[i] = val; + break; + } + } + } + + if (!coords.IsEmpty()) { +#if MOZ_TREE_FREETYPE + FT_Set_Var_Design_Coordinates(aFTFace, coords.Length(), coords.Elements()); +#else + typedef FT_Error (*SetCoordsFunc)(FT_Face, FT_UInt, FT_Fixed*); + static SetCoordsFunc setCoords; + static bool firstTime = true; + if (firstTime) { + firstTime = false; + setCoords = + (SetCoordsFunc)dlsym(RTLD_DEFAULT, "FT_Set_Var_Design_Coordinates"); + } + if (setCoords) { + (*setCoords)(aFTFace, coords.Length(), coords.Elements()); + } +#endif + } +} + +already_AddRefed<SharedFTFace> FTUserFontData::CloneFace(int aFaceIndex) { + RefPtr<SharedFTFace> face = Factory::NewSharedFTFaceFromData( + nullptr, mFontData, mLength, aFaceIndex, this); + if (!face || + (FT_Select_Charmap(face->GetFace(), FT_ENCODING_UNICODE) != FT_Err_Ok && + FT_Select_Charmap(face->GetFace(), FT_ENCODING_MS_SYMBOL) != + FT_Err_Ok)) { + return nullptr; + } + return face.forget(); +} diff --git a/gfx/thebes/gfxFT2FontBase.h b/gfx/thebes/gfxFT2FontBase.h new file mode 100644 index 0000000000..8575bb0666 --- /dev/null +++ b/gfx/thebes/gfxFT2FontBase.h @@ -0,0 +1,173 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FT2FONTBASE_H +#define GFX_FT2FONTBASE_H + +#include "gfxContext.h" +#include "gfxFont.h" +#include "gfxFontEntry.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/UnscaledFontFreeType.h" +#include "nsDataHashtable.h" +#include "nsHashKeys.h" + +class gfxFT2FontEntryBase : public gfxFontEntry { + public: + explicit gfxFT2FontEntryBase(const nsACString& aName) : gfxFontEntry(aName) {} + + struct CmapCacheSlot { + CmapCacheSlot() : mCharCode(0), mGlyphIndex(0) {} + + uint32_t mCharCode; + uint32_t mGlyphIndex; + }; + + CmapCacheSlot* GetCmapCacheSlot(uint32_t aCharCode); + + static bool FaceHasTable(mozilla::gfx::SharedFTFace*, uint32_t aTableTag); + static nsresult CopyFaceTable(mozilla::gfx::SharedFTFace*, uint32_t aTableTag, + nsTArray<uint8_t>&); + + private: + enum { kNumCmapCacheSlots = 256 }; + + mozilla::UniquePtr<CmapCacheSlot[]> mCmapCache; +}; + +class gfxFT2FontBase : public gfxFont { + public: + gfxFT2FontBase( + const RefPtr<mozilla::gfx::UnscaledFontFreeType>& aUnscaledFont, + RefPtr<mozilla::gfx::SharedFTFace>&& aFTFace, gfxFontEntry* aFontEntry, + const gfxFontStyle* aFontStyle, int aLoadFlags, bool aEmbolden); + virtual ~gfxFT2FontBase(); + + uint32_t GetGlyph(uint32_t aCharCode); + bool ProvidesGetGlyph() const override { return true; } + virtual uint32_t GetGlyph(uint32_t unicode, + uint32_t variation_selector) override; + bool ProvidesGlyphWidths() const override { return true; } + int32_t GetGlyphWidth(uint16_t aGID) override; + bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override; + + FontType GetType() const override { return FONT_TYPE_FT2; } + + bool ShouldRoundXOffset(cairo_t* aCairo) const override; + + static void SetupVarCoords(FT_MM_Var* aMMVar, + const nsTArray<gfxFontVariation>& aVariations, + FT_Face aFTFace); + + FT_Face LockFTFace(); + void UnlockFTFace(); + + private: + uint32_t GetCharExtents(char aChar, gfxFloat* aWidth, + gfxRect* aBounds = nullptr); + + // Get advance (and optionally bounds) of a single glyph from FreeType, + // and return true, or return false if we failed. + bool GetFTGlyphExtents(uint16_t aGID, int32_t* aWidth, + mozilla::gfx::IntRect* aBounds = nullptr); + + protected: + void InitMetrics(); + const Metrics& GetHorizontalMetrics() override; + FT_Vector GetEmboldenStrength(FT_Face aFace); + + RefPtr<mozilla::gfx::SharedFTFace> mFTFace; + + Metrics mMetrics; + int mFTLoadFlags; + bool mEmbolden; + gfxFloat mFTSize; + + // For variation/multiple-master fonts, this will be an array of the values + // for each axis, as specified by mStyle.variationSettings (or the font's + // default for axes not present in variationSettings). Values here are in + // freetype's 16.16 fixed-point format, and clamped to the valid min/max + // range reported by the face. + nsTArray<FT_Fixed> mCoords; + + // Store cached glyph metrics for reasonably small glyph sizes. The bounds + // are stored unscaled to losslessly compress 26.6 fixed point to an int16_t. + // Larger glyphs are handled directly via GetFTGlyphExtents. + struct GlyphMetrics { + // Set the X coord to INT16_MIN to signal the bounds are invalid, or + // INT16_MAX to signal that the bounds would overflow an int16_t. + enum { INVALID = INT16_MIN, LARGE = INT16_MAX }; + + GlyphMetrics() : mAdvance(0), mX(INVALID), mY(0), mWidth(0), mHeight(0) {} + + bool HasValidBounds() const { return mX != INVALID; } + bool HasCachedBounds() const { return mX != LARGE; } + + // If the bounds can fit in an int16_t, set them. Otherwise, leave the + // bounds invalid to signal that GetFTGlyphExtents should be queried + // directly. + void SetBounds(const mozilla::gfx::IntRect& aBounds) { + if (aBounds.x > INT16_MIN && aBounds.x < INT16_MAX && + aBounds.y > INT16_MIN && aBounds.y < INT16_MAX && + aBounds.width <= UINT16_MAX && aBounds.height <= UINT16_MAX) { + mX = aBounds.x; + mY = aBounds.y; + mWidth = aBounds.width; + mHeight = aBounds.height; + } else { + mX = LARGE; + } + } + + mozilla::gfx::IntRect GetBounds() const { + return mozilla::gfx::IntRect(mX, mY, mWidth, mHeight); + } + + int32_t mAdvance; + int16_t mX; + int16_t mY; + uint16_t mWidth; + uint16_t mHeight; + }; + + const GlyphMetrics& GetCachedGlyphMetrics( + uint16_t aGID, mozilla::gfx::IntRect* aBounds = nullptr); + + mozilla::UniquePtr<nsDataHashtable<nsUint32HashKey, GlyphMetrics>> + mGlyphMetrics; +}; + +// Helper classes used for clearing out user font data when FT 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 FT face created from that font entry contains a refptr to that +// same FTUserFontData object. + +class FTUserFontData final + : public mozilla::gfx::SharedFTFaceRefCountedData<FTUserFontData> { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FTUserFontData) + + FTUserFontData(const uint8_t* aData, uint32_t aLength) + : mFontData(aData), mLength(aLength) {} + + const uint8_t* FontData() const { return mFontData; } + + already_AddRefed<mozilla::gfx::SharedFTFace> CloneFace( + int aFaceIndex = 0) override; + + private: + ~FTUserFontData() { + if (mFontData) { + free((void*)mFontData); + } + } + + const uint8_t* mFontData; + uint32_t mLength; +}; + +#endif /* GFX_FT2FONTBASE_H */ diff --git a/gfx/thebes/gfxFT2FontList.cpp b/gfx/thebes/gfxFT2FontList.cpp new file mode 100644 index 0000000000..81ddece4cd --- /dev/null +++ b/gfx/thebes/gfxFT2FontList.cpp @@ -0,0 +1,1816 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Base64.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/MemoryReporting.h" + +#include "mozilla/dom/ContentChild.h" +#include "gfxAndroidPlatform.h" +#include "mozilla/Omnijar.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsReadableUtils.h" + +#include "nsXULAppAPI.h" +#include <dirent.h> +#include <android/log.h> +#define ALOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gecko", ##args) + +#include "ft2build.h" +#include FT_FREETYPE_H +#include FT_TRUETYPE_TAGS_H +#include FT_TRUETYPE_TABLES_H +#include FT_MULTIPLE_MASTERS_H +#include "cairo-ft.h" + +#include "gfxFT2FontList.h" +#include "gfxFT2Fonts.h" +#include "gfxFT2Utils.h" +#include "gfxUserFontSet.h" +#include "gfxFontUtils.h" +#include "SharedFontList-impl.h" +#include "harfbuzz/hb-ot.h" // for name ID constants + +#include "nsServiceManagerUtils.h" +#include "nsIObserverService.h" +#include "nsTArray.h" +#include "nsUnicharUtils.h" +#include "nsCRT.h" + +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsIMemory.h" +#include "nsMemory.h" +#include "gfxFontConstants.h" + +#include "mozilla/EndianUtils.h" +#include "mozilla/Preferences.h" +#include "mozilla/scache/StartupCache.h" +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/stat.h> + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/jni/Utils.h" +# include <dlfcn.h> +#endif + +using namespace mozilla; +using namespace mozilla::gfx; + +static LazyLogModule sFontInfoLog("fontInfoLog"); + +#undef LOG +#define LOG(args) MOZ_LOG(sFontInfoLog, mozilla::LogLevel::Debug, args) +#define LOG_ENABLED() MOZ_LOG_TEST(sFontInfoLog, mozilla::LogLevel::Debug) + +static __inline void BuildKeyNameFromFontName(nsACString& aName) { + ToLowerCase(aName); +} + +// Helper to access the FT_Face for a given FT2FontEntry, +// creating a temporary face if the entry does not have one yet. +// This allows us to read font names, tables, etc if necessary +// without permanently instantiating a freetype face and consuming +// memory long-term. +// This may fail (resulting in a null FT_Face), e.g. if it fails to +// allocate memory to uncompress a font from omnijar. +already_AddRefed<SharedFTFace> FT2FontEntry::GetFTFace(bool aCommit) { + if (mFTFace) { + return do_AddRef(mFTFace); + } + + NS_ASSERTION(!mFilename.IsEmpty(), + "can't use GetFTFace for fonts without a filename"); + + // A relative path (no initial "/") means this is a resource in + // omnijar, not an installed font on the device. + // The NS_ASSERTIONs here should never fail, as the resource must have + // been read successfully during font-list initialization or we'd never + // have created the font entry. The only legitimate runtime failure + // here would be memory allocation, in which case mFace remains null. + RefPtr<SharedFTFace> face; + if (mFilename[0] != '/') { + RefPtr<nsZipArchive> reader = Omnijar::GetReader(Omnijar::Type::GRE); + nsZipItem* item = reader->GetItem(mFilename.get()); + NS_ASSERTION(item, "failed to find zip entry"); + + uint32_t bufSize = item->RealSize(); + uint8_t* fontDataBuf = static_cast<uint8_t*>(malloc(bufSize)); + if (fontDataBuf) { + nsZipCursor cursor(item, reader, fontDataBuf, bufSize); + cursor.Copy(&bufSize); + NS_ASSERTION(bufSize == item->RealSize(), "error reading bundled font"); + RefPtr<FTUserFontData> ufd = new FTUserFontData(fontDataBuf, bufSize); + face = ufd->CloneFace(mFTFontIndex); + if (!face) { + NS_WARNING("failed to create freetype face"); + return nullptr; + } + } + } else { + face = Factory::NewSharedFTFace(nullptr, mFilename.get(), mFTFontIndex); + if (!face) { + NS_WARNING("failed to create freetype face"); + return nullptr; + } + if (FT_Err_Ok != FT_Select_Charmap(face->GetFace(), FT_ENCODING_UNICODE) && + FT_Err_Ok != + FT_Select_Charmap(face->GetFace(), FT_ENCODING_MS_SYMBOL)) { + NS_WARNING("failed to select Unicode or symbol charmap"); + } + } + + if (aCommit) { + mFTFace = face; + } + + return face.forget(); +} + +FTUserFontData* FT2FontEntry::GetUserFontData() { + if (mFTFace && mFTFace->GetData()) { + return static_cast<FTUserFontData*>(mFTFace->GetData()); + } + return nullptr; +} + +/* + * FT2FontEntry + * gfxFontEntry subclass corresponding to a specific face that can be + * rendered by freetype. This is associated with a face index in a + * file (normally a .ttf/.otf file holding a single face, but in principle + * there could be .ttc files with multiple faces). + * The FT2FontEntry can create the necessary FT_Face on demand, and can + * then create a Cairo font_face and scaled_font for drawing. + */ + +FT2FontEntry::~FT2FontEntry() { + if (mMMVar) { + FT_Done_MM_Var(mFTFace->GetFace()->glyph->library, mMMVar); + } +} + +gfxFontEntry* FT2FontEntry::Clone() const { + MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!"); + FT2FontEntry* fe = new FT2FontEntry(Name()); + fe->mFilename = mFilename; + fe->mFTFontIndex = mFTFontIndex; + fe->mWeightRange = mWeightRange; + fe->mStretchRange = mStretchRange; + fe->mStyleRange = mStyleRange; + return fe; +} + +gfxFont* FT2FontEntry::CreateFontInstance(const gfxFontStyle* aStyle) { + RefPtr<SharedFTFace> face = GetFTFace(true); + if (!face) { + return nullptr; + } + + // If variations are present, we will not use our cached mFTFace + // but always create a new one as it will have custom variation + // coordinates applied. + if ((!mVariationSettings.IsEmpty() || + (aStyle && !aStyle->variationSettings.IsEmpty())) && + (face->GetFace()->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS)) { + // Create a separate FT_Face because we need to apply custom + // variation settings to it. + RefPtr<SharedFTFace> varFace; + if (!mFilename.IsEmpty() && mFilename[0] == '/') { + varFace = + Factory::NewSharedFTFace(nullptr, mFilename.get(), mFTFontIndex); + } else { + varFace = face->GetData()->CloneFace(mFTFontIndex); + } + if (varFace) { + // Resolve variations from entry (descriptor) and style (property) + AutoTArray<gfxFontVariation, 8> settings; + GetVariationsForStyle(settings, aStyle ? *aStyle : gfxFontStyle()); + gfxFT2FontBase::SetupVarCoords(GetMMVar(), settings, varFace->GetFace()); + face = std::move(varFace); + } + } + + int loadFlags = gfxPlatform::GetPlatform()->FontHintingEnabled() + ? FT_LOAD_DEFAULT + : (FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING); + if (face->GetFace()->face_flags & FT_FACE_FLAG_TRICKY) { + loadFlags &= ~FT_LOAD_NO_AUTOHINT; + } + + RefPtr<UnscaledFontFreeType> unscaledFont(mUnscaledFont); + if (!unscaledFont) { + unscaledFont = !mFilename.IsEmpty() && mFilename[0] == '/' + ? new UnscaledFontFreeType(mFilename.BeginReading(), + mFTFontIndex, mFTFace) + : new UnscaledFontFreeType(mFTFace); + mUnscaledFont = unscaledFont; + } + + gfxFont* font = + new gfxFT2Font(unscaledFont, std::move(face), this, aStyle, loadFlags); + return font; +} + +/* static */ +FT2FontEntry* FT2FontEntry::CreateFontEntry( + const nsACString& aFontName, WeightRange aWeight, StretchRange aStretch, + SlantStyleRange aStyle, const uint8_t* aFontData, uint32_t aLength) { + // Ownership of aFontData is passed in here; the fontEntry must + // retain it as long as the FT_Face needs it, and ensure it is + // eventually deleted. + RefPtr<FTUserFontData> ufd = new FTUserFontData(aFontData, aLength); + RefPtr<SharedFTFace> face = ufd->CloneFace(); + if (!face) { + return nullptr; + } + // Create our FT2FontEntry, which inherits the name of the userfont entry + // as it's not guaranteed that the face has valid names (bug 737315) + FT2FontEntry* fe = + FT2FontEntry::CreateFontEntry(aFontName, nullptr, 0, nullptr); + if (fe) { + fe->mFTFace = face; + fe->mStyleRange = aStyle; + fe->mWeightRange = aWeight; + fe->mStretchRange = aStretch; + fe->mIsDataUserFont = true; + } + return fe; +} + +/* static */ +FT2FontEntry* FT2FontEntry::CreateFontEntry(const FontListEntry& aFLE) { + FT2FontEntry* fe = new FT2FontEntry(aFLE.faceName()); + fe->mFilename = aFLE.filepath(); + fe->mFTFontIndex = aFLE.index(); + fe->mWeightRange = WeightRange::FromScalar(aFLE.weightRange()); + fe->mStretchRange = StretchRange::FromScalar(aFLE.stretchRange()); + fe->mStyleRange = SlantStyleRange::FromScalar(aFLE.styleRange()); + return fe; +} + +// Extract font entry properties from an hb_face_t +static void SetPropertiesFromFace(gfxFontEntry* aFontEntry, + const hb_face_t* aFace) { + // OS2 width class to CSS 'stretch' mapping from + // https://docs.microsoft.com/en-gb/typography/opentype/spec/os2#uswidthclass + const float kOS2WidthToStretch[] = { + 100, // (invalid, treat as normal) + 50, // Ultra-condensed + 62.5, // Extra-condensed + 75, // Condensed + 87.5, // Semi-condensed + 100, // Normal + 112.5, // Semi-expanded + 125, // Expanded + 150, // Extra-expanded + 200 // Ultra-expanded + }; + + // Get the macStyle field from the 'head' table + hb_blob_t* blob = hb_face_reference_table(aFace, HB_TAG('h', 'e', 'a', 'd')); + unsigned int len; + const char* data = hb_blob_get_data(blob, &len); + uint16_t style = 0; + if (len >= sizeof(HeadTable)) { + const HeadTable* head = reinterpret_cast<const HeadTable*>(data); + style = head->macStyle; + } + hb_blob_destroy(blob); + + // Get the OS/2 table for weight & width fields + blob = hb_face_reference_table(aFace, HB_TAG('O', 'S', '/', '2')); + data = hb_blob_get_data(blob, &len); + uint16_t os2weight = 400; + float stretch = 100.0; + if (len >= offsetof(OS2Table, fsType)) { + const OS2Table* os2 = reinterpret_cast<const OS2Table*>(data); + os2weight = os2->usWeightClass; + uint16_t os2width = os2->usWidthClass; + if (os2width < ArrayLength(kOS2WidthToStretch)) { + stretch = kOS2WidthToStretch[os2width]; + } + } + hb_blob_destroy(blob); + + aFontEntry->mStyleRange = SlantStyleRange( + (style & 2) ? FontSlantStyle::Italic() : FontSlantStyle::Normal()); + aFontEntry->mWeightRange = WeightRange(FontWeight(int(os2weight))); + aFontEntry->mStretchRange = StretchRange(FontStretch(stretch)); +} + +// Used to create the font entry for installed faces on the device, +// when iterating over the fonts directories. +// We use the hb_face_t to retrieve the details needed for the font entry, +// but do *not* save a reference to it, nor create a cairo face. +/* static */ +FT2FontEntry* FT2FontEntry::CreateFontEntry(const nsACString& aName, + const char* aFilename, + uint8_t aIndex, + const hb_face_t* aFace) { + FT2FontEntry* fe = new FT2FontEntry(aName); + fe->mFilename = aFilename; + fe->mFTFontIndex = aIndex; + + if (aFace) { + SetPropertiesFromFace(fe, aFace); + } else { + // If nullptr is passed for aFace, the caller is intending to override + // these attributes anyway. We just set defaults here to be safe. + fe->mStyleRange = SlantStyleRange(FontSlantStyle::Normal()); + fe->mWeightRange = WeightRange(FontWeight::Normal()); + fe->mStretchRange = StretchRange(FontStretch::Normal()); + } + + return fe; +} + +FT2FontEntry* gfxFT2Font::GetFontEntry() { + return static_cast<FT2FontEntry*>(mFontEntry.get()); +} + +// Copied/modified from similar code in gfxMacPlatformFontList.mm: +// Complex scripts will not render correctly unless Graphite or OT +// layout tables are present. +// For OpenType, we also check that the GSUB table supports the relevant +// script tag, to avoid using things like Arial Unicode MS for Lao (it has +// the characters, but lacks OpenType support). + +// TODO: consider whether we should move this to gfxFontEntry and do similar +// cmap-masking on all platforms to avoid using fonts that won't shape +// properly. + +nsresult FT2FontEntry::ReadCMAP(FontInfoData* aFontInfoData) { + if (mCharacterMap || mShmemCharacterMap) { + return NS_OK; + } + + RefPtr<gfxCharacterMap> charmap = new gfxCharacterMap(); + + nsresult rv = NS_ERROR_NOT_AVAILABLE; + hb_blob_t* cmapBlob = GetFontTable(TTAG_cmap); + if (cmapBlob) { + unsigned int length; + const char* data = hb_blob_get_data(cmapBlob, &length); + rv = gfxFontUtils::ReadCMAP((const uint8_t*)data, length, *charmap, + mUVSOffset); + hb_blob_destroy(cmapBlob); + } + + if (NS_SUCCEEDED(rv) && !mIsDataUserFont && !HasGraphiteTables()) { + // For downloadable fonts, trust the author and don't + // try to munge the cmap based on script shaping support. + + // We also assume a Graphite font knows what it's doing, + // and provides whatever shaping is needed for the + // characters it supports, so only check/clear the + // complex-script ranges for non-Graphite fonts + + // for layout support, check for the presence of opentype layout tables + bool hasGSUB = HasFontTable(TRUETYPE_TAG('G', 'S', 'U', 'B')); + + for (const ScriptRange* sr = gfxPlatformFontList::sComplexScriptRanges; + sr->rangeStart; sr++) { + // check to see if the cmap includes complex script codepoints + if (charmap->TestRange(sr->rangeStart, sr->rangeEnd)) { + // We check for GSUB here, as GPOS alone would not be ok. + if (hasGSUB && SupportsScriptInGSUB(sr->tags, sr->numTags)) { + continue; + } + charmap->ClearRange(sr->rangeStart, sr->rangeEnd); + } + } + } + +#ifdef MOZ_WIDGET_ANDROID + // Hack for the SamsungDevanagari font, bug 1012365: + // pretend the font supports U+0972. + if (!charmap->test(0x0972) && charmap->test(0x0905) && + charmap->test(0x0945)) { + charmap->set(0x0972); + } +#endif + + mHasCmapTable = NS_SUCCEEDED(rv); + + if (mHasCmapTable) { + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + fontlist::FontList* sharedFontList = pfl->SharedFontList(); + if (!IsUserFont() && mShmemFace) { + mShmemFace->SetCharacterMap(sharedFontList, charmap); // async + if (!TrySetShmemCharacterMap()) { + // Temporarily retain charmap, until the shared version is + // ready for use. + mCharacterMap = charmap; + } + } else { + mCharacterMap = pfl->FindCharMap(charmap); + } + } else { + // if error occurred, initialize to null cmap + mCharacterMap = new gfxCharacterMap(); + } + + return rv; +} + +hb_face_t* FT2FontEntry::CreateHBFace() const { + hb_face_t* result = nullptr; + + if (mFilename[0] == '/') { + // An absolute path means a normal file in the filesystem, so we can use + // hb_blob_create_from_file to read it. + hb_blob_t* fileBlob = hb_blob_create_from_file(mFilename.get()); + if (hb_blob_get_length(fileBlob) > 0) { + result = hb_face_create(fileBlob, mFTFontIndex); + } + hb_blob_destroy(fileBlob); + } else { + // A relative path means an omnijar resource, which we may need to + // decompress to a temporary buffer. + RefPtr<nsZipArchive> reader = Omnijar::GetReader(Omnijar::Type::GRE); + nsZipItem* item = reader->GetItem(mFilename.get()); + MOZ_ASSERT(item, "failed to find zip entry"); + if (item) { + // TODO(jfkthame): + // Check whether the item is compressed; if not, we could just get a + // pointer without needing to allocate a buffer and copy the data. + // (Currently this configuration isn't used for Gecko on Android.) + uint32_t length = item->RealSize(); + uint8_t* buffer = static_cast<uint8_t*>(malloc(length)); + if (buffer) { + nsZipCursor cursor(item, reader, buffer, length); + cursor.Copy(&length); + MOZ_ASSERT(length == item->RealSize(), "error reading font"); + if (length == item->RealSize()) { + hb_blob_t* blob = + hb_blob_create((const char*)buffer, length, + HB_MEMORY_MODE_READONLY, buffer, free); + result = hb_face_create(blob, mFTFontIndex); + hb_blob_destroy(blob); + } + } + } + } + + return result; +} + +bool FT2FontEntry::HasFontTable(uint32_t aTableTag) { + if (mAvailableTables.Count() > 0) { + return mAvailableTables.GetEntry(aTableTag); + } + + // If we haven't created a FreeType face already, try to avoid that by + // reading the available table tags via harfbuzz and caching in a hashset. + if (!mFTFace && !mFilename.IsEmpty()) { + hb_face_t* face = CreateHBFace(); + if (face) { + // Read table tags in batches; 32 should be enough for most fonts in a + // single operation. + const unsigned TAG_BUF_LENGTH = 32; + hb_tag_t tags[TAG_BUF_LENGTH]; + unsigned int startOffset = 0; + unsigned int totalTables = 0; + do { + unsigned int count = TAG_BUF_LENGTH; + // Updates count to the number of table tags actually retrieved + totalTables = hb_face_get_table_tags(face, startOffset, &count, tags); + startOffset += count; + while (count-- > 0) { + mAvailableTables.PutEntry(tags[count]); + } + } while (startOffset < totalTables); + hb_face_destroy(face); + } else { + // Failed to create the HarfBuzz face! The font is probably broken. + // Put a dummy entry in mAvailableTables so that we don't bother + // re-trying here. + mAvailableTables.PutEntry(uint32_t(-1)); + } + return mAvailableTables.GetEntry(aTableTag); + } + + RefPtr<SharedFTFace> face = GetFTFace(); + return gfxFT2FontEntryBase::FaceHasTable(face, aTableTag); +} + +nsresult FT2FontEntry::CopyFontTable(uint32_t aTableTag, + nsTArray<uint8_t>& aBuffer) { + RefPtr<SharedFTFace> face = GetFTFace(); + return gfxFT2FontEntryBase::CopyFaceTable(face, aTableTag, aBuffer); +} + +hb_blob_t* FT2FontEntry::GetFontTable(uint32_t aTableTag) { + if (FTUserFontData* userFontData = GetUserFontData()) { + // If there's a cairo font face, we may be able to return a blob + // that just wraps a range of the attached user font data + if (userFontData->FontData()) { + return gfxFontUtils::GetTableFromFontData(userFontData->FontData(), + aTableTag); + } + } + + // If the FT_Face hasn't been instantiated, try to read table directly + // via harfbuzz API to avoid expensive FT_Face creation. + if (!mFTFace && !mFilename.IsEmpty()) { + hb_face_t* face = CreateHBFace(); + if (face) { + hb_blob_t* result = hb_face_reference_table(face, aTableTag); + hb_face_destroy(face); + return result; + } + } + + // Otherwise, use the default method (which in turn will call our + // implementation of CopyFontTable). + return gfxFontEntry::GetFontTable(aTableTag); +} + +bool FT2FontEntry::HasVariations() { + if (!mHasVariationsInitialized) { + mHasVariationsInitialized = true; + if (mFTFace) { + mHasVariations = + mFTFace->GetFace()->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS; + } else { + mHasVariations = gfxPlatform::GetPlatform()->HasVariationFontSupport() && + HasFontTable(TRUETYPE_TAG('f', 'v', 'a', 'r')); + } + } + return mHasVariations; +} + +void FT2FontEntry::GetVariationAxes(nsTArray<gfxFontVariationAxis>& aAxes) { + if (!HasVariations()) { + return; + } + FT_MM_Var* mmVar = GetMMVar(); + if (!mmVar) { + return; + } + gfxFT2Utils::GetVariationAxes(mmVar, aAxes); +} + +void FT2FontEntry::GetVariationInstances( + nsTArray<gfxFontVariationInstance>& aInstances) { + if (!HasVariations()) { + return; + } + FT_MM_Var* mmVar = GetMMVar(); + if (!mmVar) { + return; + } + gfxFT2Utils::GetVariationInstances(this, mmVar, aInstances); +} + +FT_MM_Var* FT2FontEntry::GetMMVar() { + if (mMMVarInitialized) { + return mMMVar; + } + mMMVarInitialized = true; + RefPtr<SharedFTFace> face = GetFTFace(true); + if (!face) { + return nullptr; + } + if (FT_Err_Ok != FT_Get_MM_Var(face->GetFace(), &mMMVar)) { + mMMVar = nullptr; + } + return mMMVar; +} + +void FT2FontEntry::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + gfxFontEntry::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); + aSizes->mFontListSize += + mFilename.SizeOfExcludingThisIfUnshared(aMallocSizeOf); +} + +void FT2FontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +/* + * FT2FontFamily + * A standard gfxFontFamily; just adds a method used to support sending + * the font list from chrome to content via IPC. + */ + +void FT2FontFamily::AddFacesToFontList(nsTArray<FontListEntry>* aFontList) { + for (int i = 0, n = mAvailableFonts.Length(); i < n; ++i) { + const FT2FontEntry* fe = + static_cast<const FT2FontEntry*>(mAvailableFonts[i].get()); + if (!fe) { + continue; + } + + aFontList->AppendElement(FontListEntry( + Name(), fe->Name(), fe->mFilename, fe->Weight().AsScalar(), + fe->Stretch().AsScalar(), fe->SlantStyle().AsScalar(), fe->mFTFontIndex, + Visibility())); + } +} + +/* + * Startup cache support for the font list: + * We store the list of families and faces, with their style attributes and the + * corresponding font files, in the startup cache. + * This allows us to recreate the gfxFT2FontList collection of families and + * faces without instantiating Freetype faces for each font file (in order to + * find their attributes), leading to significantly quicker startup. + */ + +#define CACHE_KEY "font.cached-list" + +void gfxFT2FontList::CollectInitData(const FontListEntry& aFLE, + const nsCString& aPSName, + const nsCString& aFullName, + StandardFile aStdFile) { + nsAutoCString key(aFLE.familyName()); + BuildKeyNameFromFontName(key); + auto faceList = mFaceInitData.Get(key); + if (!faceList) { + faceList = new nsTArray<fontlist::Face::InitData>; + mFaceInitData.Put(key, faceList); + mFamilyInitData.AppendElement( + fontlist::Family::InitData{key, aFLE.familyName()}); + } + faceList->AppendElement( + fontlist::Face::InitData{aFLE.filepath(), aFLE.index(), false, + WeightRange::FromScalar(aFLE.weightRange()), + StretchRange::FromScalar(aFLE.stretchRange()), + SlantStyleRange::FromScalar(aFLE.styleRange())}); + nsAutoCString psname(aPSName), fullname(aFullName); + if (!psname.IsEmpty()) { + ToLowerCase(psname); + mLocalNameTable.Put(psname, + fontlist::LocalFaceRec::InitData(key, aFLE.filepath())); + } + if (!fullname.IsEmpty()) { + ToLowerCase(fullname); + if (fullname != psname) { + mLocalNameTable.Put( + fullname, fontlist::LocalFaceRec::InitData(key, aFLE.filepath())); + } + } +} + +class FontNameCache { + public: + // Delimiters used in the cached font-list records we store in startupCache + static const char kFileSep = 0x1c; + static const char kGroupSep = 0x1d; + static const char kRecordSep = 0x1e; + static const char kFieldSep = 0x1f; + + // Separator for font property ranges; we only look for this within a + // field that holds a serialized FontPropertyValue or Range, so there's no + // risk of conflicting with printable characters in font names. + // Note that this must be a character that will terminate strtof() parsing + // of a number. + static const char kRangeSep = ':'; + + // Creates the object but does NOT load the cached data from the startup + // cache; call Init() after creation to do that. + FontNameCache() : mMap(&mOps, sizeof(FNCMapEntry), 0), mWriteNeeded(false) { + // HACK ALERT: it's weird to assign |mOps| after we passed a pointer to + // it to |mMap|'s constructor. A more normal approach here would be to + // have a static |sOps| member. Unfortunately, this mysteriously but + // consistently makes Fennec start-up slower, so we take this + // unorthodox approach instead. It's safe because PLDHashTable's + // constructor doesn't dereference the pointer; it just makes a copy of + // it. + mOps = (PLDHashTableOps){StringHash, HashMatchEntry, MoveEntry, + PLDHashTable::ClearEntryStub, nullptr}; + + MOZ_ASSERT(XRE_IsParentProcess(), + "FontNameCache should only be used in chrome process"); + mCache = mozilla::scache::StartupCache::GetSingleton(); + } + + ~FontNameCache() { WriteCache(); } + + size_t EntryCount() const { return mMap.EntryCount(); } + + void DropStaleEntries() { + for (auto iter = mMap.Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast<FNCMapEntry*>(iter.Get()); + if (!entry->mFileExists) { + iter.Remove(); + } + } + } + + void WriteCache() { + if (!mWriteNeeded || !mCache) { + return; + } + + LOG(("Writing FontNameCache:")); + nsAutoCString buf; + for (auto iter = mMap.Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast<FNCMapEntry*>(iter.Get()); + MOZ_ASSERT(entry->mFileExists); + buf.Append(entry->mFilename); + buf.Append(kGroupSep); + buf.Append(entry->mFaces); + buf.Append(kGroupSep); + buf.AppendInt(entry->mTimestamp); + buf.Append(kGroupSep); + buf.AppendInt(entry->mFilesize); + buf.Append(kFileSep); + } + + LOG(("putting FontNameCache to " CACHE_KEY ", length %u", + buf.Length() + 1)); + mCache->PutBuffer(CACHE_KEY, UniquePtr<char[]>(ToNewCString(buf)), + buf.Length() + 1); + mWriteNeeded = false; + } + + // This may be called more than once (if we re-load the font list). + void Init() { + if (!mCache) { + return; + } + + uint32_t size; + const char* cur; + if (NS_FAILED(mCache->GetBuffer(CACHE_KEY, &cur, &size))) { + LOG(("no cache of " CACHE_KEY)); + return; + } + + LOG(("got: %u bytes from the cache " CACHE_KEY, size)); + + mMap.Clear(); + mWriteNeeded = false; + + while (const char* fileEnd = strchr(cur, kFileSep)) { + // The cached record for one file is at [cur, fileEnd]. + + // Find end of field that starts at aStart, terminated by kGroupSep or + // end of record. + auto endOfField = [=](const char* aStart) -> const char* { + MOZ_ASSERT(aStart <= fileEnd); + const char* end = static_cast<const char*>( + memchr(aStart, kGroupSep, fileEnd - aStart)); + if (end) { + return end; + } + return fileEnd; + }; + + // Advance aStart and aEnd to indicate the range of the next field and + // return true, or just return false if already at end of record. + auto nextField = [=](const char*& aStart, const char*& aEnd) -> bool { + if (aEnd < fileEnd) { + aStart = aEnd + 1; + aEnd = endOfField(aStart); + return true; + } + return false; + }; + + const char* end = endOfField(cur); + nsCString filename(cur, end - cur); + if (!nextField(cur, end)) { + break; + } + nsCString faceList(cur, end - cur); + if (!nextField(cur, end)) { + break; + } + uint32_t timestamp = strtoul(cur, nullptr, 10); + if (!nextField(cur, end)) { + break; + } + uint32_t filesize = strtoul(cur, nullptr, 10); + + auto mapEntry = + static_cast<FNCMapEntry*>(mMap.Add(filename.get(), fallible)); + if (mapEntry) { + mapEntry->mFilename.Assign(filename); + mapEntry->mTimestamp = timestamp; + mapEntry->mFilesize = filesize; + mapEntry->mFaces.Assign(faceList); + // entries from the startupcache are marked "non-existing" + // until we have confirmed that the file still exists + mapEntry->mFileExists = false; + } + + cur = fileEnd + 1; + } + } + + void GetInfoForFile(const nsCString& aFileName, nsCString& aFaceList, + uint32_t* aTimestamp, uint32_t* aFilesize) { + auto entry = static_cast<FNCMapEntry*>(mMap.Search(aFileName.get())); + if (entry) { + *aTimestamp = entry->mTimestamp; + *aFilesize = entry->mFilesize; + aFaceList.Assign(entry->mFaces); + // this entry does correspond to an existing file + // (although it might not be up-to-date, in which case + // it will get overwritten via CacheFileInfo) + entry->mFileExists = true; + } + } + + void CacheFileInfo(const nsCString& aFileName, const nsCString& aFaceList, + uint32_t aTimestamp, uint32_t aFilesize) { + auto entry = static_cast<FNCMapEntry*>(mMap.Add(aFileName.get(), fallible)); + if (entry) { + entry->mFilename.Assign(aFileName); + entry->mTimestamp = aTimestamp; + entry->mFilesize = aFilesize; + entry->mFaces.Assign(aFaceList); + entry->mFileExists = true; + } + mWriteNeeded = true; + } + + private: + mozilla::scache::StartupCache* mCache; + PLDHashTable mMap; + bool mWriteNeeded; + + PLDHashTableOps mOps; + + struct FNCMapEntry : public PLDHashEntryHdr { + public: + nsCString mFilename; + uint32_t mTimestamp; + uint32_t mFilesize; + nsCString mFaces; + bool mFileExists; + }; + + static PLDHashNumber StringHash(const void* key) { + return HashString(reinterpret_cast<const char*>(key)); + } + + static bool HashMatchEntry(const PLDHashEntryHdr* aHdr, const void* key) { + const FNCMapEntry* entry = static_cast<const FNCMapEntry*>(aHdr); + return entry->mFilename.Equals(reinterpret_cast<const char*>(key)); + } + + static void MoveEntry(PLDHashTable* table, const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo) { + FNCMapEntry* to = static_cast<FNCMapEntry*>(aTo); + const FNCMapEntry* from = static_cast<const FNCMapEntry*>(aFrom); + to->mFilename.Assign(from->mFilename); + to->mTimestamp = from->mTimestamp; + to->mFilesize = from->mFilesize; + to->mFaces.Assign(from->mFaces); + to->mFileExists = from->mFileExists; + } +}; + +/*************************************************************** + * + * gfxFT2FontList + * + */ + +// For Mobile, we use gfxFT2Fonts, and we build the font list by directly +// scanning the system's Fonts directory for OpenType and TrueType files. + +#define JAR_LAST_MODIFED_TIME "jar-last-modified-time" + +class WillShutdownObserver : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + explicit WillShutdownObserver(gfxFT2FontList* aFontList) + : mFontList(aFontList) {} + + void Remove() { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID); + } + mFontList = nullptr; + } + + protected: + virtual ~WillShutdownObserver() = default; + + gfxFT2FontList* mFontList; +}; + +NS_IMPL_ISUPPORTS(WillShutdownObserver, nsIObserver) + +NS_IMETHODIMP +WillShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!nsCRT::strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID)) { + mFontList->WillShutdown(); + } else { + MOZ_ASSERT_UNREACHABLE("unexpected notification topic"); + } + return NS_OK; +} + +gfxFT2FontList::gfxFT2FontList() : mJarModifiedTime(0) { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + mObserver = new WillShutdownObserver(this); + obs->AddObserver(mObserver, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false); + } +} + +gfxFT2FontList::~gfxFT2FontList() { + if (mObserver) { + mObserver->Remove(); + } +} + +bool gfxFT2FontList::AppendFacesFromCachedFaceList(CollectFunc aCollectFace, + const nsCString& aFileName, + const nsCString& aFaceList, + StandardFile aStdFile) { + const char* start = aFaceList.get(); + int count = 0; + + while (const char* recEnd = strchr(start, FontNameCache::kRecordSep)) { + auto endOfField = [=](const char* aStart) -> const char* { + MOZ_ASSERT(aStart <= recEnd); + const char* end = static_cast<const char*>( + memchr(aStart, FontNameCache::kFieldSep, recEnd - aStart)); + if (end) { + return end; + } + return recEnd; + }; + + auto nextField = [=](const char*& aStart, const char*& aEnd) -> bool { + if (aEnd < recEnd) { + aStart = aEnd + 1; + aEnd = endOfField(aStart); + return true; + } + return false; + }; + + const char* end = endOfField(start); + nsAutoCString familyName(start, end - start); + nsAutoCString key(familyName); + ToLowerCase(key); + + if (!nextField(start, end)) { + break; + } + nsAutoCString faceName(start, end - start); + + if (!nextField(start, end)) { + break; + } + uint32_t index = strtoul(start, nullptr, 10); + + if (!nextField(start, end)) { + break; + } + nsAutoCString minStyle(start, end - start); + nsAutoCString maxStyle(minStyle); + int32_t colon = minStyle.FindChar(FontNameCache::kRangeSep); + if (colon > 0) { + maxStyle.Assign(minStyle.BeginReading() + colon + 1); + minStyle.Truncate(colon); + } + + if (!nextField(start, end)) { + break; + } + char* limit; + float minWeight = strtof(start, &limit); + float maxWeight; + if (*limit == FontNameCache::kRangeSep && limit + 1 < end) { + maxWeight = strtof(limit + 1, nullptr); + } else { + maxWeight = minWeight; + } + + if (!nextField(start, end)) { + break; + } + float minStretch = strtof(start, &limit); + float maxStretch; + if (*limit == FontNameCache::kRangeSep && limit + 1 < end) { + maxStretch = strtof(limit + 1, nullptr); + } else { + maxStretch = minStretch; + } + + if (!nextField(start, end)) { + break; + } + nsAutoCString psname(start, end - start); + + if (!nextField(start, end)) { + break; + } + nsAutoCString fullname(start, end - start); + + if (!nextField(start, end)) { + break; + } + FontVisibility visibility = FontVisibility(strtoul(start, nullptr, 10)); + + FontListEntry fle( + familyName, faceName, aFileName, + WeightRange(FontWeight(minWeight), FontWeight(maxWeight)).AsScalar(), + StretchRange(FontStretch(minStretch), FontStretch(maxStretch)) + .AsScalar(), + SlantStyleRange(FontSlantStyle::FromString(minStyle.get()), + FontSlantStyle::FromString(maxStyle.get())) + .AsScalar(), + index, visibility); + + aCollectFace(fle, psname, fullname, aStdFile); + count++; + + start = recEnd + 1; + } + + return count > 0; +} + +void FT2FontEntry::AppendToFaceList(nsCString& aFaceList, + const nsACString& aFamilyName, + const nsACString& aPSName, + const nsACString& aFullName, + FontVisibility aVisibility) { + aFaceList.Append(aFamilyName); + aFaceList.Append(FontNameCache::kFieldSep); + aFaceList.Append(Name()); + aFaceList.Append(FontNameCache::kFieldSep); + aFaceList.AppendInt(mFTFontIndex); + aFaceList.Append(FontNameCache::kFieldSep); + // Note that ToString() appends to the destination string without + // replacing existing contents (see FontPropertyTypes.h) + SlantStyle().Min().ToString(aFaceList); + aFaceList.Append(FontNameCache::kRangeSep); + SlantStyle().Max().ToString(aFaceList); + aFaceList.Append(FontNameCache::kFieldSep); + aFaceList.AppendFloat(Weight().Min().ToFloat()); + aFaceList.Append(FontNameCache::kRangeSep); + aFaceList.AppendFloat(Weight().Max().ToFloat()); + aFaceList.Append(FontNameCache::kFieldSep); + aFaceList.AppendFloat(Stretch().Min().Percentage()); + aFaceList.Append(FontNameCache::kRangeSep); + aFaceList.AppendFloat(Stretch().Max().Percentage()); + aFaceList.Append(FontNameCache::kFieldSep); + aFaceList.Append(aPSName); + aFaceList.Append(FontNameCache::kFieldSep); + aFaceList.Append(aFullName); + aFaceList.Append(FontNameCache::kFieldSep); + aFaceList.AppendInt(int(aVisibility)); + aFaceList.Append(FontNameCache::kRecordSep); +} + +void FT2FontEntry::CheckForBrokenFont(gfxFontFamily* aFamily) { + // note if the family is in the "bad underline" blocklist + if (aFamily->IsBadUnderlineFamily()) { + mIsBadUnderlineFont = true; + } + nsAutoCString familyKey(aFamily->Name()); + BuildKeyNameFromFontName(familyKey); + CheckForBrokenFont(familyKey); +} + +void FT2FontEntry::CheckForBrokenFont(const nsACString& aFamilyKey) { + // bug 721719 - set the IgnoreGSUB flag on entries for Roboto + // because of unwanted on-by-default "ae" ligature. + // (See also AppendFaceFromFontListEntry.) + if (aFamilyKey.EqualsLiteral("roboto")) { + mIgnoreGSUB = true; + return; + } + + // bug 706888 - set the IgnoreGSUB flag on the broken version of + // Droid Sans Arabic from certain phones, as identified by the + // font checksum in the 'head' table + if (aFamilyKey.EqualsLiteral("droid sans arabic")) { + RefPtr<SharedFTFace> face = GetFTFace(); + if (face) { + const TT_Header* head = static_cast<const TT_Header*>( + FT_Get_Sfnt_Table(face->GetFace(), ft_sfnt_head)); + if (head && head->CheckSum_Adjust == 0xe445242) { + mIgnoreGSUB = true; + } + } + } +} + +void gfxFT2FontList::AppendFacesFromBlob( + const nsCString& aFileName, StandardFile aStdFile, hb_blob_t* aBlob, + FontNameCache* aCache, uint32_t aTimestamp, uint32_t aFilesize) { + nsCString newFaceList; + uint32_t numFaces = 1; + unsigned int length; + const char* data = hb_blob_get_data(aBlob, &length); + // Check for a possible TrueType Collection + if (length >= sizeof(TTCHeader)) { + const TTCHeader* ttc = reinterpret_cast<const TTCHeader*>(data); + if (ttc->ttcTag == TRUETYPE_TAG('t', 't', 'c', 'f')) { + numFaces = ttc->numFonts; + } + } + for (unsigned int index = 0; index < numFaces; index++) { + hb_face_t* face = hb_face_create(aBlob, index); + if (face != hb_face_get_empty()) { + AddFaceToList(aFileName, index, aStdFile, face, newFaceList); + } + hb_face_destroy(face); + } + if (aCache && !newFaceList.IsEmpty()) { + aCache->CacheFileInfo(aFileName, newFaceList, aTimestamp, aFilesize); + } +} + +void gfxFT2FontList::AppendFacesFromFontFile(const nsCString& aFileName, + FontNameCache* aCache, + StandardFile aStdFile) { + nsCString cachedFaceList; + uint32_t filesize = 0, timestamp = 0; + if (aCache) { + aCache->GetInfoForFile(aFileName, cachedFaceList, ×tamp, &filesize); + } + + struct stat s; + int statRetval = stat(aFileName.get(), &s); + if (!cachedFaceList.IsEmpty() && 0 == statRetval && + uint32_t(s.st_mtime) == timestamp && s.st_size == filesize) { + CollectFunc unshared = + [](const FontListEntry& aFLE, const nsCString& aPSName, + const nsCString& aFullName, StandardFile aStdFile) { + PlatformFontList()->AppendFaceFromFontListEntry(aFLE, aStdFile); + }; + CollectFunc shared = [](const FontListEntry& aFLE, const nsCString& aPSName, + const nsCString& aFullName, StandardFile aStdFile) { + PlatformFontList()->CollectInitData(aFLE, aPSName, aFullName, aStdFile); + }; + if (AppendFacesFromCachedFaceList(SharedFontList() ? shared : unshared, + aFileName, cachedFaceList, aStdFile)) { + LOG(("using cached font info for %s", aFileName.get())); + return; + } + } + + hb_blob_t* fileBlob = hb_blob_create_from_file(aFileName.get()); + if (hb_blob_get_length(fileBlob) > 0) { + LOG(("reading font info via harfbuzz for %s", aFileName.get())); + AppendFacesFromBlob(aFileName, aStdFile, fileBlob, + 0 == statRetval ? aCache : nullptr, s.st_mtime, + s.st_size); + } + hb_blob_destroy(fileBlob); +} + +void gfxFT2FontList::FindFontsInOmnijar(FontNameCache* aCache) { + bool jarChanged = false; + + mozilla::scache::StartupCache* cache = + mozilla::scache::StartupCache::GetSingleton(); + const char* cachedModifiedTimeBuf; + uint32_t longSize; + if (cache && + NS_SUCCEEDED(cache->GetBuffer(JAR_LAST_MODIFED_TIME, + &cachedModifiedTimeBuf, &longSize)) && + longSize == sizeof(int64_t)) { + nsCOMPtr<nsIFile> jarFile = Omnijar::GetPath(Omnijar::Type::GRE); + jarFile->GetLastModifiedTime(&mJarModifiedTime); + if (mJarModifiedTime > LittleEndian::readInt64(cachedModifiedTimeBuf)) { + jarChanged = true; + } + } + + static const char* sJarSearchPaths[] = { + "res/fonts/*.ttf$", + }; + RefPtr<nsZipArchive> reader = Omnijar::GetReader(Omnijar::Type::GRE); + for (unsigned i = 0; i < ArrayLength(sJarSearchPaths); i++) { + nsZipFind* find; + if (NS_SUCCEEDED(reader->FindInit(sJarSearchPaths[i], &find))) { + const char* path; + uint16_t len; + while (NS_SUCCEEDED(find->FindNext(&path, &len))) { + nsCString entryName(path, len); + AppendFacesFromOmnijarEntry(reader, entryName, aCache, jarChanged); + } + delete find; + } + } +} + +static void GetName(hb_face_t* aFace, hb_ot_name_id_t aNameID, + nsACString& aName) { + unsigned int n = 0; + n = hb_ot_name_get_utf8(aFace, aNameID, HB_LANGUAGE_INVALID, &n, nullptr); + if (n) { + aName.SetLength(n++); // increment n to account for NUL terminator + n = hb_ot_name_get_utf8(aFace, aNameID, HB_LANGUAGE_INVALID, &n, + aName.BeginWriting()); + } +} + +// Given the harfbuzz face corresponding to an entryName and face index, +// add the face to the available font list and to the faceList string +void gfxFT2FontList::AddFaceToList(const nsCString& aEntryName, uint32_t aIndex, + StandardFile aStdFile, hb_face_t* aFace, + nsCString& aFaceList) { + nsAutoCString familyName; + bool preferTypographicNames = true; + GetName(aFace, HB_OT_NAME_ID_TYPOGRAPHIC_FAMILY, familyName); + if (familyName.IsEmpty()) { + preferTypographicNames = false; + GetName(aFace, HB_OT_NAME_ID_FONT_FAMILY, familyName); + } + if (familyName.IsEmpty()) { + return; + } + + nsAutoCString fullname; + GetName(aFace, HB_OT_NAME_ID_FULL_NAME, fullname); + if (fullname.IsEmpty()) { + // Construct fullname from family + style + fullname = familyName; + nsAutoCString styleName; + if (preferTypographicNames) { + GetName(aFace, HB_OT_NAME_ID_TYPOGRAPHIC_SUBFAMILY, styleName); + } + if (styleName.IsEmpty()) { + GetName(aFace, HB_OT_NAME_ID_FONT_SUBFAMILY, styleName); + } + if (!styleName.IsEmpty() && !styleName.EqualsLiteral("Regular")) { + fullname.Append(' '); + fullname.Append(styleName); + } + } + + // Build the font entry name and create an FT2FontEntry, + // but do -not- keep a reference to the FT_Face. + // (When using the shared font list, this entry will not be retained, + // it is used only to call AppendToFaceList.) + RefPtr<FT2FontEntry> fe = + FT2FontEntry::CreateFontEntry(fullname, aEntryName.get(), aIndex, aFace); + + if (fe) { + fe->mStandardFace = (aStdFile == kStandard); + nsAutoCString familyKey(familyName); + BuildKeyNameFromFontName(familyKey); + + FontVisibility visibility = FontVisibility::Unknown; + + nsAutoCString psname; + GetName(aFace, HB_OT_NAME_ID_POSTSCRIPT_NAME, psname); + + if (SharedFontList()) { + FontListEntry fle(familyName, fe->Name(), fe->mFilename, + fe->Weight().AsScalar(), fe->Stretch().AsScalar(), + fe->SlantStyle().AsScalar(), fe->mFTFontIndex, + visibility); + CollectInitData(fle, psname, fullname, aStdFile); + } else { + RefPtr<gfxFontFamily> family = mFontFamilies.GetWeak(familyKey); + if (!family) { + family = new FT2FontFamily(familyName, visibility); + mFontFamilies.Put(familyKey, RefPtr{family}); + if (mSkipSpaceLookupCheckFamilies.Contains(familyKey)) { + family->SetSkipSpaceFeatureCheck(true); + } + if (mBadUnderlineFamilyNames.ContainsSorted(familyKey)) { + family->SetBadUnderlineFamily(); + } + } + family->AddFontEntry(fe); + fe->CheckForBrokenFont(family); + } + + fe->AppendToFaceList(aFaceList, familyName, psname, fullname, visibility); + if (LOG_ENABLED()) { + nsAutoCString weightString; + fe->Weight().ToString(weightString); + nsAutoCString stretchString; + fe->Stretch().ToString(stretchString); + LOG( + ("(fontinit) added (%s) to family (%s)" + " with style: %s weight: %s stretch: %s", + fe->Name().get(), familyName.get(), + fe->IsItalic() ? "italic" : "normal", weightString.get(), + stretchString.get())); + } + } +} + +void gfxFT2FontList::AppendFacesFromOmnijarEntry(nsZipArchive* aArchive, + const nsCString& aEntryName, + FontNameCache* aCache, + bool aJarChanged) { + nsCString faceList; + if (aCache && !aJarChanged) { + uint32_t filesize, timestamp; + aCache->GetInfoForFile(aEntryName, faceList, ×tamp, &filesize); + if (faceList.Length() > 0) { + CollectFunc unshared = + [](const FontListEntry& aFLE, const nsCString& aPSName, + const nsCString& aFullName, StandardFile aStdFile) { + PlatformFontList()->AppendFaceFromFontListEntry(aFLE, aStdFile); + }; + CollectFunc shared = [](const FontListEntry& aFLE, + const nsCString& aPSName, + const nsCString& aFullName, + StandardFile aStdFile) { + PlatformFontList()->CollectInitData(aFLE, aPSName, aFullName, aStdFile); + }; + if (AppendFacesFromCachedFaceList(SharedFontList() ? shared : unshared, + aEntryName, faceList, kStandard)) { + return; + } + } + } + + nsZipItem* item = aArchive->GetItem(aEntryName.get()); + NS_ASSERTION(item, "failed to find zip entry"); + + uint32_t bufSize = item->RealSize(); + + // We use fallible allocation here; if there's not enough RAM, we'll simply + // ignore the bundled fonts and fall back to the device's installed fonts. + char* buffer = static_cast<char*>(malloc(bufSize)); + if (!buffer) { + return; + } + + nsZipCursor cursor(item, aArchive, (uint8_t*)buffer, bufSize); + uint8_t* data = cursor.Copy(&bufSize); + MOZ_ASSERT(data && bufSize == item->RealSize(), "error reading bundled font"); + if (!data) { + return; + } + + hb_blob_t* blob = + hb_blob_create(buffer, bufSize, HB_MEMORY_MODE_READONLY, buffer, free); + AppendFacesFromBlob(aEntryName, kStandard, blob, aCache, 0, bufSize); + hb_blob_destroy(blob); +} + +// Called on each family after all fonts are added to the list; +// if aSortFaces is true this will sort faces to give priority to "standard" +// font files. +static void FinalizeFamilyMemberList(nsCStringHashKey::KeyType aKey, + RefPtr<gfxFontFamily>& aFamily, + bool aSortFaces) { + gfxFontFamily* family = aFamily.get(); + + family->SetHasStyles(true); + + if (aSortFaces) { + family->SortAvailableFonts(); + } + family->CheckForSimpleFamily(); +} + +void gfxFT2FontList::FindFonts() { + MOZ_ASSERT(XRE_IsParentProcess()); + + // Chrome process: get the cached list (if any) + if (!mFontNameCache) { + mFontNameCache = MakeUnique<FontNameCache>(); + } + mFontNameCache->Init(); + +#if defined(MOZ_WIDGET_ANDROID) + // Android API 29+ provides system font and font matcher API for native code. + typedef void* (*_ASystemFontIterator_open)(); + typedef void* (*_ASystemFontIterator_next)(void*); + typedef void (*_ASystemFontIterator_close)(void*); + typedef const char* (*_AFont_getFontFilePath)(const void*); + typedef void (*_AFont_close)(void*); + + static _ASystemFontIterator_open systemFontIterator_open = nullptr; + static _ASystemFontIterator_next systemFontIterator_next = nullptr; + static _ASystemFontIterator_close systemFontIterator_close = nullptr; + static _AFont_getFontFilePath font_getFontFilePath = nullptr; + static _AFont_close font_close = nullptr; + + static bool firstTime = true; + + if (firstTime) { + if (jni::GetAPIVersion() >= 29) { + void* handle = dlopen("libandroid.so", RTLD_LAZY | RTLD_LOCAL); + MOZ_ASSERT(handle); + + systemFontIterator_open = + (_ASystemFontIterator_open)dlsym(handle, "ASystemFontIterator_open"); + systemFontIterator_next = + (_ASystemFontIterator_next)dlsym(handle, "ASystemFontIterator_next"); + systemFontIterator_close = (_ASystemFontIterator_close)dlsym( + handle, "ASystemFontIterator_close"); + font_getFontFilePath = + (_AFont_getFontFilePath)dlsym(handle, "AFont_getFontFilePath"); + font_close = (_AFont_close)dlsym(handle, "AFont_close"); + + if (NS_WARN_IF(!systemFontIterator_next) || + NS_WARN_IF(!systemFontIterator_close) || + NS_WARN_IF(!font_getFontFilePath) || NS_WARN_IF(!font_close)) { + // Since any functions aren't resolved, use old way to enumerate fonts. + systemFontIterator_open = nullptr; + } + } + firstTime = false; + } + + bool useSystemFontAPI = !!systemFontIterator_open; + if (useSystemFontAPI) { + void* iter = systemFontIterator_open(); + if (iter) { + void* font = systemFontIterator_next(iter); + while (font) { + nsAutoCString path(font_getFontFilePath(font)); + AppendFacesFromFontFile(path, mFontNameCache.get(), kStandard); + font_close(font); + font = systemFontIterator_next(iter); + } + + systemFontIterator_close(iter); + } else { + useSystemFontAPI = false; + } + } + + if (!useSystemFontAPI) +#endif + { + // 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 = "/system"_ns; + } + root.AppendLiteral("/fonts"); + + FindFontsInDir(root, mFontNameCache.get()); + } + + // Look for fonts stored in omnijar, unless we're on a low-memory + // device where we don't want to spend the RAM to decompress them. + // (Prefs may disable this, or force-enable it even with low memory.) + bool lowmem; + nsCOMPtr<nsIMemory> mem = nsMemory::GetGlobalMemoryService(); + if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() > 0 || + (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() < 0 && + NS_SUCCEEDED(mem->IsLowMemoryPlatform(&lowmem)) && !lowmem)) { + FindFontsInOmnijar(mFontNameCache.get()); + } + + // Look for downloaded fonts in a profile-agnostic "fonts" directory. + nsCOMPtr<nsIProperties> dirSvc = + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID); + if (dirSvc) { + nsCOMPtr<nsIFile> appDir; + nsresult rv = dirSvc->Get(NS_XPCOM_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(appDir)); + if (NS_SUCCEEDED(rv)) { + appDir->AppendNative("fonts"_ns); + nsCString localPath; + if (NS_SUCCEEDED(appDir->GetNativePath(localPath))) { + FindFontsInDir(localPath, mFontNameCache.get()); + } + } + } + + // look for locally-added fonts in a "fonts" subdir of the profile + nsCOMPtr<nsIFile> localDir; + nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, + getter_AddRefs(localDir)); + if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(localDir->Append(u"fonts"_ns))) { + nsCString localPath; + rv = localDir->GetNativePath(localPath); + if (NS_SUCCEEDED(rv)) { + FindFontsInDir(localPath, mFontNameCache.get()); + } + } + + mFontNameCache->DropStaleEntries(); + if (!mFontNameCache->EntryCount()) { + // if we can't find any usable fonts, we are doomed! + MOZ_CRASH("No font files found"); + } + + // Write out FontCache data if needed + WriteCache(); +} + +void gfxFT2FontList::WriteCache() { + if (mFontNameCache) { + mFontNameCache->WriteCache(); + } + mozilla::scache::StartupCache* cache = + mozilla::scache::StartupCache::GetSingleton(); + if (cache && mJarModifiedTime > 0) { + const size_t bufSize = sizeof(mJarModifiedTime); + auto buf = MakeUnique<char[]>(bufSize); + LittleEndian::writeInt64(buf.get(), mJarModifiedTime); + + LOG(("WriteCache: putting Jar, length %zu", bufSize)); + cache->PutBuffer(JAR_LAST_MODIFED_TIME, std::move(buf), bufSize); + } + LOG(("Done with writecache")); +} + +void gfxFT2FontList::FindFontsInDir(const nsCString& aDir, + FontNameCache* aFNC) { + static const char* sStandardFonts[] = {"DroidSans.ttf", + "DroidSans-Bold.ttf", + "DroidSerif-Regular.ttf", + "DroidSerif-Bold.ttf", + "DroidSerif-Italic.ttf", + "DroidSerif-BoldItalic.ttf", + "DroidSansMono.ttf", + "DroidSansArabic.ttf", + "DroidSansHebrew.ttf", + "DroidSansThai.ttf", + "MTLmr3m.ttf", + "MTLc3m.ttf", + "NanumGothic.ttf", + "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); + } + } + + closedir(d); +} + +void gfxFT2FontList::AppendFaceFromFontListEntry(const FontListEntry& aFLE, + StandardFile aStdFile) { + FT2FontEntry* fe = FT2FontEntry::CreateFontEntry(aFLE); + if (fe) { + nsAutoCString key(aFLE.familyName()); + BuildKeyNameFromFontName(key); + fe->mStandardFace = (aStdFile == kStandard); + RefPtr<gfxFontFamily> family = mFontFamilies.GetWeak(key); + if (!family) { + family = new FT2FontFamily(aFLE.familyName(), aFLE.visibility()); + mFontFamilies.Put(key, RefPtr{family}); + if (mSkipSpaceLookupCheckFamilies.Contains(key)) { + family->SetSkipSpaceFeatureCheck(true); + } + if (mBadUnderlineFamilyNames.ContainsSorted(key)) { + family->SetBadUnderlineFamily(); + } + } + family->AddFontEntry(fe); + + fe->CheckForBrokenFont(family); + } +} + +void gfxFT2FontList::ReadSystemFontList(nsTArray<FontListEntry>* aList) { + for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { + auto family = static_cast<FT2FontFamily*>(iter.Data().get()); + family->AddFacesToFontList(aList); + } +} + +static void LoadSkipSpaceLookupCheck( + nsTHashtable<nsCStringHashKey>& aSkipSpaceLookupCheck) { + AutoTArray<nsCString, 5> skiplist; + gfxFontUtils::GetPrefsFontList( + "font.whitelist.skip_default_features_space_check", skiplist); + uint32_t numFonts = skiplist.Length(); + for (uint32_t i = 0; i < numFonts; i++) { + ToLowerCase(skiplist[i]); + aSkipSpaceLookupCheck.PutEntry(skiplist[i]); + } +} + +nsresult gfxFT2FontList::InitFontListForPlatform() { + LoadSkipSpaceLookupCheck(mSkipSpaceLookupCheckFamilies); + + if (XRE_IsParentProcess()) { + // This will populate/update mFontNameCache and store it in the + // startupCache for future startups. + FindFonts(); + + // Finalize the families by sorting faces into standard order + // and marking "simple" families. + for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { + nsCStringHashKey::KeyType key = iter.Key(); + RefPtr<gfxFontFamily>& family = iter.Data(); + FinalizeFamilyMemberList(key, family, /* aSortFaces */ true); + } + + return NS_OK; + } + + // Content process: use font list passed from the chrome process via + // the GetXPCOMProcessAttributes message. + auto& fontList = dom::ContentChild::GetSingleton()->SystemFontList(); + for (FontListEntry& fle : fontList) { + // We don't need to identify "standard" font files here, + // as the faces are already sorted. + AppendFaceFromFontListEntry(fle, kUnknown); + } + + // We don't need to sort faces (because they were already sorted by the + // chrome process, so we just maintain the existing order) + for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { + nsCStringHashKey::KeyType key = iter.Key(); + RefPtr<gfxFontFamily>& family = iter.Data(); + FinalizeFamilyMemberList(key, family, /* aSortFaces */ false); + } + + LOG(("got font list from chrome process: %" PRIdPTR " faces in %" PRIu32 + " families", + fontList.Length(), mFontFamilies.Count())); + fontList.Clear(); + + return NS_OK; +} + +void gfxFT2FontList::InitSharedFontListForPlatform() { + if (!XRE_IsParentProcess()) { + // Content processes will access the shared-memory data created by the + // parent, so don't need to scan for available fonts themselves. + return; + } + + // This will populate mFontNameCache with entries for all the available font + // files, and record them in mFamilies (unshared list) or mFamilyInitData and + // mFaceInitData (shared font list). + FindFonts(); + + mozilla::fontlist::FontList* list = SharedFontList(); + list->SetFamilyNames(mFamilyInitData); + + auto families = list->Families(); + for (uint32_t i = 0; i < mFamilyInitData.Length(); i++) { + auto faceList = mFaceInitData.Get(mFamilyInitData[i].mKey); + MOZ_ASSERT(faceList); + families[i].AddFaces(list, *faceList); + } + + mFamilyInitData.Clear(); + mFaceInitData.Clear(); +} + +gfxFontEntry* gfxFT2FontList::CreateFontEntry(fontlist::Face* aFace, + const fontlist::Family* aFamily) { + fontlist::FontList* list = SharedFontList(); + nsAutoCString desc(aFace->mDescriptor.AsString(list)); + FT2FontEntry* fe = + FT2FontEntry::CreateFontEntry(desc, desc.get(), aFace->mIndex, nullptr); + fe->InitializeFrom(aFace, aFamily); + fe->CheckForBrokenFont(aFamily->Key().AsString(list)); + return fe; +} + +// called for each family name, based on the assumption that the +// first part of the full name is the family name + +gfxFontEntry* gfxFT2FontList::LookupLocalFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) { + if (SharedFontList()) { + return LookupInSharedFaceNameList(aFontName, aWeightForEntry, + aStretchForEntry, aStyleForEntry); + } + // walk over list of names + FT2FontEntry* fontEntry = nullptr; + + for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { + // Check family name, based on the assumption that the + // first part of the full name is the family name + RefPtr<gfxFontFamily>& fontFamily = iter.Data(); + + // does the family name match up to the length of the family name? + const nsCString& family = fontFamily->Name(); + + const nsAutoCString fullNameFamily( + Substring(aFontName, 0, family.Length())); + + // if so, iterate over faces in this family to see if there is a match + if (family.Equals(fullNameFamily, nsCaseInsensitiveCStringComparator)) { + nsTArray<RefPtr<gfxFontEntry> >& fontList = fontFamily->GetFontList(); + int index, len = fontList.Length(); + for (index = 0; index < len; index++) { + gfxFontEntry* fe = fontList[index]; + if (!fe) { + continue; + } + if (fe->Name().Equals(aFontName, nsCaseInsensitiveCStringComparator)) { + fontEntry = static_cast<FT2FontEntry*>(fe); + goto searchDone; + } + } + } + } + +searchDone: + if (!fontEntry) { + return nullptr; + } + + // Clone the font entry so that we can then set its style descriptors + // from the userfont entry rather than the actual font. + + // Ensure existence of mFTFace in the original entry + RefPtr<SharedFTFace> face = fontEntry->GetFTFace(true); + if (!face) { + return nullptr; + } + + FT2FontEntry* fe = FT2FontEntry::CreateFontEntry( + fontEntry->Name(), fontEntry->mFilename.get(), fontEntry->mFTFontIndex, + nullptr); + if (fe) { + fe->mStyleRange = aStyleForEntry; + fe->mWeightRange = aWeightForEntry; + fe->mStretchRange = aStretchForEntry; + fe->mIsLocalUserFont = true; + } + + return fe; +} + +FontFamily gfxFT2FontList::GetDefaultFontForPlatform(const gfxFontStyle* aStyle, + nsAtom* aLanguage) { + FontFamily ff; +#if defined(MOZ_WIDGET_ANDROID) + ff = FindFamily("Roboto"_ns); + if (ff.IsNull()) { + ff = FindFamily("Droid Sans"_ns); + } +#endif + /* TODO: what about Qt or other platforms that may use this? */ + return ff; +} + +gfxFontEntry* gfxFT2FontList::MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, + uint32_t aLength) { + // The FT2 font needs the font data to persist, so we do NOT free it here + // but instead pass ownership to the font entry. + // Deallocation will happen later, when the font face is destroyed. + return FT2FontEntry::CreateFontEntry(aFontName, aWeightForEntry, + aStretchForEntry, aStyleForEntry, + aFontData, aLength); +} + +gfxFontFamily* gfxFT2FontList::CreateFontFamily( + const nsACString& aName, FontVisibility aVisibility) const { + return new FT2FontFamily(aName, aVisibility); +} + +void gfxFT2FontList::WillShutdown() { + LOG(("WillShutdown")); + WriteCache(); + mFontNameCache = nullptr; +} diff --git a/gfx/thebes/gfxFT2FontList.h b/gfx/thebes/gfxFT2FontList.h new file mode 100644 index 0000000000..47c6affcfd --- /dev/null +++ b/gfx/thebes/gfxFT2FontList.h @@ -0,0 +1,241 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FT2FONTLIST_H +#define GFX_FT2FONTLIST_H + +#include "mozilla/MemoryReporting.h" +#include "gfxFT2FontBase.h" +#include "gfxPlatformFontList.h" + +namespace mozilla { +namespace dom { +class SystemFontListEntry; +}; +}; // namespace mozilla + +class FontNameCache; +typedef struct FT_FaceRec_* FT_Face; +class nsZipArchive; +class WillShutdownObserver; +class FTUserFontData; + +class FT2FontEntry final : public gfxFT2FontEntryBase { + friend class gfxFT2FontList; + + using FontListEntry = mozilla::dom::SystemFontListEntry; + + public: + explicit FT2FontEntry(const nsACString& aFaceName) + : gfxFT2FontEntryBase(aFaceName), mFTFontIndex(0) {} + + ~FT2FontEntry(); + + gfxFontEntry* Clone() const override; + + const nsCString& GetName() const { return Name(); } + + // create a font entry for a downloaded font + static FT2FontEntry* CreateFontEntry( + const nsACString& aFontName, WeightRange aWeight, StretchRange aStretch, + SlantStyleRange aStyle, const uint8_t* aFontData, uint32_t aLength); + + // create a font entry representing an installed font, identified by + // a FontListEntry; the freetype and cairo faces will not be instantiated + // until actually needed + static FT2FontEntry* CreateFontEntry(const FontListEntry& aFLE); + + // Create a font entry with the given name; if it is an installed font, + // also record the filename and index. + // If a non-null harfbuzz face is passed, also set style/weight/stretch + // properties of the entry from the values in the face. + static FT2FontEntry* CreateFontEntry(const nsACString& aName, + const char* aFilename, uint8_t aIndex, + const hb_face_t* aFace); + + gfxFont* CreateFontInstance(const gfxFontStyle* aStyle) override; + + nsresult ReadCMAP(FontInfoData* aFontInfoData = nullptr) override; + + hb_blob_t* GetFontTable(uint32_t aTableTag) override; + + bool HasFontTable(uint32_t aTableTag) override; + nsresult CopyFontTable(uint32_t aTableTag, nsTArray<uint8_t>&) override; + + bool HasVariations() override; + void GetVariationAxes( + nsTArray<gfxFontVariationAxis>& aVariationAxes) override; + void GetVariationInstances( + nsTArray<gfxFontVariationInstance>& aInstances) override; + + // Check for various kinds of brokenness, and set flags on the entry + // accordingly so that we avoid using bad font tables + void CheckForBrokenFont(gfxFontFamily* aFamily); + void CheckForBrokenFont(const nsACString& aFamilyKey); + + already_AddRefed<mozilla::gfx::SharedFTFace> GetFTFace(bool aCommit = false); + FTUserFontData* GetUserFontData(); + + FT_MM_Var* GetMMVar() override; + + // Get a harfbuzz face for this font, if possible. The caller is responsible + // to destroy the face when no longer needed. + // This may be a bit expensive, so avoid calling multiple times if the same + // face can be re-used for several purposes instead. + hb_face_t* CreateHBFace() const; + + /** + * Append this face's metadata to aFaceList for storage in the FontNameCache + * (for faster startup). + * The aPSName and aFullName parameters here can in principle be empty, + * but if they are missing for a given face then src:local() lookups will + * not be able to find it when the shared font list is in use. + */ + void AppendToFaceList(nsCString& aFaceList, const nsACString& aFamilyName, + const nsACString& aPSName, const nsACString& aFullName, + FontVisibility aVisibility); + + void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const override; + void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const override; + + RefPtr<mozilla::gfx::SharedFTFace> mFTFace; + + FT_MM_Var* mMMVar = nullptr; + + nsCString mFilename; + uint8_t mFTFontIndex; + + mozilla::ThreadSafeWeakPtr<mozilla::gfx::UnscaledFontFreeType> mUnscaledFont; + + nsTHashtable<nsUint32HashKey> mAvailableTables; + + bool mHasVariations = false; + bool mHasVariationsInitialized = false; + bool mMMVarInitialized = false; +}; + +class FT2FontFamily final : public gfxFontFamily { + using FontListEntry = mozilla::dom::SystemFontListEntry; + + public: + explicit FT2FontFamily(const nsACString& aName, FontVisibility aVisibility) + : gfxFontFamily(aName, aVisibility) {} + + // Append this family's faces to the IPC fontlist + void AddFacesToFontList(nsTArray<FontListEntry>* aFontList); +}; + +class gfxFT2FontList final : public gfxPlatformFontList { + using FontListEntry = mozilla::dom::SystemFontListEntry; + + public: + gfxFT2FontList(); + virtual ~gfxFT2FontList(); + + gfxFontEntry* CreateFontEntry( + mozilla::fontlist::Face* aFace, + const mozilla::fontlist::Family* aFamily) override; + + gfxFontEntry* LookupLocalFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) override; + + gfxFontEntry* MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, + uint32_t aLength) override; + + void WriteCache(); + + void ReadSystemFontList(nsTArray<FontListEntry>* aList); + + static gfxFT2FontList* PlatformFontList() { + return static_cast<gfxFT2FontList*>( + gfxPlatformFontList::PlatformFontList()); + } + + gfxFontFamily* CreateFontFamily(const nsACString& aName, + FontVisibility aVisibility) const override; + + void WillShutdown(); + + protected: + typedef enum { kUnknown, kStandard } StandardFile; + + // initialize font lists + nsresult InitFontListForPlatform() override; + + void AppendFaceFromFontListEntry(const FontListEntry& aFLE, + StandardFile aStdFile); + + void AppendFacesFromBlob(const nsCString& aFileName, StandardFile aStdFile, + hb_blob_t* aBlob, FontNameCache* aCache, + uint32_t aTimestamp, uint32_t aFilesize); + + void AppendFacesFromFontFile(const nsCString& aFileName, + FontNameCache* aCache, StandardFile aStdFile); + + void AppendFacesFromOmnijarEntry(nsZipArchive* aReader, + const nsCString& aEntryName, + FontNameCache* aCache, bool aJarChanged); + + void InitSharedFontListForPlatform() override; + void CollectInitData(const FontListEntry& aFLE, const nsCString& aPSName, + const nsCString& aFullName, StandardFile aStdFile); + + /** + * Callback passed to AppendFacesFromCachedFaceList to collect family/face + * information in either the unshared or shared list we're building. + */ + typedef void (*CollectFunc)(const FontListEntry& aFLE, + const nsCString& aPSName, + const nsCString& aFullName, + StandardFile aStdFile); + + /** + * Append faces from the face-list record for a specific file. + * aCollectFace is a callback that will store the face(s) in either the + * unshared mFontFamilies list or the mFamilyInitData/mFaceInitData tables + * that will be used to initialize the shared list. + * Returns true if it is able to read at least one face entry; false if no + * usable face entry was found. + */ + bool AppendFacesFromCachedFaceList(CollectFunc aCollectFace, + const nsCString& aFileName, + const nsCString& aFaceList, + StandardFile aStdFile); + + void AddFaceToList(const nsCString& aEntryName, uint32_t aIndex, + StandardFile aStdFile, hb_face_t* aFace, + nsCString& aFaceList); + + void FindFonts(); + + void FindFontsInOmnijar(FontNameCache* aCache); + + void FindFontsInDir(const nsCString& aDir, FontNameCache* aFNC); + + FontFamily GetDefaultFontForPlatform(const gfxFontStyle* aStyle, + nsAtom* aLanguage = nullptr) override; + + nsTHashtable<nsCStringHashKey> mSkipSpaceLookupCheckFamilies; + + private: + mozilla::UniquePtr<FontNameCache> mFontNameCache; + int64_t mJarModifiedTime; + RefPtr<WillShutdownObserver> mObserver; + + nsTArray<mozilla::fontlist::Family::InitData> mFamilyInitData; + nsClassHashtable<nsCStringHashKey, + nsTArray<mozilla::fontlist::Face::InitData>> + mFaceInitData; +}; + +#endif /* GFX_FT2FONTLIST_H */ diff --git a/gfx/thebes/gfxFT2Fonts.cpp b/gfx/thebes/gfxFT2Fonts.cpp new file mode 100644 index 0000000000..b4edd2edee --- /dev/null +++ b/gfx/thebes/gfxFT2Fonts.cpp @@ -0,0 +1,233 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(MOZ_WIDGET_GTK) +# include "gfxPlatformGtk.h" +# define gfxToolkitPlatform gfxPlatformGtk +#elif defined(XP_WIN) +# include "gfxWindowsPlatform.h" +# define gfxToolkitPlatform gfxWindowsPlatform +#elif defined(ANDROID) +# include "gfxAndroidPlatform.h" +# define gfxToolkitPlatform gfxAndroidPlatform +#endif + +#include "gfxTypes.h" +#include "gfxFT2Fonts.h" +#include "gfxFT2FontBase.h" +#include "gfxFT2Utils.h" +#include "gfxFT2FontList.h" +#include "gfxTextRun.h" +#include <locale.h> +#include "nsGkAtoms.h" +#include "nsTArray.h" +#include "nsCRT.h" +#include "nsXULAppAPI.h" + +#include "mozilla/Logging.h" +#include "prinit.h" + +#include "mozilla/MemoryReporting.h" +#include "mozilla/Preferences.h" +#include "mozilla/gfx/2D.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +/** + * gfxFT2Font + */ + +bool gfxFT2Font::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, + RoundingFlags aRounding, + gfxShapedText* aShapedText) { + if (!gfxFont::ShapeText(aDrawTarget, aText, aOffset, aLength, aScript, + aLanguage, aVertical, aRounding, aShapedText)) { + // harfbuzz must have failed(?!), just render raw glyphs + AddRange(aText, aOffset, aLength, aShapedText); + PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical, + aShapedText); + } + + return true; +} + +void gfxFT2Font::AddRange(const char16_t* aText, uint32_t aOffset, + uint32_t aLength, gfxShapedText* aShapedText) { + typedef gfxShapedText::CompressedGlyph CompressedGlyph; + + const uint32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit(); + // we'll pass this in/figure it out dynamically, but at this point there can + // be only one face. + gfxFT2LockedFace faceLock(this); + FT_Face face = faceLock.get(); + + CompressedGlyph* charGlyphs = aShapedText->GetCharacterGlyphs(); + + const gfxFT2Font::CachedGlyphData *cgd = nullptr, *cgdNext = nullptr; + + FT_UInt spaceGlyph = GetSpaceGlyph(); + + for (uint32_t i = 0; i < aLength; i++, aOffset++) { + char16_t ch = aText[i]; + + if (ch == 0) { + // treat this null byte as a missing glyph, don't create a glyph for it + aShapedText->SetMissingGlyph(aOffset, 0, this); + continue; + } + + NS_ASSERTION(!gfxFontGroup::IsInvalidChar(ch), "Invalid char detected"); + + if (cgdNext) { + cgd = cgdNext; + cgdNext = nullptr; + } else { + cgd = GetGlyphDataForChar(face, ch); + } + + FT_UInt gid = cgd->glyphIndex; + int32_t advance = 0; + + if (gid == 0) { + advance = -1; // trigger the missing glyphs case below + } else { + // find next character and its glyph -- in case they exist + // and exist in the current font face -- to compute kerning + char16_t chNext = 0; + FT_UInt gidNext = 0; + FT_Pos lsbDeltaNext = 0; + + if (FT_HAS_KERNING(face) && i + 1 < aLength) { + chNext = aText[i + 1]; + if (chNext != 0) { + cgdNext = GetGlyphDataForChar(face, chNext); + gidNext = cgdNext->glyphIndex; + if (gidNext && gidNext != spaceGlyph) + lsbDeltaNext = cgdNext->lsbDelta; + } + } + + advance = cgd->xAdvance; + + // now add kerning to the current glyph's advance + if (chNext && gidNext) { + FT_Vector kerning; + kerning.x = 0; + FT_Get_Kerning(face, gid, gidNext, FT_KERNING_DEFAULT, &kerning); + advance += kerning.x; + if (cgd->rsbDelta - lsbDeltaNext >= 32) { + advance -= 64; + } else if (cgd->rsbDelta - lsbDeltaNext < -32) { + advance += 64; + } + } + + // convert 26.6 fixed point to app units + // round rather than truncate to nearest pixel + // because these advances are often scaled + advance = ((advance * appUnitsPerDevUnit + 32) >> 6); + } + + if (advance >= 0 && CompressedGlyph::IsSimpleAdvance(advance) && + CompressedGlyph::IsSimpleGlyphID(gid)) { + charGlyphs[aOffset].SetSimpleGlyph(advance, gid); + } else if (gid == 0) { + // gid = 0 only happens when the glyph is missing from the font + aShapedText->SetMissingGlyph(aOffset, ch, this); + } else { + gfxTextRun::DetailedGlyph details; + details.mGlyphID = gid; + NS_ASSERTION(details.mGlyphID == gid, + "Seriously weird glyph ID detected!"); + details.mAdvance = advance; + aShapedText->SetDetailedGlyphs(aOffset, 1, &details); + } + } +} + +gfxFT2Font::gfxFT2Font(const RefPtr<UnscaledFontFreeType>& aUnscaledFont, + RefPtr<mozilla::gfx::SharedFTFace>&& aFTFace, + FT2FontEntry* aFontEntry, const gfxFontStyle* aFontStyle, + int aLoadFlags) + : gfxFT2FontBase(aUnscaledFont, std::move(aFTFace), aFontEntry, aFontStyle, + aLoadFlags, aFontStyle->NeedsSyntheticBold(aFontEntry)), + mCharGlyphCache(32) { + NS_ASSERTION(mFontEntry, + "Unable to find font entry for font. Something is whack."); + InitMetrics(); +} + +gfxFT2Font::~gfxFT2Font() {} + +already_AddRefed<ScaledFont> gfxFT2Font::GetScaledFont(DrawTarget* aTarget) { + if (!mAzureScaledFont) { + mAzureScaledFont = Factory::CreateScaledFontForFreeTypeFont( + GetUnscaledFont(), GetAdjustedSize(), mFTFace, + GetStyle()->NeedsSyntheticBold(GetFontEntry())); + InitializeScaledFont(); + } + + RefPtr<ScaledFont> scaledFont(mAzureScaledFont); + return scaledFont.forget(); +} + +bool gfxFT2Font::ShouldHintMetrics() const { + return !gfxPlatform::GetPlatform()->RequiresLinearZoom(); +} + +void gfxFT2Font::FillGlyphDataForChar(FT_Face face, uint32_t ch, + CachedGlyphData* gd) { + if (!face->charmap || (face->charmap->encoding != FT_ENCODING_UNICODE && + face->charmap->encoding != FT_ENCODING_MS_SYMBOL)) { + if (FT_Err_Ok != FT_Select_Charmap(face, FT_ENCODING_UNICODE) && + FT_Err_Ok != FT_Select_Charmap(face, FT_ENCODING_MS_SYMBOL)) { + NS_WARNING("failed to select Unicode or symbol charmap!"); + } + } + FT_UInt gid = FT_Get_Char_Index(face, ch); + + if (gid == 0) { + // this font doesn't support this char! + NS_ASSERTION(gid != 0, + "We don't have a glyph, but font indicated that it supported " + "this char in tables?"); + gd->glyphIndex = 0; + return; + } + + FT_Error err = Factory::LoadFTGlyph(face, gid, mFTLoadFlags); + + if (err) { + // hmm, this is weird, we failed to load a glyph that we had? + NS_WARNING("Failed to load glyph that we got from Get_Char_index"); + + gd->glyphIndex = 0; + return; + } + + gd->glyphIndex = gid; + gd->lsbDelta = face->glyph->lsb_delta; + gd->rsbDelta = face->glyph->rsb_delta; + gd->xAdvance = face->glyph->advance.x; + if (gd->xAdvance) { + gd->xAdvance += GetEmboldenStrength(face).x; + } +} + +void gfxFT2Font::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const { + gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); + aSizes->mFontInstances += + mCharGlyphCache.ShallowSizeOfExcludingThis(aMallocSizeOf); +} + +void gfxFT2Font::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const { + aSizes->mFontInstances += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} diff --git a/gfx/thebes/gfxFT2Fonts.h b/gfx/thebes/gfxFT2Fonts.h new file mode 100644 index 0000000000..65699b27b7 --- /dev/null +++ b/gfx/thebes/gfxFT2Fonts.h @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FT2FONTS_H +#define GFX_FT2FONTS_H + +#include "mozilla/MemoryReporting.h" +#include "gfxTypes.h" +#include "gfxFont.h" +#include "gfxFT2FontBase.h" +#include "gfxContext.h" +#include "gfxFontUtils.h" +#include "gfxUserFontSet.h" + +class FT2FontEntry; + +class gfxFT2Font : public gfxFT2FontBase { + public: // new functions + gfxFT2Font(const RefPtr<mozilla::gfx::UnscaledFontFreeType>& aUnscaledFont, + RefPtr<mozilla::gfx::SharedFTFace>&& aFTFace, + FT2FontEntry* aFontEntry, const gfxFontStyle* aFontStyle, + int aLoadFlags); + virtual ~gfxFT2Font(); + + FT2FontEntry* GetFontEntry(); + + already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont( + DrawTarget* aTarget) override; + + bool ShouldHintMetrics() const override; + + void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const override; + void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const override; + + protected: + struct CachedGlyphData { + CachedGlyphData() : glyphIndex(0xffffffffU) {} + + explicit CachedGlyphData(uint32_t gid) : glyphIndex(gid) {} + + uint32_t glyphIndex; + int32_t lsbDelta; + int32_t rsbDelta; + int32_t xAdvance; + }; + + const CachedGlyphData* GetGlyphDataForChar(FT_Face aFace, uint32_t ch) { + CharGlyphMapEntryType* entry = mCharGlyphCache.PutEntry(ch); + + if (!entry) return nullptr; + + if (entry->GetData().glyphIndex == 0xffffffffU) { + // this is a new entry, fill it + FillGlyphDataForChar(aFace, ch, entry->GetModifiableData()); + } + + return &entry->GetData(); + } + + bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, RoundingFlags aRounding, + gfxShapedText* aShapedText) override; + + void FillGlyphDataForChar(FT_Face face, uint32_t ch, CachedGlyphData* gd); + + void AddRange(const char16_t* aText, uint32_t aOffset, uint32_t aLength, + gfxShapedText* aShapedText); + + typedef nsBaseHashtableET<nsUint32HashKey, CachedGlyphData> + CharGlyphMapEntryType; + typedef nsTHashtable<CharGlyphMapEntryType> CharGlyphMap; + CharGlyphMap mCharGlyphCache; +}; + +#endif /* GFX_FT2FONTS_H */ diff --git a/gfx/thebes/gfxFT2Utils.cpp b/gfx/thebes/gfxFT2Utils.cpp new file mode 100644 index 0000000000..a4b193ae88 --- /dev/null +++ b/gfx/thebes/gfxFT2Utils.cpp @@ -0,0 +1,151 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxFT2FontBase.h" +#include "gfxFT2Utils.h" +#include "mozilla/Likely.h" + +#ifdef HAVE_FONTCONFIG_FCFREETYPE_H +# include <fontconfig/fcfreetype.h> +#endif + +#include "ft2build.h" +#include FT_MULTIPLE_MASTERS_H + +#include "prlink.h" + +uint32_t gfxFT2LockedFace::GetGlyph(uint32_t aCharCode) { + if (MOZ_UNLIKELY(!mFace)) return 0; + +#ifdef 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; failing that, try MS_SYMBOL. + // (FcFreeTypeCharIndex usually does the appropriate Unicode conversion, + // but some fonts have non-Roman glyphs for FT_ENCODING_APPLE_ROMAN + // characters.) + if (!mFace->charmap || (mFace->charmap->encoding != FT_ENCODING_UNICODE && + mFace->charmap->encoding != FT_ENCODING_MS_SYMBOL)) { + if (FT_Err_Ok != FT_Select_Charmap(mFace, FT_ENCODING_UNICODE) && + FT_Err_Ok != FT_Select_Charmap(mFace, FT_ENCODING_MS_SYMBOL)) { + NS_WARNING("failed to select Unicode or symbol charmap"); + } + } + + 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) { + MOZ_ASSERT(aVariantSelector, "aVariantSelector should not be NULL"); + + if (MOZ_UNLIKELY(!mFace)) return 0; + + // This function is available from FreeType 2.3.6 (June 2008). + static CharVariantFunction sGetCharVariantPtr = FindCharVariantFunction(); + if (!sGetCharVariantPtr) return 0; + +#ifdef 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); +} + +gfxFT2LockedFace::CharVariantFunction +gfxFT2LockedFace::FindCharVariantFunction() { + // This function is available from FreeType 2.3.6 (June 2008). + PRLibrary* lib = nullptr; + CharVariantFunction function = reinterpret_cast<CharVariantFunction>( + PR_FindFunctionSymbolAndLibrary("FT_Face_GetCharVariantIndex", &lib)); + if (!lib) { + return nullptr; + } + + FT_Int major; + FT_Int minor; + FT_Int patch; + FT_Library_Version(mFace->glyph->library, &major, &minor, &patch); + + // Versions 2.4.0 to 2.4.3 crash if configured with + // FT_CONFIG_OPTION_OLD_INTERNALS. Presence of the symbol FT_Alloc + // indicates FT_CONFIG_OPTION_OLD_INTERNALS. + if (major == 2 && minor == 4 && patch < 4 && + PR_FindFunctionSymbol(lib, "FT_Alloc")) { + function = nullptr; + } + + // Decrement the reference count incremented in + // PR_FindFunctionSymbolAndLibrary. + PR_UnloadLibrary(lib); + + return function; +} + +/*static*/ +void gfxFT2Utils::GetVariationAxes(const FT_MM_Var* aMMVar, + nsTArray<gfxFontVariationAxis>& aAxes) { + MOZ_ASSERT(aAxes.IsEmpty()); + if (!aMMVar) { + return; + } + aAxes.SetCapacity(aMMVar->num_axis); + for (unsigned i = 0; i < aMMVar->num_axis; i++) { + const auto& a = aMMVar->axis[i]; + gfxFontVariationAxis axis; + axis.mMinValue = a.minimum / 65536.0; + axis.mMaxValue = a.maximum / 65536.0; + axis.mDefaultValue = a.def / 65536.0; + axis.mTag = a.tag; + axis.mName = a.name; + aAxes.AppendElement(axis); + } +} + +/*static*/ +void gfxFT2Utils::GetVariationInstances( + gfxFontEntry* aFontEntry, const FT_MM_Var* aMMVar, + nsTArray<gfxFontVariationInstance>& aInstances) { + MOZ_ASSERT(aInstances.IsEmpty()); + if (!aMMVar) { + return; + } + hb_blob_t* nameTable = + aFontEntry->GetFontTable(TRUETYPE_TAG('n', 'a', 'm', 'e')); + if (!nameTable) { + return; + } + aInstances.SetCapacity(aMMVar->num_namedstyles); + for (unsigned i = 0; i < aMMVar->num_namedstyles; i++) { + const auto& ns = aMMVar->namedstyle[i]; + gfxFontVariationInstance inst; + nsresult rv = + gfxFontUtils::ReadCanonicalName(nameTable, ns.strid, inst.mName); + if (NS_FAILED(rv)) { + continue; + } + inst.mValues.SetCapacity(aMMVar->num_axis); + for (unsigned j = 0; j < aMMVar->num_axis; j++) { + gfxFontVariationValue value; + value.mAxis = aMMVar->axis[j].tag; + value.mValue = ns.coords[j] / 65536.0; + inst.mValues.AppendElement(value); + } + aInstances.AppendElement(inst); + } + hb_blob_destroy(nameTable); +} diff --git a/gfx/thebes/gfxFT2Utils.h b/gfx/thebes/gfxFT2Utils.h new file mode 100644 index 0000000000..4defe373c7 --- /dev/null +++ b/gfx/thebes/gfxFT2Utils.h @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FT2UTILS_H +#define GFX_FT2UTILS_H + +#include "cairo-ft.h" +#include "gfxFT2FontBase.h" +#include "mozilla/Likely.h" + +// Rounding and truncation functions for a FreeType fixed point number +// (FT26Dot6) stored in a 32bit integer with high 26 bits for the integer +// part and low 6 bits for the fractional part. +#define FLOAT_FROM_26_6(x) ((x) / 64.0) +#define FLOAT_FROM_16_16(x) ((x) / 65536.0) +#define ROUND_26_6_TO_INT(x) ((x) >= 0 ? ((32 + (x)) >> 6) : -((32 - (x)) >> 6)) + +typedef struct FT_FaceRec_* FT_Face; + +/** + * BEWARE: Recursively locking with gfxFT2LockedFace is not supported. + * Do not instantiate gfxFT2LockedFace within the scope of another instance. + * Do not attempt to call into Cairo within the scope of gfxFT2LockedFace, + * as that may accidentally try to re-lock the face within Cairo itself + * and thus deadlock. + */ +class MOZ_STACK_CLASS gfxFT2LockedFace { + public: + explicit gfxFT2LockedFace(gfxFT2FontBase* aFont) + : mGfxFont(aFont), mFace(aFont->LockFTFace()) {} + ~gfxFT2LockedFace() { + if (mFace) { + mGfxFont->UnlockFTFace(); + } + } + + FT_Face get() { return mFace; }; + + /** + * Get the glyph id for a Unicode character representable by a single + * glyph, or return zero if there is no such glyph. This does no caching, + * so you probably want gfxFcFont::GetGlyph. + */ + uint32_t GetGlyph(uint32_t aCharCode); + /** + * Returns 0 if there is no variation selector cmap subtable. + */ + uint32_t GetUVSGlyph(uint32_t aCharCode, uint32_t aVariantSelector); + + protected: + typedef FT_UInt (*CharVariantFunction)(FT_Face face, FT_ULong charcode, + FT_ULong variantSelector); + CharVariantFunction FindCharVariantFunction(); + + gfxFT2FontBase* MOZ_NON_OWNING_REF mGfxFont; // owned by caller + FT_Face mFace; +}; + +// A couple of FreeType-based utilities shared by gfxFontconfigFontEntry +// and FT2FontEntry. + +typedef struct FT_MM_Var_ FT_MM_Var; + +class gfxFT2Utils { + public: + static void GetVariationAxes(const FT_MM_Var* aMMVar, + nsTArray<gfxFontVariationAxis>& aAxes); + + static void GetVariationInstances( + gfxFontEntry* aFontEntry, const FT_MM_Var* aMMVar, + nsTArray<gfxFontVariationInstance>& aInstances); +}; + +#endif /* GFX_FT2UTILS_H */ diff --git a/gfx/thebes/gfxFailure.h b/gfx/thebes/gfxFailure.h new file mode 100644 index 0000000000..04192a5a6e --- /dev/null +++ b/gfx/thebes/gfxFailure.h @@ -0,0 +1,24 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef gfxFailure_h_ +#define gfxFailure_h_ + +#include "nsString.h" +#include "nsIGfxInfo.h" +#include "nsServiceManagerUtils.h" + +namespace mozilla { +namespace gfx { +inline void LogFailure(const nsCString& failure) { + nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1"); + gfxInfo->LogFailure(failure); +} +} // namespace gfx +} // namespace mozilla + +#endif // gfxFailure_h_ diff --git a/gfx/thebes/gfxFcPlatformFontList.cpp b/gfx/thebes/gfxFcPlatformFontList.cpp new file mode 100644 index 0000000000..baec762d70 --- /dev/null +++ b/gfx/thebes/gfxFcPlatformFontList.cpp @@ -0,0 +1,2498 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Logging.h" + +#include "gfxFcPlatformFontList.h" +#include "gfxFont.h" +#include "gfxFontConstants.h" +#include "gfxFontFamilyList.h" +#include "gfxFT2Utils.h" +#include "gfxPlatform.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/Preferences.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/TimeStamp.h" +#include "nsGkAtoms.h" +#include "nsUnicodeProperties.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsXULAppAPI.h" +#include "SharedFontList-impl.h" +#include "StandardFonts-linux.inc" + +#include "mozilla/gfx/HelpersCairo.h" + +#include <cairo-ft.h> +#include <fontconfig/fcfreetype.h> +#include <dlfcn.h> +#include <unistd.h> + +#ifdef MOZ_WIDGET_GTK +# include <gdk/gdk.h> +# include "gfxPlatformGtk.h" +#endif + +#ifdef MOZ_X11 +# include "mozilla/X11Util.h" +#endif + +#if defined(MOZ_SANDBOX) && defined(XP_LINUX) +# include "mozilla/SandboxBrokerPolicyFactory.h" +# include "mozilla/SandboxSettings.h" +#endif + +#include FT_MULTIPLE_MASTERS_H + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::unicode; + +#ifndef FC_POSTSCRIPT_NAME +# define FC_POSTSCRIPT_NAME "postscriptname" /* String */ +#endif +#ifndef FC_VARIABLE +# define FC_VARIABLE "variable" /* Bool */ +#endif + +#define PRINTING_FC_PROPERTY "gfx.printing" + +#define LOG_FONTLIST(args) \ + MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug, args) +#define LOG_FONTLIST_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug) +#define LOG_CMAPDATA_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_cmapdata), LogLevel::Debug) + +static const FcChar8* ToFcChar8Ptr(const char* aStr) { + return reinterpret_cast<const FcChar8*>(aStr); +} + +static const char* ToCharPtr(const FcChar8* aStr) { + return reinterpret_cast<const char*>(aStr); +} + +// canonical name ==> first en name or first name if no en name +// This is the required logic for fullname lookups as per CSS3 Fonts spec. +static uint32_t FindCanonicalNameIndex(FcPattern* aFont, + const char* aLangField) { + uint32_t n = 0, en = 0; + FcChar8* lang; + while (FcPatternGetString(aFont, aLangField, n, &lang) == FcResultMatch) { + // look for 'en' or variants, en-US, en-JP etc. + uint32_t len = strlen(ToCharPtr(lang)); + bool enPrefix = (strncmp(ToCharPtr(lang), "en", 2) == 0); + if (enPrefix && (len == 2 || (len > 2 && aLangField[2] == '-'))) { + en = n; + break; + } + n++; + } + return en; +} + +static void GetFaceNames(FcPattern* aFont, const nsACString& aFamilyName, + nsACString& aPostscriptName, nsACString& aFullname) { + // get the Postscript name + FcChar8* psname; + if (FcPatternGetString(aFont, FC_POSTSCRIPT_NAME, 0, &psname) == + FcResultMatch) { + aPostscriptName = ToCharPtr(psname); + } + + // get the canonical fullname (i.e. en name or first name) + uint32_t en = FindCanonicalNameIndex(aFont, FC_FULLNAMELANG); + FcChar8* fullname; + if (FcPatternGetString(aFont, FC_FULLNAME, en, &fullname) == FcResultMatch) { + aFullname = ToCharPtr(fullname); + } + + // if have fullname, done + if (!aFullname.IsEmpty()) { + return; + } + + // otherwise, set the fullname to family + style name [en] and use that + aFullname = aFamilyName; + + // figure out the en style name + en = FindCanonicalNameIndex(aFont, FC_STYLELANG); + nsAutoCString style; + FcChar8* stylename = nullptr; + FcPatternGetString(aFont, FC_STYLE, en, &stylename); + if (stylename) { + style = ToCharPtr(stylename); + } + + if (!style.IsEmpty() && !style.EqualsLiteral("Regular")) { + aFullname.Append(' '); + aFullname.Append(style); + } +} + +static FontWeight MapFcWeight(int aFcWeight) { + if (aFcWeight <= (FC_WEIGHT_THIN + FC_WEIGHT_EXTRALIGHT) / 2) { + return FontWeight(100); + } + if (aFcWeight <= (FC_WEIGHT_EXTRALIGHT + FC_WEIGHT_LIGHT) / 2) { + return FontWeight(200); + } + if (aFcWeight <= (FC_WEIGHT_LIGHT + FC_WEIGHT_BOOK) / 2) { + return FontWeight(300); + } + if (aFcWeight <= (FC_WEIGHT_REGULAR + FC_WEIGHT_MEDIUM) / 2) { + // This includes FC_WEIGHT_BOOK + return FontWeight(400); + } + if (aFcWeight <= (FC_WEIGHT_MEDIUM + FC_WEIGHT_DEMIBOLD) / 2) { + return FontWeight(500); + } + if (aFcWeight <= (FC_WEIGHT_DEMIBOLD + FC_WEIGHT_BOLD) / 2) { + return FontWeight(600); + } + if (aFcWeight <= (FC_WEIGHT_BOLD + FC_WEIGHT_EXTRABOLD) / 2) { + return FontWeight(700); + } + if (aFcWeight <= (FC_WEIGHT_EXTRABOLD + FC_WEIGHT_BLACK) / 2) { + return FontWeight(800); + } + if (aFcWeight <= FC_WEIGHT_BLACK) { + return FontWeight(900); + } + + // including FC_WEIGHT_EXTRABLACK + return FontWeight(901); +} + +// TODO(emilio, jfkthame): I think this can now be more fine-grained. +static FontStretch MapFcWidth(int aFcWidth) { + if (aFcWidth <= (FC_WIDTH_ULTRACONDENSED + FC_WIDTH_EXTRACONDENSED) / 2) { + return FontStretch::UltraCondensed(); + } + if (aFcWidth <= (FC_WIDTH_EXTRACONDENSED + FC_WIDTH_CONDENSED) / 2) { + return FontStretch::ExtraCondensed(); + } + if (aFcWidth <= (FC_WIDTH_CONDENSED + FC_WIDTH_SEMICONDENSED) / 2) { + return FontStretch::Condensed(); + } + if (aFcWidth <= (FC_WIDTH_SEMICONDENSED + FC_WIDTH_NORMAL) / 2) { + return FontStretch::SemiCondensed(); + } + if (aFcWidth <= (FC_WIDTH_NORMAL + FC_WIDTH_SEMIEXPANDED) / 2) { + return FontStretch::Normal(); + } + if (aFcWidth <= (FC_WIDTH_SEMIEXPANDED + FC_WIDTH_EXPANDED) / 2) { + return FontStretch::SemiExpanded(); + } + if (aFcWidth <= (FC_WIDTH_EXPANDED + FC_WIDTH_EXTRAEXPANDED) / 2) { + return FontStretch::Expanded(); + } + if (aFcWidth <= (FC_WIDTH_EXTRAEXPANDED + FC_WIDTH_ULTRAEXPANDED) / 2) { + return FontStretch::ExtraExpanded(); + } + return FontStretch::UltraExpanded(); +} + +static void GetFontProperties(FcPattern* aFontPattern, WeightRange* aWeight, + StretchRange* aStretch, + SlantStyleRange* aSlantStyle) { + // weight + int weight; + if (FcPatternGetInteger(aFontPattern, FC_WEIGHT, 0, &weight) != + FcResultMatch) { + weight = FC_WEIGHT_REGULAR; + } + *aWeight = WeightRange(MapFcWeight(weight)); + + // width + int width; + if (FcPatternGetInteger(aFontPattern, FC_WIDTH, 0, &width) != FcResultMatch) { + width = FC_WIDTH_NORMAL; + } + *aStretch = StretchRange(MapFcWidth(width)); + + // italic + int slant; + if (FcPatternGetInteger(aFontPattern, FC_SLANT, 0, &slant) != FcResultMatch) { + slant = FC_SLANT_ROMAN; + } + if (slant == FC_SLANT_OBLIQUE) { + *aSlantStyle = SlantStyleRange(FontSlantStyle::Oblique()); + } else if (slant > 0) { + *aSlantStyle = SlantStyleRange(FontSlantStyle::Italic()); + } +} + +gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsACString& aFaceName, + FcPattern* aFontPattern, + bool aIgnoreFcCharmap) + : gfxFT2FontEntryBase(aFaceName), + mFontPattern(aFontPattern), + mFTFaceInitialized(false), + mIgnoreFcCharmap(aIgnoreFcCharmap), + mHasVariationsInitialized(false), + mAspect(0.0) { + GetFontProperties(aFontPattern, &mWeightRange, &mStretchRange, &mStyleRange); +} + +gfxFontEntry* gfxFontconfigFontEntry::Clone() const { + MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!"); + return new gfxFontconfigFontEntry(Name(), mFontPattern, mIgnoreFcCharmap); +} + +static already_AddRefed<FcPattern> CreatePatternForFace(FT_Face aFace) { + // Use fontconfig to fill out the pattern from the FTFace. + // The "file" argument cannot be nullptr (in fontconfig-2.6.0 at + // least). The dummy file passed here is removed below. + // + // When fontconfig scans the system fonts, FcConfigGetBlanks(nullptr) + // is passed as the "blanks" argument, which provides that unexpectedly + // blank glyphs are elided. Here, however, we pass nullptr for + // "blanks", effectively assuming that, if the font has a blank glyph, + // then the author intends any associated character to be rendered + // blank. + RefPtr<FcPattern> pattern = + dont_AddRef(FcFreeTypeQueryFace(aFace, ToFcChar8Ptr(""), 0, nullptr)); + // given that we have a FT_Face, not really sure this is possible... + if (!pattern) { + pattern = dont_AddRef(FcPatternCreate()); + } + FcPatternDel(pattern, FC_FILE); + FcPatternDel(pattern, FC_INDEX); + + // Make a new pattern and store the face in it so that cairo uses + // that when creating a cairo font face. + FcPatternAddFTFace(pattern, FC_FT_FACE, aFace); + + return pattern.forget(); +} + +static already_AddRefed<SharedFTFace> CreateFaceForPattern( + FcPattern* aPattern) { + FcChar8* filename; + if (FcPatternGetString(aPattern, FC_FILE, 0, &filename) != FcResultMatch) { + return nullptr; + } + int index; + if (FcPatternGetInteger(aPattern, FC_INDEX, 0, &index) != FcResultMatch) { + index = 0; // default to 0 if not found in pattern + } + return Factory::NewSharedFTFace(nullptr, ToCharPtr(filename), index); +} + +gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsACString& aFaceName, + WeightRange aWeight, + StretchRange aStretch, + SlantStyleRange aStyle, + RefPtr<SharedFTFace>&& aFace) + : gfxFT2FontEntryBase(aFaceName), + mFTFace(std::move(aFace)), + mFTFaceInitialized(true), + mIgnoreFcCharmap(true), + mHasVariationsInitialized(false), + mAspect(0.0) { + mWeightRange = aWeight; + mStyleRange = aStyle; + mStretchRange = aStretch; + mIsDataUserFont = true; + + mFontPattern = CreatePatternForFace(mFTFace->GetFace()); +} + +gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsACString& aFaceName, + FcPattern* aFontPattern, + WeightRange aWeight, + StretchRange aStretch, + SlantStyleRange aStyle) + : gfxFT2FontEntryBase(aFaceName), + mFontPattern(aFontPattern), + mFTFaceInitialized(false), + mHasVariationsInitialized(false), + mAspect(0.0) { + mWeightRange = aWeight; + mStyleRange = aStyle; + mStretchRange = aStretch; + mIsLocalUserFont = true; + + // The proper setting of mIgnoreFcCharmap is tricky for fonts loaded + // via src:local()... + // If the local font happens to come from the application fontset, + // we want to set it to true so that color/svg fonts will work even + // if the default glyphs are blank; but if the local font is a non- + // sfnt face (e.g. legacy type 1) then we need to set it to false + // because our cmap-reading code will fail and we depend on FT+Fc to + // determine the coverage. + // We set the flag here, but may flip it the first time TestCharacterMap + // is called, at which point we'll look to see whether a 'cmap' is + // actually present in the font. + mIgnoreFcCharmap = true; +} + +typedef FT_Error (*GetVarFunc)(FT_Face, FT_MM_Var**); +typedef FT_Error (*DoneVarFunc)(FT_Library, FT_MM_Var*); +static GetVarFunc sGetVar; +static DoneVarFunc sDoneVar; +static bool sInitializedVarFuncs = false; + +static void InitializeVarFuncs() { + if (sInitializedVarFuncs) { + return; + } + sInitializedVarFuncs = true; +#if MOZ_TREE_FREETYPE + sGetVar = &FT_Get_MM_Var; + sDoneVar = &FT_Done_MM_Var; +#else + sGetVar = (GetVarFunc)dlsym(RTLD_DEFAULT, "FT_Get_MM_Var"); + sDoneVar = (DoneVarFunc)dlsym(RTLD_DEFAULT, "FT_Done_MM_Var"); +#endif +} + +gfxFontconfigFontEntry::~gfxFontconfigFontEntry() { + if (mMMVar) { + // Prior to freetype 2.9, there was no specific function to free the + // FT_MM_Var record, and the docs just said to use free(). + // InitializeVarFuncs must have been called in order for mMMVar to be + // non-null here, so we don't need to do it again. + if (sDoneVar) { + MOZ_ASSERT(mFTFace, "How did mMMVar get set without a face?"); + (*sDoneVar)(mFTFace->GetFace()->glyph->library, mMMVar); + } else { + free(mMMVar); + } + } +} + +nsresult gfxFontconfigFontEntry::ReadCMAP(FontInfoData* aFontInfoData) { + // attempt this once, if errors occur leave a blank cmap + if (mCharacterMap) { + return NS_OK; + } + + RefPtr<gfxCharacterMap> charmap; + nsresult rv; + + if (aFontInfoData && + (charmap = GetCMAPFromFontInfo(aFontInfoData, mUVSOffset))) { + rv = NS_OK; + } else { + uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p'); + charmap = new gfxCharacterMap(); + AutoTable cmapTable(this, kCMAP); + + if (cmapTable) { + uint32_t cmapLen; + const uint8_t* cmapData = reinterpret_cast<const uint8_t*>( + hb_blob_get_data(cmapTable, &cmapLen)); + rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, *charmap, mUVSOffset); + } else { + rv = NS_ERROR_NOT_AVAILABLE; + } + } + + mHasCmapTable = NS_SUCCEEDED(rv); + if (mHasCmapTable) { + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + fontlist::FontList* sharedFontList = pfl->SharedFontList(); + if (!IsUserFont() && mShmemFace) { + mShmemFace->SetCharacterMap(sharedFontList, charmap); // async + if (!TrySetShmemCharacterMap()) { + // Temporarily retain charmap, until the shared version is + // ready for use. + mCharacterMap = charmap; + } + } else { + mCharacterMap = pfl->FindCharMap(charmap); + } + } else { + // if error occurred, initialize to null cmap + mCharacterMap = new gfxCharacterMap(); + } + + LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %zu hash: %8.8x%s\n", + mName.get(), charmap->SizeOfIncludingThis(moz_malloc_size_of), + charmap->mHash, mCharacterMap == charmap ? " new" : "")); + if (LOG_CMAPDATA_ENABLED()) { + char prefix[256]; + SprintfLiteral(prefix, "(cmapdata) name: %.220s", mName.get()); + charmap->Dump(prefix, eGfxLog_cmapdata); + } + + return rv; +} + +static bool HasChar(FcPattern* aFont, FcChar32 aCh) { + FcCharSet* charset = nullptr; + FcPatternGetCharSet(aFont, FC_CHARSET, 0, &charset); + return charset && FcCharSetHasChar(charset, aCh); +} + +bool gfxFontconfigFontEntry::TestCharacterMap(uint32_t aCh) { + // For user fonts, or for fonts bundled with the app (which might include + // color/svg glyphs where the default glyphs may be blank, and thus confuse + // fontconfig/freetype's char map checking), we instead check the cmap + // directly for character coverage. + if (mIgnoreFcCharmap) { + // If it does not actually have a cmap, switch our strategy to use + // fontconfig's charmap after all (except for data fonts, which must + // always have a cmap to have passed OTS validation). + if (!mIsDataUserFont && !HasFontTable(TRUETYPE_TAG('c', 'm', 'a', 'p'))) { + mIgnoreFcCharmap = false; + // ...and continue with HasChar() below. + } else { + return gfxFontEntry::TestCharacterMap(aCh); + } + } + // otherwise (for system fonts), use the charmap in the pattern + return HasChar(mFontPattern, aCh); +} + +bool gfxFontconfigFontEntry::HasFontTable(uint32_t aTableTag) { + if (FTUserFontData* ufd = GetUserFontData()) { + return !!gfxFontUtils::FindTableDirEntry(ufd->FontData(), aTableTag); + } + return gfxFT2FontEntryBase::FaceHasTable(GetFTFace(), aTableTag); +} + +hb_blob_t* gfxFontconfigFontEntry::GetFontTable(uint32_t aTableTag) { + // for data fonts, read directly from the font data + if (FTUserFontData* ufd = GetUserFontData()) { + return gfxFontUtils::GetTableFromFontData(ufd->FontData(), aTableTag); + } + + return gfxFontEntry::GetFontTable(aTableTag); +} + +double gfxFontconfigFontEntry::GetAspect() { + if (mAspect != 0.0) { + return mAspect; + } + + // try to compute aspect from OS/2 metrics if available + AutoTable os2Table(this, TRUETYPE_TAG('O', 'S', '/', '2')); + if (os2Table) { + uint16_t upem = UnitsPerEm(); + if (upem != kInvalidUPEM) { + uint32_t len; + auto os2 = + reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len)); + if (uint16_t(os2->version) >= 2) { + if (len >= offsetof(OS2Table, sxHeight) + sizeof(int16_t) && + int16_t(os2->sxHeight) > 0.1 * upem) { + mAspect = double(int16_t(os2->sxHeight)) / upem; + return mAspect; + } + } + } + } + + // default to aspect = 0.5 if the code below fails + mAspect = 0.5; + + // create a font to calculate x-height / em-height + gfxFontStyle s; + s.size = 100.0; // pick large size to avoid possible hinting artifacts + RefPtr<gfxFont> font = FindOrMakeFont(&s); + if (font) { + const gfxFont::Metrics& metrics = + font->GetMetrics(nsFontMetrics::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, int* aOutLoadFlags, + unsigned int* aOutSynthFlags) { + int loadFlags = FT_LOAD_DEFAULT; + unsigned int synthFlags = 0; + + // xxx - taken from the gfxFontconfigFonts code, needs to be reviewed + + FcBool printing; + if (FcPatternGetBool(aPattern, PRINTING_FC_PROPERTY, 0, &printing) != + FcResultMatch) { + printing = FcFalse; + } + + // Font options are set explicitly here to improve cairo's caching + // behavior and to record the relevant parts of the pattern so that + // the pattern can be released. + // + // Most font_options have already been set as defaults on the FcPattern + // with cairo_ft_font_options_substitute(), then user and system + // fontconfig configurations were applied. The resulting font_options + // have been recorded on the face during + // cairo_ft_font_face_create_for_pattern(). + // + // None of the settings here cause this scaled_font to behave any + // differently from how it would behave if it were created from the same + // face with default font_options. + // + // We set options explicitly so that the same scaled_font will be found in + // the cairo_scaled_font_map when cairo loads glyphs from a context with + // the same font_face, font_matrix, ctm, and surface font_options. + // + // Unfortunately, _cairo_scaled_font_keys_equal doesn't know about the + // font_options on the cairo_ft_font_face, and doesn't consider default + // option values to not match any explicit values. + // + // Even after cairo_set_scaled_font is used to set font_options for the + // cairo context, when cairo looks for a scaled_font for the context, it + // will look for a font with some option values from the target surface if + // any values are left default on the context font_options. If this + // scaled_font is created with default font_options, cairo will not find + // it. + // + // The one option not recorded in the pattern is hint_metrics, which will + // affect glyph metrics. The default behaves as CAIRO_HINT_METRICS_ON. + // We should be considering the font_options of the surface on which this + // font will be used, but currently we don't have different gfxFonts for + // different surface font_options, so we'll create a font suitable for the + // Screen. Image and xlib surfaces default to CAIRO_HINT_METRICS_ON. + + // The remaining options have been recorded on the pattern and the face. + // _cairo_ft_options_merge has some logic to decide which options from the + // scaled_font or from the cairo_ft_font_face take priority in the way the + // font behaves. + // + // In the majority of cases, _cairo_ft_options_merge uses the options from + // the cairo_ft_font_face, so sometimes it is not so important which + // values are set here so long as they are not defaults, but we'll set + // them to the exact values that we expect from the font, to be consistent + // and to protect against changes in cairo. + // + // In some cases, _cairo_ft_options_merge uses some options from the + // scaled_font's font_options rather than options on the + // cairo_ft_font_face (from fontconfig). + // https://bugs.freedesktop.org/show_bug.cgi?id=11838 + // + // Surface font options were set on the pattern in + // cairo_ft_font_options_substitute. If fontconfig has changed the + // hint_style then that is what the user (or distribution) wants, so we + // use the setting from the FcPattern. + // + // Fallback values here mirror treatment of defaults in cairo-ft-font.c. + FcBool hinting = FcFalse; + if (FcPatternGetBool(aPattern, FC_HINTING, 0, &hinting) != FcResultMatch) { + hinting = FcTrue; + } + + int fc_hintstyle = FC_HINT_NONE; + if ((!printing || hinting) && + FcPatternGetInteger(aPattern, FC_HINT_STYLE, 0, &fc_hintstyle) != + FcResultMatch) { + fc_hintstyle = FC_HINT_FULL; + } + switch (fc_hintstyle) { + case FC_HINT_NONE: + loadFlags = FT_LOAD_NO_HINTING; + break; + case FC_HINT_SLIGHT: + loadFlags = FT_LOAD_TARGET_LIGHT; + break; + } + + FcBool fc_antialias; + if (FcPatternGetBool(aPattern, FC_ANTIALIAS, 0, &fc_antialias) != + FcResultMatch) { + fc_antialias = FcTrue; + } + if (!fc_antialias) { + if (fc_hintstyle != FC_HINT_NONE) { + loadFlags = FT_LOAD_TARGET_MONO; + } + loadFlags |= FT_LOAD_MONOCHROME; + } else if (fc_hintstyle == FC_HINT_FULL) { + int fc_rgba; + if (FcPatternGetInteger(aPattern, FC_RGBA, 0, &fc_rgba) != FcResultMatch) { + fc_rgba = FC_RGBA_UNKNOWN; + } + switch (fc_rgba) { + case FC_RGBA_RGB: + case FC_RGBA_BGR: + loadFlags = FT_LOAD_TARGET_LCD; + break; + case FC_RGBA_VRGB: + case FC_RGBA_VBGR: + loadFlags = FT_LOAD_TARGET_LCD_V; + break; + } + } + + FcBool bitmap; + if (FcPatternGetBool(aPattern, FC_EMBEDDED_BITMAP, 0, &bitmap) != + FcResultMatch) { + bitmap = FcFalse; + } + if (fc_antialias && (fc_hintstyle == FC_HINT_NONE || !bitmap)) { + loadFlags |= FT_LOAD_NO_BITMAP; + } + + FcBool autohint; + if (FcPatternGetBool(aPattern, FC_AUTOHINT, 0, &autohint) == FcResultMatch && + autohint) { + loadFlags |= FT_LOAD_FORCE_AUTOHINT; + } + + FcBool embolden; + if (FcPatternGetBool(aPattern, FC_EMBOLDEN, 0, &embolden) == FcResultMatch && + embolden) { + synthFlags |= CAIRO_FT_SYNTHESIZE_BOLD; + } + + *aOutLoadFlags = loadFlags; + *aOutSynthFlags = synthFlags; +} + +#ifdef MOZ_WIDGET_GTK +// defintion included below +static void ApplyGdkScreenFontOptions(FcPattern* aPattern); +#endif + +#ifdef MOZ_X11 +static bool GetXftInt(Display* aDisplay, const char* aName, int* aResult) { + if (!aDisplay) { + return false; + } + char* value = XGetDefault(aDisplay, "Xft", aName); + if (!value) { + return false; + } + if (FcNameConstant(const_cast<FcChar8*>(ToFcChar8Ptr(value)), aResult)) { + return true; + } + char* end; + *aResult = strtol(value, &end, 0); + if (end != value) { + return true; + } + return false; +} +#endif + +static void PreparePattern(FcPattern* aPattern, bool aIsPrinterFont) { + FcConfigSubstitute(nullptr, aPattern, FcMatchPattern); + + // This gets cairo_font_options_t for the Screen. We should have + // different font options for printing (no hinting) but we are not told + // what we are measuring for. + // + // If cairo adds support for lcd_filter, gdk will not provide the default + // setting for that option. We could get the default setting by creating + // an xlib surface once, recording its font_options, and then merging the + // gdk options. + // + // Using an xlib surface would also be an option to get Screen font + // options for non-GTK X11 toolkits, but less efficient than using GDK to + // pick up dynamic changes. + if (aIsPrinterFont) { + cairo_font_options_t* options = cairo_font_options_create(); + cairo_font_options_set_hint_style(options, CAIRO_HINT_STYLE_NONE); + cairo_font_options_set_antialias(options, CAIRO_ANTIALIAS_GRAY); + cairo_ft_font_options_substitute(options, aPattern); + cairo_font_options_destroy(options); + FcPatternAddBool(aPattern, PRINTING_FC_PROPERTY, FcTrue); + } else if (!gfxPlatform::IsHeadless()) { +#ifdef MOZ_WIDGET_GTK + ApplyGdkScreenFontOptions(aPattern); + +# ifdef MOZ_X11 + FcValue value; + int lcdfilter; + if (FcPatternGet(aPattern, FC_LCD_FILTER, 0, &value) == FcResultNoMatch) { + GdkDisplay* dpy = gdk_display_get_default(); + if (GDK_IS_X11_DISPLAY(dpy) && + GetXftInt(GDK_DISPLAY_XDISPLAY(dpy), "lcdfilter", &lcdfilter)) { + FcPatternAddInteger(aPattern, FC_LCD_FILTER, lcdfilter); + } + } +# endif // MOZ_X11 +#endif // MOZ_WIDGET_GTK + } + + FcDefaultSubstitute(aPattern); +} + +void gfxFontconfigFontEntry::UnscaledFontCache::MoveToFront(size_t aIndex) { + if (aIndex > 0) { + ThreadSafeWeakPtr<UnscaledFontFontconfig> front = + std::move(mUnscaledFonts[aIndex]); + for (size_t i = aIndex; i > 0; i--) { + mUnscaledFonts[i] = std::move(mUnscaledFonts[i - 1]); + } + mUnscaledFonts[0] = std::move(front); + } +} + +already_AddRefed<UnscaledFontFontconfig> +gfxFontconfigFontEntry::UnscaledFontCache::Lookup(const std::string& aFile, + uint32_t aIndex) { + for (size_t i = 0; i < kNumEntries; i++) { + RefPtr<UnscaledFontFontconfig> entry(mUnscaledFonts[i]); + if (entry && entry->GetFile() == aFile && entry->GetIndex() == aIndex) { + MoveToFront(i); + return entry.forget(); + } + } + return nullptr; +} + +static inline gfxFloat SizeForStyle(gfxFontconfigFontEntry* aEntry, + const gfxFontStyle& aStyle) { + return 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; + } + } + // If the font has bitmaps but wants to be scaled, then let it scale. + if (bestSize >= 0.0) { + FcBool scalable; + if (FcPatternGetBool(aEntry->GetPattern(), FC_SCALABLE, 0, &scalable) == + FcResultMatch && + scalable) { + return requestedSize; + } + } + return bestSize; +} + +gfxFont* gfxFontconfigFontEntry::CreateFontInstance( + const gfxFontStyle* aFontStyle) { + RefPtr<FcPattern> pattern = dont_AddRef(FcPatternCreate()); + if (!pattern) { + NS_WARNING("Failed to create Fontconfig pattern for font instance"); + return nullptr; + } + + double size = ChooseFontSize(this, *aFontStyle); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, size); + + RefPtr<SharedFTFace> face = GetFTFace(); + if (!face) { + NS_WARNING("Failed to get FreeType face for pattern"); + return nullptr; + } + if (HasVariations()) { + // For variation fonts, we create a new FT_Face here so that + // variation coordinates from the style can be applied without + // affecting other font instances created from the same entry + // (font resource). + // For user fonts: create a new FT_Face from the font data, and then make + // a pattern from that. + // For system fonts: create a new FT_Face and store it in a copy of the + // original mFontPattern. + RefPtr<SharedFTFace> varFace = face->GetData() + ? face->GetData()->CloneFace() + : CreateFaceForPattern(mFontPattern); + if (varFace) { + AutoTArray<gfxFontVariation, 8> settings; + GetVariationsForStyle(settings, *aFontStyle); + gfxFT2FontBase::SetupVarCoords(GetMMVar(), settings, varFace->GetFace()); + face = std::move(varFace); + } + } + + PreparePattern(pattern, aFontStyle->printerFont); + RefPtr<FcPattern> renderPattern = + dont_AddRef(FcFontRenderPrepare(nullptr, pattern, mFontPattern)); + if (!renderPattern) { + NS_WARNING("Failed to prepare Fontconfig pattern for font instance"); + return nullptr; + } + + if (aFontStyle->NeedsSyntheticBold(this)) { + FcPatternAddBool(renderPattern, FC_EMBOLDEN, FcTrue); + } + + // will synthetic oblique be applied using a transform? + if (IsUpright() && aFontStyle->style != FontSlantStyle::Normal() && + aFontStyle->allowSyntheticStyle) { + // disable embedded bitmaps (mimics behavior in 90-synthetic.conf) + FcPatternDel(renderPattern, FC_EMBEDDED_BITMAP); + FcPatternAddBool(renderPattern, FC_EMBEDDED_BITMAP, FcFalse); + } + + int loadFlags; + unsigned int synthFlags; + PrepareFontOptions(renderPattern, &loadFlags, &synthFlags); + + std::string file; + int index = 0; + if (!face->GetData()) { + const FcChar8* fcFile; + if (FcPatternGetString(renderPattern, FC_FILE, 0, + const_cast<FcChar8**>(&fcFile)) != FcResultMatch || + FcPatternGetInteger(renderPattern, FC_INDEX, 0, &index) != + FcResultMatch) { + NS_WARNING("No file in Fontconfig pattern for font instance"); + return nullptr; + } + file = ToCharPtr(fcFile); + } + + RefPtr<UnscaledFontFontconfig> unscaledFont = + mUnscaledFontCache.Lookup(file, index); + if (!unscaledFont) { + unscaledFont = mFTFace->GetData() ? new UnscaledFontFontconfig(mFTFace) + : new UnscaledFontFontconfig( + std::move(file), index, mFTFace); + mUnscaledFontCache.Add(unscaledFont); + } + + gfxFont* newFont = new gfxFontconfigFont( + unscaledFont, std::move(face), renderPattern, size, this, aFontStyle, + loadFlags, (synthFlags & CAIRO_FT_SYNTHESIZE_BOLD) != 0); + + return newFont; +} + +const RefPtr<SharedFTFace>& gfxFontconfigFontEntry::GetFTFace() { + if (!mFTFaceInitialized) { + mFTFaceInitialized = true; + mFTFace = CreateFaceForPattern(mFontPattern); + } + return mFTFace; +} + +FTUserFontData* gfxFontconfigFontEntry::GetUserFontData() { + if (mFTFace && mFTFace->GetData()) { + return static_cast<FTUserFontData*>(mFTFace->GetData()); + } + return nullptr; +} + +bool gfxFontconfigFontEntry::HasVariations() { + if (mHasVariationsInitialized) { + return mHasVariations; + } + mHasVariationsInitialized = true; + mHasVariations = false; + + if (!gfxPlatform::GetPlatform()->HasVariationFontSupport()) { + return mHasVariations; + } + + // For installed fonts, query the fontconfig pattern rather than paying + // the cost of loading a FT_Face that we otherwise might never need. + if (!IsUserFont() || IsLocalUserFont()) { + FcBool variable; + if ((FcPatternGetBool(mFontPattern, FC_VARIABLE, 0, &variable) == + FcResultMatch) && + variable) { + mHasVariations = true; + } + } else { + if (GetFTFace()) { + mHasVariations = + mFTFace->GetFace()->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS; + } + } + + return mHasVariations; +} + +FT_MM_Var* gfxFontconfigFontEntry::GetMMVar() { + if (mMMVarInitialized) { + return mMMVar; + } + mMMVarInitialized = true; + InitializeVarFuncs(); + if (!sGetVar) { + return nullptr; + } + if (!GetFTFace()) { + return nullptr; + } + if (FT_Err_Ok != (*sGetVar)(mFTFace->GetFace(), &mMMVar)) { + mMMVar = nullptr; + } + return mMMVar; +} + +void gfxFontconfigFontEntry::GetVariationAxes( + nsTArray<gfxFontVariationAxis>& aAxes) { + if (!HasVariations()) { + return; + } + gfxFT2Utils::GetVariationAxes(GetMMVar(), aAxes); +} + +void gfxFontconfigFontEntry::GetVariationInstances( + nsTArray<gfxFontVariationInstance>& aInstances) { + if (!HasVariations()) { + return; + } + gfxFT2Utils::GetVariationInstances(this, GetMMVar(), aInstances); +} + +nsresult gfxFontconfigFontEntry::CopyFontTable(uint32_t aTableTag, + nsTArray<uint8_t>& aBuffer) { + NS_ASSERTION(!mIsDataUserFont, + "data fonts should be reading tables directly from memory"); + return gfxFT2FontEntryBase::CopyFaceTable(GetFTFace(), aTableTag, aBuffer); +} + +void gfxFontconfigFontFamily::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 + nsAutoCString psname, fullname; + GetFaceNames(face, mName, psname, fullname); + const nsAutoCString& faceName = !psname.IsEmpty() ? psname : fullname; + + gfxFontconfigFontEntry* fontEntry = + new gfxFontconfigFontEntry(faceName, face, mContainsAppFonts); + + if (gfxPlatform::GetPlatform()->HasVariationFontSupport()) { + fontEntry->SetupVariationRanges(); + } + + AddFontEntry(fontEntry); + + if (fontEntry->IsNormalStyle()) { + numRegularFaces++; + } + + if (LOG_FONTLIST_ENABLED()) { + nsAutoCString weightString; + fontEntry->Weight().ToString(weightString); + nsAutoCString stretchString; + fontEntry->Stretch().ToString(stretchString); + nsAutoCString styleString; + fontEntry->SlantStyle().ToString(styleString); + LOG_FONTLIST( + ("(fontlist) added (%s) to family (%s)" + " with style: %s weight: %s stretch: %s" + " psname: %s fullname: %s", + fontEntry->Name().get(), Name().get(), styleString.get(), + weightString.get(), stretchString.get(), psname.get(), + fullname.get())); + } + } + + // somewhat arbitrary, but define a family with two or more regular + // faces as a family for which intra-family fallback should be used + if (numRegularFaces > 1) { + mCheckForFallbackFaces = true; + } + mFaceNamesInitialized = true; + mFontPatterns.Clear(); + SetHasStyles(true); + + CheckForSimpleFamily(); +} + +void gfxFontconfigFontFamily::AddFontPattern(FcPattern* aFontPattern) { + NS_ASSERTION( + !mHasStyles, + "font patterns must not be added to already enumerated families"); + + FcBool outline; + if (FcPatternGetBool(aFontPattern, FC_OUTLINE, 0, &outline) != + FcResultMatch || + !outline) { + mHasNonScalableFaces = true; + + FcBool scalable; + if (FcPatternGetBool(aFontPattern, FC_SCALABLE, 0, &scalable) == + FcResultMatch && + scalable) { + mForceScalable = true; + } + } + + mFontPatterns.AppendElement(aFontPattern); +} + +static const double kRejectDistance = 10000.0; + +// Calculate a distance score representing the size disparity between the +// requested style's size and the font entry's size. +static double SizeDistance(gfxFontconfigFontEntry* aEntry, + const gfxFontStyle& aStyle, bool aForceScalable) { + double requestedSize = SizeForStyle(aEntry, aStyle); + double bestDist = -1.0; + double size; + int v = 0; + while (FcPatternGetDouble(aEntry->GetPattern(), FC_PIXEL_SIZE, v, &size) == + FcResultMatch) { + ++v; + double dist = fabs(size - requestedSize); + if (bestDist < 0.0 || dist < bestDist) { + bestDist = dist; + } + } + if (bestDist < 0.0) { + // No size means scalable + return -1.0; + } else if (aForceScalable || 5.0 * bestDist < requestedSize) { + // fontconfig prefers a matching family or lang to pixelsize of bitmap + // fonts. CSS suggests a tolerance of 20% on pixelsize. + return bestDist; + } else { + // Reject any non-scalable fonts that are not within tolerance. + return kRejectDistance; + } +} + +void gfxFontconfigFontFamily::FindAllFontsForStyle( + const gfxFontStyle& aFontStyle, nsTArray<gfxFontEntry*>& aFontEntryList, + bool aIgnoreSizeTolerance) { + gfxFontFamily::FindAllFontsForStyle(aFontStyle, aFontEntryList, + aIgnoreSizeTolerance); + + if (!mHasNonScalableFaces) { + return; + } + + // Iterate over the the available fonts while compacting any groups + // of unscalable fonts with matching styles into a single entry + // corresponding to the closest available size. If the closest + // available size is rejected for being outside tolerance, then the + // entire group will be skipped. + size_t skipped = 0; + gfxFontconfigFontEntry* bestEntry = nullptr; + double bestDist = -1.0; + for (size_t i = 0; i < aFontEntryList.Length(); i++) { + gfxFontconfigFontEntry* entry = + static_cast<gfxFontconfigFontEntry*>(aFontEntryList[i]); + double dist = + SizeDistance(entry, aFontStyle, mForceScalable || aIgnoreSizeTolerance); + // If the entry is scalable or has a style that does not match + // the group of unscalable fonts, then start a new group. + if (dist < 0.0 || !bestEntry || bestEntry->Stretch() != entry->Stretch() || + bestEntry->Weight() != entry->Weight() || + bestEntry->SlantStyle() != entry->SlantStyle()) { + // If the best entry in this group is still outside the tolerance, + // then skip the entire group. + if (bestDist >= kRejectDistance) { + skipped++; + } + // Remove any compacted entries from the previous group. + if (skipped) { + i -= skipped; + aFontEntryList.RemoveElementsAt(i, skipped); + skipped = 0; + } + // Mark the start of the new group. + bestEntry = entry; + bestDist = dist; + } else { + // If this entry more closely matches the requested size than the + // current best in the group, then take this entry instead. + if (dist < bestDist) { + aFontEntryList[i - 1 - skipped] = entry; + bestEntry = entry; + bestDist = dist; + } + skipped++; + } + } + // If the best entry in this group is still outside the tolerance, + // then skip the entire group. + if (bestDist >= kRejectDistance) { + skipped++; + } + // Remove any compacted entries from the current group. + if (skipped) { + aFontEntryList.TruncateLength(aFontEntryList.Length() - skipped); + } +} + +static bool PatternHasLang(const FcPattern* aPattern, const FcChar8* aLang) { + FcLangSet* langset; + + if (FcPatternGetLangSet(aPattern, FC_LANG, 0, &langset) != FcResultMatch) { + return false; + } + + if (FcLangSetHasLang(langset, aLang) != FcLangDifferentLang) { + return true; + } + return false; +} + +bool gfxFontconfigFontFamily::SupportsLangGroup(nsAtom* aLangGroup) const { + if (!aLangGroup || aLangGroup == nsGkAtoms::Unicode) { + return true; + } + + nsAutoCString fcLang; + gfxFcPlatformFontList* pfl = gfxFcPlatformFontList::PlatformFontList(); + pfl->GetSampleLangForGroup(aLangGroup, fcLang); + if (fcLang.IsEmpty()) { + return true; + } + + // Before FindStyleVariations has been called, mFontPatterns will contain + // the font patterns. Afterward, it'll be empty, but mAvailableFonts + // will contain the font entries, each of which holds a reference to its + // pattern. We only check the first pattern in each list, because support + // for langs is considered to be consistent across all faces in a family. + FcPattern* fontPattern; + if (mFontPatterns.Length()) { + fontPattern = mFontPatterns[0]; + } else if (mAvailableFonts.Length()) { + fontPattern = static_cast<gfxFontconfigFontEntry*>(mAvailableFonts[0].get()) + ->GetPattern(); + } else { + return true; + } + + // is lang included in the underlying pattern? + return PatternHasLang(fontPattern, ToFcChar8Ptr(fcLang.get())); +} + +/* virtual */ +gfxFontconfigFontFamily::~gfxFontconfigFontFamily() { + // Should not be dropped by stylo + MOZ_ASSERT(NS_IsMainThread()); +} + +template <typename Func> +void gfxFontconfigFontFamily::AddFacesToFontList(Func aAddPatternFunc) { + if (HasStyles()) { + for (auto& fe : mAvailableFonts) { + if (!fe) { + continue; + } + auto fce = static_cast<gfxFontconfigFontEntry*>(fe.get()); + aAddPatternFunc(fce->GetPattern(), mContainsAppFonts); + } + } else { + for (auto& pat : mFontPatterns) { + aAddPatternFunc(pat, mContainsAppFonts); + } + } +} + +gfxFontconfigFont::gfxFontconfigFont( + const RefPtr<UnscaledFontFontconfig>& aUnscaledFont, + RefPtr<SharedFTFace>&& aFTFace, FcPattern* aPattern, gfxFloat aAdjustedSize, + gfxFontEntry* aFontEntry, const gfxFontStyle* aFontStyle, int aLoadFlags, + bool aEmbolden) + : gfxFT2FontBase(aUnscaledFont, std::move(aFTFace), aFontEntry, aFontStyle, + aLoadFlags, aEmbolden), + mPattern(aPattern) { + mAdjustedSize = aAdjustedSize; + InitMetrics(); +} + +gfxFontconfigFont::~gfxFontconfigFont() = default; + +already_AddRefed<ScaledFont> gfxFontconfigFont::GetScaledFont( + mozilla::gfx::DrawTarget* aTarget) { + if (!mAzureScaledFont) { + mAzureScaledFont = Factory::CreateScaledFontForFontconfigFont( + GetUnscaledFont(), GetAdjustedSize(), mFTFace, GetPattern()); + InitializeScaledFont(); + } + + RefPtr<ScaledFont> scaledFont(mAzureScaledFont); + return scaledFont.forget(); +} + +bool gfxFontconfigFont::ShouldHintMetrics() const { + return !GetStyle()->printerFont; +} + +gfxFcPlatformFontList::gfxFcPlatformFontList() + : mLocalNames(64), + mGenericMappings(32), + mFcSubstituteCache(64), + mLastConfig(nullptr), + mAlwaysUseFontconfigGenerics(true) { + CheckFamilyList(kBaseFonts_Ubuntu_20_04, + ArrayLength(kBaseFonts_Ubuntu_20_04)); + CheckFamilyList(kLangFonts_Ubuntu_20_04, + ArrayLength(kLangFonts_Ubuntu_20_04)); + CheckFamilyList(kBaseFonts_Fedora_32, ArrayLength(kBaseFonts_Fedora_32)); + mLastConfig = FcConfigGetCurrent(); + if (XRE_IsParentProcess()) { + // if the rescan interval is set, start the timer + int rescanInterval = FcConfigGetRescanInterval(nullptr); + if (rescanInterval) { + NS_NewTimerWithFuncCallback( + getter_AddRefs(mCheckFontUpdatesTimer), CheckFontUpdates, this, + (rescanInterval + 1) * 1000, nsITimer::TYPE_REPEATING_SLACK, + "gfxFcPlatformFontList::gfxFcPlatformFontList"); + if (!mCheckFontUpdatesTimer) { + NS_WARNING("Failure to create font updates timer"); + } + } + } + +#ifdef MOZ_BUNDLED_FONTS + mBundledFontsInitialized = false; +#endif +} + +gfxFcPlatformFontList::~gfxFcPlatformFontList() { + if (mCheckFontUpdatesTimer) { + mCheckFontUpdatesTimer->Cancel(); + mCheckFontUpdatesTimer = nullptr; + } +} + +void gfxFcPlatformFontList::AddFontSetFamilies(FcFontSet* aFontSet, + const SandboxPolicy* aPolicy, + bool aAppFonts) { + // This iterates over the fonts in a font set and adds in gfxFontFamily + // objects for each family. Individual gfxFontEntry objects for each face + // are not created here; the patterns are just stored in the family. When + // a family is actually used, it will be populated with gfxFontEntry + // records and the patterns moved to those. + + if (!aFontSet) { + NS_WARNING("AddFontSetFamilies called with a null font set."); + return; + } + + FcChar8* lastFamilyName = (FcChar8*)""; + RefPtr<gfxFontconfigFontFamily> fontFamily; + nsAutoCString familyName; + for (int f = 0; f < aFontSet->nfont; f++) { + FcPattern* pattern = aFontSet->fonts[f]; + + // Skip any fonts that aren't readable for us (e.g. due to restrictive + // file ownership/permissions). + FcChar8* path; + if (FcPatternGetString(pattern, FC_FILE, 0, &path) != FcResultMatch) { + continue; + } + if (access(reinterpret_cast<const char*>(path), F_OK | R_OK) != 0) { + continue; + } + +#if defined(MOZ_SANDBOX) && defined(XP_LINUX) + // Skip any fonts that will be blocked by the content-process sandbox + // policy. + if (aPolicy && !(aPolicy->Lookup(reinterpret_cast<const char*>(path)) & + SandboxBroker::Perms::MAY_READ)) { + continue; + } +#endif + + AddPatternToFontList(pattern, lastFamilyName, familyName, fontFamily, + aAppFonts); + } +} + +void gfxFcPlatformFontList::AddPatternToFontList( + FcPattern* aFont, FcChar8*& aLastFamilyName, nsACString& aFamilyName, + RefPtr<gfxFontconfigFontFamily>& aFontFamily, bool aAppFonts) { + // get canonical name + uint32_t cIndex = FindCanonicalNameIndex(aFont, FC_FAMILYLANG); + FcChar8* canonical = nullptr; + FcPatternGetString(aFont, FC_FAMILY, cIndex, &canonical); + if (!canonical) { + return; + } + + // same as the last one? no need to add a new family, skip + if (FcStrCmp(canonical, aLastFamilyName) != 0) { + aLastFamilyName = canonical; + + // add new family if one doesn't already exist + aFamilyName.Truncate(); + aFamilyName = ToCharPtr(canonical); + nsAutoCString keyName(aFamilyName); + ToLowerCase(keyName); + + aFontFamily = + static_cast<gfxFontconfigFontFamily*>(mFontFamilies.GetWeak(keyName)); + if (!aFontFamily) { + FontVisibility visibility = + aAppFonts ? FontVisibility::Base : GetVisibilityForFamily(keyName); + aFontFamily = new gfxFontconfigFontFamily(aFamilyName, visibility); + mFontFamilies.Put(keyName, RefPtr{aFontFamily}); + } + // Record if the family contains fonts from the app font set + // (in which case we won't rely on fontconfig's charmap, due to + // bug 1276594). + if (aAppFonts) { + aFontFamily->SetFamilyContainsAppFonts(true); + } + + // Add pointers to other localized family names. Most fonts + // only have a single name, so the first call to GetString + // will usually not match + FcChar8* otherName; + int n = (cIndex == 0 ? 1 : 0); + while (FcPatternGetString(aFont, FC_FAMILY, n, &otherName) == + FcResultMatch) { + nsAutoCString otherFamilyName(ToCharPtr(otherName)); + AddOtherFamilyName(aFontFamily, otherFamilyName); + n++; + if (n == int(cIndex)) { + n++; // skip over canonical name + } + } + } + + MOZ_ASSERT(aFontFamily, "font must belong to a font family"); + aFontFamily->AddFontPattern(aFont); + + // map the psname, fullname ==> font family for local font lookups + nsAutoCString psname, fullname; + GetFaceNames(aFont, aFamilyName, psname, fullname); + if (!psname.IsEmpty()) { + ToLowerCase(psname); + mLocalNames.Put(psname, aFont); + } + if (!fullname.IsEmpty()) { + ToLowerCase(fullname); + mLocalNames.Put(fullname, aFont); + } +} + +nsresult gfxFcPlatformFontList::InitFontListForPlatform() { +#ifdef MOZ_BUNDLED_FONTS + if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) { + ActivateBundledFonts(); + } +#endif + + mLocalNames.Clear(); + mFcSubstituteCache.Clear(); + + mAlwaysUseFontconfigGenerics = PrefFontListsUseOnlyGenerics(); + mOtherFamilyNamesInitialized = true; + + mLastConfig = FcConfigGetCurrent(); + + if (XRE_IsContentProcess()) { + // Content process: use the font list passed from the chrome process, + // because we can't rely on fontconfig in the presence of sandboxing; + // it may report fonts that we can't actually access. + + FcChar8* lastFamilyName = (FcChar8*)""; + RefPtr<gfxFontconfigFontFamily> fontFamily; + nsAutoCString familyName; + + // Get font list that was passed during XPCOM startup + // or in an UpdateFontList message. + auto& fontList = dom::ContentChild::GetSingleton()->SystemFontList(); + + // For fontconfig versions between 2.10.94 and 2.11.1 inclusive, + // we need to escape any leading space in the charset element, + // otherwise FcNameParse will fail. :( + // + // The bug was introduced on 2013-05-24 by + // https://cgit.freedesktop.org/fontconfig/commit/?id=cd9b1033a68816a7acfbba1718ba0aa5888f6ec7 + // "Bug 64906 - FcNameParse() should ignore leading whitespace in + // parameters" + // because ignoring a leading space in the encoded value of charset + // causes erroneous decoding of the whole element. + // This first shipped in version 2.10.94, and was eventually fixed as + // a side-effect of switching to the "human-readable" representation of + // charsets on 2014-07-03 in + // https://cgit.freedesktop.org/fontconfig/commit/?id=e708e97c351d3bc9f7030ef22ac2f007d5114730 + // "Change charset parse/unparse format to be human readable" + // (with a followup fix next day) which means a leading space is no + // longer significant. This fix landed after 2.11.1 had been shipped, + // so the first version tag without the bug is 2.11.91. + int fcVersion = FcGetVersion(); + bool fcCharsetParseBug = fcVersion >= 21094 && fcVersion <= 21101; + + for (FontPatternListEntry& fpe : fontList) { + nsCString& patternStr = fpe.pattern(); + if (fcCharsetParseBug) { + int32_t index = patternStr.Find(":charset= "); + if (index != kNotFound) { + // insert backslash after the =, before the space + patternStr.Insert('\\', index + 9); + } + } + FcPattern* pattern = FcNameParse((const FcChar8*)patternStr.get()); + AddPatternToFontList(pattern, lastFamilyName, familyName, fontFamily, + fpe.appFontFamily()); + FcPatternDestroy(pattern); + } + + LOG_FONTLIST( + ("got font list from chrome process: " + "%u faces in %u families", + (unsigned)fontList.Length(), mFontFamilies.Count())); + + fontList.Clear(); + + return NS_OK; + } + + UniquePtr<SandboxPolicy> policy; + +#if defined(MOZ_SANDBOX) && defined(XP_LINUX) + // If read sandboxing is enabled, create a temporary SandboxPolicy to + // check font paths; use a fake PID to avoid picking up any PID-specific + // rules by accident. + SandboxBrokerPolicyFactory policyFactory; + if (GetEffectiveContentSandboxLevel() > 2 && + !PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX")) { + policy = policyFactory.GetContentPolicy(-1, false); + } +#endif + + // iterate over available fonts + FcFontSet* systemFonts = FcConfigGetFonts(nullptr, FcSetSystem); + AddFontSetFamilies(systemFonts, policy.get(), /* aAppFonts = */ false); + +#ifdef MOZ_BUNDLED_FONTS + if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) { + FcFontSet* appFonts = FcConfigGetFonts(nullptr, FcSetApplication); + AddFontSetFamilies(appFonts, policy.get(), /* aAppFonts = */ true); + } +#endif + + return NS_OK; +} + +void gfxFcPlatformFontList::ReadSystemFontList( + nsTArray<FontPatternListEntry>* retValue) { + // Fontconfig versions below 2.9 drop the FC_FILE element in FcNameUnparse + // (see https://bugs.freedesktop.org/show_bug.cgi?id=26718), so when using + // an older version, we manually append it to the unparsed pattern. + if (FcGetVersion() < 20900) { + for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { + auto family = static_cast<gfxFontconfigFontFamily*>(iter.Data().get()); + family->AddFacesToFontList([&](FcPattern* aPat, bool aAppFonts) { + char* s = (char*)FcNameUnparse(aPat); + nsDependentCString patternStr(s); + char* file = nullptr; + if (FcResultMatch == + FcPatternGetString(aPat, FC_FILE, 0, (FcChar8**)&file)) { + patternStr.Append(":file="); + patternStr.Append(file); + } + retValue->AppendElement(FontPatternListEntry(patternStr, aAppFonts)); + free(s); + }); + } + } else { + for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { + auto family = static_cast<gfxFontconfigFontFamily*>(iter.Data().get()); + family->AddFacesToFontList([&](FcPattern* aPat, bool aAppFonts) { + char* s = (char*)FcNameUnparse(aPat); + nsDependentCString patternStr(s); + retValue->AppendElement(FontPatternListEntry(patternStr, aAppFonts)); + free(s); + }); + } + } +} + +void gfxFcPlatformFontList::InitSharedFontListForPlatform() { + mLocalNames.Clear(); + mFcSubstituteCache.Clear(); + + mAlwaysUseFontconfigGenerics = PrefFontListsUseOnlyGenerics(); + mOtherFamilyNamesInitialized = true; + + mLastConfig = FcConfigGetCurrent(); + + if (!XRE_IsParentProcess()) { + // Content processes will access the shared-memory data created by the + // parent, so they do not need to query fontconfig for the available + // fonts themselves. + return; + } + +#ifdef MOZ_BUNDLED_FONTS + if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) { + ActivateBundledFonts(); + } +#endif + + UniquePtr<SandboxPolicy> policy; + +#if defined(MOZ_CONTENT_SANDBOX) && defined(XP_LINUX) + // If read sandboxing is enabled, create a temporary SandboxPolicy to + // check font paths; use a fake PID to avoid picking up any PID-specific + // rules by accident. + SandboxBrokerPolicyFactory policyFactory; + if (GetEffectiveContentSandboxLevel() > 2 && + !PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX")) { + policy = policyFactory.GetContentPolicy(-1, false); + } +#endif + + nsTArray<fontlist::Family::InitData> families; + + using FaceInitArray = nsTArray<fontlist::Face::InitData>; + nsClassHashtable<nsCStringHashKey, FaceInitArray> faces; + + // Do we need to work around the fontconfig FcNameParse/FcNameUnparse bug + // (present in versions between 2.10.94 and 2.11.1 inclusive)? See comment + // in InitFontListForPlatform for details. + int fcVersion = FcGetVersion(); + bool fcCharsetParseBug = fcVersion >= 21094 && fcVersion <= 21101; + + auto addPattern = [this, fcCharsetParseBug, &families, &faces]( + FcPattern* aPattern, FcChar8*& aLastFamilyName, + nsCString& aFamilyName, bool aAppFont) -> void { + // get canonical name + uint32_t cIndex = FindCanonicalNameIndex(aPattern, FC_FAMILYLANG); + FcChar8* canonical = nullptr; + FcPatternGetString(aPattern, FC_FAMILY, cIndex, &canonical); + if (!canonical) { + return; + } + + nsAutoCString keyName; + keyName = ToCharPtr(canonical); + ToLowerCase(keyName); + FaceInitArray* faceListPtr = nullptr; + + // Same canonical family name as the last one? Definitely no need to add a + // new family record. + if (FcStrCmp(canonical, aLastFamilyName) == 0) { + faceListPtr = faces.Get(keyName); + MOZ_ASSERT(faceListPtr); + } else { + aLastFamilyName = canonical; + aFamilyName = ToCharPtr(canonical); + + // Add new family record if one doesn't already exist. + auto faceList = faces.LookupForAdd(keyName); + if (!faceList) { + faceList.OrInsert([]() { return new FaceInitArray; }); + FontVisibility visibility = + aAppFont ? FontVisibility::Base : GetVisibilityForFamily(keyName); + families.AppendElement(fontlist::Family::InitData( + keyName, aFamilyName, fontlist::Family::kNoIndex, visibility, + /*bundled*/ aAppFont, /*badUnderline*/ false)); + } + faceListPtr = faceList.Data().get(); + } + + char* s = (char*)FcNameUnparse(aPattern); + nsAutoCString descriptor(s); + free(s); + + if (fcCharsetParseBug) { + // Escape any leading space in charset to work around FcNameParse bug. + int32_t index = descriptor.Find(":charset= "); + if (index != kNotFound) { + // insert backslash after the =, before the space + descriptor.Insert('\\', index + 9); + } + } + + WeightRange weight(FontWeight::Normal()); + StretchRange stretch(FontStretch::Normal()); + SlantStyleRange style(FontSlantStyle::Normal()); + GetFontProperties(aPattern, &weight, &stretch, &style); + + faceListPtr->AppendElement( + fontlist::Face::InitData{descriptor, 0, false, weight, stretch, style}); + // map the psname, fullname ==> font family for local font lookups + nsAutoCString psname, fullname; + GetFaceNames(aPattern, aFamilyName, psname, fullname); + if (!psname.IsEmpty()) { + ToLowerCase(psname); + mLocalNameTable.Put( + psname, fontlist::LocalFaceRec::InitData(keyName, descriptor)); + } + if (!fullname.IsEmpty()) { + ToLowerCase(fullname); + if (fullname != psname) { + mLocalNameTable.Put( + fullname, fontlist::LocalFaceRec::InitData(keyName, descriptor)); + } + } + + // Add entries for any other localized family names. (Most fonts only have + // a single family name, so the first call to GetString will usually fail). + FcChar8* otherName; + int n = (cIndex == 0 ? 1 : 0); + while (FcPatternGetString(aPattern, FC_FAMILY, n, &otherName) == + FcResultMatch) { + nsAutoCString otherFamilyName(ToCharPtr(otherName)); + keyName = otherFamilyName; + ToLowerCase(keyName); + + auto faceList = faces.LookupForAdd(keyName); + if (!faceList) { + faceList.OrInsert([]() { return new FaceInitArray; }); + FontVisibility visibility = + aAppFont ? FontVisibility::Base : GetVisibilityForFamily(keyName); + families.AppendElement(fontlist::Family::InitData( + keyName, otherFamilyName, fontlist::Family::kNoIndex, visibility, + /*bundled*/ aAppFont, /*badUnderline*/ false)); + } + faceList.Data()->AppendElement(fontlist::Face::InitData{ + descriptor, 0, false, weight, stretch, style}); + + n++; + if (n == int(cIndex)) { + n++; // skip over canonical name + } + } + }; + + auto addFontSetFamilies = [&addPattern](FcFontSet* aFontSet, + SandboxPolicy* aPolicy, + bool aAppFonts) -> void { + FcChar8* lastFamilyName = (FcChar8*)""; + RefPtr<gfxFontconfigFontFamily> fontFamily; + nsAutoCString familyName; + for (int f = 0; f < aFontSet->nfont; f++) { + FcPattern* pattern = aFontSet->fonts[f]; + + // Skip any fonts that aren't readable for us (e.g. due to restrictive + // file ownership/permissions). + FcChar8* path; + if (FcPatternGetString(pattern, FC_FILE, 0, &path) != FcResultMatch) { + continue; + } + if (access(reinterpret_cast<const char*>(path), F_OK | R_OK) != 0) { + continue; + } + +#if defined(MOZ_CONTENT_SANDBOX) && defined(XP_LINUX) + // Skip any fonts that will be blocked by the content-process sandbox + // policy. + if (aPolicy && !(aPolicy->Lookup(reinterpret_cast<const char*>(path)) & + SandboxBroker::Perms::MAY_READ)) { + continue; + } +#endif + + addPattern(pattern, lastFamilyName, familyName, aAppFonts); + } + }; + + // iterate over available fonts + FcFontSet* systemFonts = FcConfigGetFonts(nullptr, FcSetSystem); + addFontSetFamilies(systemFonts, policy.get(), /* aAppFonts = */ false); + +#ifdef MOZ_BUNDLED_FONTS + if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) { + FcFontSet* appFonts = FcConfigGetFonts(nullptr, FcSetApplication); + addFontSetFamilies(appFonts, policy.get(), /* aAppFonts = */ true); + } +#endif + + mozilla::fontlist::FontList* list = SharedFontList(); + list->SetFamilyNames(families); + + for (uint32_t i = 0; i < families.Length(); i++) { + list->Families()[i].AddFaces(list, *faces.Get(families[i].mKey)); + } +} + +gfxFcPlatformFontList::DistroID gfxFcPlatformFontList::GetDistroID() const { + // Helper called to initialize sResult the first time this is used. + auto getDistroID = []() { + DistroID result = DistroID::Unknown; + FILE* fp = fopen("/etc/os-release", "r"); + if (fp) { + char buf[512]; + while (fgets(buf, sizeof(buf), fp)) { + if (strncmp(buf, "ID=", 3) == 0) { + if (strncmp(buf + 3, "ubuntu", 6) == 0) { + result = DistroID::Ubuntu; + } else if (strncmp(buf + 3, "fedora", 6) == 0) { + result = DistroID::Fedora; + } + break; + } + } + fclose(fp); + } + return result; + }; + static DistroID sResult = getDistroID(); + return sResult; +} + +FontVisibility gfxFcPlatformFontList::GetVisibilityForFamily( + const nsACString& aName) const { + switch (GetDistroID()) { + case DistroID::Ubuntu: + if (FamilyInList(aName, kBaseFonts_Ubuntu_20_04, + ArrayLength(kBaseFonts_Ubuntu_20_04))) { + return FontVisibility::Base; + } + if (FamilyInList(aName, kLangFonts_Ubuntu_20_04, + ArrayLength(kLangFonts_Ubuntu_20_04))) { + return FontVisibility::LangPack; + } + return FontVisibility::User; + case DistroID::Fedora: + if (FamilyInList(aName, kBaseFonts_Fedora_32, + ArrayLength(kBaseFonts_Fedora_32))) { + return FontVisibility::Base; + } + return FontVisibility::User; + default: + // We don't know how to categorize fonts on this system + return FontVisibility::Unknown; + } +} + +gfxFontEntry* gfxFcPlatformFontList::CreateFontEntry( + fontlist::Face* aFace, const fontlist::Family* aFamily) { + nsAutoCString desc(aFace->mDescriptor.AsString(SharedFontList())); + FcPattern* pattern = FcNameParse((const FcChar8*)desc.get()); + auto* fe = new gfxFontconfigFontEntry(desc, pattern, true); + FcPatternDestroy(pattern); + fe->InitializeFrom(aFace, aFamily); + return fe; +} + +// For displaying the fontlist in UI, use explicit call to FcFontList. Using +// FcFontList results in the list containing the localized names as dictated +// by system defaults. +static void GetSystemFontList(nsTArray<nsString>& aListOfFonts, + nsAtom* aLangGroup) { + aListOfFonts.Clear(); + + RefPtr<FcPattern> pat = dont_AddRef(FcPatternCreate()); + if (!pat) { + return; + } + + UniquePtr<FcObjectSet> os(FcObjectSetBuild(FC_FAMILY, nullptr)); + if (!os) { + return; + } + + // add the lang to the pattern + nsAutoCString fcLang; + gfxFcPlatformFontList* pfl = gfxFcPlatformFontList::PlatformFontList(); + pfl->GetSampleLangForGroup(aLangGroup, fcLang, + /*aForFontEnumerationThread*/ true); + if (!fcLang.IsEmpty()) { + FcPatternAddString(pat, FC_LANG, ToFcChar8Ptr(fcLang.get())); + } + + UniquePtr<FcFontSet> fs(FcFontList(nullptr, pat, os.get())); + if (!fs) { + return; + } + + for (int i = 0; i < fs->nfont; i++) { + char* family; + + if (FcPatternGetString(fs->fonts[i], FC_FAMILY, 0, (FcChar8**)&family) != + FcResultMatch) { + continue; + } + + // Remove duplicates... + nsAutoString strFamily; + AppendUTF8toUTF16(MakeStringSpan(family), strFamily); + if (aListOfFonts.Contains(strFamily)) { + continue; + } + + aListOfFonts.AppendElement(strFamily); + } + + aListOfFonts.Sort(); +} + +void gfxFcPlatformFontList::GetFontList(nsAtom* aLangGroup, + const nsACString& aGenericFamily, + nsTArray<nsString>& aListOfFonts) { + // Get the list of font family names using fontconfig + GetSystemFontList(aListOfFonts, aLangGroup); + + // Under Linux, the generics "serif", "sans-serif" and "monospace" + // are included in the pref fontlist. These map to whatever fontconfig + // decides they should be for a given language, rather than one of the + // fonts listed in the prefs font lists (e.g. font.name.*, font.name-list.*) + bool serif = false, sansSerif = false, monospace = false; + if (aGenericFamily.IsEmpty()) + serif = sansSerif = monospace = true; + else if (aGenericFamily.LowerCaseEqualsLiteral("serif")) + serif = true; + else if (aGenericFamily.LowerCaseEqualsLiteral("sans-serif")) + sansSerif = true; + else if (aGenericFamily.LowerCaseEqualsLiteral("monospace")) + monospace = true; + else if (aGenericFamily.LowerCaseEqualsLiteral("cursive") || + aGenericFamily.LowerCaseEqualsLiteral("fantasy")) + serif = sansSerif = true; + else + MOZ_ASSERT_UNREACHABLE("unexpected CSS generic font family"); + + // The first in the list becomes the default in + // FontBuilder.readFontSelection() if the preference-selected font is not + // available, so put system configured defaults first. + if (monospace) aListOfFonts.InsertElementAt(0, u"monospace"_ns); + if (sansSerif) aListOfFonts.InsertElementAt(0, u"sans-serif"_ns); + if (serif) aListOfFonts.InsertElementAt(0, u"serif"_ns); +} + +FontFamily gfxFcPlatformFontList::GetDefaultFontForPlatform( + const gfxFontStyle* aStyle, nsAtom* aLanguage) { + // Get the default font by using a fake name to retrieve the first + // scalable font that fontconfig suggests for the given language. + PrefFontList* prefFonts = FindGenericFamilies( + "-moz-default"_ns, aLanguage ? aLanguage : nsGkAtoms::x_western); + NS_ASSERTION(prefFonts, "null list of generic fonts"); + if (prefFonts && !prefFonts->IsEmpty()) { + return (*prefFonts)[0]; + } + return FontFamily(); +} + +gfxFontEntry* gfxFcPlatformFontList::LookupLocalFont( + const nsACString& aFontName, WeightRange aWeightForEntry, + StretchRange aStretchForEntry, SlantStyleRange aStyleForEntry) { + nsAutoCString keyName(aFontName); + ToLowerCase(keyName); + + if (SharedFontList()) { + return LookupInSharedFaceNameList(aFontName, aWeightForEntry, + aStretchForEntry, aStyleForEntry); + } + + // if name is not in the global list, done + FcPattern* fontPattern = mLocalNames.Get(keyName); + if (!fontPattern) { + return nullptr; + } + + return new gfxFontconfigFontEntry(aFontName, fontPattern, aWeightForEntry, + aStretchForEntry, aStyleForEntry); +} + +gfxFontEntry* gfxFcPlatformFontList::MakePlatformFont( + const nsACString& aFontName, WeightRange aWeightForEntry, + StretchRange aStretchForEntry, SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, uint32_t aLength) { + RefPtr<FTUserFontData> ufd = new FTUserFontData(aFontData, aLength); + RefPtr<SharedFTFace> face = ufd->CloneFace(); + if (!face) { + return nullptr; + } + return new gfxFontconfigFontEntry(aFontName, aWeightForEntry, + aStretchForEntry, aStyleForEntry, + std::move(face)); +} + +bool gfxFcPlatformFontList::FindAndAddFamilies( + StyleGenericFontFamily aGeneric, const nsACString& aFamily, + nsTArray<FamilyAndGeneric>* aOutput, FindFamiliesFlags aFlags, + gfxFontStyle* aStyle, nsAtom* aLanguage, gfxFloat aDevToCssSize) { + nsAutoCString familyName(aFamily); + ToLowerCase(familyName); + + if (!(aFlags & FindFamiliesFlags::eQuotedFamilyName)) { + // deprecated generic names are explicitly converted to standard generics + bool isDeprecatedGeneric = false; + if (familyName.EqualsLiteral("sans") || + familyName.EqualsLiteral("sans serif")) { + familyName.AssignLiteral("sans-serif"); + isDeprecatedGeneric = true; + } else if (familyName.EqualsLiteral("mono")) { + familyName.AssignLiteral("monospace"); + isDeprecatedGeneric = true; + } + + // fontconfig generics? use fontconfig to determine the family for lang + if (isDeprecatedGeneric || + mozilla::FontFamilyName::Convert(familyName).IsGeneric()) { + PrefFontList* prefFonts = FindGenericFamilies(familyName, aLanguage); + if (prefFonts && !prefFonts->IsEmpty()) { + aOutput->AppendElements(*prefFonts); + return true; + } + return false; + } + } + + // fontconfig allows conditional substitutions in such a way that it's + // difficult to distinguish an explicit substitution from other suggested + // choices. To sniff out explicit substitutions, compare the substitutions + // for "font, -moz-sentinel" to "-moz-sentinel" to sniff out the + // substitutions + // + // Example: + // + // serif ==> DejaVu Serif, ... + // Helvetica, serif ==> Helvetica, TeX Gyre Heros, Nimbus Sans L, DejaVu + // Serif + // + // In this case fontconfig is including Tex Gyre Heros and + // Nimbus Sans L as alternatives for Helvetica. + + // Because the FcConfigSubstitute call is quite expensive, we cache the + // actual font families found via this process. So check the cache first: + if (auto* cachedFamilies = mFcSubstituteCache.GetValue(familyName)) { + 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; + RefPtr<FcPattern> sentinelSubst = dont_AddRef(FcPatternCreate()); + FcPatternAddString(sentinelSubst, FC_FAMILY, kSentinelName); + FcConfigSubstitute(nullptr, sentinelSubst, FcMatchPattern); + FcPatternGetString(sentinelSubst, FC_FAMILY, 0, &sentinelFirstFamily); + + // substitutions for font, -moz-sentinel pattern + RefPtr<FcPattern> fontWithSentinel = dont_AddRef(FcPatternCreate()); + FcPatternAddString(fontWithSentinel, FC_FAMILY, + ToFcChar8Ptr(familyName.get())); + FcPatternAddString(fontWithSentinel, FC_FAMILY, kSentinelName); + FcConfigSubstitute(nullptr, fontWithSentinel, FcMatchPattern); + + // Add all font family matches until reaching the sentinel. + AutoTArray<FamilyAndGeneric, 10> cachedFamilies; + FcChar8* substName = nullptr; + for (int i = 0; FcPatternGetString(fontWithSentinel, FC_FAMILY, i, + &substName) == FcResultMatch; + i++) { + if (sentinelFirstFamily && FcStrCmp(substName, sentinelFirstFamily) == 0) { + break; + } + gfxPlatformFontList::FindAndAddFamilies( + aGeneric, nsDependentCString(ToCharPtr(substName)), &cachedFamilies, + aFlags, aStyle, aLanguage); + } + + // Cache the resulting list, so we don't have to do this again. + mFcSubstituteCache.Put(familyName, cachedFamilies); + + if (cachedFamilies.IsEmpty()) { + return false; + } + aOutput->AppendElements(cachedFamilies); + return true; +} + +bool gfxFcPlatformFontList::GetStandardFamilyName(const nsCString& aFontName, + nsACString& aFamilyName) { + aFamilyName.Truncate(); + + // The fontconfig list of fonts includes generic family names in the + // font list. For these, just use the generic name. + if (aFontName.EqualsLiteral("serif") || + aFontName.EqualsLiteral("sans-serif") || + aFontName.EqualsLiteral("monospace")) { + aFamilyName.Assign(aFontName); + return true; + } + + RefPtr<FcPattern> pat = dont_AddRef(FcPatternCreate()); + if (!pat) { + return true; + } + + UniquePtr<FcObjectSet> os(FcObjectSetBuild(FC_FAMILY, nullptr)); + if (!os) { + return true; + } + + // add the family name to the pattern + FcPatternAddString(pat, FC_FAMILY, ToFcChar8Ptr(aFontName.get())); + + UniquePtr<FcFontSet> givenFS(FcFontList(nullptr, pat, os.get())); + if (!givenFS) { + return true; + } + + // See if there is a font face with first family equal to the given family + // (needs to be in sync with names coming from GetFontList()) + nsTArray<nsCString> candidates; + for (int i = 0; i < givenFS->nfont; i++) { + char* firstFamily; + + if (FcPatternGetString(givenFS->fonts[i], FC_FAMILY, 0, + (FcChar8**)&firstFamily) != FcResultMatch) { + continue; + } + + nsDependentCString first(firstFamily); + if (!candidates.Contains(first)) { + candidates.AppendElement(first); + + if (aFontName.Equals(first)) { + aFamilyName.Assign(aFontName); + return true; + } + } + } + + // Because fontconfig conflates different family name types, need to + // double check that the candidate name is not simply a different + // name type. For example, if a font with nameID=16 "Minion Pro" and + // nameID=21 "Minion Pro Caption" exists, calling FcFontList with + // family="Minion Pro" will return a set of patterns some of which + // will have a first family of "Minion Pro Caption". Ignore these + // patterns and use the first candidate that maps to a font set with + // the same number of faces and an identical set of patterns. + for (uint32_t j = 0; j < candidates.Length(); ++j) { + FcPatternDel(pat, FC_FAMILY); + FcPatternAddString(pat, FC_FAMILY, (FcChar8*)candidates[j].get()); + + UniquePtr<FcFontSet> candidateFS(FcFontList(nullptr, pat, os.get())); + if (!candidateFS) { + return true; + } + + if (candidateFS->nfont != givenFS->nfont) { + continue; + } + + bool equal = true; + for (int i = 0; i < givenFS->nfont; ++i) { + if (!FcPatternEqual(candidateFS->fonts[i], givenFS->fonts[i])) { + equal = false; + break; + } + } + if (equal) { + aFamilyName = candidates[j]; + return true; + } + } + + // didn't find localized name, leave family name blank + return true; +} + +void gfxFcPlatformFontList::AddGenericFonts( + StyleGenericFontFamily aGenericType, nsAtom* aLanguage, + nsTArray<FamilyAndGeneric>& aFamilyList) { + bool usePrefFontList = false; + + 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 + nsAutoCString genericToLookup(generic); + if ((!mAlwaysUseFontconfigGenerics && aLanguage) || + aLanguage == nsGkAtoms::x_math) { + nsAtom* langGroup = GetLangGroup(aLanguage); + nsAutoString fontlistValue; + Preferences::GetString(NamePref(generic, langGroup).get(), fontlistValue); + nsresult rv; + if (fontlistValue.IsEmpty()) { + // The font name list may have two or more family names as comma + // separated list. In such case, not matching with generic font + // name is fine because if the list prefers specific font, we + // should try to use the pref with complicated path. + rv = Preferences::GetString(NameListPref(generic, langGroup).get(), + fontlistValue); + } else { + rv = NS_OK; + } + if (NS_SUCCEEDED(rv)) { + if (!fontlistValue.EqualsLiteral("serif") && + !fontlistValue.EqualsLiteral("sans-serif") && + !fontlistValue.EqualsLiteral("monospace")) { + usePrefFontList = true; + } else { + // serif, sans-serif or monospace was specified + genericToLookup.Truncate(); + AppendUTF16toUTF8(fontlistValue, genericToLookup); + } + } + } + + // 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.SetCapacity(aFamilyList.Length() + prefFonts->Length()); + for (auto& f : *prefFonts) { + aFamilyList.AppendElement(FamilyAndGeneric(f, aGenericType)); + } +} + +void gfxFcPlatformFontList::ClearLangGroupPrefFonts() { + ClearGenericMappings(); + gfxPlatformFontList::ClearLangGroupPrefFonts(); + mAlwaysUseFontconfigGenerics = PrefFontListsUseOnlyGenerics(); +} + +gfxPlatformFontList::PrefFontList* gfxFcPlatformFontList::FindGenericFamilies( + const nsCString& aGeneric, nsAtom* aLanguage) { + // set up name + nsAutoCString fcLang; + GetSampleLangForGroup(aLanguage, fcLang); + ToLowerCase(fcLang); + + nsAutoCString genericLang(aGeneric); + 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 + RefPtr<FcPattern> genericPattern = dont_AddRef(FcPatternCreate()); + FcPatternAddString(genericPattern, FC_FAMILY, ToFcChar8Ptr(aGeneric.get())); + + // -- prefer scalable fonts + FcPatternAddBool(genericPattern, FC_SCALABLE, FcTrue); + + // -- add the lang to the pattern + if (!fcLang.IsEmpty()) { + FcPatternAddString(genericPattern, FC_LANG, ToFcChar8Ptr(fcLang.get())); + } + + // -- perform substitutions + FcConfigSubstitute(nullptr, genericPattern, FcMatchPattern); + FcDefaultSubstitute(genericPattern); + + // -- sort to get the closest matches + FcResult result; + UniquePtr<FcFontSet> faces( + FcFontSort(nullptr, genericPattern, FcFalse, nullptr, &result)); + + if (!faces) { + return nullptr; + } + + // -- select the fonts to be used for the generic + 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) { + nsAutoCString mappedGenericName(ToCharPtr(mappedGeneric)); + AutoTArray<FamilyAndGeneric, 1> genericFamilies; + if (gfxPlatformFontList::FindAndAddFamilies( + StyleGenericFontFamily::None, mappedGenericName, &genericFamilies, + FindFamiliesFlags(0))) { + MOZ_ASSERT(genericFamilies.Length() == 1, "expected a single family"); + if (!prefFonts->Contains(genericFamilies[0].mFamily)) { + prefFonts->AppendElement(genericFamilies[0].mFamily); + bool foundLang = !fcLang.IsEmpty() && + PatternHasLang(font, ToFcChar8Ptr(fcLang.get())); + foundFontWithLang = foundFontWithLang || foundLang; + // check to see if the list is full + if (prefFonts->Length() >= limit) { + break; + } + } + } + } + } + + // if no font in the list matches the lang, trim all but the first one + if (!prefFonts->IsEmpty() && !foundFontWithLang) { + prefFonts->TruncateLength(1); + } + + mGenericMappings.Put(genericLang, prefFonts); + return prefFonts; +} + +bool gfxFcPlatformFontList::PrefFontListsUseOnlyGenerics() { + static const char kFontNamePrefix[] = "font.name."; + + bool prefFontsUseOnlyGenerics = true; + nsTArray<nsCString> names; + nsresult rv = + Preferences::GetRootBranch()->GetChildList(kFontNamePrefix, names); + if (NS_SUCCEEDED(rv)) { + for (auto& name : names) { + // Check whether all font.name prefs map to generic keywords + // and that the pref name and keyword match. + // Ex: font.name.serif.ar ==> "serif" (ok) + // Ex: font.name.serif.ar ==> "monospace" (return false) + // Ex: font.name.serif.ar ==> "DejaVu Serif" (return false) + // Ex: font.name.serif.ar ==> "" and + // font.name-list.serif.ar ==> "serif" (ok) + // Ex: font.name.serif.ar ==> "" and + // font.name-list.serif.ar ==> "Something, serif" + // (return false) + + nsDependentCSubstring prefName = + Substring(name, ArrayLength(kFontNamePrefix) - 1); + nsCCharSeparatedTokenizer tokenizer(prefName, '.'); + const nsDependentCSubstring& generic = tokenizer.nextToken(); + const nsDependentCSubstring& langGroup = tokenizer.nextToken(); + nsAutoCString fontPrefValue; + Preferences::GetCString(name.get(), fontPrefValue); + if (fontPrefValue.IsEmpty()) { + // The font name list may have two or more family names as comma + // separated list. In such case, not matching with generic font + // name is fine because if the list prefers specific font, this + // should return false. + Preferences::GetCString(NameListPref(generic, langGroup).get(), + fontPrefValue); + } + + if (!langGroup.EqualsLiteral("x-math") && + !generic.Equals(fontPrefValue)) { + prefFontsUseOnlyGenerics = false; + break; + } + } + } + return prefFontsUseOnlyGenerics; +} + +/* static */ +void gfxFcPlatformFontList::CheckFontUpdates(nsITimer* aTimer, void* aThis) { + // A content process is not supposed to check this directly; + // it will be notified by the parent when the font list changes. + MOZ_ASSERT(XRE_IsParentProcess()); + + // check for font updates + FcInitBringUptoDate(); + + // update fontlist if current config changed + gfxFcPlatformFontList* pfl = static_cast<gfxFcPlatformFontList*>(aThis); + FcConfig* current = FcConfigGetCurrent(); + if (current != pfl->GetLastConfig()) { + pfl->UpdateFontList(); + pfl->ForceGlobalReflow(); + + mozilla::dom::ContentParent::NotifyUpdatedFonts(true); + } +} + +gfxFontFamily* gfxFcPlatformFontList::CreateFontFamily( + const nsACString& aName, FontVisibility aVisibility) const { + return new gfxFontconfigFontFamily(aName, aVisibility); +} + +// mapping of moz lang groups ==> default lang +struct MozLangGroupData { + nsAtom* const& mozLangGroup; + const char* defaultLang; +}; + +const MozLangGroupData MozLangGroups[] = { + {nsGkAtoms::x_western, "en"}, {nsGkAtoms::x_cyrillic, "ru"}, + {nsGkAtoms::x_devanagari, "hi"}, {nsGkAtoms::x_tamil, "ta"}, + {nsGkAtoms::x_armn, "hy"}, {nsGkAtoms::x_beng, "bn"}, + {nsGkAtoms::x_cans, "iu"}, {nsGkAtoms::x_ethi, "am"}, + {nsGkAtoms::x_geor, "ka"}, {nsGkAtoms::x_gujr, "gu"}, + {nsGkAtoms::x_guru, "pa"}, {nsGkAtoms::x_khmr, "km"}, + {nsGkAtoms::x_knda, "kn"}, {nsGkAtoms::x_mlym, "ml"}, + {nsGkAtoms::x_orya, "or"}, {nsGkAtoms::x_sinh, "si"}, + {nsGkAtoms::x_tamil, "ta"}, {nsGkAtoms::x_telu, "te"}, + {nsGkAtoms::x_tibt, "bo"}, {nsGkAtoms::Unicode, 0}}; + +bool gfxFcPlatformFontList::TryLangForGroup(const nsACString& aOSLang, + nsAtom* aLangGroup, + nsACString& aFcLang, + bool aForFontEnumerationThread) { + // Truncate at '.' or '@' from aOSLang, and convert '_' to '-'. + // aOSLang is in the form "language[_territory][.codeset][@modifier]". + // fontconfig takes languages in the form "language-territory". + // nsLanguageAtomService takes languages in the form language-subtag, + // where subtag may be a territory. fontconfig and nsLanguageAtomService + // handle case-conversion for us. + const char *pos, *end; + aOSLang.BeginReading(pos); + aOSLang.EndReading(end); + aFcLang.Truncate(); + while (pos < end) { + switch (*pos) { + case '.': + case '@': + end = pos; + break; + case '_': + aFcLang.Append('-'); + break; + default: + aFcLang.Append(*pos); + } + ++pos; + } + + if (!aForFontEnumerationThread) { + nsAtom* atom = mLangService->LookupLanguage(aFcLang); + return atom == aLangGroup; + } + + // If we were called by the font enumeration thread, we can't use + // mLangService->LookupLanguage because it is not thread-safe. + // Use GetUncachedLanguageGroup to avoid unsafe access to the lang-group + // mapping cache hashtable. + nsAutoCString lowered(aFcLang); + ToLowerCase(lowered); + RefPtr<nsAtom> lang = NS_Atomize(lowered); + RefPtr<nsAtom> group = mLangService->GetUncachedLanguageGroup(lang); + return group.get() == aLangGroup; +} + +void gfxFcPlatformFontList::GetSampleLangForGroup( + nsAtom* aLanguage, nsACString& aLangStr, bool aForFontEnumerationThread) { + aLangStr.Truncate(); + if (!aLanguage) { + return; + } + + // set up lang string + const MozLangGroupData* mozLangGroup = nullptr; + + // -- look it up in the list of moz lang groups + for (unsigned int i = 0; i < ArrayLength(MozLangGroups); ++i) { + if (aLanguage == MozLangGroups[i].mozLangGroup) { + mozLangGroup = &MozLangGroups[i]; + break; + } + } + + // -- not a mozilla lang group? Just return the BCP47 string + // representation of the lang group + if (!mozLangGroup) { + // Not a special mozilla language group. + // Use aLanguage as a language code. + aLanguage->ToUTF8String(aLangStr); + return; + } + + // -- check the environment for the user's preferred language that + // corresponds to this mozilla lang group. + const char* languages = getenv("LANGUAGE"); + if (languages) { + const char separator = ':'; + + for (const char* pos = languages; true; ++pos) { + if (*pos == '\0' || *pos == separator) { + if (languages < pos && + TryLangForGroup(Substring(languages, pos), aLanguage, aLangStr, + aForFontEnumerationThread)) { + return; + } + + if (*pos == '\0') { + break; + } + + languages = pos + 1; + } + } + } + const char* ctype = setlocale(LC_CTYPE, nullptr); + if (ctype && TryLangForGroup(nsDependentCString(ctype), aLanguage, aLangStr, + aForFontEnumerationThread)) { + return; + } + + if (mozLangGroup->defaultLang) { + aLangStr.Assign(mozLangGroup->defaultLang); + } else { + aLangStr.Truncate(); + } +} + +#ifdef MOZ_BUNDLED_FONTS +void gfxFcPlatformFontList::ActivateBundledFonts() { + if (!mBundledFontsInitialized) { + mBundledFontsInitialized = true; + nsCOMPtr<nsIFile> localDir; + nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(localDir)); + if (NS_FAILED(rv)) { + return; + } + if (NS_FAILED(localDir->Append(u"fonts"_ns))) { + return; + } + bool isDir; + if (NS_FAILED(localDir->IsDirectory(&isDir)) || !isDir) { + return; + } + if (NS_FAILED(localDir->GetNativePath(mBundledFontsPath))) { + return; + } + } + if (!mBundledFontsPath.IsEmpty()) { + FcConfigAppFontAddDir(nullptr, ToFcChar8Ptr(mBundledFontsPath.get())); + } +} +#endif + +#ifdef MOZ_WIDGET_GTK +/*************************************************************************** + * + * 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 0000000000..e5803e0b34 --- /dev/null +++ b/gfx/thebes/gfxFcPlatformFontList.h @@ -0,0 +1,380 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFXFCPLATFORMFONTLIST_H_ +#define GFXFCPLATFORMFONTLIST_H_ + +#include "gfxFT2FontBase.h" +#include "gfxPlatformFontList.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/mozalloc.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "nsClassHashtable.h" + +#include <fontconfig/fontconfig.h> +#include "ft2build.h" +#include FT_FREETYPE_H +#include FT_TRUETYPE_TABLES_H +#include FT_MULTIPLE_MASTERS_H + +#if defined(MOZ_SANDBOX) && defined(XP_LINUX) +# include "mozilla/SandboxBroker.h" +#endif + +namespace mozilla { +namespace dom { +class SystemFontListEntry; +}; + +template <> +class RefPtrTraits<FcPattern> { + public: + static void Release(FcPattern* ptr) { FcPatternDestroy(ptr); } + static void AddRef(FcPattern* ptr) { FcPatternReference(ptr); } +}; + +template <> +class RefPtrTraits<FcConfig> { + public: + static void Release(FcConfig* ptr) { FcConfigDestroy(ptr); } + static void AddRef(FcConfig* ptr) { FcConfigReference(ptr); } +}; + +template <> +class DefaultDelete<FcFontSet> { + public: + void operator()(FcFontSet* aPtr) { FcFontSetDestroy(aPtr); } +}; + +template <> +class DefaultDelete<FcObjectSet> { + public: + void operator()(FcObjectSet* aPtr) { FcObjectSetDestroy(aPtr); } +}; + +}; // namespace mozilla + +// The names for the font entry and font classes should really +// the common 'Fc' abbreviation but the gfxPangoFontGroup code already +// defines versions of these, so use the verbose name for now. + +class gfxFontconfigFontEntry final : public gfxFT2FontEntryBase { + friend class gfxFcPlatformFontList; + + public: + // used for system fonts with explicit patterns + explicit gfxFontconfigFontEntry(const nsACString& aFaceName, + FcPattern* aFontPattern, + bool aIgnoreFcCharmap); + + // used for data fonts where the fontentry takes ownership + // of the font data and the FT_Face + explicit gfxFontconfigFontEntry(const nsACString& aFaceName, + WeightRange aWeight, StretchRange aStretch, + SlantStyleRange aStyle, + RefPtr<mozilla::gfx::SharedFTFace>&& aFace); + + // used for @font-face local system fonts with explicit patterns + explicit gfxFontconfigFontEntry(const nsACString& aFaceName, + FcPattern* aFontPattern, WeightRange aWeight, + StretchRange aStretch, + SlantStyleRange aStyle); + + gfxFontEntry* Clone() const override; + + FcPattern* GetPattern() { return mFontPattern; } + + nsresult ReadCMAP(FontInfoData* aFontInfoData = nullptr) override; + bool TestCharacterMap(uint32_t aCh) override; + + const RefPtr<mozilla::gfx::SharedFTFace>& GetFTFace(); + FTUserFontData* GetUserFontData(); + + FT_MM_Var* GetMMVar() override; + + bool HasVariations() override; + void GetVariationAxes(nsTArray<gfxFontVariationAxis>& aAxes) override; + void GetVariationInstances( + nsTArray<gfxFontVariationInstance>& aInstances) override; + + bool HasFontTable(uint32_t aTableTag) override; + nsresult CopyFontTable(uint32_t aTableTag, nsTArray<uint8_t>&) override; + hb_blob_t* GetFontTable(uint32_t aTableTag) override; + + double GetAspect(); + + protected: + virtual ~gfxFontconfigFontEntry(); + + gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle) override; + + // pattern for a single face of a family + RefPtr<FcPattern> mFontPattern; + + // FTFace - initialized when needed + RefPtr<mozilla::gfx::SharedFTFace> 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; + + // Whether the face supports variations. For system-installed fonts, we + // query fontconfig for this (so they will only work if fontconfig is + // recent enough to include support); for downloaded user-fonts we query + // the FreeType face. + bool mHasVariations; + bool mHasVariationsInitialized; + + double mAspect; + + class UnscaledFontCache { + public: + already_AddRefed<mozilla::gfx::UnscaledFontFontconfig> Lookup( + const std::string& aFile, uint32_t aIndex); + + void Add( + const RefPtr<mozilla::gfx::UnscaledFontFontconfig>& aUnscaledFont) { + mUnscaledFonts[kNumEntries - 1] = aUnscaledFont; + MoveToFront(kNumEntries - 1); + } + + private: + void MoveToFront(size_t aIndex); + + static const size_t kNumEntries = 3; + mozilla::ThreadSafeWeakPtr<mozilla::gfx::UnscaledFontFontconfig> + mUnscaledFonts[kNumEntries]; + }; + + UnscaledFontCache mUnscaledFontCache; + + // Because of FreeType bug 52955, we keep the FT_MM_Var struct when it is + // first loaded, rather than releasing it and re-fetching it as needed. + FT_MM_Var* mMMVar = nullptr; + bool mMMVarInitialized = false; +}; + +class gfxFontconfigFontFamily final : public gfxFontFamily { + public: + gfxFontconfigFontFamily(const nsACString& aName, FontVisibility aVisibility) + : gfxFontFamily(aName, aVisibility), + mContainsAppFonts(false), + mHasNonScalableFaces(false), + mForceScalable(false) {} + + template <typename Func> + void AddFacesToFontList(Func aAddPatternFunc); + + void FindStyleVariations(FontInfoData* aFontInfoData = nullptr) override; + + // Families are constructed initially with just references to patterns. + // When necessary, these are enumerated within FindStyleVariations. + void AddFontPattern(FcPattern* aFontPattern); + + void SetFamilyContainsAppFonts(bool aContainsAppFonts) { + mContainsAppFonts = aContainsAppFonts; + } + + void FindAllFontsForStyle(const gfxFontStyle& aFontStyle, + nsTArray<gfxFontEntry*>& aFontEntryList, + bool aIgnoreSizeTolerance) override; + + bool FilterForFontList(nsAtom* aLangGroup, + const nsACString& aGeneric) const final { + return SupportsLangGroup(aLangGroup); + } + + protected: + virtual ~gfxFontconfigFontFamily(); + + // helper for FilterForFontList + bool SupportsLangGroup(nsAtom* aLangGroup) const; + + nsTArray<RefPtr<FcPattern>> mFontPatterns; + + bool mContainsAppFonts; + bool mHasNonScalableFaces; + bool mForceScalable; +}; + +class gfxFontconfigFont final : public gfxFT2FontBase { + public: + gfxFontconfigFont( + const RefPtr<mozilla::gfx::UnscaledFontFontconfig>& aUnscaledFont, + RefPtr<mozilla::gfx::SharedFTFace>&& aFTFace, FcPattern* aPattern, + gfxFloat aAdjustedSize, gfxFontEntry* aFontEntry, + const gfxFontStyle* aFontStyle, int aLoadFlags, bool aEmbolden); + + FontType GetType() const override { return FONT_TYPE_FONTCONFIG; } + FcPattern* GetPattern() const { return mPattern; } + + already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont( + DrawTarget* aTarget) override; + + bool ShouldHintMetrics() const override; + + private: + virtual ~gfxFontconfigFont(); + + RefPtr<FcPattern> mPattern; +}; + +class gfxFcPlatformFontList final : public gfxPlatformFontList { + using FontPatternListEntry = mozilla::dom::SystemFontListEntry; + + public: + gfxFcPlatformFontList(); + + static gfxFcPlatformFontList* PlatformFontList() { + return static_cast<gfxFcPlatformFontList*>(sPlatformFontList); + } + + // initialize font lists + nsresult InitFontListForPlatform() override; + void InitSharedFontListForPlatform() override; + + void GetFontList(nsAtom* aLangGroup, const nsACString& aGenericFamily, + nsTArray<nsString>& aListOfFonts) override; + + void ReadSystemFontList(nsTArray<FontPatternListEntry>* retValue); + + gfxFontEntry* CreateFontEntry( + mozilla::fontlist::Face* aFace, + const mozilla::fontlist::Family* aFamily) override; + + gfxFontEntry* LookupLocalFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) override; + + gfxFontEntry* MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, + uint32_t aLength) override; + + bool FindAndAddFamilies(mozilla::StyleGenericFontFamily aGeneric, + const nsACString& aFamily, + nsTArray<FamilyAndGeneric>* aOutput, + FindFamiliesFlags aFlags, + gfxFontStyle* aStyle = nullptr, + nsAtom* aLanguage = nullptr, + gfxFloat aDevToCssSize = 1.0) override; + + bool GetStandardFamilyName(const nsCString& aFontName, + nsACString& aFamilyName) override; + + FcConfig* GetLastConfig() const { return mLastConfig; } + + // override to use fontconfig lookup for generics + void AddGenericFonts(mozilla::StyleGenericFontFamily, nsAtom* aLanguage, + nsTArray<FamilyAndGeneric>& aFamilyList) override; + + void ClearLangGroupPrefFonts() override; + + // clear out cached generic-lang ==> family-list mappings + void ClearGenericMappings() { mGenericMappings.Clear(); } + + // map lang group ==> lang string + // When aForFontEnumerationThread is true, this method will avoid using + // LanguageService::LookupLanguage, because it is not safe for off-main- + // thread use (except by stylo traversal, which does the necessary locking) + void GetSampleLangForGroup(nsAtom* aLanguage, nsACString& aLangStr, + bool aForFontEnumerationThread = false); + + protected: + virtual ~gfxFcPlatformFontList(); + +#if defined(MOZ_SANDBOX) && defined(XP_LINUX) + typedef mozilla::SandboxBroker::Policy SandboxPolicy; +#else + // Dummy type just so we can still have a SandboxPolicy* parameter. + struct SandboxPolicy {}; +#endif + + // Add all the font families found in a font set. + // aAppFonts indicates whether this is the system or application fontset. + void AddFontSetFamilies(FcFontSet* aFontSet, const SandboxPolicy* aPolicy, + bool aAppFonts); + + // Helper for above, to add a single font pattern. + void AddPatternToFontList(FcPattern* aFont, FcChar8*& aLastFamilyName, + nsACString& aFamilyName, + RefPtr<gfxFontconfigFontFamily>& aFontFamily, + bool aAppFonts); + + // figure out which families fontconfig maps a generic to + // (aGeneric assumed already lowercase) + PrefFontList* FindGenericFamilies(const nsCString& aGeneric, + nsAtom* aLanguage); + + // are all pref font settings set to use fontconfig generics? + bool PrefFontListsUseOnlyGenerics(); + + static void CheckFontUpdates(nsITimer* aTimer, void* aThis); + + FontFamily GetDefaultFontForPlatform(const gfxFontStyle* aStyle, + nsAtom* aLanguage = nullptr) override; + + enum class DistroID : int8_t { + Unknown = 0, + Ubuntu = 1, + Fedora = 2, + // To be extended with any distros that ship a useful base set of fonts + // that we want to explicitly support. + }; + DistroID GetDistroID() const; // -> DistroID::Unknown if we can't tell + + FontVisibility GetVisibilityForFamily(const nsACString& aName) const; + + gfxFontFamily* CreateFontFamily(const nsACString& aName, + FontVisibility aVisibility) const override; + + // helper method for finding an appropriate lang string + bool TryLangForGroup(const nsACString& aOSLang, nsAtom* aLangGroup, + nsACString& aLang, bool aForFontEnumerationThread); + +#ifdef MOZ_BUNDLED_FONTS + void ActivateBundledFonts(); + nsCString mBundledFontsPath; + bool mBundledFontsInitialized; +#endif + + // to avoid enumerating all fonts, maintain a mapping of local font + // names to family + nsBaseHashtable<nsCStringHashKey, RefPtr<FcPattern>, FcPattern*> mLocalNames; + + // caching generic/lang ==> font family list + nsClassHashtable<nsCStringHashKey, PrefFontList> mGenericMappings; + + // Caching family lookups as found by FindAndAddFamilies after resolving + // substitutions. The gfxFontFamily objects cached here are owned by the + // gfxFcPlatformFontList via its mFamilies table; note that if the main + // font list is rebuilt (e.g. due to a fontconfig configuration change), + // these pointers will be invalidated. InitFontList() flushes the cache + // in this case. + nsDataHashtable<nsCStringHashKey, CopyableTArray<FamilyAndGeneric>> + mFcSubstituteCache; + + nsCOMPtr<nsITimer> mCheckFontUpdatesTimer; + RefPtr<FcConfig> mLastConfig; + + // By default, font prefs under Linux are set to simply lookup + // via fontconfig the appropriate font for serif/sans-serif/monospace. + // Rather than check each time a font pref is used, check them all at startup + // and set a boolean to flag the case that non-default user font prefs exist + // Note: langGroup == x-math is handled separately + bool mAlwaysUseFontconfigGenerics; + + static FT_Library sFTLibrary; +}; + +#endif /* GFXPLATFORMFONTLIST_H_ */ diff --git a/gfx/thebes/gfxFont.cpp b/gfx/thebes/gfxFont.cpp new file mode 100644 index 0000000000..57ec757a0e --- /dev/null +++ b/gfx/thebes/gfxFont.cpp @@ -0,0 +1,4115 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxFont.h" + +#include "mozilla/BinarySearch.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/IntegerRange.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/SVGContextPaint.h" + +#include "mozilla/Logging.h" + +#include "nsITimer.h" + +#include "gfxGlyphExtents.h" +#include "gfxPlatform.h" +#include "gfxTextRun.h" +#include "nsGkAtoms.h" + +#include "gfxTypes.h" +#include "gfxContext.h" +#include "gfxFontMissingGlyphs.h" +#include "gfxGraphiteShaper.h" +#include "gfxHarfBuzzShaper.h" +#include "gfxUserFontSet.h" +#include "nsCRT.h" +#include "nsSpecialCasingData.h" +#include "nsTextRunTransformations.h" +#include "nsUGenCategory.h" +#include "nsUnicodeProperties.h" +#include "nsStyleConsts.h" +#include "mozilla/AppUnits.h" +#include "mozilla/Likely.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/Telemetry.h" +#include "gfxMathTable.h" +#include "gfxSVGGlyphs.h" +#include "gfx2DGlue.h" +#include "TextDrawTarget.h" + +#include "ThebesRLBox.h" + +#include "GreekCasing.h" + +#include "cairo.h" +#ifdef XP_WIN +# include "cairo-win32.h" +# include "gfxWindowsPlatform.h" +#endif + +#include "harfbuzz/hb.h" +#include "harfbuzz/hb-ot.h" + +#include <algorithm> +#include <limits> +#include <cmath> + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::unicode; +using mozilla::services::GetObserverService; + +gfxFontCache* gfxFontCache::gGlobalCache = nullptr; + +#ifdef DEBUG_roc +# define DEBUG_TEXT_RUN_STORAGE_METRICS +#endif + +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS +uint32_t gTextRunStorageHighWaterMark = 0; +uint32_t gTextRunStorage = 0; +uint32_t gFontCount = 0; +uint32_t gGlyphExtentsCount = 0; +uint32_t gGlyphExtentsWidthsTotalSize = 0; +uint32_t gGlyphExtentsSetupEagerSimple = 0; +uint32_t gGlyphExtentsSetupEagerTight = 0; +uint32_t gGlyphExtentsSetupLazyTight = 0; +uint32_t gGlyphExtentsSetupFallBackToTight = 0; +#endif + +#define LOG_FONTINIT(args) \ + MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug, args) +#define LOG_FONTINIT_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug) + +/* + * gfxFontCache - global cache of gfxFont instances. + * Expires unused fonts after a short interval; + * notifies fonts to age their cached shaped-word records; + * observes memory-pressure notification and tells fonts to clear their + * shaped-word caches to free up memory. + */ + +MOZ_DEFINE_MALLOC_SIZE_OF(FontCacheMallocSizeOf) + +NS_IMPL_ISUPPORTS(gfxFontCache::MemoryReporter, nsIMemoryReporter) + +/*virtual*/ +gfxTextRunFactory::~gfxTextRunFactory() { + // Should not be dropped by stylo + MOZ_ASSERT(NS_IsMainThread()); +} + +NS_IMETHODIMP +gfxFontCache::MemoryReporter::CollectReports( + nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) { + FontCacheSizes sizes; + + gfxFontCache::GetCache()->AddSizeOfIncludingThis(&FontCacheMallocSizeOf, + &sizes); + + MOZ_COLLECT_REPORT("explicit/gfx/font-cache", KIND_HEAP, UNITS_BYTES, + sizes.mFontInstances, + "Memory used for active font instances."); + + MOZ_COLLECT_REPORT("explicit/gfx/font-shaped-words", KIND_HEAP, UNITS_BYTES, + sizes.mShapedWords, + "Memory used to cache shaped glyph data."); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(gfxFontCache::Observer, nsIObserver) + +NS_IMETHODIMP +gfxFontCache::Observer::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* someData) { + if (!nsCRT::strcmp(aTopic, "memory-pressure")) { + gfxFontCache* fontCache = gfxFontCache::GetCache(); + if (fontCache) { + fontCache->FlushShapedWordCaches(); + } + } else { + MOZ_ASSERT_UNREACHABLE("unexpected notification topic"); + } + return NS_OK; +} + +nsresult gfxFontCache::Init() { + NS_ASSERTION(!gGlobalCache, "Where did this come from?"); + gGlobalCache = new gfxFontCache(GetMainThreadSerialEventTarget()); + if (!gGlobalCache) { + return NS_ERROR_OUT_OF_MEMORY; + } + RegisterStrongMemoryReporter(new MemoryReporter()); + return NS_OK; +} + +void gfxFontCache::Shutdown() { + delete gGlobalCache; + gGlobalCache = nullptr; + +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + printf("Textrun storage high water mark=%d\n", gTextRunStorageHighWaterMark); + printf("Total number of fonts=%d\n", gFontCount); + printf("Total glyph extents allocated=%d (size %d)\n", gGlyphExtentsCount, + int(gGlyphExtentsCount * sizeof(gfxGlyphExtents))); + printf("Total glyph extents width-storage size allocated=%d\n", + gGlyphExtentsWidthsTotalSize); + printf("Number of simple glyph extents eagerly requested=%d\n", + gGlyphExtentsSetupEagerSimple); + printf("Number of tight glyph extents eagerly requested=%d\n", + gGlyphExtentsSetupEagerTight); + printf("Number of tight glyph extents lazily requested=%d\n", + gGlyphExtentsSetupLazyTight); + printf("Number of simple glyph extent setups that fell back to tight=%d\n", + gGlyphExtentsSetupFallBackToTight); +#endif +} + +gfxFontCache::gfxFontCache(nsIEventTarget* aEventTarget) + : gfxFontCacheExpirationTracker(aEventTarget) { + nsCOMPtr<nsIObserverService> obs = GetObserverService(); + if (obs) { + obs->AddObserver(new Observer, "memory-pressure", false); + } + + nsIEventTarget* target = nullptr; + if (XRE_IsContentProcess() && NS_IsMainThread()) { + target = aEventTarget; + } + NS_NewTimerWithFuncCallback(getter_AddRefs(mWordCacheExpirationTimer), + WordCacheExpirationTimerCallback, this, + SHAPED_WORD_TIMEOUT_SECONDS * 1000, + nsITimer::TYPE_REPEATING_SLACK, + "gfxFontCache::gfxFontCache", target); +} + +gfxFontCache::~gfxFontCache() { + // Ensure the user font cache releases its references to font entries, + // so they aren't kept alive after the font instances and font-list + // have been shut down. + gfxUserFontSet::UserFontCache::Shutdown(); + + if (mWordCacheExpirationTimer) { + mWordCacheExpirationTimer->Cancel(); + mWordCacheExpirationTimer = nullptr; + } + + // Expire everything 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))); +} + +gfxFont* gfxFontCache::Lookup(const gfxFontEntry* aFontEntry, + const gfxFontStyle* aStyle, + const gfxCharacterMap* aUnicodeRangeMap) { + Key key(aFontEntry, aStyle, aUnicodeRangeMap); + HashEntry* entry = mFonts.GetEntry(key); + + Telemetry::Accumulate(Telemetry::FONT_CACHE_HIT, entry != nullptr); + if (!entry) return nullptr; + + return entry->mFont; +} + +void gfxFontCache::AddNew(gfxFont* aFont) { + Key key(aFont->GetFontEntry(), aFont->GetStyle(), + aFont->GetUnicodeRangeMap()); + HashEntry* entry = mFonts.PutEntry(key); + if (!entry) return; + gfxFont* oldFont = entry->mFont; + entry->mFont = aFont; + // Assert that we can find the entry we just put in (this fails if the key + // has a NaN float value in it, e.g. 'sizeAdjust'). + MOZ_ASSERT(entry == mFonts.GetEntry(key)); + // If someone's asked us to replace an existing font entry, then that's a + // bit weird, but let it happen, and expire the old font if it's not used. + if (oldFont && oldFont->GetExpirationState()->IsTracked()) { + // if oldFont == aFont, recount should be > 0, + // so we shouldn't be here. + NS_ASSERTION(aFont != oldFont, "new font is tracked for expiry!"); + NotifyExpired(oldFont); + } +} + +void gfxFontCache::NotifyReleased(gfxFont* aFont) { + nsresult rv = AddObject(aFont); + if (NS_FAILED(rv)) { + // We couldn't track it for some reason. Kill it now. + DestroyFont(aFont); + } + // Note that we might have fonts that aren't in the hashtable, perhaps because + // of OOM adding to the hashtable or because someone did an AddNew where + // we already had a font. These fonts are added to the expiration tracker + // anyway, even though Lookup can't resurrect them. Eventually they will + // expire and be deleted. +} + +void gfxFontCache::NotifyExpired(gfxFont* aFont) { + aFont->ClearCachedWords(); + RemoveObject(aFont); + DestroyFont(aFont); +} + +void gfxFontCache::DestroyFont(gfxFont* aFont) { + Key key(aFont->GetFontEntry(), aFont->GetStyle(), + aFont->GetUnicodeRangeMap()); + HashEntry* entry = mFonts.GetEntry(key); + if (entry && entry->mFont == aFont) { + mFonts.RemoveEntry(entry); + } + NS_ASSERTION(aFont->GetRefCount() == 0, + "Destroying with non-zero ref count!"); + delete aFont; +} + +/*static*/ +void gfxFontCache::WordCacheExpirationTimerCallback(nsITimer* aTimer, + void* aCache) { + gfxFontCache* cache = static_cast<gfxFontCache*>(aCache); + for (auto it = cache->mFonts.Iter(); !it.Done(); it.Next()) { + it.Get()->mFont->AgeCachedWords(); + } +} + +void gfxFontCache::FlushShapedWordCaches() { + for (auto it = mFonts.Iter(); !it.Done(); it.Next()) { + it.Get()->mFont->ClearCachedWords(); + } +} + +void gfxFontCache::NotifyGlyphsChanged() { + for (auto it = mFonts.Iter(); !it.Done(); it.Next()) { + it.Get()->mFont->NotifyGlyphsChanged(); + } +} + +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(const gfxFontFeatureValueSet& aFeatureLookup, + const nsACString& aFamily, + const StyleVariantAlternates& aAlternates, + nsTArray<gfxFontFeature>& aFontFeatures) { + using Tag = StyleVariantAlternates::Tag; + + // historical-forms gets handled in nsFont::AddFontFeaturesToStyle. + if (aAlternates.IsHistoricalForms()) { + return; + } + + gfxFontFeature feature; + if (aAlternates.IsCharacterVariant()) { + for (auto& ident : aAlternates.AsCharacterVariant().AsSpan()) { + Span<const uint32_t> values = aFeatureLookup.GetFontFeatureValuesFor( + aFamily, NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT, + ident.AsAtom()); + // nothing defined, skip + if (values.IsEmpty()) { + continue; + } + NS_ASSERTION(values.Length() <= 2, + "too many values allowed for character-variant"); + // character-variant(12 3) ==> 'cv12' = 3 + uint32_t nn = values[0]; + // ignore values greater than 99 + if (nn == 0 || nn > MAX_CVXX_VALUE) { + continue; + } + feature.mValue = values.Length() > 1 ? values[1] : 1; + feature.mTag = HB_TAG('c', 'v', ('0' + nn / 10), ('0' + nn % 10)); + aFontFeatures.AppendElement(feature); + } + return; + } + + if (aAlternates.IsStyleset()) { + for (auto& ident : aAlternates.AsStyleset().AsSpan()) { + Span<const uint32_t> values = aFeatureLookup.GetFontFeatureValuesFor( + aFamily, NS_FONT_VARIANT_ALTERNATES_STYLESET, ident.AsAtom()); + + // styleset(1 2 7) ==> 'ss01' = 1, 'ss02' = 1, 'ss07' = 1 + feature.mValue = 1; + for (uint32_t nn : values) { + if (nn == 0 || nn > MAX_SSXX_VALUE) { + continue; + } + feature.mTag = HB_TAG('s', 's', ('0' + nn / 10), ('0' + nn % 10)); + aFontFeatures.AppendElement(feature); + } + } + return; + } + + uint32_t constant = 0; + nsAtom* name = nullptr; + switch (aAlternates.tag) { + case Tag::Swash: + constant = NS_FONT_VARIANT_ALTERNATES_SWASH; + name = aAlternates.AsSwash().AsAtom(); + break; + case Tag::Stylistic: + constant = NS_FONT_VARIANT_ALTERNATES_STYLISTIC; + name = aAlternates.AsStylistic().AsAtom(); + break; + case Tag::Ornaments: + constant = NS_FONT_VARIANT_ALTERNATES_ORNAMENTS; + name = aAlternates.AsOrnaments().AsAtom(); + break; + case Tag::Annotation: + constant = NS_FONT_VARIANT_ALTERNATES_ANNOTATION; + name = aAlternates.AsAnnotation().AsAtom(); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown font-variant-alternates value!"); + return; + } + + Span<const uint32_t> values = + aFeatureLookup.GetFontFeatureValuesFor(aFamily, constant, name); + if (values.IsEmpty()) { + return; + } + MOZ_ASSERT(values.Length() == 1, + "too many values for font-specific font-variant-alternates"); + + feature.mValue = values[0]; + switch (aAlternates.tag) { + case Tag::Swash: // swsh, cswh + feature.mTag = HB_TAG('s', 'w', 's', 'h'); + aFontFeatures.AppendElement(feature); + feature.mTag = HB_TAG('c', 's', 'w', 'h'); + break; + case Tag::Stylistic: // salt + feature.mTag = HB_TAG('s', 'a', 'l', 't'); + break; + case Tag::Ornaments: // ornm + feature.mTag = HB_TAG('o', 'r', 'n', 'm'); + break; + case Tag::Annotation: // nalt + feature.mTag = HB_TAG('n', 'a', 'l', 't'); + break; + default: + MOZ_ASSERT_UNREACHABLE("how?"); + return; + } + aFontFeatures.AppendElement(feature); +} + +/* static */ +void gfxFontShaper::MergeFontFeatures( + const gfxFontStyle* aStyle, const nsTArray<gfxFontFeature>& aFontFeatures, + bool aDisableLigatures, const nsACString& aFamilyName, bool aAddSmallCaps, + void (*aHandleFeature)(const uint32_t&, uint32_t&, void*), + void* aHandleFeatureData) { + const nsTArray<gfxFontFeature>& styleRuleFeatures = aStyle->featureSettings; + + // Bail immediately if nothing to do, which is the common case. + if (styleRuleFeatures.IsEmpty() && aFontFeatures.IsEmpty() && + !aDisableLigatures && + aStyle->variantCaps == NS_FONT_VARIANT_CAPS_NORMAL && + aStyle->variantSubSuper == NS_FONT_VARIANT_POSITION_NORMAL && + aStyle->variantAlternates.IsEmpty()) { + return; + } + + nsDataHashtable<nsUint32HashKey, uint32_t> mergedFeatures; + + // add feature values from font + for (const gfxFontFeature& feature : aFontFeatures) { + 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 + [[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 + [[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 && !aStyle->variantAlternates.IsEmpty()) { + AutoTArray<gfxFontFeature, 4> featureList; + + // insert list of alternate feature settings + for (auto& alternate : aStyle->variantAlternates.AsSpan()) { + LookupAlternateValues(*aStyle->featureValueLookup, aFamilyName, alternate, + featureList); + } + + for (const gfxFontFeature& feature : featureList) { + mergedFeatures.Put(feature.mTag, feature.mValue); + } + } + + // Add features that are already resolved to tags & values in the style. + if (styleRuleFeatures.IsEmpty()) { + // Disable common ligatures if non-zero letter-spacing is in effect. + if (aDisableLigatures) { + mergedFeatures.Put(HB_TAG('l', 'i', 'g', 'a'), 0); + mergedFeatures.Put(HB_TAG('c', 'l', 'i', 'g'), 0); + } + } else { + for (const gfxFontFeature& feature : styleRuleFeatures) { + // A dummy feature (0,0) is used as a sentinel to separate features + // originating from font-variant-* or other high-level properties from + // those directly specified as font-feature-settings. The high-level + // features may be overridden by aDisableLigatures, while low-level + // features specified directly as tags will come last and therefore + // take precedence over everything else. + if (feature.mTag) { + mergedFeatures.Put(feature.mTag, feature.mValue); + } else if (aDisableLigatures) { + // Handle ligature-disabling setting at the boundary between high- + // and low-level features. + mergedFeatures.Put(HB_TAG('l', 'i', 'g', 'a'), 0); + mergedFeatures.Put(HB_TAG('c', 'l', 'i', 'g'), 0); + } + } + } + + if (mergedFeatures.Count() != 0) { + for (auto iter = mergedFeatures.Iter(); !iter.Done(); iter.Next()) { + aHandleFeature(iter.Key(), iter.Data(), aHandleFeatureData); + } + } +} + +void gfxShapedText::SetupClusterBoundaries(uint32_t aOffset, + const char16_t* aString, + uint32_t aLength) { + CompressedGlyph* glyphs = GetCharacterGlyphs() + aOffset; + + CompressedGlyph extendCluster = CompressedGlyph::MakeComplex(false, true); + + 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_SURROGATE_PAIR(ch, aString[1])) { + ch = SURROGATE_TO_UCS4(ch, aString[1]); + } + if (IsClusterExtender(ch)) { + *glyphs = extendCluster; + } + } + + while (!iter.AtEnd()) { + if (*iter == char16_t(' ')) { + glyphs->SetIsSpace(); + } + // advance iter to the next cluster-start (or end of text) + iter.Next(); + // step past the first char of the cluster + aString++; + glyphs++; + // mark all the rest as cluster-continuations + while (aString < iter) { + *glyphs = extendCluster; + glyphs++; + aString++; + } + } +} + +void gfxShapedText::SetupClusterBoundaries(uint32_t aOffset, + const uint8_t* aString, + uint32_t aLength) { + CompressedGlyph* glyphs = GetCharacterGlyphs() + aOffset; + const uint8_t* limit = aString + aLength; + + while (aString < limit) { + if (*aString == uint8_t(' ')) { + glyphs->SetIsSpace(); + } + aString++; + glyphs++; + } +} + +gfxShapedText::DetailedGlyph* gfxShapedText::AllocateDetailedGlyphs( + uint32_t aIndex, uint32_t aCount) { + NS_ASSERTION(aIndex < GetLength(), "Index out of range"); + + if (!mDetailedGlyphs) { + mDetailedGlyphs = MakeUnique<DetailedGlyphStore>(); + } + + return mDetailedGlyphs->Allocate(aIndex, aCount); +} + +void gfxShapedText::SetDetailedGlyphs(uint32_t aIndex, uint32_t aGlyphCount, + const DetailedGlyph* aGlyphs) { + CompressedGlyph& g = GetCharacterGlyphs()[aIndex]; + + MOZ_ASSERT(aIndex > 0 || g.IsLigatureGroupStart(), + "First character can't be a ligature continuation!"); + + if (aGlyphCount > 0) { + DetailedGlyph* details = AllocateDetailedGlyphs(aIndex, aGlyphCount); + memcpy(details, aGlyphs, sizeof(DetailedGlyph) * aGlyphCount); + } + + g.SetGlyphCount(aGlyphCount); +} + +#define ZWNJ 0x200C +#define ZWJ 0x200D +static inline bool IsIgnorable(uint32_t aChar) { + return (IsDefaultIgnorable(aChar)) || aChar == ZWNJ || aChar == ZWJ; +} + +void gfxShapedText::SetMissingGlyph(uint32_t aIndex, uint32_t aChar, + gfxFont* aFont) { + CompressedGlyph& g = GetCharacterGlyphs()[aIndex]; + uint8_t category = GetGeneralCategory(aChar); + if (category >= HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK && + category <= HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) { + g.SetComplex(false, true); + } + + // Leaving advance as zero will prevent drawing the hexbox for ignorables. + int32_t advance = 0; + if (!IsIgnorable(aChar)) { + gfxFloat width = + std::max(aFont->GetMetrics(nsFontMetrics::eHorizontal).aveCharWidth, + gfxFloat(gfxFontMissingGlyphs::GetDesiredMinWidth( + aChar, mAppUnitsPerDevUnit))); + advance = int32_t(width * mAppUnitsPerDevUnit); + } + DetailedGlyph detail = {aChar, advance, gfx::Point()}; + SetDetailedGlyphs(aIndex, 1, &detail); + g.SetMissing(); +} + +bool gfxShapedText::FilterIfIgnorable(uint32_t aIndex, uint32_t aCh) { + if (IsIgnorable(aCh)) { + // There are a few default-ignorables of Letter category (currently, + // just the Hangul filler characters) that we'd better not discard + // if they're followed by additional characters in the same cluster. + // Some fonts use them to carry the width of a whole cluster of + // combining jamos; see bug 1238243. + auto* charGlyphs = GetCharacterGlyphs(); + if (GetGenCategory(aCh) == nsUGenCategory::kLetter && + aIndex + 1 < GetLength() && !charGlyphs[aIndex + 1].IsClusterStart()) { + return false; + } + // A compressedGlyph that is set to MISSING but has no DetailedGlyphs list + // will be zero-width/invisible, which is what we want here. + CompressedGlyph& g = charGlyphs[aIndex]; + g.SetComplex(g.IsClusterStart(), g.IsLigatureGroupStart()).SetMissing(); + return true; + } + return false; +} + +void gfxShapedText::AdjustAdvancesForSyntheticBold(float aSynBoldOffset, + uint32_t aOffset, + uint32_t aLength) { + 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(); + if (advance > 0) { + advance += synAppUnitOffset; + if (CompressedGlyph::IsSimpleAdvance(advance)) { + glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph()); + } else { + // rare case, tested by making this the default + uint32_t glyphIndex = glyphData->GetSimpleGlyph(); + // convert the simple CompressedGlyph to an empty complex record + glyphData->SetComplex(true, true); + // then set its details (glyph ID with its new advance) + DetailedGlyph detail = {glyphIndex, advance, gfx::Point()}; + SetDetailedGlyphs(i, 1, &detail); + } + } + } else { + // complex glyphs ==> add offset at cluster/ligature boundaries + uint32_t detailedLength = glyphData->GetGlyphCount(); + if (detailedLength) { + DetailedGlyph* details = GetDetailedGlyphs(i); + if (!details) { + continue; + } + if (IsRightToLeft()) { + if (details[0].mAdvance > 0) { + details[0].mAdvance += synAppUnitOffset; + } + } else { + if (details[detailedLength - 1].mAdvance > 0) { + details[detailedLength - 1].mAdvance += synAppUnitOffset; + } + } + } + } + } +} + +float gfxFont::AngleForSyntheticOblique() const { + // If the style doesn't call for italic/oblique, or if the face already + // provides it, no synthetic style should be added. + if (mStyle.style == FontSlantStyle::Normal() || !mStyle.allowSyntheticStyle || + !mFontEntry->IsUpright()) { + return 0.0f; + } + + // If style calls for italic, and face doesn't support it, use default + // oblique angle as a simulation. + if (mStyle.style.IsItalic()) { + return mFontEntry->SupportsItalic() ? 0.0f : FontSlantStyle::kDefaultAngle; + } + + // Default or custom oblique angle + return mStyle.style.ObliqueAngle(); +} + +float gfxFont::SkewForSyntheticOblique() const { + // Precomputed value of tan(kDefaultAngle), the default italic/oblique slant; + // avoids calling tan() at runtime except for custom oblique values. + static const float kTanDefaultAngle = + tan(FontSlantStyle::kDefaultAngle * (M_PI / 180.0)); + + float angle = AngleForSyntheticOblique(); + if (angle == 0.0f) { + return 0.0f; + } else if (angle == FontSlantStyle::kDefaultAngle) { + return kTanDefaultAngle; + } else { + return tan(angle * (M_PI / 180.0)); + } +} + +void gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther, + bool aOtherIsOnLeft) { + mAscent = std::max(mAscent, aOther.mAscent); + mDescent = std::max(mDescent, aOther.mDescent); + if (aOtherIsOnLeft) { + mBoundingBox = (mBoundingBox + gfxPoint(aOther.mAdvanceWidth, 0)) + .Union(aOther.mBoundingBox); + } else { + mBoundingBox = + mBoundingBox.Union(aOther.mBoundingBox + gfxPoint(mAdvanceWidth, 0)); + } + mAdvanceWidth += aOther.mAdvanceWidth; +} + +gfxFont::gfxFont(const RefPtr<UnscaledFont>& aUnscaledFont, + gfxFontEntry* aFontEntry, const gfxFontStyle* aFontStyle, + AntialiasOption anAAOption) + : mFontEntry(aFontEntry), + mUnscaledFont(aUnscaledFont), + mStyle(*aFontStyle), + mAdjustedSize(0.0), + mFUnitsConvFactor(-1.0f), // negative to indicate "not yet initialized" + mAntialiasOption(anAAOption), + mIsValid(true), + mApplySyntheticBold(false), + mKerningEnabled(false), + mMathInitialized(false) { +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + ++gFontCount; +#endif + + if (MOZ_UNLIKELY(StaticPrefs::gfx_text_disable_aa_AtStartup())) { + mAntialiasOption = kAntialiasNone; + } + + // Turn off AA for Ahem for testing purposes when requested. + if (MOZ_UNLIKELY(StaticPrefs::gfx_font_rendering_ahem_antialias_none() && + mFontEntry->FamilyName().EqualsLiteral("Ahem"))) { + mAntialiasOption = kAntialiasNone; + } + + mKerningSet = HasFeatureSet(HB_TAG('k', 'e', 'r', 'n'), mKerningEnabled); +} + +gfxFont::~gfxFont() { + mFontEntry->NotifyFontDestroyed(this); + + if (mGlyphChangeObservers) { + for (auto it = mGlyphChangeObservers->Iter(); !it.Done(); it.Next()) { + it.Get()->GetKey()->ForgetFont(); + } + } +} + +// Work out whether cairo will snap inter-glyph spacing to pixels. +// +// Layout does not align text to pixel boundaries, so, with font drawing +// backends that snap glyph positions to pixels, it is important that +// inter-glyph spacing within words is always an integer number of pixels. +// This ensures that the drawing backend snaps all of the word's glyphs in the +// same direction and so inter-glyph spacing remains the same. +// +gfxFont::RoundingFlags gfxFont::GetRoundOffsetsToPixels( + DrawTarget* aDrawTarget) { + // Could do something fancy here for ScaleFactors of + // AxisAlignedTransforms, but we leave things simple. + // Not much point rounding if a matrix will mess things up anyway. + // Also check if the font already knows hint metrics is off... + if (aDrawTarget->GetTransform().HasNonTranslation() || !ShouldHintMetrics()) { + return RoundingFlags(0); + } + + cairo_t* cr = static_cast<cairo_t*>( + aDrawTarget->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT)); + if (cr) { + cairo_surface_t* target = cairo_get_target(cr); + + // Check whether the cairo surface's font options hint metrics. + cairo_font_options_t* fontOptions = cairo_font_options_create(); + cairo_surface_get_font_options(target, fontOptions); + cairo_hint_metrics_t hintMetrics = + cairo_font_options_get_hint_metrics(fontOptions); + cairo_font_options_destroy(fontOptions); + + switch (hintMetrics) { + case CAIRO_HINT_METRICS_OFF: + return RoundingFlags(0); + case CAIRO_HINT_METRICS_ON: + return RoundingFlags::kRoundX | RoundingFlags::kRoundY; + default: + break; + } + } + + if (ShouldRoundXOffset(cr)) { + return RoundingFlags::kRoundX | RoundingFlags::kRoundY; + } else { + return RoundingFlags::kRoundY; + } +} + +gfxFloat gfxFont::GetGlyphHAdvance(DrawTarget* aDrawTarget, uint16_t aGID) { + if (ProvidesGlyphWidths()) { + return GetGlyphWidth(aGID) / 65536.0; + } + if (mFUnitsConvFactor < 0.0f) { + GetMetrics(nsFontMetrics::eHorizontal); + } + NS_ASSERTION(mFUnitsConvFactor >= 0.0f, + "missing font unit conversion factor"); + if (!mHarfBuzzShaper) { + mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this); + } + gfxHarfBuzzShaper* shaper = + static_cast<gfxHarfBuzzShaper*>(mHarfBuzzShaper.get()); + if (!shaper->Initialize()) { + return 0; + } + return shaper->GetGlyphHAdvance(aGID) / 65536.0; +} + +static void CollectLookupsByFeature(hb_face_t* aFace, hb_tag_t aTableTag, + uint32_t aFeatureIndex, + hb_set_t* aLookups) { + uint32_t lookups[32]; + uint32_t i, len, offset; + + offset = 0; + do { + len = ArrayLength(lookups); + hb_ot_layout_feature_get_lookups(aFace, aTableTag, aFeatureIndex, offset, + &len, lookups); + for (i = 0; i < len; i++) { + hb_set_add(aLookups, lookups[i]); + } + offset += len; + } while (len == ArrayLength(lookups)); +} + +static void CollectLookupsByLanguage( + hb_face_t* aFace, hb_tag_t aTableTag, + const nsTHashtable<nsUint32HashKey>& aSpecificFeatures, + hb_set_t* aOtherLookups, hb_set_t* aSpecificFeatureLookups, + uint32_t aScriptIndex, uint32_t aLangIndex) { + uint32_t reqFeatureIndex; + if (hb_ot_layout_language_get_required_feature_index( + aFace, aTableTag, aScriptIndex, aLangIndex, &reqFeatureIndex)) { + CollectLookupsByFeature(aFace, aTableTag, reqFeatureIndex, aOtherLookups); + } + + uint32_t featureIndexes[32]; + uint32_t i, len, offset; + + offset = 0; + do { + len = ArrayLength(featureIndexes); + hb_ot_layout_language_get_feature_indexes(aFace, aTableTag, aScriptIndex, + aLangIndex, offset, &len, + featureIndexes); + + for (i = 0; i < len; i++) { + uint32_t featureIndex = featureIndexes[i]; + + // get the feature tag + hb_tag_t featureTag; + uint32_t tagLen = 1; + hb_ot_layout_language_get_feature_tags(aFace, aTableTag, aScriptIndex, + aLangIndex, offset + i, &tagLen, + &featureTag); + + // collect lookups + hb_set_t* lookups = aSpecificFeatures.GetEntry(featureTag) + ? aSpecificFeatureLookups + : aOtherLookups; + CollectLookupsByFeature(aFace, aTableTag, featureIndex, lookups); + } + offset += len; + } while (len == ArrayLength(featureIndexes)); +} + +static bool HasLookupRuleWithGlyphByScript( + hb_face_t* aFace, hb_tag_t aTableTag, hb_tag_t aScriptTag, + uint32_t aScriptIndex, uint16_t aGlyph, + const nsTHashtable<nsUint32HashKey>& aDefaultFeatures, + bool& aHasDefaultFeatureWithGlyph) { + uint32_t numLangs, lang; + hb_set_t* defaultFeatureLookups = hb_set_create(); + hb_set_t* nonDefaultFeatureLookups = hb_set_create(); + + // default lang + CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures, + nonDefaultFeatureLookups, defaultFeatureLookups, + aScriptIndex, HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX); + + // iterate over langs + numLangs = hb_ot_layout_script_get_language_tags( + aFace, aTableTag, aScriptIndex, 0, nullptr, nullptr); + for (lang = 0; lang < numLangs; lang++) { + CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures, + nonDefaultFeatureLookups, defaultFeatureLookups, + aScriptIndex, lang); + } + + // look for the glyph among default feature lookups + aHasDefaultFeatureWithGlyph = false; + hb_set_t* glyphs = hb_set_create(); + hb_codepoint_t index = -1; + while (hb_set_next(defaultFeatureLookups, &index)) { + hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs, + glyphs, nullptr); + if (hb_set_has(glyphs, aGlyph)) { + aHasDefaultFeatureWithGlyph = true; + break; + } + } + + // look for the glyph among non-default feature lookups + // if no default feature lookups contained spaces + bool hasNonDefaultFeatureWithGlyph = false; + if (!aHasDefaultFeatureWithGlyph) { + hb_set_clear(glyphs); + index = -1; + while (hb_set_next(nonDefaultFeatureLookups, &index)) { + hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, + glyphs, glyphs, nullptr); + if (hb_set_has(glyphs, aGlyph)) { + hasNonDefaultFeatureWithGlyph = true; + break; + } + } + } + + hb_set_destroy(glyphs); + hb_set_destroy(defaultFeatureLookups); + hb_set_destroy(nonDefaultFeatureLookups); + + return aHasDefaultFeatureWithGlyph || hasNonDefaultFeatureWithGlyph; +} + +static void HasLookupRuleWithGlyph(hb_face_t* aFace, hb_tag_t aTableTag, + bool& aHasGlyph, hb_tag_t aSpecificFeature, + bool& aHasGlyphSpecific, uint16_t aGlyph) { + // iterate over the scripts in the font + uint32_t numScripts, numLangs, script, lang; + hb_set_t* otherLookups = hb_set_create(); + hb_set_t* specificFeatureLookups = hb_set_create(); + nsTHashtable<nsUint32HashKey> specificFeature; + + specificFeature.PutEntry(aSpecificFeature); + + numScripts = + hb_ot_layout_table_get_script_tags(aFace, aTableTag, 0, nullptr, nullptr); + + for (script = 0; script < numScripts; script++) { + // default lang + CollectLookupsByLanguage(aFace, aTableTag, specificFeature, otherLookups, + specificFeatureLookups, script, + HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX); + + // iterate over langs + numLangs = hb_ot_layout_script_get_language_tags( + aFace, HB_OT_TAG_GPOS, script, 0, nullptr, nullptr); + for (lang = 0; lang < numLangs; lang++) { + CollectLookupsByLanguage(aFace, aTableTag, specificFeature, otherLookups, + specificFeatureLookups, script, lang); + } + } + + // look for the glyph among non-specific feature lookups + hb_set_t* glyphs = hb_set_create(); + hb_codepoint_t index = -1; + while (hb_set_next(otherLookups, &index)) { + hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs, + glyphs, nullptr); + if (hb_set_has(glyphs, aGlyph)) { + aHasGlyph = true; + break; + } + } + + // look for the glyph among specific feature lookups + hb_set_clear(glyphs); + index = -1; + while (hb_set_next(specificFeatureLookups, &index)) { + hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs, + glyphs, nullptr); + if (hb_set_has(glyphs, aGlyph)) { + aHasGlyphSpecific = true; + break; + } + } + + hb_set_destroy(glyphs); + hb_set_destroy(specificFeatureLookups); + hb_set_destroy(otherLookups); +} + +nsDataHashtable<nsUint32HashKey, Script>* gfxFont::sScriptTagToCode = nullptr; +nsTHashtable<nsUint32HashKey>* gfxFont::sDefaultFeatures = nullptr; + +static inline bool HasSubstitution(uint32_t* aBitVector, Script aScript) { + return (aBitVector[static_cast<uint32_t>(aScript) >> 5] & + (1 << (static_cast<uint32_t>(aScript) & 0x1f))) != 0; +} + +// union of all default substitution features across scripts +static const hb_tag_t defaultFeatures[] = { + HB_TAG('a', 'b', 'v', 'f'), HB_TAG('a', 'b', 'v', 's'), + HB_TAG('a', 'k', 'h', 'n'), HB_TAG('b', 'l', 'w', 'f'), + HB_TAG('b', 'l', 'w', 's'), HB_TAG('c', 'a', 'l', 't'), + HB_TAG('c', 'c', 'm', 'p'), HB_TAG('c', 'f', 'a', 'r'), + HB_TAG('c', 'j', 'c', 't'), HB_TAG('c', 'l', 'i', 'g'), + HB_TAG('f', 'i', 'n', '2'), HB_TAG('f', 'i', 'n', '3'), + HB_TAG('f', 'i', 'n', 'a'), HB_TAG('h', 'a', 'l', 'f'), + HB_TAG('h', 'a', 'l', 'n'), HB_TAG('i', 'n', 'i', 't'), + HB_TAG('i', 's', 'o', 'l'), HB_TAG('l', 'i', 'g', 'a'), + HB_TAG('l', 'j', 'm', 'o'), HB_TAG('l', 'o', 'c', 'l'), + HB_TAG('l', 't', 'r', 'a'), HB_TAG('l', 't', 'r', 'm'), + HB_TAG('m', 'e', 'd', '2'), HB_TAG('m', 'e', 'd', 'i'), + HB_TAG('m', 's', 'e', 't'), HB_TAG('n', 'u', 'k', 't'), + HB_TAG('p', 'r', 'e', 'f'), HB_TAG('p', 'r', 'e', 's'), + HB_TAG('p', 's', 't', 'f'), HB_TAG('p', 's', 't', 's'), + HB_TAG('r', 'c', 'l', 't'), HB_TAG('r', 'l', 'i', 'g'), + HB_TAG('r', 'k', 'r', 'f'), HB_TAG('r', 'p', 'h', 'f'), + HB_TAG('r', 't', 'l', 'a'), HB_TAG('r', 't', 'l', 'm'), + HB_TAG('t', 'j', 'm', 'o'), HB_TAG('v', 'a', 't', 'u'), + HB_TAG('v', 'e', 'r', 't'), HB_TAG('v', 'j', 'm', 'o')}; + +void gfxFont::CheckForFeaturesInvolvingSpace() { + mFontEntry->mHasSpaceFeaturesInitialized = true; + + bool log = LOG_FONTINIT_ENABLED(); + TimeStamp start; + if (MOZ_UNLIKELY(log)) { + start = TimeStamp::Now(); + } + + bool result = false; + + uint32_t spaceGlyph = GetSpaceGlyph(); + if (!spaceGlyph) { + return; + } + + hb_face_t* face = GetFontEntry()->GetHBFace(); + + // GSUB lookups - examine per script + if (hb_ot_layout_has_substitution(face)) { + // set up the script ==> code hashtable if needed + if (!sScriptTagToCode) { + sScriptTagToCode = new nsDataHashtable<nsUint32HashKey, Script>( + size_t(Script::NUM_SCRIPT_CODES)); + sScriptTagToCode->Put(HB_TAG('D', 'F', 'L', 'T'), Script::COMMON); + // Ensure that we don't try to look at script codes beyond what the + // current version of ICU (at runtime -- in case of system ICU) + // knows about. + Script scriptCount = + Script(std::min<int>(u_getIntPropertyMaxValue(UCHAR_SCRIPT) + 1, + int(Script::NUM_SCRIPT_CODES))); + for (Script s = Script::ARABIC; s < scriptCount; + s = Script(static_cast<int>(s) + 1)) { + hb_script_t script = hb_script_t(GetScriptTagForCode(s)); + unsigned int scriptCount = 4; + hb_tag_t scriptTags[4]; + hb_ot_tags_from_script_and_language(script, HB_LANGUAGE_INVALID, + &scriptCount, scriptTags, nullptr, + nullptr); + for (unsigned int i = 0; i < scriptCount; i++) { + sScriptTagToCode->Put(scriptTags[i], s); + } + } + + uint32_t numDefaultFeatures = ArrayLength(defaultFeatures); + sDefaultFeatures = new nsTHashtable<nsUint32HashKey>(numDefaultFeatures); + for (uint32_t i = 0; i < numDefaultFeatures; i++) { + sDefaultFeatures->PutEntry(defaultFeatures[i]); + } + } + + // iterate over the scripts in the font + hb_tag_t scriptTags[8]; + + uint32_t len, offset = 0; + do { + len = ArrayLength(scriptTags); + hb_ot_layout_table_get_script_tags(face, HB_OT_TAG_GSUB, offset, &len, + scriptTags); + for (uint32_t i = 0; i < len; i++) { + bool isDefaultFeature = false; + Script s; + if (!HasLookupRuleWithGlyphByScript( + face, HB_OT_TAG_GSUB, scriptTags[i], offset + i, spaceGlyph, + *sDefaultFeatures, isDefaultFeature) || + !sScriptTagToCode->Get(scriptTags[i], &s)) { + continue; + } + result = true; + uint32_t index = static_cast<uint32_t>(s) >> 5; + uint32_t bit = static_cast<uint32_t>(s) & 0x1f; + if (isDefaultFeature) { + mFontEntry->mDefaultSubSpaceFeatures[index] |= (1 << bit); + } else { + mFontEntry->mNonDefaultSubSpaceFeatures[index] |= (1 << bit); + } + } + offset += len; + } while (len == ArrayLength(scriptTags)); + } + + // spaces in default features of default script? + // ==> can't use word cache, skip GPOS analysis + bool canUseWordCache = true; + if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, Script::COMMON)) { + canUseWordCache = false; + } + + // GPOS lookups - distinguish kerning from non-kerning features + mFontEntry->mHasSpaceFeaturesKerning = false; + mFontEntry->mHasSpaceFeaturesNonKerning = false; + + if (canUseWordCache && hb_ot_layout_has_positioning(face)) { + bool hasKerning = false, hasNonKerning = false; + HasLookupRuleWithGlyph(face, HB_OT_TAG_GPOS, hasNonKerning, + HB_TAG('k', 'e', 'r', 'n'), hasKerning, spaceGlyph); + if (hasKerning || hasNonKerning) { + result = true; + } + mFontEntry->mHasSpaceFeaturesKerning = hasKerning; + mFontEntry->mHasSpaceFeaturesNonKerning = hasNonKerning; + } + + hb_face_destroy(face); + mFontEntry->mHasSpaceFeatures = result; + + if (MOZ_UNLIKELY(log)) { + TimeDuration elapsed = TimeStamp::Now() - start; + LOG_FONTINIT( + ("(fontinit-spacelookups) font: %s - " + "subst default: %8.8x %8.8x %8.8x %8.8x " + "subst non-default: %8.8x %8.8x %8.8x %8.8x " + "kerning: %s non-kerning: %s time: %6.3f\n", + 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; +} + +tainted_boolean_hint gfxFont::SpaceMayParticipateInShaping(Script aRunScript) { + // avoid checking fonts known not to include default space-dependent features + if (MOZ_UNLIKELY(mFontEntry->mSkipDefaultFeatureSpaceCheck)) { + if (!mKerningSet && mStyle.featureSettings.IsEmpty() && + mFontEntry->mFeatureSettings.IsEmpty()) { + return false; + } + } + + if (FontCanSupportGraphite()) { + if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) { + return mFontEntry->HasGraphiteSpaceContextuals(); + } + } + + // We record the presence of space-dependent features in the font entry + // so that subsequent instantiations for the same font face won't + // require us to re-check the tables; however, the actual check is done + // by gfxFont because not all font entry subclasses know how to create + // a harfbuzz face for introspection. + if (!mFontEntry->mHasSpaceFeaturesInitialized) { + CheckForFeaturesInvolvingSpace(); + } + + if (!mFontEntry->mHasSpaceFeatures) { + return false; + } + + // if font has substitution rules or non-kerning positioning rules + // that involve spaces, bypass + if (HasSubstitutionRulesWithSpaceLookups(aRunScript) || + mFontEntry->mHasSpaceFeaturesNonKerning) { + return true; + } + + // if kerning explicitly enabled/disabled via font-feature-settings or + // font-kerning and kerning rules use spaces, only bypass when enabled + if (mKerningSet && mFontEntry->mHasSpaceFeaturesKerning) { + return mKerningEnabled; + } + + return false; +} + +bool gfxFont::SupportsFeature(Script aScript, uint32_t aFeatureTag) { + if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) { + return GetFontEntry()->SupportsGraphiteFeature(aFeatureTag); + } + return GetFontEntry()->SupportsOpenTypeFeature(aScript, aFeatureTag); +} + +bool gfxFont::SupportsVariantCaps(Script aScript, uint32_t aVariantCaps, + bool& aFallbackToSmallCaps, + bool& aSyntheticLowerToSmallCaps, + bool& aSyntheticUpperToSmallCaps) { + bool ok = true; // cases without fallback are fine + aFallbackToSmallCaps = false; + aSyntheticLowerToSmallCaps = false; + aSyntheticUpperToSmallCaps = false; + switch (aVariantCaps) { + case NS_FONT_VARIANT_CAPS_SMALLCAPS: + ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p')); + if (!ok) { + aSyntheticLowerToSmallCaps = true; + } + break; + case NS_FONT_VARIANT_CAPS_ALLSMALL: + ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p')) && + SupportsFeature(aScript, HB_TAG('c', '2', 's', 'c')); + if (!ok) { + aSyntheticLowerToSmallCaps = true; + aSyntheticUpperToSmallCaps = true; + } + break; + case NS_FONT_VARIANT_CAPS_PETITECAPS: + ok = SupportsFeature(aScript, HB_TAG('p', 'c', 'a', 'p')); + if (!ok) { + ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p')); + aFallbackToSmallCaps = ok; + } + if (!ok) { + aSyntheticLowerToSmallCaps = true; + } + break; + case NS_FONT_VARIANT_CAPS_ALLPETITE: + ok = SupportsFeature(aScript, HB_TAG('p', 'c', 'a', 'p')) && + SupportsFeature(aScript, HB_TAG('c', '2', 'p', 'c')); + if (!ok) { + ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p')) && + SupportsFeature(aScript, HB_TAG('c', '2', 's', 'c')); + aFallbackToSmallCaps = ok; + } + if (!ok) { + aSyntheticLowerToSmallCaps = true; + aSyntheticUpperToSmallCaps = true; + } + break; + default: + break; + } + + NS_ASSERTION( + !(ok && (aSyntheticLowerToSmallCaps || aSyntheticUpperToSmallCaps)), + "shouldn't use synthetic features if we found real ones"); + + NS_ASSERTION(!(!ok && aFallbackToSmallCaps), + "if we found a usable fallback, that counts as ok"); + + return ok; +} + +bool gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript, + const uint8_t* aString, uint32_t aLength, + Script aRunScript) { + NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aString), + aLength); + return SupportsSubSuperscript(aSubSuperscript, unicodeString.get(), aLength, + aRunScript); +} + +bool gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript, + const char16_t* aString, uint32_t aLength, + Script aRunScript) { + NS_ASSERTION(aSubSuperscript == NS_FONT_VARIANT_POSITION_SUPER || + aSubSuperscript == NS_FONT_VARIANT_POSITION_SUB, + "unknown value of font-variant-position"); + + uint32_t feature = aSubSuperscript == NS_FONT_VARIANT_POSITION_SUPER + ? HB_TAG('s', 'u', 'p', 's') + : HB_TAG('s', 'u', 'b', 's'); + + if (!SupportsFeature(aRunScript, feature)) { + return false; + } + + // xxx - for graphite, don't really know how to sniff lookups so bail + if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) { + return true; + } + + if (!mHarfBuzzShaper) { + mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this); + } + gfxHarfBuzzShaper* shaper = + static_cast<gfxHarfBuzzShaper*>(mHarfBuzzShaper.get()); + if (!shaper->Initialize()) { + return false; + } + + // get the hbset containing input glyphs for the feature + const hb_set_t* inputGlyphs = + mFontEntry->InputsForOpenTypeFeature(aRunScript, feature); + + // create an hbset containing default glyphs for the script run + hb_set_t* defaultGlyphsInRun = hb_set_create(); + + // for each character, get the glyph id + for (uint32_t i = 0; i < aLength; i++) { + uint32_t ch = aString[i]; + + if (i + 1 < aLength && NS_IS_SURROGATE_PAIR(ch, 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::FeatureWillHandleChar(Script aRunScript, uint32_t aFeature, + uint32_t aUnicode) { + if (!SupportsFeature(aRunScript, aFeature)) { + return false; + } + + // xxx - for graphite, don't really know how to sniff lookups so bail + if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) { + return true; + } + + if (!mHarfBuzzShaper) { + mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this); + } + gfxHarfBuzzShaper* shaper = + static_cast<gfxHarfBuzzShaper*>(mHarfBuzzShaper.get()); + if (!shaper->Initialize()) { + return false; + } + + // get the hbset containing input glyphs for the feature + const hb_set_t* inputGlyphs = + mFontEntry->InputsForOpenTypeFeature(aRunScript, aFeature); + + if (aUnicode == 0xa0) { + aUnicode = ' '; + } + + hb_codepoint_t gid = shaper->GetNominalGlyph(aUnicode); + return hb_set_has(inputGlyphs, gid); +} + +bool gfxFont::HasFeatureSet(uint32_t aFeature, bool& aFeatureOn) { + aFeatureOn = false; + + if (mStyle.featureSettings.IsEmpty() && + GetFontEntry()->mFeatureSettings.IsEmpty()) { + return false; + } + + // add feature values from font + bool featureSet = false; + uint32_t i, count; + + nsTArray<gfxFontFeature>& fontFeatures = GetFontEntry()->mFeatureSettings; + count = fontFeatures.Length(); + for (i = 0; i < count; i++) { + const gfxFontFeature& feature = fontFeatures.ElementAt(i); + if (feature.mTag == aFeature) { + featureSet = true; + aFeatureOn = (feature.mValue != 0); + } + } + + // add feature values from style rules + nsTArray<gfxFontFeature>& styleFeatures = mStyle.featureSettings; + count = styleFeatures.Length(); + for (i = 0; i < count; i++) { + const gfxFontFeature& feature = styleFeatures.ElementAt(i); + if (feature.mTag == aFeature) { + featureSet = true; + aFeatureOn = (feature.mValue != 0); + } + } + + return featureSet; +} + +void gfxFont::InitializeScaledFont() { + if (!mAzureScaledFont) { + return; + } + + float angle = AngleForSyntheticOblique(); + if (angle != 0.0f) { + mAzureScaledFont->SetSyntheticObliqueAngle(angle); + } +} + +/** + * A helper function in case we need to do any rounding or other + * processing here. + */ +#define ToDeviceUnits(aAppUnits, aDevUnitsPerAppUnit) \ + (double(aAppUnits) * double(aDevUnitsPerAppUnit)) + +static AntialiasMode Get2DAAMode(gfxFont::AntialiasOption aAAOption) { + switch (aAAOption) { + case gfxFont::kAntialiasSubpixel: + return AntialiasMode::SUBPIXEL; + case gfxFont::kAntialiasGrayscale: + return AntialiasMode::GRAY; + case gfxFont::kAntialiasNone: + return AntialiasMode::NONE; + default: + return AntialiasMode::DEFAULT; + } +} + +class GlyphBufferAzure { +#define AUTO_BUFFER_SIZE (2048 / sizeof(Glyph)) + + typedef mozilla::image::imgDrawingParams imgDrawingParams; + + public: + GlyphBufferAzure(const TextRunDrawParams& aRunParams, + const FontDrawParams& aFontParams) + : mRunParams(aRunParams), + mFontParams(aFontParams), + mBuffer(*mAutoBuffer.addr()), + mBufSize(AUTO_BUFFER_SIZE), + mCapacity(0), + mNumGlyphs(0) {} + + ~GlyphBufferAzure() { + if (mNumGlyphs > 0) { + FlushGlyphs(); + } + + if (mBuffer != *mAutoBuffer.addr()) { + free(mBuffer); + } + } + + // Ensure the buffer has enough space for aGlyphCount glyphs to be added, + // considering the supplied strike multipler aStrikeCount. + // This MUST be called before OutputGlyph is used to actually store glyph + // records in the buffer. It may be called repeated to add further capacity + // in case we don't know up-front exactly what will be needed. + void AddCapacity(uint32_t aGlyphCount, uint32_t aStrikeCount) { + // Calculate the new capacity and ensure it will fit within the maximum + // allowed capacity. + static const uint64_t kMaxCapacity = 64 * 1024; + mCapacity = uint32_t(std::min( + kMaxCapacity, + uint64_t(mCapacity) + uint64_t(aGlyphCount) * uint64_t(aStrikeCount))); + // See if the required capacity fits within the already-allocated space + if (mCapacity <= mBufSize) { + return; + } + // We need to grow the buffer: determine a new size, allocate, and + // copy the existing data over if we didn't use realloc (which would + // do it automatically). + mBufSize = std::max(mCapacity, mBufSize * 2); + if (mBuffer == *mAutoBuffer.addr()) { + // switching from autobuffer to malloc, so we need to copy + mBuffer = reinterpret_cast<Glyph*>(moz_xmalloc(mBufSize * sizeof(Glyph))); + std::memcpy(mBuffer, *mAutoBuffer.addr(), mNumGlyphs * sizeof(Glyph)); + } else { + mBuffer = reinterpret_cast<Glyph*>( + moz_xrealloc(mBuffer, mBufSize * sizeof(Glyph))); + } + } + + void OutputGlyph(uint32_t aGlyphID, const gfx::Point& aPt) { + // If the buffer is full, flush to make room for the new glyph. + if (mNumGlyphs >= mCapacity) { + // Check that AddCapacity has been used appropriately! + MOZ_ASSERT(mCapacity > 0 && mNumGlyphs == mCapacity); + Flush(); + } + Glyph* glyph = mBuffer + mNumGlyphs++; + glyph->mIndex = aGlyphID; + glyph->mPosition = aPt; + } + + void Flush() { + if (mNumGlyphs > 0) { + FlushGlyphs(); + mNumGlyphs = 0; + } + } + + const TextRunDrawParams& mRunParams; + const FontDrawParams& mFontParams; + + private: + static DrawMode GetStrokeMode(DrawMode aMode) { + return aMode & (DrawMode::GLYPH_STROKE | DrawMode::GLYPH_STROKE_UNDERNEATH); + } + + // Render the buffered glyphs to the draw target. + void FlushGlyphs() { + gfx::GlyphBuffer buf; + buf.mGlyphs = mBuffer; + buf.mNumGlyphs = mNumGlyphs; + + const gfxContext::AzureState& state = mRunParams.context->CurrentState(); + + // Draw stroke first if the UNDERNEATH flag is set in drawMode. + if (mRunParams.strokeOpts && + GetStrokeMode(mRunParams.drawMode) == + (DrawMode::GLYPH_STROKE | DrawMode::GLYPH_STROKE_UNDERNEATH)) { + DrawStroke(state, buf); + } + + if (mRunParams.drawMode & DrawMode::GLYPH_FILL) { + if (state.pattern || mFontParams.contextPaint) { + Pattern* pat; + + RefPtr<gfxPattern> fillPattern; + if (mFontParams.contextPaint) { + imgDrawingParams imgParams; + fillPattern = mFontParams.contextPaint->GetFillPattern( + mRunParams.context->GetDrawTarget(), + mRunParams.context->CurrentMatrixDouble(), imgParams); + } + if (!fillPattern) { + if (state.pattern) { + RefPtr<gfxPattern> statePattern = + mRunParams.context->CurrentState().pattern; + pat = statePattern->GetPattern(mRunParams.dt, + state.patternTransformChanged + ? &state.patternTransform + : nullptr); + } else { + pat = nullptr; + } + } else { + pat = fillPattern->GetPattern(mRunParams.dt); + } + + if (pat) { + mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf, *pat, + mFontParams.drawOptions); + } + } else { + mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf, + ColorPattern(state.color), + mFontParams.drawOptions); + } + } + + // Draw stroke if the UNDERNEATH flag is not set. + if (mRunParams.strokeOpts && + GetStrokeMode(mRunParams.drawMode) == DrawMode::GLYPH_STROKE) { + DrawStroke(state, buf); + } + + if (mRunParams.drawMode & DrawMode::GLYPH_PATH) { + mRunParams.context->EnsurePathBuilder(); + Matrix mat = mRunParams.dt->GetTransform(); + mFontParams.scaledFont->CopyGlyphsToBuilder( + buf, mRunParams.context->mPathBuilder, &mat); + } + } + + void DrawStroke(const gfxContext::AzureState& aState, + gfx::GlyphBuffer& aBuffer) { + if (mRunParams.textStrokePattern) { + Pattern* pat = mRunParams.textStrokePattern->GetPattern( + mRunParams.dt, + aState.patternTransformChanged ? &aState.patternTransform : nullptr); + + if (pat) { + FlushStroke(aBuffer, *pat); + } + } else { + FlushStroke(aBuffer, + ColorPattern(ToDeviceColor(mRunParams.textStrokeColor))); + } + } + + void FlushStroke(gfx::GlyphBuffer& aBuf, const Pattern& aPattern) { + mRunParams.dt->StrokeGlyphs(mFontParams.scaledFont, aBuf, aPattern, + *mRunParams.strokeOpts, + mFontParams.drawOptions); + } + + // We use an "inline" buffer automatically allocated (on the stack) as part + // of the GlyphBufferAzure object to hold the glyphs in most cases, falling + // back to a separately-allocated heap buffer if the count of buffered + // glyphs gets too big. + // + // This is basically a rudimentary AutoTArray; so why not use AutoTArray + // itself? + // + // If we used an AutoTArray, we'd want to avoid using SetLength or + // AppendElements to allocate the space we actually need, because those + // methods would default-construct the new elements. + // + // Could we use SetCapacity to reserve the necessary buffer space without + // default-constructing all the Glyph records? No, because of a failure + // that could occur when we need to grow the buffer, which happens when we + // encounter a DetailedGlyph in the textrun that refers to a sequence of + // several real glyphs. At that point, we need to add some extra capacity + // to the buffer we initially allocated based on the length of the textrun + // range we're rendering. + // + // This buffer growth would work fine as long as it still fits within the + // array's inline buffer (we just use a bit more of it), or if the buffer + // was already heap-allocated (in which case AutoTArray will use realloc(), + // preserving its contents). But a problem will arise when the initial + // capacity we allocated (based on the length of the run) fits within the + // array's inline buffer, but subsequently we need to extend the buffer + // beyond the inline buffer size, so we reallocate to the heap. Because we + // haven't "officially" filled the array with SetLength or AppendElements, + // its mLength is still zero; as far as it's concerned the buffer is just + // uninitialized space, and when it switches to use a malloc'd buffer it + // won't copy the existing contents. + + // Allocate space for a buffer of Glyph records, without initializing them. + AlignedStorage2<Glyph[AUTO_BUFFER_SIZE]> mAutoBuffer; + + // Pointer to the buffer we're currently using -- initially mAutoBuffer, + // but may be changed to a malloc'd buffer, in which case that buffer must + // be free'd on destruction. + Glyph* mBuffer; + + uint32_t mBufSize; // size of allocated buffer; capacity can grow to + // this before reallocation is needed + uint32_t mCapacity; // amount of buffer size reserved + uint32_t mNumGlyphs; // number of glyphs actually present in the buffer + +#undef AUTO_BUFFER_SIZE +}; + +// Bug 674909. When synthetic bolding text by drawing twice, need to +// render using a pixel offset in device pixels, otherwise text +// doesn't appear bolded, it appears as if a bad text shadow exists +// when a non-identity transform exists. Use an offset factor so that +// the second draw occurs at a constant offset in device pixels. + +gfx::Float gfxFont::CalcXScale(DrawTarget* aDrawTarget) { + // determine magnitude of a 1px x offset in device space + Size t = aDrawTarget->GetTransform().TransformSize(Size(1.0, 0.0)); + if (t.width == 1.0 && t.height == 0.0) { + // short-circuit the most common case to avoid sqrt() and division + return 1.0; + } + + gfx::Float m = sqrtf(t.width * t.width + t.height * t.height); + + NS_ASSERTION(m != 0.0, "degenerate transform while synthetic bolding"); + if (m == 0.0) { + return 0.0; // effectively disables offset + } + + // scale factor so that offsets are 1px in device pixels + return 1.0 / m; +} + +// Draw a run of CharacterGlyph records from the given offset in aShapedText. +// Returns true if glyph paths were actually emitted. +template <gfxFont::FontComplexityT FC, gfxFont::SpacingT S> +bool gfxFont::DrawGlyphs(const gfxShapedText* aShapedText, + uint32_t aOffset, // offset in the textrun + uint32_t aCount, // length of run to draw + gfx::Point* aPt, + const gfx::Matrix* aOffsetMatrix, // may be null + GlyphBufferAzure& aBuffer) { + float& inlineCoord = aBuffer.mFontParams.isVerticalFont ? aPt->y : aPt->x; + + const gfxShapedText::CompressedGlyph* glyphData = + &aShapedText->GetCharacterGlyphs()[aOffset]; + + if (S == SpacingT::HasSpacing) { + float space = aBuffer.mRunParams.spacing[0].mBefore * + aBuffer.mFontParams.advanceDirection; + inlineCoord += space; + } + + // Allocate buffer space for the run, assuming all simple glyphs. + uint32_t capacityMult = 1 + aBuffer.mFontParams.extraStrikes; + aBuffer.AddCapacity(aCount, capacityMult); + + bool emittedGlyphs = false; + + for (uint32_t i = 0; i < aCount; ++i, ++glyphData) { + if (glyphData->IsSimpleGlyph()) { + float advance = + glyphData->GetSimpleAdvance() * aBuffer.mFontParams.advanceDirection; + if (aBuffer.mRunParams.isRTL) { + inlineCoord += advance; + } + DrawOneGlyph<FC>(glyphData->GetSimpleGlyph(), *aPt, aBuffer, + &emittedGlyphs); + if (!aBuffer.mRunParams.isRTL) { + inlineCoord += advance; + } + } else { + uint32_t glyphCount = glyphData->GetGlyphCount(); + if (glyphCount > 0) { + // Add extra buffer capacity to allow for multiple-glyph entry. + aBuffer.AddCapacity(glyphCount - 1, capacityMult); + const gfxShapedText::DetailedGlyph* details = + aShapedText->GetDetailedGlyphs(aOffset + i); + MOZ_ASSERT(details, "missing DetailedGlyph!"); + for (uint32_t j = 0; j < glyphCount; ++j, ++details) { + float advance = + details->mAdvance * aBuffer.mFontParams.advanceDirection; + if (aBuffer.mRunParams.isRTL) { + inlineCoord += advance; + } + if (glyphData->IsMissing()) { + if (!DrawMissingGlyph(aBuffer.mRunParams, aBuffer.mFontParams, + details, *aPt)) { + return false; + } + } else { + gfx::Point glyphPt( + *aPt + (aOffsetMatrix + ? aOffsetMatrix->TransformPoint(details->mOffset) + : details->mOffset)); + DrawOneGlyph<FC>(details->mGlyphID, glyphPt, aBuffer, + &emittedGlyphs); + } + if (!aBuffer.mRunParams.isRTL) { + inlineCoord += advance; + } + } + } + } + + if (S == SpacingT::HasSpacing) { + float space = aBuffer.mRunParams.spacing[i].mAfter; + if (i + 1 < aCount) { + space += aBuffer.mRunParams.spacing[i + 1].mBefore; + } + space *= aBuffer.mFontParams.advanceDirection; + inlineCoord += space; + } + } + + return emittedGlyphs; +} + +// Draw an individual glyph at a specific location. +// *aPt is the glyph position in appUnits; it is converted to device +// coordinates (devPt) here. +template <gfxFont::FontComplexityT FC> +void gfxFont::DrawOneGlyph(uint32_t aGlyphID, const gfx::Point& aPt, + GlyphBufferAzure& aBuffer, bool* aEmittedGlyphs) { + const TextRunDrawParams& runParams(aBuffer.mRunParams); + + gfx::Point devPt(ToDeviceUnits(aPt.x, runParams.devPerApp), + ToDeviceUnits(aPt.y, runParams.devPerApp)); + + if (FC == FontComplexityT::ComplexFont) { + const FontDrawParams& fontParams(aBuffer.mFontParams); + + auto* textDrawer = runParams.context->GetTextDrawer(); + + gfxContextMatrixAutoSaveRestore matrixRestore; + + if (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont && + !textDrawer) { + // We have to flush each glyph individually when doing + // synthetic-oblique for vertical-upright text, because + // the skew transform needs to be applied to a separate + // origin for each glyph, not once for the whole run. + aBuffer.Flush(); + matrixRestore.SetContext(runParams.context); + gfx::Point skewPt( + devPt.x + GetMetrics(nsFontMetrics::eVertical).emHeight / 2, devPt.y); + gfx::Matrix mat = + runParams.context->CurrentMatrix() + .PreTranslate(skewPt) + .PreMultiply(gfx::Matrix(1, fontParams.obliqueSkew, 0, 1, 0, 0)) + .PreTranslate(-skewPt); + runParams.context->SetMatrix(mat); + } + + if (fontParams.haveSVGGlyphs) { + if (!runParams.paintSVGGlyphs) { + return; + } + NS_WARNING_ASSERTION( + runParams.drawMode != DrawMode::GLYPH_PATH, + "Rendering SVG glyph despite request for glyph path"); + if (RenderSVGGlyph(runParams.context, textDrawer, devPt, aGlyphID, + fontParams.contextPaint, runParams.callbacks, + *aEmittedGlyphs)) { + return; + } + } + + if (fontParams.haveColorGlyphs && + !gfxPlatform::GetPlatform()->HasNativeColrFontSupport() && + RenderColorGlyph(runParams.dt, runParams.context, textDrawer, + fontParams.scaledFont, fontParams.drawOptions, devPt, + aGlyphID)) { + return; + } + + aBuffer.OutputGlyph(aGlyphID, devPt); + + // Synthetic bolding (if required) by multi-striking. + for (int32_t i = 0; i < fontParams.extraStrikes; ++i) { + if (fontParams.isVerticalFont) { + devPt.y += fontParams.synBoldOnePixelOffset; + } else { + devPt.x += fontParams.synBoldOnePixelOffset; + } + aBuffer.OutputGlyph(aGlyphID, devPt); + } + + if (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont && + !textDrawer) { + aBuffer.Flush(); + } + } else { + aBuffer.OutputGlyph(aGlyphID, devPt); + } + + *aEmittedGlyphs = true; +} + +bool gfxFont::DrawMissingGlyph(const TextRunDrawParams& aRunParams, + const FontDrawParams& aFontParams, + const gfxShapedText::DetailedGlyph* aDetails, + const gfx::Point& aPt) { + // Default-ignorable chars will have zero advance width; + // we don't have to draw the hexbox for them. + float advance = aDetails->mAdvance; + if (aRunParams.drawMode != DrawMode::GLYPH_PATH && advance > 0) { + auto* textDrawer = aRunParams.context->GetTextDrawer(); + const Matrix* matPtr = nullptr; + Matrix mat; + if (textDrawer) { + // Generate an orientation matrix for the current writing mode + wr::FontInstanceFlags flags = textDrawer->GetWRGlyphFlags(); + if (flags & wr::FontInstanceFlags::TRANSPOSE) { + std::swap(mat._11, mat._12); + std::swap(mat._21, mat._22); + } + mat.PostScale(flags & wr::FontInstanceFlags::FLIP_X ? -1.0f : 1.0f, + flags & wr::FontInstanceFlags::FLIP_Y ? -1.0f : 1.0f); + matPtr = &mat; + } + + Point pt(Float(ToDeviceUnits(aPt.x, aRunParams.devPerApp)), + Float(ToDeviceUnits(aPt.y, aRunParams.devPerApp))); + Float advanceDevUnits = Float(ToDeviceUnits(advance, aRunParams.devPerApp)); + Float height = GetMetrics(nsFontMetrics::eHorizontal).maxAscent; + // Horizontally center if drawing vertically upright with no sideways + // transform. + Rect glyphRect = + aFontParams.isVerticalFont && !mat.HasNonAxisAlignedTransform() + ? Rect(pt.x - height / 2, pt.y, height, advanceDevUnits) + : Rect(pt.x, pt.y - height, advanceDevUnits, height); + + // If there's a fake-italic skew in effect as part + // of the drawTarget's transform, we need to undo + // this before drawing the hexbox. (Bug 983985) + gfxContextMatrixAutoSaveRestore matrixRestore; + if (aFontParams.obliqueSkew != 0.0f && !aFontParams.isVerticalFont && + !textDrawer) { + matrixRestore.SetContext(aRunParams.context); + gfx::Matrix mat = + aRunParams.context->CurrentMatrix() + .PreTranslate(pt) + .PreMultiply(gfx::Matrix(1, 0, aFontParams.obliqueSkew, 1, 0, 0)) + .PreTranslate(-pt); + aRunParams.context->SetMatrix(mat); + } + + gfxFontMissingGlyphs::DrawMissingGlyph(aDetails->mGlyphID, glyphRect, + *aRunParams.dt, + PatternFromState(aRunParams.context), + 1.0 / aRunParams.devPerApp, matPtr); + } + return true; +} + +// This method is mostly parallel to DrawGlyphs. +void gfxFont::DrawEmphasisMarks(const gfxTextRun* aShapedText, gfx::Point* aPt, + uint32_t aOffset, uint32_t aCount, + const EmphasisMarkDrawParams& aParams) { + float& inlineCoord = aParams.isVertical ? aPt->y : aPt->x; + gfxTextRun::Range markRange(aParams.mark); + gfxTextRun::DrawParams params(aParams.context); + + float clusterStart = -std::numeric_limits<float>::infinity(); + bool shouldDrawEmphasisMark = false; + for (uint32_t i = 0, idx = aOffset; i < aCount; ++i, ++idx) { + if (aParams.spacing) { + inlineCoord += aParams.direction * aParams.spacing[i].mBefore; + } + if (aShapedText->IsClusterStart(idx) || + clusterStart == -std::numeric_limits<float>::infinity()) { + clusterStart = inlineCoord; + } + if (aShapedText->CharMayHaveEmphasisMark(idx)) { + shouldDrawEmphasisMark = true; + } + inlineCoord += aParams.direction * aShapedText->GetAdvanceForGlyph(idx); + if (shouldDrawEmphasisMark && + (i + 1 == aCount || aShapedText->IsClusterStart(idx + 1))) { + float clusterAdvance = inlineCoord - clusterStart; + // Move the coord backward to get the needed start point. + float delta = (clusterAdvance + aParams.advance) / 2; + inlineCoord -= delta; + aParams.mark->Draw(markRange, *aPt, params); + inlineCoord += delta; + shouldDrawEmphasisMark = false; + } + if (aParams.spacing) { + inlineCoord += aParams.direction * aParams.spacing[i].mAfter; + } + } +} + +void gfxFont::Draw(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd, + gfx::Point* aPt, const TextRunDrawParams& aRunParams, + gfx::ShapedTextFlags aOrientation) { + NS_ASSERTION(aRunParams.drawMode == DrawMode::GLYPH_PATH || + !(int(aRunParams.drawMode) & int(DrawMode::GLYPH_PATH)), + "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or " + "GLYPH_STROKE_UNDERNEATH"); + + if (aStart >= aEnd) { + return; + } + + FontDrawParams fontParams; + + if (aRunParams.drawOpts) { + fontParams.drawOptions = *aRunParams.drawOpts; + } + + fontParams.scaledFont = GetScaledFont(aRunParams.dt); + if (!fontParams.scaledFont) { + return; + } + + auto* textDrawer = aRunParams.context->GetTextDrawer(); + + fontParams.obliqueSkew = SkewForSyntheticOblique(); + fontParams.haveSVGGlyphs = GetFontEntry()->TryGetSVGData(this); + fontParams.haveColorGlyphs = GetFontEntry()->TryGetColorGlyphs(); + fontParams.contextPaint = aRunParams.runContextPaint; + + if (textDrawer) { + fontParams.isVerticalFont = aRunParams.isVerticalRun; + } else { + fontParams.isVerticalFont = + aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT; + } + + gfxContextMatrixAutoSaveRestore matrixRestore; + layout::TextDrawTarget::AutoRestoreWRGlyphFlags glyphFlagsRestore; + + // Save the current baseline offset for restoring later, in case it is + // modified. + float& baseline = fontParams.isVerticalFont ? aPt->x : aPt->y; + float origBaseline = baseline; + + // The point may be advanced in local-space, while the resulting point on + // return must be advanced in transformed space. So save the original point so + // we can properly transform the advance later. + gfx::Point origPt = *aPt; + const gfx::Matrix* offsetMatrix = nullptr; + + // Default to advancing along the +X direction (-X if RTL). + fontParams.advanceDirection = aRunParams.isRTL ? -1.0f : 1.0f; + // Default to offsetting baseline downward along the +Y direction. + float baselineDir = 1.0f; + // The direction of sideways rotation, if applicable. + // -1 for rotating left/counter-clockwise + // 1 for rotating right/clockwise + // 0 for no rotation + float sidewaysDir = + (aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT + ? -1.0f + : (aOrientation == + gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT + ? 1.0f + : 0.0f)); + // If we're rendering a sideways run, we need to push a rotation transform to + // the context. + if (sidewaysDir != 0.0f) { + if (textDrawer) { + // For WebRender, we can't use a DrawTarget transform and must instead use + // flags that locally transform the glyph, without affecting the glyph + // origin. The glyph origins must thus be offset in the transformed + // directions (instead of local-space directions). Modify the advance and + // baseline directions to account for the indicated transform. + + // The default text orientation is down being +Y and right being +X. + // Rotating 90 degrees left/CCW makes down be +X and right be -Y. + // Rotating 90 degrees right/CW makes down be -X and right be +Y. + // Thus the advance direction (moving right) is just sidewaysDir, + // i.e. negative along Y axis if rotated left and positive if + // rotated right. + fontParams.advanceDirection *= sidewaysDir; + // The baseline direction (moving down) is negated relative to the + // advance direction for sideways transforms. + baselineDir *= -sidewaysDir; + + glyphFlagsRestore.Save(textDrawer); + // Set the transform flags accordingly. Both sideways rotations transpose + // X and Y, while left rotation flips the resulting Y axis, and right + // rotation flips the resulting X axis. + textDrawer->SetWRGlyphFlags( + textDrawer->GetWRGlyphFlags() | wr::FontInstanceFlags::TRANSPOSE | + (aOrientation == + gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT + ? wr::FontInstanceFlags::FLIP_Y + : wr::FontInstanceFlags::FLIP_X)); + // We also need to set up a transform for the glyph offset vector that + // may be present in DetailedGlyph records. + static const gfx::Matrix kSidewaysLeft = {0, -1, 1, 0, 0, 0}; + static const gfx::Matrix kSidewaysRight = {0, 1, -1, 0, 0, 0}; + offsetMatrix = + (aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT) + ? &kSidewaysLeft + : &kSidewaysRight; + } else { + // For non-WebRender targets, just push a rotation transform. + matrixRestore.SetContext(aRunParams.context); + gfxPoint p(aPt->x * aRunParams.devPerApp, aPt->y * aRunParams.devPerApp); + // Get a matrix we can use to draw the (horizontally-shaped) textrun + // with 90-degree CW rotation. + const gfxFloat rotation = sidewaysDir * M_PI / 2.0f; + gfxMatrix mat = aRunParams.context->CurrentMatrixDouble() + .PreTranslate(p) + . // translate origin for rotation + PreRotate(rotation) + . // turn 90deg CCW (sideways-left) or CW (*-right) + PreTranslate(-p); // undo the translation + + aRunParams.context->SetMatrixDouble(mat); + } + + // If we're drawing rotated horizontal text for an element styled + // text-orientation:mixed, the dominant baseline will be vertical- + // centered. So in this case, we need to adjust the position so that + // the rotated horizontal text (which uses an alphabetic baseline) will + // look OK when juxtaposed with upright glyphs (rendered on a centered + // vertical baseline). The adjustment here is somewhat ad hoc; we + // should eventually look for baseline tables[1] in the fonts and use + // those if available. + // [1] See http://www.microsoft.com/typography/otspec/base.htm + if (aTextRun->UseCenterBaseline()) { + const Metrics& metrics = GetMetrics(nsFontMetrics::eHorizontal); + float baseAdj = (metrics.emAscent - metrics.emDescent) / 2; + baseline += baseAdj * aTextRun->GetAppUnitsPerDevUnit() * baselineDir; + } + } else if (textDrawer && + aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT) { + glyphFlagsRestore.Save(textDrawer); + textDrawer->SetWRGlyphFlags(textDrawer->GetWRGlyphFlags() | + wr::FontInstanceFlags::VERTICAL); + } + + if (fontParams.obliqueSkew != 0.0f && !fontParams.isVerticalFont && + !textDrawer) { + // Adjust matrix for synthetic-oblique, except if we're doing vertical- + // upright text, in which case this will be handled for each glyph + // individually in DrawOneGlyph. + if (!matrixRestore.HasMatrix()) { + matrixRestore.SetContext(aRunParams.context); + } + gfx::Point p(aPt->x * aRunParams.devPerApp, aPt->y * aRunParams.devPerApp); + gfx::Matrix mat = + aRunParams.context->CurrentMatrix() + .PreTranslate(p) + .PreMultiply(gfx::Matrix(1, 0, -fontParams.obliqueSkew, 1, 0, 0)) + .PreTranslate(-p); + aRunParams.context->SetMatrix(mat); + } + + RefPtr<SVGContextPaint> contextPaint; + if (fontParams.haveSVGGlyphs && !fontParams.contextPaint) { + // If no pattern is specified for fill, use the current pattern + NS_ASSERTION((int(aRunParams.drawMode) & int(DrawMode::GLYPH_STROKE)) == 0, + "no pattern supplied for stroking text"); + RefPtr<gfxPattern> fillPattern = aRunParams.context->GetPattern(); + contextPaint = new SimpleTextContextPaint( + fillPattern, nullptr, aRunParams.context->CurrentMatrixDouble()); + fontParams.contextPaint = contextPaint.get(); + } + + // Synthetic-bold strikes are each offset one device pixel in run direction. + // (these values are only needed if IsSyntheticBold() is true) + // WebRender handles synthetic bold independently via FontInstanceFlags, + // so just ignore requests in that case. + if (IsSyntheticBold() && !textDrawer) { + gfx::Float xscale = CalcXScale(aRunParams.context->GetDrawTarget()); + fontParams.synBoldOnePixelOffset = aRunParams.direction * xscale; + if (xscale != 0.0) { + static const int32_t kMaxExtraStrikes = 128; + gfxFloat extraStrikes = GetSyntheticBoldOffset() / xscale; + if (extraStrikes > kMaxExtraStrikes) { + // if too many strikes are required, limit them and increase the step + // size to compensate + fontParams.extraStrikes = kMaxExtraStrikes; + fontParams.synBoldOnePixelOffset = aRunParams.direction * + GetSyntheticBoldOffset() / + fontParams.extraStrikes; + } else { + // use as many strikes as needed for the increased advance + fontParams.extraStrikes = NS_lroundf(std::max(1.0, extraStrikes)); + } + } + } else { + fontParams.synBoldOnePixelOffset = 0; + fontParams.extraStrikes = 0; + } + + bool oldSubpixelAA = aRunParams.dt->GetPermitSubpixelAA(); + if (!AllowSubpixelAA()) { + aRunParams.dt->SetPermitSubpixelAA(false); + } + + Matrix mat; + Matrix oldMat = aRunParams.dt->GetTransform(); + + fontParams.drawOptions.mAntialiasMode = Get2DAAMode(mAntialiasOption); + + if (mStyle.baselineOffset != 0.0) { + baseline += + mStyle.baselineOffset * aTextRun->GetAppUnitsPerDevUnit() * baselineDir; + } + + bool emittedGlyphs; + { + // Select appropriate version of the templated DrawGlyphs method + // to output glyphs to the buffer, depending on complexity needed + // for the type of font, and whether added inter-glyph spacing + // is specified. + GlyphBufferAzure buffer(aRunParams, fontParams); + if (fontParams.haveSVGGlyphs || fontParams.haveColorGlyphs || + fontParams.extraStrikes || + (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont && + !textDrawer)) { + if (aRunParams.spacing) { + emittedGlyphs = + DrawGlyphs<FontComplexityT::ComplexFont, SpacingT::HasSpacing>( + aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer); + } else { + emittedGlyphs = + DrawGlyphs<FontComplexityT::ComplexFont, SpacingT::NoSpacing>( + aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer); + } + } else { + if (aRunParams.spacing) { + emittedGlyphs = + DrawGlyphs<FontComplexityT::SimpleFont, SpacingT::HasSpacing>( + aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer); + } else { + emittedGlyphs = + DrawGlyphs<FontComplexityT::SimpleFont, SpacingT::NoSpacing>( + aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer); + } + } + } + + baseline = origBaseline; + + if (aRunParams.callbacks && emittedGlyphs) { + aRunParams.callbacks->NotifyGlyphPathEmitted(); + } + + aRunParams.dt->SetTransform(oldMat); + aRunParams.dt->SetPermitSubpixelAA(oldSubpixelAA); + + if (sidewaysDir != 0.0f && !textDrawer) { + // Adjust updated aPt to account for the transform we were using. + // The advance happened horizontally in local-space, but the transformed + // sideways advance is actually vertical, with sign depending on the + // direction of rotation. + float advance = aPt->x - origPt.x; + *aPt = gfx::Point(origPt.x, origPt.y + advance * sidewaysDir); + } +} + +bool gfxFont::RenderSVGGlyph(gfxContext* aContext, + layout::TextDrawTarget* aTextDrawer, + gfx::Point aPoint, uint32_t aGlyphId, + SVGContextPaint* aContextPaint) const { + if (!GetFontEntry()->HasSVGGlyph(aGlyphId)) { + return false; + } + + if (aTextDrawer) { + // WebRender doesn't support SVG Glyphs. + // (pretend to succeed, output doesn't matter, we will emit a blob) + aTextDrawer->FoundUnsupportedFeature(); + return true; + } + + const gfxFloat devUnitsPerSVGUnit = + GetAdjustedSize() / GetFontEntry()->UnitsPerEm(); + gfxContextMatrixAutoSaveRestore matrixRestore(aContext); + + aContext->SetMatrix(aContext->CurrentMatrix() + .PreTranslate(aPoint.x, aPoint.y) + .PreScale(devUnitsPerSVGUnit, devUnitsPerSVGUnit)); + + aContextPaint->InitStrokeGeometry(aContext, devUnitsPerSVGUnit); + + GetFontEntry()->RenderSVGGlyph(aContext, aGlyphId, aContextPaint); + aContext->NewPath(); + return true; +} + +bool gfxFont::RenderSVGGlyph(gfxContext* aContext, + layout::TextDrawTarget* aTextDrawer, + gfx::Point aPoint, uint32_t aGlyphId, + SVGContextPaint* aContextPaint, + gfxTextRunDrawCallbacks* aCallbacks, + bool& aEmittedGlyphs) const { + if (aCallbacks && aEmittedGlyphs) { + aCallbacks->NotifyGlyphPathEmitted(); + aEmittedGlyphs = false; + } + return RenderSVGGlyph(aContext, aTextDrawer, aPoint, aGlyphId, aContextPaint); +} + +bool gfxFont::RenderColorGlyph(DrawTarget* aDrawTarget, gfxContext* aContext, + layout::TextDrawTarget* aTextDrawer, + mozilla::gfx::ScaledFont* scaledFont, + mozilla::gfx::DrawOptions aDrawOptions, + const mozilla::gfx::Point& aPoint, + uint32_t aGlyphId) const { + AutoTArray<uint16_t, 8> layerGlyphs; + AutoTArray<mozilla::gfx::DeviceColor, 8> layerColors; + + mozilla::gfx::DeviceColor defaultColor; + if (!aContext->GetDeviceColor(defaultColor)) { + defaultColor = ToDeviceColor(mozilla::gfx::sRGBColor::OpaqueBlack()); + } + if (!GetFontEntry()->GetColorLayersInfo(aGlyphId, defaultColor, layerGlyphs, + layerColors)) { + return false; + } + + // Default to opaque rendering (non-webrender applies alpha with a layer) + float alpha = 1.0; + if (aTextDrawer) { + // defaultColor is the one that comes from CSS, so it has transparency info. + bool hasComplexTransparency = 0.f < defaultColor.a && defaultColor.a < 1.f; + if (hasComplexTransparency && layerGlyphs.Length() > 1) { + // WebRender doesn't support drawing multi-layer transparent color-glyphs, + // as it requires compositing all the layers before applying transparency. + // (pretend to succeed, output doesn't matter, we will emit a blob) + aTextDrawer->FoundUnsupportedFeature(); + return true; + } + + // If we get here, then either alpha is 0 or 1, or there's only one layer + // which shouldn't have composition issues. In all of these cases, applying + // transparency directly to the glyph should work perfectly fine. + // + // Note that we must still emit completely transparent emoji, because they + // might be wrapped in a shadow that uses the text run's glyphs. + alpha = defaultColor.a; + } + + 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; + + mozilla::gfx::DeviceColor layerColor = layerColors[layerIndex]; + layerColor.a *= alpha; + aDrawTarget->FillGlyphs(scaledFont, buffer, ColorPattern(layerColor), + aDrawOptions); + } + return true; +} + +bool gfxFont::HasColorGlyphFor(uint32_t aCh, uint32_t aNextCh) { + // Bitmap fonts are assumed to provide "color" glyphs for all supported chars. + gfxFontEntry* fe = GetFontEntry(); + if (fe->HasColorBitmapTable()) { + return true; + } + // Use harfbuzz shaper to look up the default glyph ID for the character. + if (!mHarfBuzzShaper) { + mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this); + } + auto* shaper = static_cast<gfxHarfBuzzShaper*>(mHarfBuzzShaper.get()); + if (!shaper->Initialize()) { + return false; + } + uint32_t gid = 0; + if (gfxFontUtils::IsVarSelector(aNextCh)) { + gid = shaper->GetVariationGlyph(aCh, aNextCh); + } + if (!gid) { + gid = shaper->GetNominalGlyph(aCh); + } + if (!gid) { + return false; + } + // Check if there is a COLR/CPAL or SVG glyph for this ID. + if (fe->TryGetColorGlyphs() && fe->HasColorLayersForGlyph(gid)) { + return true; + } + if (fe->TryGetSVGData(this) && fe->HasSVGGlyph(gid)) { + return true; + } + return false; +} + +static void UnionRange(gfxFloat aX, gfxFloat* aDestMin, gfxFloat* aDestMax) { + *aDestMin = std::min(*aDestMin, aX); + *aDestMax = std::max(*aDestMax, aX); +} + +// We get precise glyph extents if the textrun creator requested them, or +// if the font is a user font --- in which case the author may be relying +// on overflowing glyphs. +static bool NeedsGlyphExtents(gfxFont* aFont, const gfxTextRun* aTextRun) { + return (aTextRun->GetFlags() & + gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX) || + aFont->GetFontEntry()->IsUserFont(); +} + +bool gfxFont::IsSpaceGlyphInvisible(DrawTarget* aRefDrawTarget, + const gfxTextRun* aTextRun) { + 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, + gfx::ShapedTextFlags aOrientation) { + // If aBoundingBoxType is TIGHT_HINTED_OUTLINE_EXTENTS + // and the underlying cairo font may be antialiased, + // we need to create a copy in order to avoid getting cached extents. + // This is only used by MathML layout at present. + if (aBoundingBoxType == TIGHT_HINTED_OUTLINE_EXTENTS && + mAntialiasOption != kAntialiasNone) { + if (!mNonAAFont) { + mNonAAFont = 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 + Orientation orientation = + aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT + ? nsFontMetrics::eVertical + : nsFontMetrics::eHorizontal; + const gfxFont::Metrics& fontMetrics = GetMetrics(orientation); + + gfxFloat baselineOffset = 0; + if (aTextRun->UseCenterBaseline() && + orientation == nsFontMetrics::eHorizontal) { + // For a horizontal font being used in vertical writing mode with + // text-orientation:mixed, the overall metrics we're accumulating + // will be aimed at a center baseline. But this font's metrics were + // based on the alphabetic baseline. So we compute a baseline offset + // that will be applied to ascent/descent values and glyph rects + // to effectively shift them relative to the baseline. + // XXX Eventually we should probably use the BASE table, if present. + // But it usually isn't, so we need an ad hoc adjustment for now. + baselineOffset = + appUnitsPerDevUnit * (fontMetrics.emAscent - fontMetrics.emDescent) / 2; + } + + RunMetrics metrics; + metrics.mAscent = fontMetrics.maxAscent * appUnitsPerDevUnit; + metrics.mDescent = fontMetrics.maxDescent * appUnitsPerDevUnit; + + if (aStart == aEnd) { + // exit now before we look at aSpacing[0], which is undefined + metrics.mAscent -= baselineOffset; + metrics.mDescent += baselineOffset; + metrics.mBoundingBox = + gfxRect(0, -metrics.mAscent, 0, metrics.mAscent + metrics.mDescent); + return metrics; + } + + gfxFloat advanceMin = 0, advanceMax = 0; + const gfxTextRun::CompressedGlyph* charGlyphs = + aTextRun->GetCharacterGlyphs(); + bool isRTL = aTextRun->IsRightToLeft(); + 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 += 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 + extentsWidth, &advanceMin, &advanceMax); + } else { + gfxRect glyphRect; + if (!extents->GetTightGlyphExtentsAppUnits(this, aRefDrawTarget, + glyphIndex, &glyphRect)) { + glyphRect = gfxRect(0, metrics.mBoundingBox.Y(), advance, + metrics.mBoundingBox.Height()); + } + if (isRTL) { + // In effect, swap left and right sidebearings of the glyph, for + // proper accumulation of potentially-overlapping glyph rects. + glyphRect.MoveToX(advance - glyphRect.XMost()); + } + glyphRect.MoveByX(x); + metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect); + } + } + x += advance; + } else { + allGlyphsInvisible = false; + uint32_t glyphCount = glyphData->GetGlyphCount(); + if (glyphCount > 0) { + const gfxTextRun::DetailedGlyph* details = + aTextRun->GetDetailedGlyphs(i); + NS_ASSERTION(details != nullptr, + "detailedGlyph record should not be missing!"); + uint32_t j; + for (j = 0; j < glyphCount; ++j, ++details) { + uint32_t glyphIndex = details->mGlyphID; + double advance = details->mAdvance; + gfxRect glyphRect; + if (glyphData->IsMissing() || !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 (isRTL) { + // Swap left/right sidebearings of the glyph, because we're doing + // mirrored measurement. + glyphRect.MoveToX(advance - glyphRect.XMost()); + // Move to current x position, mirroring any x-offset amount. + glyphRect.MoveByX(x - details->mOffset.x); + } else { + glyphRect.MoveByX(x + details->mOffset.x); + } + glyphRect.MoveByY(details->mOffset.y); + metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect); + x += 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 += 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) { + // Reverse the effect of having swapped each glyph's sidebearings, to get + // the correct sidebearings of the merged bounding box. + metrics.mBoundingBox.MoveToX(x - metrics.mBoundingBox.XMost()); + } + + // If the font may be rendered with a fake-italic effect, we need to allow + // for the top-right of the glyphs being skewed to the right, and the + // bottom-left being skewed further left. + gfxFloat skew = SkewForSyntheticOblique(); + if (skew != 0.0) { + gfxFloat extendLeftEdge, extendRightEdge; + if (orientation == nsFontMetrics::eVertical) { + // The glyph will actually be skewed vertically, but "left" and "right" + // here refer to line-left (physical top) and -right (bottom), so these + // are still the directions in which we need to extend the box. + extendLeftEdge = skew < 0.0 ? ceil(-skew * metrics.mBoundingBox.XMost()) + : ceil(skew * -metrics.mBoundingBox.X()); + extendRightEdge = skew < 0.0 ? ceil(-skew * -metrics.mBoundingBox.X()) + : ceil(skew * metrics.mBoundingBox.XMost()); + } else { + extendLeftEdge = skew < 0.0 ? ceil(-skew * -metrics.mBoundingBox.Y()) + : ceil(skew * metrics.mBoundingBox.YMost()); + extendRightEdge = skew < 0.0 ? ceil(-skew * metrics.mBoundingBox.YMost()) + : ceil(skew * -metrics.mBoundingBox.Y()); + } + metrics.mBoundingBox.SetWidth(metrics.mBoundingBox.Width() + + extendLeftEdge + extendRightEdge); + metrics.mBoundingBox.MoveByX(-extendLeftEdge); + } + + if (baselineOffset != 0) { + metrics.mAscent -= baselineOffset; + metrics.mDescent += baselineOffset; + metrics.mBoundingBox.MoveByY(baselineOffset); + } + + metrics.mAdvanceWidth = x; + + return metrics; +} + +void gfxFont::AgeCachedWords() { + if (mWordCache) { + for (auto it = mWordCache->Iter(); !it.Done(); it.Next()) { + CacheHashEntry* entry = it.Get(); + if (!entry->mShapedWord) { + NS_ASSERTION(entry->mShapedWord, "cache entry has no gfxShapedWord!"); + it.Remove(); + } else if (entry->mShapedWord->IncrementAge() == kShapedWordCacheMaxAge) { + it.Remove(); + } + } + } +} + +void gfxFont::NotifyGlyphsChanged() { + uint32_t i, count = mGlyphExtentsArray.Length(); + for (i = 0; i < count; ++i) { + // Flush cached extents array + mGlyphExtentsArray[i]->NotifyGlyphsChanged(); + } + + if (mGlyphChangeObservers) { + for (auto it = mGlyphChangeObservers->Iter(); !it.Done(); it.Next()) { + it.Get()->GetKey()->NotifyGlyphsChanged(); + } + } +} + +// If aChar is a "word boundary" for shaped-word caching purposes, return it; +// else return 0. +static char16_t IsBoundarySpace(char16_t aChar, char16_t aNextChar) { + if ((aChar == ' ' || aChar == 0x00A0) && !IsClusterExtender(aNextChar)) { + return aChar; + } + return 0; +} + +#ifdef __GNUC__ +# define GFX_MAYBE_UNUSED __attribute__((unused)) +#else +# define GFX_MAYBE_UNUSED +#endif + +template <typename T> +gfxShapedWord* gfxFont::GetShapedWord( + DrawTarget* aDrawTarget, const T* aText, uint32_t aLength, uint32_t aHash, + Script aRunScript, nsAtom* aLanguage, bool aVertical, + int32_t aAppUnitsPerDevUnit, gfx::ShapedTextFlags aFlags, + RoundingFlags aRounding, 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, aLanguage, + aAppUnitsPerDevUnit, aFlags, aRounding); + + CacheHashEntry* entry = mWordCache->PutEntry(key, fallible); + if (!entry) { + NS_WARNING("failed to create word cache entry - expect missing text"); + return nullptr; + } + gfxShapedWord* sw = entry->mShapedWord.get(); + + if (sw) { + sw->ResetAge(); +#ifndef RELEASE_OR_BETA + if (aTextPerf) { + aTextPerf->current.wordCacheHit++; + } +#endif + return sw; + } + +#ifndef RELEASE_OR_BETA + if (aTextPerf) { + aTextPerf->current.wordCacheMiss++; + } +#endif + + sw = gfxShapedWord::Create(aText, aLength, aRunScript, aLanguage, + aAppUnitsPerDevUnit, aFlags, aRounding); + entry->mShapedWord.reset(sw); + if (!sw) { + NS_WARNING("failed to create gfxShapedWord - expect missing text"); + return nullptr; + } + + DebugOnly<bool> ok = ShapeText(aDrawTarget, aText, 0, aLength, aRunScript, + aLanguage, aVertical, aRounding, sw); + + NS_WARNING_ASSERTION(ok, "failed to shape word - expect garbled text"); + + return sw; +} + +template gfxShapedWord* gfxFont::GetShapedWord( + DrawTarget* aDrawTarget, const uint8_t* aText, uint32_t aLength, + uint32_t aHash, Script aRunScript, nsAtom* aLanguage, bool aVertical, + int32_t aAppUnitsPerDevUnit, gfx::ShapedTextFlags aFlags, + RoundingFlags aRounding, gfxTextPerfMetrics* aTextPerf); + +bool gfxFont::CacheHashEntry::KeyEquals(const KeyTypePointer aKey) const { + const gfxShapedWord* sw = mShapedWord.get(); + if (!sw) { + return false; + } + if (sw->GetLength() != aKey->mLength || sw->GetFlags() != aKey->mFlags || + sw->GetRounding() != aKey->mRounding || + sw->GetAppUnitsPerDevUnit() != aKey->mAppUnitsPerDevUnit || + sw->GetScript() != aKey->mScript || + sw->GetLanguage() != aKey->mLanguage) { + return false; + } + if (sw->TextIs8Bit()) { + if (aKey->mTextIs8Bit) { + return (0 == memcmp(sw->Text8Bit(), aKey->mText.mSingle, + aKey->mLength * sizeof(uint8_t))); + } + // The key has 16-bit text, even though all the characters are < 256, + // so the TEXT_IS_8BIT flag was set and the cached ShapedWord we're + // comparing with will have 8-bit text. + const uint8_t* s1 = sw->Text8Bit(); + const char16_t* s2 = aKey->mText.mDouble; + const char16_t* s2end = s2 + aKey->mLength; + while (s2 < s2end) { + if (*s1++ != *s2++) { + return false; + } + } + return true; + } + NS_ASSERTION(!(aKey->mFlags & gfx::ShapedTextFlags::TEXT_IS_8BIT) && + !aKey->mTextIs8Bit, + "didn't expect 8-bit text here"); + return (0 == memcmp(sw->TextUnicode(), aKey->mText.mDouble, + aKey->mLength * sizeof(char16_t))); +} + +bool gfxFont::ShapeText(DrawTarget* aDrawTarget, const uint8_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, + RoundingFlags aRounding, gfxShapedText* aShapedText) { + nsDependentCSubstring ascii((const char*)aText, aLength); + nsAutoString utf16; + AppendASCIItoUTF16(ascii, utf16); + if (utf16.Length() != aLength) { + return false; + } + return ShapeText(aDrawTarget, utf16.BeginReading(), aOffset, aLength, aScript, + aLanguage, aVertical, aRounding, aShapedText); +} + +bool gfxFont::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, + RoundingFlags aRounding, gfxShapedText* aShapedText) { + // XXX Currently, we do all vertical shaping through harfbuzz. + // Vertical graphite support may be wanted as a future enhancement. + if (FontCanSupportGraphite() && !aVertical) { + if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) { + if (!mGraphiteShaper) { + mGraphiteShaper = MakeUnique<gfxGraphiteShaper>(this); + Telemetry::ScalarAdd(Telemetry::ScalarID::BROWSER_USAGE_GRAPHITE, 1); + } + if (mGraphiteShaper->ShapeText(aDrawTarget, aText, aOffset, aLength, + aScript, aLanguage, aVertical, aRounding, + aShapedText)) { + PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical, + aShapedText); + return true; + } + } + } + + if (!mHarfBuzzShaper) { + mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this); + } + if (mHarfBuzzShaper->ShapeText(aDrawTarget, aText, aOffset, aLength, aScript, + aLanguage, aVertical, aRounding, + aShapedText)) { + PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical, + aShapedText); + if (GetFontEntry()->HasTrackingTable()) { + // Convert font size from device pixels back to CSS px + // to use in selecting tracking value + float trackSize = GetAdjustedSize() * + aShapedText->GetAppUnitsPerDevUnit() / + AppUnitsPerCSSPixel(); + float tracking = + GetFontEntry()->TrackingForCSSPx(trackSize) * mFUnitsConvFactor; + // Applying tracking is a lot like the adjustment we do for + // synthetic bold: we want to apply between clusters, not to + // non-spacing glyphs within a cluster. So we can reuse that + // helper here. + aShapedText->AdjustAdvancesForSyntheticBold(tracking, aOffset, aLength); + } + return true; + } + + NS_WARNING_ASSERTION(false, "shaper failed, expect scrambled/missing text"); + return false; +} + +void gfxFont::PostShapingFixup(DrawTarget* aDrawTarget, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, + bool aVertical, gfxShapedText* aShapedText) { + if (IsSyntheticBold()) { + const Metrics& metrics = GetMetrics(aVertical ? nsFontMetrics::eVertical + : nsFontMetrics::eHorizontal); + if (metrics.maxAdvance > metrics.aveCharWidth) { + float synBoldOffset = GetSyntheticBoldOffset() * CalcXScale(aDrawTarget); + aShapedText->AdjustAdvancesForSyntheticBold(synBoldOffset, aOffset, + aLength); + } + } +} + +#define MAX_SHAPING_LENGTH \ + 32760 // slightly less than 32K, trying to avoid + // over-stressing platform shapers +#define BACKTRACK_LIMIT \ + 16 // backtrack this far looking for a good place + // to split into fragments for separate shaping + +template <typename T> +bool gfxFont::ShapeFragmentWithoutWordCache(DrawTarget* aDrawTarget, + const T* aText, uint32_t aOffset, + uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, + RoundingFlags aRounding, + gfxTextRun* aTextRun) { + aTextRun->SetupClusterBoundaries(aOffset, aText, aLength); + + bool ok = true; + + while (ok && aLength > 0) { + uint32_t fragLen = aLength; + + // limit the length of text we pass to shapers in a single call + if (fragLen > MAX_SHAPING_LENGTH) { + fragLen = MAX_SHAPING_LENGTH; + + // in the 8-bit case, there are no multi-char clusters, + // so we don't need to do this check + if (sizeof(T) == sizeof(char16_t)) { + uint32_t i; + for (i = 0; i < BACKTRACK_LIMIT; ++i) { + if (aTextRun->IsClusterStart(aOffset + fragLen - i)) { + fragLen -= i; + break; + } + } + if (i == BACKTRACK_LIMIT) { + // if we didn't find any cluster start while backtracking, + // just check that we're not in the middle of a surrogate + // pair; back up by one code unit if we are. + if (NS_IS_SURROGATE_PAIR(aText[fragLen - 1], aText[fragLen])) { + --fragLen; + } + } + } + } + + ok = ShapeText(aDrawTarget, aText, aOffset, fragLen, aScript, aLanguage, + aVertical, aRounding, aTextRun); + + aText += fragLen; + aOffset += fragLen; + aLength -= fragLen; + } + + return ok; +} + +// Check if aCh is an unhandled control character that should be displayed +// as a hexbox rather than rendered by some random font on the system. +// We exclude \r as stray s are rather common (bug 941940). +// Note that \n and \t don't come through here, as they have specific +// meanings that have already been handled. +static bool IsInvalidControlChar(uint32_t aCh) { + return aCh != '\r' && ((aCh & 0x7f) < 0x20 || aCh == 0x7f); +} + +template <typename T> +bool gfxFont::ShapeTextWithoutWordCache(DrawTarget* aDrawTarget, const T* aText, + uint32_t aOffset, uint32_t aLength, + Script aScript, nsAtom* aLanguage, + bool aVertical, RoundingFlags aRounding, + gfxTextRun* aTextRun) { + uint32_t fragStart = 0; + bool ok = true; + + for (uint32_t i = 0; i <= aLength && ok; ++i) { + T ch = (i < aLength) ? aText[i] : '\n'; + bool invalid = gfxFontGroup::IsInvalidChar(ch); + uint32_t length = i - fragStart; + + // break into separate fragments when we hit an invalid char + if (!invalid) { + continue; + } + + if (length > 0) { + ok = ShapeFragmentWithoutWordCache( + aDrawTarget, aText + fragStart, aOffset + fragStart, length, aScript, + aLanguage, aVertical, aRounding, aTextRun); + } + + if (i == aLength) { + break; + } + + // fragment was terminated by an invalid char: skip it, + // unless it's a control char that we want to show as a hexbox, + // but record where TAB or NEWLINE occur + if (ch == '\t') { + aTextRun->SetIsTab(aOffset + i); + } else if (ch == '\n') { + aTextRun->SetIsNewline(aOffset + i); + } else if (GetGeneralCategory(ch) == HB_UNICODE_GENERAL_CATEGORY_FORMAT) { + aTextRun->SetIsFormattingControl(aOffset + i); + } else if (IsInvalidControlChar(ch) && + !(aTextRun->GetFlags() & + gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS)) { + if (GetFontEntry()->IsUserFont() && HasCharacter(ch)) { + ShapeFragmentWithoutWordCache(aDrawTarget, aText + i, aOffset + i, 1, + aScript, aLanguage, aVertical, aRounding, + aTextRun); + } else { + aTextRun->SetMissingGlyph(aOffset + i, ch, this); + } + } + fragStart = i + 1; + } + + NS_WARNING_ASSERTION(ok, "failed to shape text - expect garbled text"); + return ok; +} + +#ifndef RELEASE_OR_BETA +# define TEXT_PERF_INCR(tp, m) (tp ? (tp)->current.m++ : 0) +#else +# define TEXT_PERF_INCR(tp, m) +#endif + +inline static bool IsChar8Bit(uint8_t /*aCh*/) { return true; } +inline static bool IsChar8Bit(char16_t aCh) { return aCh < 0x100; } + +inline static bool HasSpaces(const uint8_t* aString, uint32_t aLen) { + return memchr(aString, 0x20, aLen) != nullptr; +} + +inline static bool HasSpaces(const char16_t* aString, uint32_t aLen) { + for (const char16_t* ch = aString; ch < aString + aLen; ch++) { + if (*ch == 0x20) { + return true; + } + } + return false; +} + +template <typename T> +bool gfxFont::SplitAndInitTextRun( + DrawTarget* aDrawTarget, gfxTextRun* aTextRun, + const T* aString, // text for this font run + uint32_t aRunStart, // position in the textrun + uint32_t aRunLength, Script aRunScript, nsAtom* aLanguage, + ShapedTextFlags aOrientation) { + if (aRunLength == 0) { + return true; + } + + gfxTextPerfMetrics* tp = nullptr; + RoundingFlags rounding = GetRoundOffsetsToPixels(aDrawTarget); + +#ifndef RELEASE_OR_BETA + tp = aTextRun->GetFontGroup()->GetTextPerfMetrics(); + if (tp) { + if (mStyle.systemFont) { + tp->current.numChromeTextRuns++; + } else { + tp->current.numContentTextRuns++; + } + tp->current.numChars += aRunLength; + if (aRunLength > tp->current.maxTextRunLen) { + tp->current.maxTextRunLen = aRunLength; + } + } +#endif + + uint32_t wordCacheCharLimit = + gfxPlatform::GetPlatform()->WordCacheCharLimit(); + + bool vertical = aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT; + + // If spaces can participate in shaping (e.g. within lookups for automatic + // fractions), need to shape without using the word cache which segments + // textruns on space boundaries. Word cache can be used if the textrun + // is short enough to fit in the word cache and it lacks spaces. + tainted_boolean_hint t_canParticipate = + SpaceMayParticipateInShaping(aRunScript); + bool canParticipate = t_canParticipate.unverified_safe_because( + "We need to ensure that this function operates safely independent of " + "t_canParticipate. The worst that can happen here is that the decision " + "to use the cache is incorrectly made, resulting in a bad " + "rendering/slowness. However, this would not compromise the memory " + "safety of Firefox in any way, and can thus be permitted"); + + if (canParticipate) { + if (aRunLength > wordCacheCharLimit || HasSpaces(aString, aRunLength)) { + TEXT_PERF_INCR(tp, wordCacheSpaceRules); + return ShapeTextWithoutWordCache(aDrawTarget, aString, aRunStart, + aRunLength, aRunScript, aLanguage, + vertical, rounding, aTextRun); + } + } + + InitWordCache(); + + // the only flags we care about for ShapedWord construction/caching + gfx::ShapedTextFlags flags = aTextRun->GetFlags(); + flags &= (gfx::ShapedTextFlags::TEXT_IS_RTL | + gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES | + gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT | + gfx::ShapedTextFlags::TEXT_ORIENT_MASK); + if (sizeof(T) == sizeof(uint8_t)) { + flags |= gfx::ShapedTextFlags::TEXT_IS_8BIT; + } + + uint32_t wordStart = 0; + uint32_t hash = 0; + bool wordIs8Bit = true; + int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit(); + + T nextCh = aString[0]; + for (uint32_t i = 0; i <= aRunLength; ++i) { + T ch = nextCh; + nextCh = (i < aRunLength - 1) ? aString[i + 1] : '\n'; + T boundary = IsBoundarySpace(ch, nextCh); + bool invalid = !boundary && gfxFontGroup::IsInvalidChar(ch); + uint32_t length = i - wordStart; + + // break into separate ShapedWords when we hit an invalid char, + // or a boundary space (always handled individually), + // or the first non-space after a space + if (!boundary && !invalid) { + if (!IsChar8Bit(ch)) { + wordIs8Bit = false; + } + // include this character in the hash, and move on to next + hash = gfxShapedWord::HashMix(hash, ch); + continue; + } + + // We've decided to break here (i.e. we're at the end of a "word"); + // shape the word and add it to the textrun. + // For words longer than the limit, we don't use the + // font's word cache but just shape directly into the textrun. + if (length > wordCacheCharLimit) { + TEXT_PERF_INCR(tp, wordCacheLong); + bool ok = ShapeFragmentWithoutWordCache( + aDrawTarget, aString + wordStart, aRunStart + wordStart, length, + aRunScript, aLanguage, vertical, rounding, aTextRun); + if (!ok) { + return false; + } + } else if (length > 0) { + gfx::ShapedTextFlags wordFlags = flags; + // in the 8-bit version of this method, TEXT_IS_8BIT was + // already set as part of |flags|, so no need for a per-word + // adjustment here + if (sizeof(T) == sizeof(char16_t)) { + if (wordIs8Bit) { + wordFlags |= gfx::ShapedTextFlags::TEXT_IS_8BIT; + } + } + gfxShapedWord* sw = GetShapedWord( + aDrawTarget, aString + wordStart, length, hash, aRunScript, aLanguage, + vertical, appUnitsPerDevUnit, wordFlags, rounding, 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 + MOZ_ASSERT(aOrientation != ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED, + "text-orientation:mixed should be resolved earlier"); + if (boundary != ' ' || !aTextRun->SetSpaceGlyphIfSimple( + this, aRunStart + i, ch, aOrientation)) { + // Currently, the only "boundary" characters we recognize are + // space and no-break space, which are both 8-bit, so we force + // that flag (below). If we ever change IsBoundarySpace, we + // may need to revise this. + // Avoid tautological-constant-out-of-range-compare in 8-bit: + DebugOnly<char16_t> boundary16 = boundary; + NS_ASSERTION(boundary16 < 256, "unexpected boundary!"); + gfxShapedWord* sw = GetShapedWord( + aDrawTarget, &boundary, 1, gfxShapedWord::HashMix(0, boundary), + aRunScript, aLanguage, vertical, appUnitsPerDevUnit, + flags | gfx::ShapedTextFlags::TEXT_IS_8BIT, rounding, tp); + if (sw) { + aTextRun->CopyGlyphDataFrom(sw, aRunStart + i); + if (boundary == ' ') { + aTextRun->GetCharacterGlyphs()[aRunStart + i].SetIsSpace(); + } + } 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 (GetGeneralCategory(ch) == HB_UNICODE_GENERAL_CATEGORY_FORMAT) { + aTextRun->SetIsFormattingControl(aRunStart + i); + } else if (IsInvalidControlChar(ch) && + !(aTextRun->GetFlags() & + gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS)) { + if (GetFontEntry()->IsUserFont() && HasCharacter(ch)) { + ShapeFragmentWithoutWordCache(aDrawTarget, aString + i, aRunStart + i, + 1, aRunScript, aLanguage, vertical, + rounding, aTextRun); + } else { + aTextRun->SetMissingGlyph(aRunStart + i, ch, this); + } + } + + hash = 0; + wordStart = i + 1; + wordIs8Bit = true; + } + + return true; +} + +// Explicit instantiations of SplitAndInitTextRun, to avoid libxul link failure +template bool gfxFont::SplitAndInitTextRun( + DrawTarget* aDrawTarget, gfxTextRun* aTextRun, const uint8_t* aString, + uint32_t aRunStart, uint32_t aRunLength, Script aRunScript, + nsAtom* aLanguage, ShapedTextFlags aOrientation); +template bool gfxFont::SplitAndInitTextRun( + DrawTarget* aDrawTarget, gfxTextRun* aTextRun, const char16_t* aString, + uint32_t aRunStart, uint32_t aRunLength, Script aRunScript, + nsAtom* aLanguage, ShapedTextFlags aOrientation); + +template <> +bool gfxFont::InitFakeSmallCapsRun(DrawTarget* aDrawTarget, + gfxTextRun* aTextRun, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, + FontMatchType aMatchType, + gfx::ShapedTextFlags aOrientation, + Script aScript, nsAtom* aLanguage, + bool aSyntheticLower, bool aSyntheticUpper) { + bool ok = true; + + RefPtr<gfxFont> smallCapsFont = GetSmallCapsFont(); + if (!smallCapsFont) { + NS_WARNING("failed to get reduced-size font for smallcaps!"); + smallCapsFont = this; + } + + bool isCJK = gfxTextRun::IsCJKScript(aScript); + + enum RunCaseAction { kNoChange, kUppercaseReduce, kUppercase }; + + RunCaseAction runAction = kNoChange; + uint32_t runStart = 0; + + for (uint32_t i = 0; i <= aLength; ++i) { + uint32_t extraCodeUnits = 0; // Will be set to 1 if we need to consume + // a trailing surrogate as well as the + // current code unit. + RunCaseAction chAction = kNoChange; + // Unless we're at the end, figure out what treatment the current + // character will need. + if (i < aLength) { + uint32_t ch = aText[i]; + if (i < aLength - 1 && NS_IS_SURROGATE_PAIR(ch, aText[i + 1])) { + ch = SURROGATE_TO_UCS4(ch, aText[i + 1]); + extraCodeUnits = 1; + } + // Characters that aren't the start of a cluster are ignored here. + // They get added to whatever lowercase/non-lowercase run we're in. + if (IsClusterExtender(ch)) { + chAction = runAction; + } else { + if (ch != ToUpperCase(ch) || SpecialUpper(ch)) { + // ch is lower case + chAction = (aSyntheticLower ? kUppercaseReduce : kNoChange); + } else if (ch != ToLowerCase(ch)) { + // ch is upper case + chAction = (aSyntheticUpper ? kUppercaseReduce : kNoChange); + if (aLanguage == nsGkAtoms::el) { + // In Greek, check for characters that will be modified by + // the GreekUpperCase mapping - this catches accented + // capitals where the accent is to be removed (bug 307039). + // These are handled by using the full-size font with the + // uppercasing transform. + mozilla::GreekCasing::State state; + bool markEta, updateEta; + uint32_t ch2 = + mozilla::GreekCasing::UpperCase(ch, state, markEta, updateEta); + if ((ch != ch2 || markEta) && !aSyntheticUpper) { + chAction = kUppercase; + } + } + } + } + } + + // At the end of the text or when the current character needs different + // casing treatment from the current run, finish the run-in-progress + // and prepare to accumulate a new run. + // Note that we do not look at any source data for offset [i] here, + // as that would be invalid in the case where i==length. + if ((i == aLength || runAction != chAction) && runStart < i) { + uint32_t runLength = i - runStart; + gfxFont* f = this; + switch (runAction) { + case kNoChange: + // just use the current font and the existing string + aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart, true, + aOrientation, isCJK); + if (!f->SplitAndInitTextRun(aDrawTarget, aTextRun, aText + runStart, + aOffset + runStart, runLength, aScript, + aLanguage, aOrientation)) { + ok = false; + } + break; + + case kUppercaseReduce: + // use reduced-size font, then fall through to uppercase the text + f = smallCapsFont; + [[fallthrough]]; + + case kUppercase: + // apply uppercase transform to the string + nsDependentSubstring origString(aText + runStart, runLength); + nsAutoString convertedString; + AutoTArray<bool, 50> charsToMergeArray; + AutoTArray<bool, 50> deletedCharsArray; + + bool mergeNeeded = nsCaseTransformTextRunFactory::TransformString( + origString, convertedString, /* aAllUppercase = */ true, + /* aCaseTransformsOnly = */ false, aLanguage, charsToMergeArray, + deletedCharsArray); + + if (mergeNeeded) { + // This is the hard case: the transformation caused chars + // to be inserted or deleted, so we can't shape directly + // into the destination textrun but have to handle the + // mismatch of character positions. + gfxTextRunFactory::Parameters params = { + aDrawTarget, nullptr, nullptr, + nullptr, 0, aTextRun->GetAppUnitsPerDevUnit()}; + RefPtr<gfxTextRun> tempRun(gfxTextRun::Create( + ¶ms, convertedString.Length(), aTextRun->GetFontGroup(), + gfx::ShapedTextFlags(), nsTextFrameUtils::Flags())); + tempRun->AddGlyphRun(f, aMatchType, 0, true, aOrientation, isCJK); + if (!f->SplitAndInitTextRun(aDrawTarget, tempRun.get(), + convertedString.BeginReading(), 0, + convertedString.Length(), aScript, + aLanguage, aOrientation)) { + ok = false; + } else { + RefPtr<gfxTextRun> mergedRun(gfxTextRun::Create( + ¶ms, runLength, aTextRun->GetFontGroup(), + gfx::ShapedTextFlags(), nsTextFrameUtils::Flags())); + MergeCharactersInTextRun(mergedRun.get(), tempRun.get(), + charsToMergeArray.Elements(), + deletedCharsArray.Elements()); + gfxTextRun::Range runRange(0, runLength); + aTextRun->CopyGlyphDataFrom(mergedRun.get(), runRange, + aOffset + runStart); + } + } else { + aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart, true, + aOrientation, isCJK); + if (!f->SplitAndInitTextRun(aDrawTarget, aTextRun, + convertedString.BeginReading(), + aOffset + runStart, runLength, aScript, + aLanguage, aOrientation)) { + ok = false; + } + } + break; + } + + runStart = i; + } + + i += extraCodeUnits; + if (i < aLength) { + runAction = chAction; + } + } + + return ok; +} + +template <> +bool gfxFont::InitFakeSmallCapsRun(DrawTarget* aDrawTarget, + gfxTextRun* aTextRun, const uint8_t* aText, + uint32_t aOffset, uint32_t aLength, + FontMatchType aMatchType, + gfx::ShapedTextFlags aOrientation, + Script aScript, nsAtom* aLanguage, + bool aSyntheticLower, bool aSyntheticUpper) { + NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aText), + aLength); + return InitFakeSmallCapsRun( + aDrawTarget, aTextRun, static_cast<const char16_t*>(unicodeString.get()), + aOffset, aLength, aMatchType, aOrientation, aScript, aLanguage, + aSyntheticLower, aSyntheticUpper); +} + +gfxFont* gfxFont::GetSmallCapsFont() { + gfxFontStyle style(*GetStyle()); + style.size *= SMALL_CAPS_SCALE_FACTOR; + style.variantCaps = NS_FONT_VARIANT_CAPS_NORMAL; + gfxFontEntry* fe = GetFontEntry(); + return fe->FindOrMakeFont(&style, mUnicodeRangeMap); +} + +gfxFont* gfxFont::GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel) { + gfxFontStyle style(*GetStyle()); + style.AdjustForSubSuperscript(aAppUnitsPerDevPixel); + gfxFontEntry* fe = GetFontEntry(); + return fe->FindOrMakeFont(&style, mUnicodeRangeMap); +} + +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, GetAdjustedSize(), + &svgBounds)) { + gfxFloat d2a = aExtents->GetAppUnitsPerDevUnit(); + aExtents->SetTightGlyphExtents( + aGlyphID, gfxRect(svgBounds.X() * d2a, svgBounds.Y() * d2a, + svgBounds.Width() * d2a, svgBounds.Height() * d2a)); + return; + } + + gfxRect bounds; + GetGlyphBounds(aGlyphID, &bounds, mAntialiasOption == kAntialiasNone); + + const Metrics& fontMetrics = GetMetrics(nsFontMetrics::eHorizontal); + int32_t appUnitsPerDevUnit = aExtents->GetAppUnitsPerDevUnit(); + if (!aNeedTight && bounds.x >= 0.0 && bounds.y >= -fontMetrics.maxAscent && + bounds.height + bounds.y <= fontMetrics.maxDescent) { + uint32_t appUnitsWidth = + uint32_t(ceil((bounds.x + bounds.width) * appUnitsPerDevUnit)); + if (appUnitsWidth < gfxGlyphExtents::INVALID_WIDTH) { + aExtents->SetContainedGlyphWidthAppUnits(aGlyphID, + uint16_t(appUnitsWidth)); + return; + } + } +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + if (!aNeedTight) { + ++gGlyphExtentsSetupFallBackToTight; + } +#endif + + gfxFloat d2a = appUnitsPerDevUnit; + aExtents->SetTightGlyphExtents( + aGlyphID, gfxRect(bounds.x * d2a, bounds.y * d2a, bounds.width * d2a, + bounds.height * d2a)); +} + +// Try to initialize font metrics by reading sfnt tables directly; +// set mIsValid=TRUE and return TRUE on success. +// Return FALSE if the gfxFontEntry subclass does not +// implement GetFontTable(), or for non-sfnt fonts where tables are +// not available. +// If this returns TRUE without setting the mIsValid flag, then we -did- +// apparently find an sfnt, but it was too broken to be used. +bool gfxFont::InitMetricsFromSfntTables(Metrics& aMetrics) { + mIsValid = false; // font is NOT valid in case of early return + + const uint32_t kHheaTableTag = TRUETYPE_TAG('h', 'h', 'e', 'a'); + const uint32_t kOS_2TableTag = TRUETYPE_TAG('O', 'S', '/', '2'); + + uint32_t len; + + if (mFUnitsConvFactor < 0.0) { + // If the conversion factor from FUnits is not yet set, + // get the unitsPerEm from the 'head' table via the font entry + uint16_t unitsPerEm = GetFontEntry()->UnitsPerEm(); + if (unitsPerEm == gfxFontEntry::kInvalidUPEM) { + return false; + } + mFUnitsConvFactor = GetAdjustedSize() / unitsPerEm; + } + + // 'hhea' table is required for the advanceWidthMax field + gfxFontEntry::AutoTable hheaTable(mFontEntry, kHheaTableTag); + if (!hheaTable) { + return false; // no 'hhea' table -> not an sfnt + } + const MetricsHeader* hhea = + reinterpret_cast<const MetricsHeader*>(hb_blob_get_data(hheaTable, &len)); + if (len < sizeof(MetricsHeader)) { + return false; + } + +#define SET_UNSIGNED(field, src) \ + aMetrics.field = uint16_t(src) * mFUnitsConvFactor +#define SET_SIGNED(field, src) aMetrics.field = int16_t(src) * mFUnitsConvFactor + + SET_UNSIGNED(maxAdvance, hhea->advanceWidthMax); + + // 'OS/2' table is optional, if not found we'll estimate xHeight + // and aveCharWidth by measuring glyphs + gfxFontEntry::AutoTable os2Table(mFontEntry, kOS_2TableTag); + if (os2Table) { + const OS2Table* os2 = + reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len)); + // this should always be present in any valid OS/2 of any version + if (len >= offsetof(OS2Table, xAvgCharWidth) + sizeof(int16_t)) { + SET_SIGNED(aveCharWidth, os2->xAvgCharWidth); + } + } + +#undef SET_SIGNED +#undef SET_UNSIGNED + + hb_font_t* hbFont = gfxHarfBuzzShaper::CreateHBFont(this); + hb_position_t position; + + auto FixedToFloat = [](hb_position_t f) -> gfxFloat { return f / 65536.0; }; + + if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_ASCENDER, + &position)) { + aMetrics.maxAscent = FixedToFloat(position); + } + if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_DESCENDER, + &position)) { + aMetrics.maxDescent = -FixedToFloat(position); + } + if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_LINE_GAP, + &position)) { + aMetrics.externalLeading = FixedToFloat(position); + } + + if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_UNDERLINE_OFFSET, + &position)) { + aMetrics.underlineOffset = FixedToFloat(position); + } + if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_UNDERLINE_SIZE, + &position)) { + aMetrics.underlineSize = FixedToFloat(position); + } + if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_STRIKEOUT_OFFSET, + &position)) { + aMetrics.strikeoutOffset = FixedToFloat(position); + } + if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_STRIKEOUT_SIZE, + &position)) { + aMetrics.strikeoutSize = FixedToFloat(position); + } + + // Although sxHeight and sCapHeight are signed fields, we consider + // zero/negative values to be erroneous and just ignore them. + if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_X_HEIGHT, + &position) && + position > 0) { + aMetrics.xHeight = FixedToFloat(position); + } + if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_CAP_HEIGHT, + &position) && + position > 0) { + aMetrics.capHeight = FixedToFloat(position); + } + hb_font_destroy(hbFont); + + mIsValid = true; + + return true; +} + +static double RoundToNearestMultiple(double aValue, double aFraction) { + return floor(aValue / aFraction + 0.5) * aFraction; +} + +void gfxFont::CalculateDerivedMetrics(Metrics& aMetrics) { + aMetrics.maxAscent = + ceil(RoundToNearestMultiple(aMetrics.maxAscent, 1 / 1024.0)); + aMetrics.maxDescent = + ceil(RoundToNearestMultiple(aMetrics.maxDescent, 1 / 1024.0)); + + if (aMetrics.xHeight <= 0) { + // only happens if we couldn't find either font metrics + // or a char to measure; + // pick an arbitrary value that's better than zero + aMetrics.xHeight = aMetrics.maxAscent * DEFAULT_XHEIGHT_FACTOR; + } + + // If we have a font that doesn't provide a capHeight value, use maxAscent + // as a reasonable fallback. + if (aMetrics.capHeight <= 0) { + aMetrics.capHeight = aMetrics.maxAscent; + } + + aMetrics.maxHeight = aMetrics.maxAscent + aMetrics.maxDescent; + + if (aMetrics.maxHeight - aMetrics.emHeight > 0.0) { + aMetrics.internalLeading = aMetrics.maxHeight - aMetrics.emHeight; + } else { + aMetrics.internalLeading = 0.0; + } + + aMetrics.emAscent = + aMetrics.maxAscent * aMetrics.emHeight / aMetrics.maxHeight; + aMetrics.emDescent = aMetrics.emHeight - aMetrics.emAscent; + + if (GetFontEntry()->IsFixedPitch()) { + // Some Quartz fonts are fixed pitch, but there's some glyph with a bigger + // advance than the average character width... this forces + // those fonts to be recognized like fixed pitch fonts by layout. + aMetrics.maxAdvance = aMetrics.aveCharWidth; + } + + if (!aMetrics.strikeoutOffset) { + aMetrics.strikeoutOffset = aMetrics.xHeight * 0.5; + } + if (!aMetrics.strikeoutSize) { + aMetrics.strikeoutSize = aMetrics.underlineSize; + } +} + +void gfxFont::SanitizeMetrics(gfxFont::Metrics* aMetrics, + bool aIsBadUnderlineFont) { + // Even if this font size is zero, this font is created with non-zero size. + // However, for layout and others, we should return the metrics of zero size + // font. + if (mStyle.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). +UniquePtr<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; + + UniquePtr<Metrics> metrics = MakeUnique<Metrics>(); + ::memset(metrics.get(), 0, sizeof(Metrics)); + + // Some basic defaults, in case the font lacks any real metrics tables. + // TODO: consider what rounding (if any) we should apply to these. + metrics->emHeight = GetAdjustedSize(); + metrics->emAscent = metrics->emHeight / 2; + metrics->emDescent = metrics->emHeight - metrics->emAscent; + + metrics->maxAscent = metrics->emAscent; + metrics->maxDescent = metrics->emDescent; + + const float UNINITIALIZED_LEADING = -10000.0f; + metrics->externalLeading = UNINITIALIZED_LEADING; + + if (mFUnitsConvFactor < 0.0) { + uint16_t upem = GetFontEntry()->UnitsPerEm(); + if (upem != gfxFontEntry::kInvalidUPEM) { + mFUnitsConvFactor = GetAdjustedSize() / upem; + } + } + +#define SET_UNSIGNED(field, src) \ + metrics->field = uint16_t(src) * mFUnitsConvFactor +#define SET_SIGNED(field, src) metrics->field = int16_t(src) * mFUnitsConvFactor + + gfxFontEntry::AutoTable os2Table(mFontEntry, kOS_2TableTag); + if (os2Table && mFUnitsConvFactor >= 0.0) { + const OS2Table* os2 = + reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len)); + // These fields should always be present in any valid OS/2 table + if (len >= offsetof(OS2Table, sTypoLineGap) + sizeof(int16_t)) { + SET_SIGNED(strikeoutSize, os2->yStrikeoutSize); + // Use ascent+descent from the horizontal metrics as the default + // advance (aveCharWidth) in vertical mode + gfxFloat ascentDescent = + gfxFloat(mFUnitsConvFactor) * + (int16_t(os2->sTypoAscender) - int16_t(os2->sTypoDescender)); + metrics->aveCharWidth = std::max(metrics->emHeight, ascentDescent); + // Use xAvgCharWidth from horizontal metrics as minimum font extent + // for vertical layout, applying half of it to ascent and half to + // descent (to work with a default centered baseline). + gfxFloat halfCharWidth = + int16_t(os2->xAvgCharWidth) * gfxFloat(mFUnitsConvFactor) / 2; + metrics->maxAscent = std::max(metrics->maxAscent, halfCharWidth); + metrics->maxDescent = std::max(metrics->maxDescent, halfCharWidth); + } + } + + // If we didn't set aveCharWidth from OS/2, try to read 'hhea' metrics + // and use the line height from its ascent/descent. + if (!metrics->aveCharWidth) { + gfxFontEntry::AutoTable hheaTable(mFontEntry, kHheaTableTag); + if (hheaTable && mFUnitsConvFactor >= 0.0) { + const MetricsHeader* hhea = reinterpret_cast<const MetricsHeader*>( + hb_blob_get_data(hheaTable, &len)); + if (len >= sizeof(MetricsHeader)) { + SET_SIGNED(aveCharWidth, + int16_t(hhea->ascender) - int16_t(hhea->descender)); + metrics->maxAscent = metrics->aveCharWidth / 2; + metrics->maxDescent = metrics->aveCharWidth - metrics->maxAscent; + } + } + } + + // Read real vertical metrics if available. + gfxFontEntry::AutoTable vheaTable(mFontEntry, kVheaTableTag); + if (vheaTable && mFUnitsConvFactor >= 0.0) { + const MetricsHeader* vhea = reinterpret_cast<const MetricsHeader*>( + hb_blob_get_data(vheaTable, &len)); + if (len >= sizeof(MetricsHeader)) { + SET_UNSIGNED(maxAdvance, vhea->advanceWidthMax); + // Redistribute space between ascent/descent because we want a + // centered vertical baseline by default. + gfxFloat halfExtent = + 0.5 * gfxFloat(mFUnitsConvFactor) * + (int16_t(vhea->ascender) + std::abs(int16_t(vhea->descender))); + // Some bogus fonts have ascent and descent set to zero in 'vhea'. + // In that case we just ignore them and keep our synthetic values + // from above. + if (halfExtent > 0) { + metrics->maxAscent = halfExtent; + metrics->maxDescent = halfExtent; + SET_SIGNED(externalLeading, vhea->lineGap); + } + } + } + + // If we didn't set aveCharWidth above, we must be dealing with a non-sfnt + // font of some kind (Type1, bitmap, vector, ...), so fall back to using + // whatever the platform backend figured out for horizontal layout. + // And if we haven't set externalLeading yet, then copy that from the + // horizontal metrics as well, to help consistency of CSS line-height. + if (!metrics->aveCharWidth || + metrics->externalLeading == UNINITIALIZED_LEADING) { + const Metrics& horizMetrics = GetHorizontalMetrics(); + if (!metrics->aveCharWidth) { + metrics->aveCharWidth = horizMetrics.maxAscent + horizMetrics.maxDescent; + } + if (metrics->externalLeading == UNINITIALIZED_LEADING) { + metrics->externalLeading = horizMetrics.externalLeading; + } + } + + // Get underline thickness from the 'post' table if available. + // We also read the underline position, although in vertical-upright mode + // this will not be appropriate to use directly (see nsTextFrame.cpp). + gfxFontEntry::AutoTable postTable(mFontEntry, kPostTableTag); + if (postTable) { + const PostTable* post = + reinterpret_cast<const PostTable*>(hb_blob_get_data(postTable, &len)); + if (len >= offsetof(PostTable, underlineThickness) + sizeof(uint16_t)) { + static_assert(offsetof(PostTable, underlinePosition) < + offsetof(PostTable, underlineThickness), + "broken PostTable struct?"); + SET_SIGNED(underlineOffset, post->underlinePosition); + SET_UNSIGNED(underlineSize, post->underlineThickness); + // Also use for strikeout if we didn't find that in OS/2 above. + if (!metrics->strikeoutSize) { + metrics->strikeoutSize = metrics->underlineSize; + } + } + } + +#undef SET_UNSIGNED +#undef SET_SIGNED + + // If we didn't read this from a vhea table, it will still be zero. + // In any case, let's make sure it is not less than the value we've + // come up with for aveCharWidth. + metrics->maxAdvance = std::max(metrics->maxAdvance, metrics->aveCharWidth); + + // Thickness of underline and strikeout may have been read from tables, + // but in case they were not present, ensure a minimum of 1 pixel. + metrics->underlineSize = std::max(1.0, metrics->underlineSize); + + metrics->strikeoutSize = std::max(1.0, metrics->strikeoutSize); + metrics->strikeoutOffset = -0.5 * metrics->strikeoutSize; + + // Somewhat arbitrary values for now, subject to future refinement... + metrics->spaceWidth = metrics->aveCharWidth; + metrics->zeroWidth = metrics->aveCharWidth; + metrics->maxHeight = metrics->maxAscent + metrics->maxDescent; + metrics->xHeight = metrics->emHeight / 2; + metrics->capHeight = metrics->maxAscent; + + return std::move(metrics); +} + +gfxFloat gfxFont::SynthesizeSpaceWidth(uint32_t aCh) { + // return an appropriate width for various Unicode space characters + // that we "fake" if they're not actually present in the font; + // returns negative value if the char is not a known space. + switch (aCh) { + case 0x2000: // en quad + case 0x2002: + return GetAdjustedSize() / 2; // en space + case 0x2001: // em quad + case 0x2003: + return GetAdjustedSize(); // em space + case 0x2004: + return GetAdjustedSize() / 3; // three-per-em space + case 0x2005: + return GetAdjustedSize() / 4; // four-per-em space + case 0x2006: + return GetAdjustedSize() / 6; // six-per-em space + case 0x2007: + return GetMetrics(nsFontMetrics::eHorizontal) + .ZeroOrAveCharWidth(); // figure space + case 0x2008: + return GetMetrics(nsFontMetrics::eHorizontal) + .spaceWidth; // punctuation space + case 0x2009: + return GetAdjustedSize() / 5; // thin space + case 0x200a: + return GetAdjustedSize() / 10; // hair space + case 0x202f: + return GetAdjustedSize() / 5; // narrow no-break space + case 0x3000: + return GetAdjustedSize(); // ideographic space + default: + return -1.0; + } +} + +void gfxFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const { + 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 = + MakeUnique<nsTHashtable<nsPtrHashKey<GlyphChangeObserver>>>(); + } + mGlyphChangeObservers->PutEntry(aObserver); +} + +void gfxFont::RemoveGlyphChangeObserver(GlyphChangeObserver* aObserver) { + NS_ASSERTION(mGlyphChangeObservers, "No observers registered"); + NS_ASSERTION(mGlyphChangeObservers->Contains(aObserver), + "Observer not registered"); + mGlyphChangeObservers->RemoveEntry(aObserver); +} + +#define DEFAULT_PIXEL_FONT_SIZE 16.0f + +gfxFontStyle::gfxFontStyle() + : size(DEFAULT_PIXEL_FONT_SIZE), + sizeAdjust(-1.0f), + baselineOffset(0.0f), + languageOverride(NO_FONT_LANGUAGE_OVERRIDE), + fontSmoothingBackgroundColor(NS_RGBA(0, 0, 0, 0)), + weight(FontWeight::Normal()), + stretch(FontStretch::Normal()), + style(FontSlantStyle::Normal()), + variantCaps(NS_FONT_VARIANT_CAPS_NORMAL), + variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL), + systemFont(true), + printerFont(false), + useGrayscaleAntialiasing(false), + allowSyntheticWeight(true), + allowSyntheticStyle(true), + noFallbackVariantFeatures(true) {} + +gfxFontStyle::gfxFontStyle(FontSlantStyle aStyle, FontWeight aWeight, + FontStretch aStretch, gfxFloat aSize, + float aSizeAdjust, bool aSystemFont, + bool aPrinterFont, bool aAllowWeightSynthesis, + bool aAllowStyleSynthesis, + uint32_t aLanguageOverride) + : size(aSize), + sizeAdjust(aSizeAdjust), + baselineOffset(0.0f), + languageOverride(aLanguageOverride), + fontSmoothingBackgroundColor(NS_RGBA(0, 0, 0, 0)), + weight(aWeight), + stretch(aStretch), + style(aStyle), + variantCaps(NS_FONT_VARIANT_CAPS_NORMAL), + variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL), + systemFont(aSystemFont), + printerFont(aPrinterFont), + useGrayscaleAntialiasing(false), + allowSyntheticWeight(aAllowWeightSynthesis), + allowSyntheticStyle(aAllowStyleSynthesis), + noFallbackVariantFeatures(true) { + MOZ_ASSERT(!mozilla::IsNaN(size)); + MOZ_ASSERT(!mozilla::IsNaN(sizeAdjust)); + + if (weight > FontWeight(1000)) { + weight = FontWeight(1000); + } + if (weight < FontWeight(1)) { + weight = FontWeight(1); + } + + 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; + } +} + +PLDHashNumber gfxFontStyle::Hash() const { + uint32_t hash = variationSettings.IsEmpty() + ? 0 + : mozilla::HashBytes(variationSettings.Elements(), + variationSettings.Length() * + sizeof(gfxFontVariation)); + return mozilla::AddToHash(hash, systemFont, style.ForHash(), + stretch.ForHash(), weight.ForHash(), size, + int32_t(sizeAdjust * 1000.0f)); +} + +void gfxFontStyle::AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel) { + MOZ_ASSERT( + variantSubSuper != NS_FONT_VARIANT_POSITION_NORMAL && baselineOffset == 0, + "can't adjust this style for sub/superscript"); + + // calculate the baseline offset (before changing the size) + if (variantSubSuper == NS_FONT_VARIANT_POSITION_SUPER) { + baselineOffset = size * -NS_FONT_SUPERSCRIPT_OFFSET_RATIO; + } else { + baselineOffset = size * NS_FONT_SUBSCRIPT_OFFSET_RATIO; + } + + // calculate reduced size, roughly mimicing behavior of font-size: smaller + float cssSize = size * aAppUnitsPerDevPixel / AppUnitsPerCSSPixel(); + if (cssSize < NS_FONT_SUB_SUPER_SMALL_SIZE) { + size *= NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL; + } else if (cssSize >= NS_FONT_SUB_SUPER_LARGE_SIZE) { + size *= NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE; + } else { + gfxFloat t = (cssSize - NS_FONT_SUB_SUPER_SMALL_SIZE) / + (NS_FONT_SUB_SUPER_LARGE_SIZE - NS_FONT_SUB_SUPER_SMALL_SIZE); + size *= (1.0 - t) * NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL + + t * NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE; + } + + // clear the variant field + variantSubSuper = NS_FONT_VARIANT_POSITION_NORMAL; +} + +bool gfxFont::TryGetMathTable() { + if (!mMathInitialized) { + mMathInitialized = true; + + hb_face_t* face = GetFontEntry()->GetHBFace(); + if (face) { + if (hb_ot_math_has_data(face)) { + mMathTable = MakeUnique<gfxMathTable>(face, GetAdjustedSize()); + } + hb_face_destroy(face); + } + } + + return !!mMathTable; +} + +/* static */ +void SharedFontList::Initialize() { + sEmpty = new SharedFontList(); + + for (auto i : IntegerRange(ArrayLength(sSingleGenerics))) { + auto type = static_cast<StyleGenericFontFamily>(i); + if (type != StyleGenericFontFamily::None) { + sSingleGenerics[i] = new SharedFontList(type); + } + } +} + +/* static */ +void SharedFontList::Shutdown() { + sEmpty = nullptr; + + for (auto& sharedFontList : sSingleGenerics) { + sharedFontList = nullptr; + } +} + +StaticRefPtr<SharedFontList> SharedFontList::sEmpty; + +StaticRefPtr<SharedFontList> + SharedFontList::sSingleGenerics[size_t(StyleGenericFontFamily::MozEmoji)]; diff --git a/gfx/thebes/gfxFont.h b/gfx/thebes/gfxFont.h new file mode 100644 index 0000000000..009bd0ff56 --- /dev/null +++ b/gfx/thebes/gfxFont.h @@ -0,0 +1,2277 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=4 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FONT_H +#define GFX_FONT_H + +#include <new> +#include <utility> +#include "PLDHashTable.h" +#include "ThebesRLBoxTypes.h" +#include "gfxFontVariations.h" +#include "gfxRect.h" +#include "gfxTypes.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Attributes.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ServoStyleConsts.h" +#include "mozilla/TypedEnumBits.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/gfx/MatrixFwd.h" +#include "mozilla/gfx/Point.h" +#include "nsCOMPtr.h" +#include "nsColor.h" +#include "nsDataHashtable.h" +#include "nsExpirationTracker.h" +#include "nsFontMetrics.h" +#include "nsHashKeys.h" +#include "nsIMemoryReporter.h" +#include "nsIObserver.h" +#include "nsISupports.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsTHashtable.h" +#include "nsUnicodeScriptCodes.h" +#include "nscore.h" + +// Only required for function bodys +#include <stdlib.h> +#include <string.h> +#include <algorithm> +#include "mozilla/Assertions.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/ServoUtils.h" +#include "mozilla/gfx/2D.h" +#include "gfxFontEntry.h" +#include "gfxFontFeatures.h" +#include "gfxFontUtils.h" +#include "gfxPlatform.h" +#include "nsAtom.h" +#include "nsDebug.h" +#include "nsMathUtils.h" + +class gfxContext; +class gfxGlyphExtents; +class gfxMathTable; +class gfxPattern; +class gfxShapedText; +class gfxShapedWord; +class gfxSkipChars; +class gfxTextRun; +class nsIEventTarget; +class nsITimer; +struct gfxTextRunDrawCallbacks; +enum class DrawMode : int; + +namespace mozilla { +class SVGContextPaint; +namespace layout { +class TextDrawTarget; +} +} // namespace mozilla + +typedef struct _cairo cairo_t; +typedef struct _cairo_scaled_font cairo_scaled_font_t; + +#define FONT_MAX_SIZE 2000.0 + +#define SMALL_CAPS_SCALE_FACTOR 0.8 + +// The skew factor used for synthetic-italic [oblique] fonts; +// we use a platform-dependent value to harmonize with the platform's own APIs. +#ifdef XP_WIN +# define OBLIQUE_SKEW_FACTOR 0.3f +#elif defined(MOZ_WIDGET_GTK) +# define OBLIQUE_SKEW_FACTOR 0.2f +#else +# define OBLIQUE_SKEW_FACTOR 0.25f +#endif + +struct gfxFontStyle { + typedef mozilla::FontStretch FontStretch; + typedef mozilla::FontSlantStyle FontSlantStyle; + typedef mozilla::FontWeight FontWeight; + + gfxFontStyle(); + gfxFontStyle(FontSlantStyle aStyle, FontWeight aWeight, FontStretch aStretch, + gfxFloat aSize, float aSizeAdjust, bool aSystemFont, + bool aPrinterFont, bool aWeightSynthesis, bool aStyleSynthesis, + uint32_t aLanguageOverride); + // Features are composed of (1) features from style rules (2) features + // from feature settings rules and (3) family-specific features. (1) and + // (3) are guaranteed to be mutually exclusive + + // custom opentype feature settings + CopyableTArray<gfxFontFeature> featureSettings; + + // Some font-variant property values require font-specific settings + // defined via @font-feature-values rules. These are resolved after + // font matching occurs. + + // -- list of value tags for specific alternate features + mozilla::StyleVariantAlternatesList variantAlternates; + + // -- object used to look these up once the font is matched + RefPtr<gfxFontFeatureValueSet> featureValueLookup; + + // opentype variation settings + CopyableTArray<gfxFontVariation> variationSettings; + + // The logical size of the font, in pixels + gfxFloat size; + + // The aspect-value (ie., the ratio actualsize:actualxheight) that any + // actual physical font created from this font structure must have when + // rendering or measuring a string. A value of -1.0 means no adjustment + // needs to be done; otherwise the value must be nonnegative. + float sizeAdjust; + + // baseline offset, used when simulating sub/superscript glyphs + float baselineOffset; + + // Language system tag, to override document language; + // an OpenType "language system" tag represented as a 32-bit integer + // (see http://www.microsoft.com/typography/otspec/languagetags.htm). + // Normally 0, so font rendering will use the document or element language + // (see above) to control any language-specific rendering, but the author + // can override this for cases where the options implemented in the font + // do not directly match the actual language. (E.g. lang may be Macedonian, + // but the font in use does not explicitly support this; the author can + // use font-language-override to request the Serbian option in the font + // in order to get correct glyph shapes.) + uint32_t languageOverride; + + // The estimated background color behind the text. Enables a special + // rendering mode when NS_GET_A(.) > 0. Only used for text in the chrome. + nscolor fontSmoothingBackgroundColor; + + // The Font{Weight,Stretch,SlantStyle} fields are each a 16-bit type. + + // The weight of the font: 100, 200, ... 900. + FontWeight weight; + + // The stretch of the font + FontStretch stretch; + + // The style of font + FontSlantStyle style; + + // Whether face-selection properties weight/style/stretch are all 'normal' + bool IsNormalStyle() const { + return weight.IsNormal() && style.IsNormal() && stretch.IsNormal(); + } + + // We pack these two small-integer fields into a single byte to avoid + // overflowing an 8-byte boundary [in a 64-bit build] and ending up with + // 7 bytes of padding at the end of the struct. + + // caps variant (small-caps, petite-caps, etc.) + uint8_t variantCaps : 4; // uses range 0..6 + + // sub/superscript variant + uint8_t variantSubSuper : 4; // uses range 0..2 + + // Say that this font is a system font and therefore does not + // require certain fixup that we do for fonts from untrusted + // sources. + bool systemFont : 1; + + // Say that this font is used for print or print preview. + bool printerFont : 1; + + // Used to imitate -webkit-font-smoothing: antialiased + bool useGrayscaleAntialiasing : 1; + + // Whether synthetic styles are allowed + bool allowSyntheticWeight : 1; + bool allowSyntheticStyle : 1; + + // some variant features require fallback which complicates the shaping + // code, so set up a bool to indicate when shaping with fallback is needed + bool noFallbackVariantFeatures : 1; + + // Return the final adjusted font size for the given aspect ratio. + // Not meant to be called when 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; + + // Adjust this style to simulate sub/superscript (as requested in the + // variantSubSuper field) using size and baselineOffset instead. + void AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel); + + // Should this style cause the given font entry to use synthetic bold? + bool NeedsSyntheticBold(gfxFontEntry* aFontEntry) const { + return weight.IsBold() && allowSyntheticWeight && + !aFontEntry->SupportsBold(); + } + + bool Equals(const gfxFontStyle& other) const { + return (*reinterpret_cast<const uint64_t*>(&size) == + *reinterpret_cast<const uint64_t*>(&other.size)) && + (style == other.style) && (weight == other.weight) && + (stretch == other.stretch) && (variantCaps == other.variantCaps) && + (variantSubSuper == other.variantSubSuper) && + (allowSyntheticWeight == other.allowSyntheticWeight) && + (allowSyntheticStyle == other.allowSyntheticStyle) && + (systemFont == other.systemFont) && + (printerFont == other.printerFont) && + (useGrayscaleAntialiasing == other.useGrayscaleAntialiasing) && + (baselineOffset == other.baselineOffset) && + (*reinterpret_cast<const uint32_t*>(&sizeAdjust) == + *reinterpret_cast<const uint32_t*>(&other.sizeAdjust)) && + (featureSettings == other.featureSettings) && + (variantAlternates == other.variantAlternates) && + (featureValueLookup == other.featureValueLookup) && + (variationSettings == other.variationSettings) && + (languageOverride == other.languageOverride) && + (fontSmoothingBackgroundColor == other.fontSmoothingBackgroundColor); + } +}; + +/** + * 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 gfxFontCacheExpirationTracker + : public ExpirationTrackerImpl<gfxFont, 3, ::detail::PlaceholderLock, + ::detail::PlaceholderAutoLock> { + protected: + typedef ::detail::PlaceholderLock Lock; + typedef ::detail::PlaceholderAutoLock AutoLock; + + Lock mLock; + + AutoLock FakeLock() { return AutoLock(mLock); } + + Lock& GetMutex() override { + mozilla::AssertIsMainThreadOrServoFontMetricsLocked(); + return mLock; + } + + public: + enum { FONT_TIMEOUT_SECONDS = 10 }; + + explicit gfxFontCacheExpirationTracker(nsIEventTarget* aEventTarget) + : ExpirationTrackerImpl<gfxFont, 3, Lock, AutoLock>( + FONT_TIMEOUT_SECONDS * 1000, "gfxFontCache", aEventTarget) {} +}; + +class gfxFontCache final : private gfxFontCacheExpirationTracker { + public: + enum { SHAPED_WORD_TIMEOUT_SECONDS = 60 }; + + explicit gfxFontCache(nsIEventTarget* aEventTarget); + ~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 null if there's nothing matching + // in the cache + gfxFont* Lookup(const gfxFontEntry* aFontEntry, const gfxFontStyle* aStyle, + const gfxCharacterMap* aUnicodeRangeMap); + + // We created a new font (presumably because Lookup returned null); + // put it in the cache. The font's refcount should be nonzero. It is + // allowable to add a new font even if there is one already in the + // cache with the same key; we'll forget about the old one. + void AddNew(gfxFont* aFont); + + // The font's refcount has gone to zero; give ownership of it to + // the cache. We delete it if it's not acquired again after a certain + // amount of time. + void NotifyReleased(gfxFont* aFont); + + // 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(); + } + + uint32_t Count() const { return mFonts.Count(); } + + void FlushShapedWordCaches(); + void NotifyGlyphsChanged(); + + void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const; + void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const; + + void AgeAllGenerations() { AgeAllGenerationsLocked(FakeLock()); } + + void RemoveObject(gfxFont* aFont) { RemoveObjectLocked(aFont, FakeLock()); } + + protected: + class MemoryReporter final : public nsIMemoryReporter { + ~MemoryReporter() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMEMORYREPORTER + }; + + // Observer for notifications that the font cache cares about + class Observer final : public nsIObserver { + ~Observer() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + }; + + nsresult AddObject(gfxFont* aFont) { + return AddObjectLocked(aFont, FakeLock()); + } + + // This gets called when the timeout has expired on a zero-refcount + // font; we just delete it. + void NotifyExpiredLocked(gfxFont* aFont, const AutoLock&) override { + NotifyExpired(aFont); + } + + void NotifyExpired(gfxFont* aFont); + + void DestroyFont(gfxFont* aFont); + + static gfxFontCache* gGlobalCache; + + struct MOZ_STACK_CLASS Key { + const gfxFontEntry* mFontEntry; + const gfxFontStyle* mStyle; + const gfxCharacterMap* mUnicodeRangeMap; + Key(const gfxFontEntry* aFontEntry, const gfxFontStyle* aStyle, + const gfxCharacterMap* aUnicodeRangeMap) + : mFontEntry(aFontEntry), + mStyle(aStyle), + mUnicodeRangeMap(aUnicodeRangeMap) {} + }; + + class HashEntry : public PLDHashEntryHdr { + public: + typedef const Key& KeyType; + typedef const Key* KeyTypePointer; + + // When constructing a new entry in the hashtable, we'll leave this + // blank. The caller of Put() will fill this in. + explicit HashEntry(KeyTypePointer aStr) : mFont(nullptr) {} + + 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 }; + + // The cache tracks gfxFont objects whose refcount has dropped to zero, + // so they are not immediately deleted but may be "resurrected" if they + // have not yet expired from the tracker when they are needed again. + // See the custom AddRef/Release methods in gfxFont. + gfxFont* MOZ_UNSAFE_REF("tracking for deferred deletion") mFont; + }; + + nsTHashtable<HashEntry> mFonts; + + static void WordCacheExpirationTimerCallback(nsITimer* aTimer, void* aCache); + nsCOMPtr<nsITimer> mWordCacheExpirationTimer; +}; + +class gfxTextPerfMetrics { + public: + struct TextCounts { + uint32_t numContentTextRuns; + uint32_t numChromeTextRuns; + uint32_t numChars; + uint32_t maxTextRunLen; + uint32_t wordCacheSpaceRules; + uint32_t wordCacheLong; + uint32_t wordCacheHit; + uint32_t wordCacheMiss; + uint32_t fallbackPrefs; + uint32_t fallbackSystem; + uint32_t textrunConst; + uint32_t textrunDestr; + uint32_t genericLookups; + }; + + uint32_t reflowCount; + + // counts per reflow operation + TextCounts current; + + // totals for the lifetime of a document + TextCounts cumulative; + + gfxTextPerfMetrics() { memset(this, 0, sizeof(gfxTextPerfMetrics)); } + + // add current totals to cumulative ones + void Accumulate() { + if (current.numChars == 0) { + return; + } + cumulative.numContentTextRuns += current.numContentTextRuns; + cumulative.numChromeTextRuns += current.numChromeTextRuns; + cumulative.numChars += current.numChars; + if (current.maxTextRunLen > cumulative.maxTextRunLen) { + cumulative.maxTextRunLen = current.maxTextRunLen; + } + cumulative.wordCacheSpaceRules += current.wordCacheSpaceRules; + cumulative.wordCacheLong += current.wordCacheLong; + cumulative.wordCacheHit += current.wordCacheHit; + cumulative.wordCacheMiss += current.wordCacheMiss; + cumulative.fallbackPrefs += current.fallbackPrefs; + cumulative.fallbackSystem += current.fallbackSystem; + cumulative.textrunConst += current.textrunConst; + cumulative.textrunDestr += current.textrunDestr; + cumulative.genericLookups += current.genericLookups; + memset(¤t, 0, sizeof(current)); + } +}; + +namespace mozilla { +namespace gfx { +// Flags that live in the gfxShapedText::mFlags field. +// (Note that gfxTextRun has an additional mFlags2 field for use +// by textrun clients like nsTextFrame.) +// +// If you add a flag, please add support for it in gfxTextRun::Dump. +enum class ShapedTextFlags : uint16_t { + /** + * When set, the text is RTL. + */ + TEXT_IS_RTL = 0x0001, + /** + * When set, spacing is enabled and the textrun needs to call GetSpacing + * on the spacing provider. + */ + TEXT_ENABLE_SPACING = 0x0002, + /** + * When set, the text has no characters above 255 and it is stored + * in the textrun in 8-bit format. + */ + TEXT_IS_8BIT = 0x0004, + /** + * When set, GetHyphenationBreaks may return true for some character + * positions, otherwise it will always return false for all characters. + */ + TEXT_ENABLE_HYPHEN_BREAKS = 0x0008, + /** + * When set, the RunMetrics::mBoundingBox field will be initialized + * properly based on glyph extents, in particular, glyph extents that + * overflow the standard font-box (the box defined by the ascent, descent + * and advance width of the glyph). When not set, it may just be the + * standard font-box even if glyphs overflow. + */ + TEXT_NEED_BOUNDING_BOX = 0x0010, + /** + * When set, optional ligatures are disabled. Ligatures that are + * required for legible text should still be enabled. + */ + TEXT_DISABLE_OPTIONAL_LIGATURES = 0x0020, + /** + * When set, the textrun should favour speed of construction over + * quality. This may involve disabling ligatures and/or kerning or + * other effects. + */ + TEXT_OPTIMIZE_SPEED = 0x0040, + /** + * When set, the textrun should discard control characters instead of + * turning them into hexboxes. + */ + TEXT_HIDE_CONTROL_CHARACTERS = 0x0080, + + /** + * nsTextFrameThebes sets these, but they're defined here rather than + * in nsTextFrameUtils.h because ShapedWord creation/caching also needs + * to check the _INCOMING flag + */ + TEXT_TRAILING_ARABICCHAR = 0x0100, + /** + * When set, the previous character for this textrun was an Arabic + * character. This is used for the context detection necessary for + * bidi.numeral implementation. + */ + TEXT_INCOMING_ARABICCHAR = 0x0200, + + /** + * Set if the textrun should use the OpenType 'math' script. + */ + TEXT_USE_MATH_SCRIPT = 0x0400, + + /* + * Bit 0x0800 is currently unused. + */ + + /** + * Field for orientation of the textrun and glyphs within it. + * Possible values of the TEXT_ORIENT_MASK field: + * TEXT_ORIENT_HORIZONTAL + * TEXT_ORIENT_VERTICAL_UPRIGHT + * TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT + * TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT + * TEXT_ORIENT_VERTICAL_MIXED + * For all VERTICAL settings, the x and y coordinates of glyph + * positions are exchanged, so that simple advances are vertical. + * + * The MIXED value indicates vertical textRuns for which the CSS + * text-orientation property is 'mixed', but is never used for + * individual glyphRuns; it will be resolved to either UPRIGHT + * or SIDEWAYS_RIGHT according to the UTR50 properties of the + * characters, and separate glyphRuns created for the resulting + * glyph orientations. + */ + TEXT_ORIENT_MASK = 0x7000, + TEXT_ORIENT_HORIZONTAL = 0x0000, + TEXT_ORIENT_VERTICAL_UPRIGHT = 0x1000, + TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT = 0x2000, + TEXT_ORIENT_VERTICAL_MIXED = 0x3000, + TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT = 0x4000, +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ShapedTextFlags) +} // namespace gfx +} // namespace mozilla + +class gfxTextRunFactory { + // Used by stylo + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxTextRunFactory) + + public: + typedef mozilla::gfx::DrawTarget DrawTarget; + + /** + * This record contains all the parameters needed to initialize a textrun. + */ + struct MOZ_STACK_CLASS Parameters { + // Shape text params suggesting where the textrun will be rendered + DrawTarget* mDrawTarget; + // Pointer to arbitrary user data (which should outlive the textrun) + void* mUserData; + // A description of which characters have been stripped from the original + // DOM string to produce the characters in the textrun. May be null + // if that information is not relevant. + gfxSkipChars* mSkipChars; + // A list of where linebreaks are currently placed in the textrun. May + // be null if mInitialBreakCount is zero. + uint32_t* mInitialBreaks; + uint32_t mInitialBreakCount; + // The ratio to use to convert device pixels to application layout units + int32_t mAppUnitsPerDevUnit; + }; + + protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~gfxTextRunFactory(); +}; + +/** + * gfxFontShaper + * + * This class implements text shaping (character to glyph mapping and + * glyph layout). There is a gfxFontShaper subclass for each text layout + * technology (uniscribe, core text, harfbuzz,....) we support. + * + * The shaper is responsible for setting up glyph data in gfxTextRuns. + * + * A generic, platform-independent shaper relies only on the standard + * gfxFont interface and can work with any concrete subclass of gfxFont. + * + * Platform-specific implementations designed to interface to platform + * shaping APIs such as Uniscribe or CoreText may rely on features of a + * specific font subclass to access native font references + * (such as CTFont, HFONT, DWriteFont, etc). + */ + +class gfxFontShaper { + public: + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::unicode::Script Script; + + enum class RoundingFlags : uint8_t { kRoundX = 0x01, kRoundY = 0x02 }; + + explicit gfxFontShaper(gfxFont* aFont) : mFont(aFont) { + NS_ASSERTION(aFont, "shaper requires a valid font!"); + } + + virtual ~gfxFontShaper() = default; + + // Shape a piece of text and store the resulting glyph data into + // aShapedText. Parameters aOffset/aLength indicate the range of + // aShapedText to be updated; aLength is also the length of aText. + virtual bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, // may be null, indicating no + // lang-specific shaping to be + // applied + bool aVertical, RoundingFlags aRounding, + gfxShapedText* aShapedText) = 0; + + gfxFont* GetFont() const { return mFont; } + + static void MergeFontFeatures( + const gfxFontStyle* aStyle, const nsTArray<gfxFontFeature>& aFontFeatures, + bool aDisableLigatures, const nsACString& aFamilyName, bool aAddSmallCaps, + void (*aHandleFeature)(const uint32_t&, uint32_t&, void*), + void* aHandleFeatureData); + + protected: + // the font this shaper is working with. The font owns a UniquePtr reference + // to this object, and will destroy it before it dies. Thus, mFont will always + // be valid. + gfxFont* MOZ_NON_OWNING_REF mFont; +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(gfxFontShaper::RoundingFlags) + +/* + * gfxShapedText is an abstract superclass for gfxShapedWord and gfxTextRun. + * These are objects that store a list of zero or more glyphs for each + * character. For each glyph we store the glyph ID, the advance, and possibly + * x/y-offsets. The idea is that a string is rendered by a loop that draws each + * glyph at its designated offset from the current point, then advances the + * current point by the glyph's advance in the direction of the textrun (LTR or + * RTL). Each glyph advance is always rounded to the nearest appunit; this + * ensures consistent results when dividing the text in a textrun into multiple + * text frames (frame boundaries are always aligned to appunits). We optimize + * for the case where a character has a single glyph and zero xoffset and + * yoffset, and the glyph ID and advance are in a reasonable range so we can + * pack all necessary data into 32 bits. + * + * gfxFontShaper can shape text into either a gfxShapedWord (cached by a + * gfxFont) or directly into a gfxTextRun (for cases where we want to shape + * textruns in their entirety rather than using cached words, because there may + * be layout features that depend on the inter-word spaces). + */ +class gfxShapedText { + public: + typedef mozilla::unicode::Script Script; + + gfxShapedText(uint32_t aLength, mozilla::gfx::ShapedTextFlags aFlags, + uint16_t aAppUnitsPerDevUnit) + : mLength(aLength), + mFlags(aFlags), + mAppUnitsPerDevUnit(aAppUnitsPerDevUnit) {} + + virtual ~gfxShapedText() = default; + + /** + * This class records the information associated with a character in the + * input string. It's optimized for the case where there is one glyph + * representing that character alone. + * + * A character can have zero or more associated glyphs. Each glyph + * has an advance width and an x and y offset. + * A character may be the start of a cluster. + * A character may be the start of a ligature group. + * A character can be "missing", indicating that the system is unable + * to render the character. + * + * All characters in a ligature group conceptually share all the glyphs + * associated with the characters in a group. + */ + class CompressedGlyph { + public: + enum { + // Indicates that a cluster and ligature group starts at this + // character; this character has a single glyph with a reasonable + // advance and zero offsets. A "reasonable" advance + // is one that fits in the available bits (currently 12) (specified + // in appunits). + FLAG_IS_SIMPLE_GLYPH = 0x80000000U, + + // These flags are applicable to both "simple" and "complex" records. + COMMON_FLAGS_MASK = 0x70000000U, + + // Indicates whether a linebreak is allowed before this character; + // this is a two-bit field that holds a FLAG_BREAK_TYPE_xxx value + // indicating the kind of linebreak (if any) allowed here. + FLAGS_CAN_BREAK_BEFORE = 0x60000000U, + + FLAGS_CAN_BREAK_SHIFT = 29, + FLAG_BREAK_TYPE_NONE = 0, + FLAG_BREAK_TYPE_NORMAL = 1, + FLAG_BREAK_TYPE_HYPHEN = 2, + + FLAG_CHAR_IS_SPACE = 0x10000000U, + + // Fields present only when FLAG_IS_SIMPLE_GLYPH is /true/. + // The advance is stored in appunits as a 12-bit field: + ADVANCE_MASK = 0x0FFF0000U, + ADVANCE_SHIFT = 16, + // and the glyph ID is stored in the low 16 bits. + GLYPH_MASK = 0x0000FFFFU, + + // Fields present only when FLAG_IS_SIMPLE_GLYPH is /false/. + // Non-simple glyphs may or may not have glyph data in the + // corresponding mDetailedGlyphs entry. They have a glyph count + // stored in the low 16 bits, and the following flag bits: + GLYPH_COUNT_MASK = 0x0000FFFFU, + + // When NOT set, indicates that this character corresponds to a + // missing glyph and should be skipped (or possibly, render the character + // Unicode value in some special way). If there are glyphs, + // the mGlyphID is actually the UTF16 character code. The bit is + // inverted so we can memset the array to zero to indicate all missing. + FLAG_NOT_MISSING = 0x010000, + FLAG_NOT_CLUSTER_START = 0x020000, + FLAG_NOT_LIGATURE_GROUP_START = 0x040000, + // Flag bit 0x080000 is currently unused. + + // Certain types of characters are marked so that they can be given + // special treatment in rendering. This may require use of a "complex" + // CompressedGlyph record even for a character that would otherwise be + // treated as "simple". + CHAR_TYPE_FLAGS_MASK = 0xF00000, + FLAG_CHAR_IS_TAB = 0x100000, + FLAG_CHAR_IS_NEWLINE = 0x200000, + // Per CSS Text Decoration Module Level 3, emphasis marks are not + // drawn for any character in Unicode categories Z*, Cc, Cf, and Cn + // which is not combined with any combining characters. This flag is + // set for all those characters except 0x20 whitespace. + FLAG_CHAR_NO_EMPHASIS_MARK = 0x400000, + // Per CSS Text, letter-spacing is not applied to formatting chars + // (category Cf). We mark those in the textrun so as to be able to + // skip them when setting up spacing in nsTextFrame. + FLAG_CHAR_IS_FORMATTING_CONTROL = 0x800000, + + // The bits 0x0F000000 are currently unused in non-simple glyphs. + }; + + // "Simple glyphs" have a simple glyph ID, simple advance and their + // x and y offsets are zero. Also the glyph extents do not overflow + // the font-box defined by the font ascent, descent and glyph advance width. + // These case is optimized to avoid storing DetailedGlyphs. + + // Returns true if the glyph ID aGlyph fits into the compressed + // representation + static bool IsSimpleGlyphID(uint32_t aGlyph) { + return (aGlyph & GLYPH_MASK) == aGlyph; + } + // Returns true if the advance aAdvance fits into the compressed + // representation. aAdvance is in appunits. + static bool IsSimpleAdvance(uint32_t aAdvance) { + return (aAdvance & (ADVANCE_MASK >> ADVANCE_SHIFT)) == aAdvance; + } + + bool IsSimpleGlyph() const { return mValue & FLAG_IS_SIMPLE_GLYPH; } + uint32_t GetSimpleAdvance() const { + MOZ_ASSERT(IsSimpleGlyph()); + return (mValue & ADVANCE_MASK) >> ADVANCE_SHIFT; + } + uint32_t GetSimpleGlyph() const { + MOZ_ASSERT(IsSimpleGlyph()); + return mValue & GLYPH_MASK; + } + + bool IsMissing() const { + return !(mValue & (FLAG_NOT_MISSING | FLAG_IS_SIMPLE_GLYPH)); + } + bool IsClusterStart() const { + return IsSimpleGlyph() || !(mValue & FLAG_NOT_CLUSTER_START); + } + bool IsLigatureGroupStart() const { + return IsSimpleGlyph() || !(mValue & FLAG_NOT_LIGATURE_GROUP_START); + } + bool IsLigatureContinuation() const { + return !IsSimpleGlyph() && + (mValue & (FLAG_NOT_LIGATURE_GROUP_START | FLAG_NOT_MISSING)) == + (FLAG_NOT_LIGATURE_GROUP_START | FLAG_NOT_MISSING); + } + + // Return true if the original character was a normal (breakable, + // trimmable) space (U+0020). Not true for other characters that + // may happen to map to the space glyph (U+00A0). + bool CharIsSpace() const { return mValue & FLAG_CHAR_IS_SPACE; } + + bool CharIsTab() const { + return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_TAB); + } + bool CharIsNewline() const { + return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_NEWLINE); + } + bool CharMayHaveEmphasisMark() const { + return !CharIsSpace() && + (IsSimpleGlyph() || !(mValue & FLAG_CHAR_NO_EMPHASIS_MARK)); + } + bool CharIsFormattingControl() const { + return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_FORMATTING_CONTROL); + } + + uint32_t CharTypeFlags() const { + return IsSimpleGlyph() ? 0 : (mValue & CHAR_TYPE_FLAGS_MASK); + } + + void SetClusterStart(bool aIsClusterStart) { + MOZ_ASSERT(!IsSimpleGlyph()); + if (aIsClusterStart) { + mValue &= ~FLAG_NOT_CLUSTER_START; + } else { + mValue |= FLAG_NOT_CLUSTER_START; + } + } + + uint8_t CanBreakBefore() const { + return (mValue & FLAGS_CAN_BREAK_BEFORE) >> FLAGS_CAN_BREAK_SHIFT; + } + // Returns FLAGS_CAN_BREAK_BEFORE if the setting changed, 0 otherwise + uint32_t SetCanBreakBefore(uint8_t aCanBreakBefore) { + MOZ_ASSERT(aCanBreakBefore <= 2, "Bogus break-before value!"); + uint32_t breakMask = (uint32_t(aCanBreakBefore) << FLAGS_CAN_BREAK_SHIFT); + uint32_t toggle = breakMask ^ (mValue & FLAGS_CAN_BREAK_BEFORE); + mValue ^= toggle; + return toggle; + } + + // Create a CompressedGlyph value representing a simple glyph with + // no extra flags (line-break or is_space) set. + static CompressedGlyph MakeSimpleGlyph(uint32_t aAdvanceAppUnits, + uint32_t aGlyph) { + MOZ_ASSERT(IsSimpleAdvance(aAdvanceAppUnits)); + MOZ_ASSERT(IsSimpleGlyphID(aGlyph)); + CompressedGlyph g; + g.mValue = + FLAG_IS_SIMPLE_GLYPH | (aAdvanceAppUnits << ADVANCE_SHIFT) | aGlyph; + return g; + } + + // Assign a simple glyph value to an existing CompressedGlyph record, + // preserving line-break/is-space flags if present. + CompressedGlyph& SetSimpleGlyph(uint32_t aAdvanceAppUnits, + uint32_t aGlyph) { + MOZ_ASSERT(!CharTypeFlags(), "Char type flags lost"); + mValue = (mValue & COMMON_FLAGS_MASK) | + MakeSimpleGlyph(aAdvanceAppUnits, aGlyph).mValue; + return *this; + } + + // Create a CompressedGlyph value representing a complex glyph record, + // without any line-break or char-type flags. + static CompressedGlyph MakeComplex(bool aClusterStart, + bool aLigatureStart) { + CompressedGlyph g; + g.mValue = FLAG_NOT_MISSING | + (aClusterStart ? 0 : FLAG_NOT_CLUSTER_START) | + (aLigatureStart ? 0 : FLAG_NOT_LIGATURE_GROUP_START); + return g; + } + + // Assign a complex glyph value to an existing CompressedGlyph record, + // preserving line-break/char-type flags if present. + // This sets the glyphCount to zero; it will be updated when we call + // gfxShapedText::SetDetailedGlyphs. + CompressedGlyph& SetComplex(bool aClusterStart, bool aLigatureStart) { + mValue = (mValue & COMMON_FLAGS_MASK) | CharTypeFlags() | + MakeComplex(aClusterStart, aLigatureStart).mValue; + return *this; + } + + /** + * Mark a glyph record as being a missing-glyph. + * Missing glyphs are treated as ligature group starts; don't mess with + * the cluster-start flag (see bugs 618870 and 619286). + * We also preserve the glyph count here, as this is used after any + * required DetailedGlyphs (to store the char code for a hexbox) has been + * set up. + * This must be called *after* SetDetailedGlyphs is used for the relevant + * offset in the shaped-word, because that will mark it as not-missing. + */ + CompressedGlyph& SetMissing() { + MOZ_ASSERT(!IsSimpleGlyph()); + mValue &= ~(FLAG_NOT_MISSING | FLAG_NOT_LIGATURE_GROUP_START); + return *this; + } + + uint32_t GetGlyphCount() const { + MOZ_ASSERT(!IsSimpleGlyph()); + return mValue & GLYPH_COUNT_MASK; + } + void SetGlyphCount(uint32_t aGlyphCount) { + MOZ_ASSERT(!IsSimpleGlyph()); + MOZ_ASSERT(GetGlyphCount() == 0, "Glyph count already set"); + MOZ_ASSERT(aGlyphCount <= 0xffff, "Glyph count out of range"); + mValue |= FLAG_NOT_MISSING | aGlyphCount; + } + + void SetIsSpace() { mValue |= FLAG_CHAR_IS_SPACE; } + void SetIsTab() { + MOZ_ASSERT(!IsSimpleGlyph()); + mValue |= FLAG_CHAR_IS_TAB; + } + void SetIsNewline() { + MOZ_ASSERT(!IsSimpleGlyph()); + mValue |= FLAG_CHAR_IS_NEWLINE; + } + void SetNoEmphasisMark() { + MOZ_ASSERT(!IsSimpleGlyph()); + mValue |= FLAG_CHAR_NO_EMPHASIS_MARK; + } + void SetIsFormattingControl() { + MOZ_ASSERT(!IsSimpleGlyph()); + mValue |= FLAG_CHAR_IS_FORMATTING_CONTROL; + } + + private: + uint32_t mValue; + }; + + // Accessor for the array of CompressedGlyph records, which will be in + // a different place in gfxShapedWord vs gfxTextRun + virtual const CompressedGlyph* GetCharacterGlyphs() const = 0; + virtual CompressedGlyph* GetCharacterGlyphs() = 0; + + /** + * When the glyphs for a character don't fit into a CompressedGlyph record + * in SimpleGlyph format, we use an array of DetailedGlyphs instead. + */ + struct DetailedGlyph { + // The glyphID, or the Unicode character if this is a missing glyph + uint32_t mGlyphID; + // The advance of the glyph, in appunits. + // mAdvance is in the text direction (RTL or LTR), + // and will normally be non-negative (although this is not guaranteed) + int32_t mAdvance; + // The offset from the glyph's default position, in line-relative + // coordinates (so mOffset.x is an offset in the line-right direction, + // and mOffset.y is an offset in line-downwards direction). + // These values are in floating-point appUnits. + mozilla::gfx::Point mOffset; + }; + + // Store DetailedGlyph records for the given index. (This does not modify + // the associated CompressedGlyph character-type or break flags.) + void SetDetailedGlyphs(uint32_t aIndex, uint32_t aGlyphCount, + const DetailedGlyph* aGlyphs); + + void SetMissingGlyph(uint32_t aIndex, uint32_t aChar, gfxFont* aFont); + + void SetIsSpace(uint32_t aIndex) { + GetCharacterGlyphs()[aIndex].SetIsSpace(); + } + + bool HasDetailedGlyphs() const { return mDetailedGlyphs != nullptr; } + + bool IsLigatureGroupStart(uint32_t aPos) { + NS_ASSERTION(aPos < GetLength(), "aPos out of range"); + return GetCharacterGlyphs()[aPos].IsLigatureGroupStart(); + } + + // NOTE that this must not be called for a character offset that does + // not have any DetailedGlyph records; callers must have verified that + // GetCharacterGlyphs()[aCharIndex].GetGlyphCount() is greater than zero. + DetailedGlyph* GetDetailedGlyphs(uint32_t aCharIndex) const { + NS_ASSERTION(GetCharacterGlyphs() && HasDetailedGlyphs() && + !GetCharacterGlyphs()[aCharIndex].IsSimpleGlyph() && + GetCharacterGlyphs()[aCharIndex].GetGlyphCount() > 0, + "invalid use of GetDetailedGlyphs; check the caller!"); + return mDetailedGlyphs->Get(aCharIndex); + } + + void AdjustAdvancesForSyntheticBold(float aSynBoldOffset, uint32_t aOffset, + uint32_t aLength); + + // Mark clusters in the CompressedGlyph records, starting at aOffset, + // based on the Unicode properties of the text in aString. + // This is also responsible to set the IsSpace flag for space characters. + void SetupClusterBoundaries(uint32_t aOffset, const char16_t* aString, + uint32_t aLength); + // In 8-bit text, there won't actually be any clusters, but we still need + // the space-marking functionality. + void SetupClusterBoundaries(uint32_t aOffset, const uint8_t* aString, + uint32_t aLength); + + mozilla::gfx::ShapedTextFlags GetFlags() const { return mFlags; } + + bool IsVertical() const { + return (GetFlags() & mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_MASK) != + mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_HORIZONTAL; + } + + bool UseCenterBaseline() const { + mozilla::gfx::ShapedTextFlags orient = + GetFlags() & mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_MASK; + return orient == + mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED || + orient == + mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT; + } + + bool IsRightToLeft() const { + return (GetFlags() & mozilla::gfx::ShapedTextFlags::TEXT_IS_RTL) == + mozilla::gfx::ShapedTextFlags::TEXT_IS_RTL; + } + + bool IsSidewaysLeft() const { + return (GetFlags() & mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_MASK) == + mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT; + } + + // Return true if the logical inline direction is reversed compared to + // normal physical coordinates (i.e. if it is leftwards or upwards) + bool IsInlineReversed() const { return IsSidewaysLeft() != IsRightToLeft(); } + + gfxFloat GetDirection() const { return IsInlineReversed() ? -1.0f : 1.0f; } + + bool DisableLigatures() const { + return (GetFlags() & + mozilla::gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES) == + mozilla::gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES; + } + + bool TextIs8Bit() const { + return (GetFlags() & mozilla::gfx::ShapedTextFlags::TEXT_IS_8BIT) == + mozilla::gfx::ShapedTextFlags::TEXT_IS_8BIT; + } + + int32_t GetAppUnitsPerDevUnit() const { return mAppUnitsPerDevUnit; } + + uint32_t GetLength() const { return mLength; } + + bool FilterIfIgnorable(uint32_t aIndex, uint32_t aCh); + + protected: + // Allocate aCount DetailedGlyphs for the given index + DetailedGlyph* AllocateDetailedGlyphs(uint32_t aCharIndex, uint32_t aCount); + + // Ensure the glyph on the given index is complex glyph so that we can use + // it to record specific characters that layout may need to detect. + void EnsureComplexGlyph(uint32_t aIndex, CompressedGlyph& aGlyph) { + MOZ_ASSERT(GetCharacterGlyphs() + aIndex == &aGlyph); + if (aGlyph.IsSimpleGlyph()) { + DetailedGlyph details = {aGlyph.GetSimpleGlyph(), + (int32_t)aGlyph.GetSimpleAdvance(), + mozilla::gfx::Point()}; + aGlyph.SetComplex(true, true); + SetDetailedGlyphs(aIndex, 1, &details); + } + } + + // For characters whose glyph data does not fit the "simple" glyph criteria + // in CompressedGlyph, we use a sorted array to store the association + // between the source character offset and an index into an array + // DetailedGlyphs. The CompressedGlyph record includes a count of + // the number of DetailedGlyph records that belong to the character, + // starting at the given index. + class DetailedGlyphStore { + public: + DetailedGlyphStore() = default; + + // This is optimized for the most common calling patterns: + // we rarely need random access to the records, access is most commonly + // sequential through the textRun, so we record the last-used index + // and check whether the caller wants the same record again, or the + // next; if not, it's most likely we're starting over from the start + // of the run, so we check the first entry before resorting to binary + // search as a last resort. + // NOTE that this must not be called for a character offset that does + // not have any DetailedGlyph records; callers must have verified that + // mCharacterGlyphs[aOffset].GetGlyphCount() is greater than zero + // before calling this, otherwise the assertions here will fire (in a + // debug build), and we'll probably crash. + DetailedGlyph* Get(uint32_t aOffset) { + NS_ASSERTION(mOffsetToIndex.Length() > 0, "no detailed glyph records!"); + DetailedGlyph* details = mDetails.Elements(); + // check common cases (fwd iteration, initial entry, etc) first + if (mLastUsed < mOffsetToIndex.Length() - 1 && + aOffset == mOffsetToIndex[mLastUsed + 1].mOffset) { + ++mLastUsed; + } else if (aOffset == mOffsetToIndex[0].mOffset) { + mLastUsed = 0; + } else if (aOffset == mOffsetToIndex[mLastUsed].mOffset) { + // do nothing + } else if (mLastUsed > 0 && + aOffset == mOffsetToIndex[mLastUsed - 1].mOffset) { + --mLastUsed; + } else { + mLastUsed = mOffsetToIndex.BinaryIndexOf(aOffset, CompareToOffset()); + } + NS_ASSERTION(mLastUsed != nsTArray<DGRec>::NoIndex, + "detailed glyph record missing!"); + return details + mOffsetToIndex[mLastUsed].mIndex; + } + + DetailedGlyph* Allocate(uint32_t aOffset, uint32_t aCount) { + uint32_t detailIndex = mDetails.Length(); + DetailedGlyph* details = mDetails.AppendElements(aCount); + // We normally set up glyph records sequentially, so the common case + // here is to append new records to the mOffsetToIndex array; + // test for that before falling back to the InsertElementSorted + // method. + if (mOffsetToIndex.Length() == 0 || + aOffset > mOffsetToIndex[mOffsetToIndex.Length() - 1].mOffset) { + mOffsetToIndex.AppendElement(DGRec(aOffset, detailIndex)); + } else { + mOffsetToIndex.InsertElementSorted(DGRec(aOffset, detailIndex), + CompareRecordOffsets()); + } + return details; + } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) { + return aMallocSizeOf(this) + + mDetails.ShallowSizeOfExcludingThis(aMallocSizeOf) + + mOffsetToIndex.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + private: + struct DGRec { + DGRec(const uint32_t& aOffset, const uint32_t& aIndex) + : mOffset(aOffset), mIndex(aIndex) {} + uint32_t mOffset; // source character offset in the textrun + uint32_t mIndex; // index where this char's DetailedGlyphs begin + }; + + struct CompareToOffset { + bool Equals(const DGRec& a, const uint32_t& b) const { + return a.mOffset == b; + } + bool LessThan(const DGRec& a, const uint32_t& b) const { + return a.mOffset < b; + } + }; + + struct CompareRecordOffsets { + bool Equals(const DGRec& a, const DGRec& b) const { + return a.mOffset == b.mOffset; + } + bool LessThan(const DGRec& a, const DGRec& b) const { + return a.mOffset < b.mOffset; + } + }; + + // Concatenated array of all the DetailedGlyph records needed for the + // textRun; individual character offsets are associated with indexes + // into this array via the mOffsetToIndex table. + nsTArray<DetailedGlyph> mDetails; + + // For each character offset that needs DetailedGlyphs, we record the + // index in mDetails where the list of glyphs begins. This array is + // sorted by mOffset. + nsTArray<DGRec> mOffsetToIndex; + + // Records the most recently used index into mOffsetToIndex, so that + // we can support sequential access more quickly than just doing + // a binary search each time. + nsTArray<DGRec>::index_type mLastUsed = 0; + }; + + mozilla::UniquePtr<DetailedGlyphStore> mDetailedGlyphs; + + // Number of char16_t characters and CompressedGlyph glyph records + uint32_t mLength; + + // Shaping flags (direction, ligature-suppression) + mozilla::gfx::ShapedTextFlags mFlags; + + uint16_t mAppUnitsPerDevUnit; +}; + +/* + * gfxShapedWord: an individual (space-delimited) run of text shaped with a + * particular font, without regard to external context. + * + * The glyph data is copied into gfxTextRuns as needed from the cache of + * ShapedWords associated with each gfxFont instance. + */ +class gfxShapedWord final : public gfxShapedText { + public: + typedef mozilla::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, nsAtom* aLanguage, + uint16_t aAppUnitsPerDevUnit, + mozilla::gfx::ShapedTextFlags aFlags, + gfxFontShaper::RoundingFlags aRounding) { + NS_ASSERTION(aLength <= gfxPlatform::GetPlatform()->WordCacheCharLimit(), + "excessive length for gfxShapedWord!"); + + // Compute size needed including the mCharacterGlyphs array + // and a copy of the original text + uint32_t size = offsetof(gfxShapedWord, mCharGlyphsStorage) + + aLength * (sizeof(CompressedGlyph) + sizeof(uint8_t)); + void* storage = malloc(size); + if (!storage) { + return nullptr; + } + + // Construct in the pre-allocated storage, using placement new + return new (storage) gfxShapedWord(aText, aLength, aRunScript, aLanguage, + aAppUnitsPerDevUnit, aFlags, aRounding); + } + + static gfxShapedWord* Create(const char16_t* aText, uint32_t aLength, + Script aRunScript, nsAtom* aLanguage, + uint16_t aAppUnitsPerDevUnit, + mozilla::gfx::ShapedTextFlags aFlags, + gfxFontShaper::RoundingFlags aRounding) { + NS_ASSERTION(aLength <= gfxPlatform::GetPlatform()->WordCacheCharLimit(), + "excessive length for gfxShapedWord!"); + + // In the 16-bit version of Create, if the TEXT_IS_8BIT flag is set, + // then we convert the text to an 8-bit version and call the 8-bit + // Create function instead. + if (aFlags & mozilla::gfx::ShapedTextFlags::TEXT_IS_8BIT) { + nsAutoCString narrowText; + LossyAppendUTF16toASCII(nsDependentSubstring(aText, aLength), narrowText); + return Create((const uint8_t*)(narrowText.BeginReading()), aLength, + aRunScript, aLanguage, aAppUnitsPerDevUnit, aFlags, + aRounding); + } + + uint32_t size = offsetof(gfxShapedWord, mCharGlyphsStorage) + + aLength * (sizeof(CompressedGlyph) + sizeof(char16_t)); + void* storage = malloc(size); + if (!storage) { + return nullptr; + } + + return new (storage) gfxShapedWord(aText, aLength, aRunScript, aLanguage, + aAppUnitsPerDevUnit, aFlags, aRounding); + } + + // Override operator delete to properly free the object that was + // allocated via malloc. + void operator delete(void* p) { free(p); } + + const CompressedGlyph* GetCharacterGlyphs() const override { + return &mCharGlyphsStorage[0]; + } + CompressedGlyph* GetCharacterGlyphs() override { + return &mCharGlyphsStorage[0]; + } + + const uint8_t* Text8Bit() const { + NS_ASSERTION(TextIs8Bit(), "invalid use of Text8Bit()"); + return reinterpret_cast<const uint8_t*>(mCharGlyphsStorage + GetLength()); + } + + const char16_t* TextUnicode() const { + NS_ASSERTION(!TextIs8Bit(), "invalid use of TextUnicode()"); + return reinterpret_cast<const char16_t*>(mCharGlyphsStorage + GetLength()); + } + + char16_t GetCharAt(uint32_t aOffset) const { + NS_ASSERTION(aOffset < GetLength(), "aOffset out of range"); + return TextIs8Bit() ? char16_t(Text8Bit()[aOffset]) + : TextUnicode()[aOffset]; + } + + Script GetScript() const { return mScript; } + nsAtom* GetLanguage() const { return mLanguage.get(); } + + gfxFontShaper::RoundingFlags GetRounding() const { return mRounding; } + + void ResetAge() { mAgeCounter = 0; } + uint32_t IncrementAge() { return ++mAgeCounter; } + + // Helper used when hashing a word for the shaped-word caches + static uint32_t HashMix(uint32_t aHash, char16_t aCh) { + return (aHash >> 28) ^ (aHash << 4) ^ aCh; + } + + private: + // so that gfxTextRun can share our DetailedGlyphStore class + friend class gfxTextRun; + + // Construct storage for a ShapedWord, ready to receive glyph data + gfxShapedWord(const uint8_t* aText, uint32_t aLength, Script aRunScript, + nsAtom* aLanguage, uint16_t aAppUnitsPerDevUnit, + mozilla::gfx::ShapedTextFlags aFlags, + gfxFontShaper::RoundingFlags aRounding) + : gfxShapedText(aLength, + aFlags | mozilla::gfx::ShapedTextFlags::TEXT_IS_8BIT, + aAppUnitsPerDevUnit), + mLanguage(aLanguage), + mScript(aRunScript), + mRounding(aRounding), + mAgeCounter(0) { + memset(mCharGlyphsStorage, 0, aLength * sizeof(CompressedGlyph)); + uint8_t* text = reinterpret_cast<uint8_t*>(&mCharGlyphsStorage[aLength]); + memcpy(text, aText, aLength * sizeof(uint8_t)); + } + + gfxShapedWord(const char16_t* aText, uint32_t aLength, Script aRunScript, + nsAtom* aLanguage, uint16_t aAppUnitsPerDevUnit, + mozilla::gfx::ShapedTextFlags aFlags, + gfxFontShaper::RoundingFlags aRounding) + : gfxShapedText(aLength, aFlags, aAppUnitsPerDevUnit), + mLanguage(aLanguage), + mScript(aRunScript), + mRounding(aRounding), + mAgeCounter(0) { + memset(mCharGlyphsStorage, 0, aLength * sizeof(CompressedGlyph)); + char16_t* text = reinterpret_cast<char16_t*>(&mCharGlyphsStorage[aLength]); + memcpy(text, aText, aLength * sizeof(char16_t)); + SetupClusterBoundaries(0, aText, aLength); + } + + RefPtr<nsAtom> mLanguage; + Script mScript; + + gfxFontShaper::RoundingFlags mRounding; + + 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; + + typedef gfxFontShaper::RoundingFlags RoundingFlags; + + public: + typedef mozilla::FontSlantStyle FontSlantStyle; + + nsrefcnt AddRef(void) { + MOZ_ASSERT(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) { + MOZ_ASSERT(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 : uint8_t { + kAntialiasDefault, + kAntialiasNone, + kAntialiasGrayscale, + kAntialiasSubpixel + } AntialiasOption; + + protected: + nsAutoRefCnt mRefCnt; + + 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(const RefPtr<mozilla::gfx::UnscaledFont>& aUnscaledFont, + gfxFontEntry* aFontEntry, const gfxFontStyle* aFontStyle, + AntialiasOption anAAOption = kAntialiasDefault); + + 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 nsCString& GetName() const { return mFontEntry->Name(); } + const gfxFontStyle* GetStyle() const { return &mStyle; } + + virtual mozilla::UniquePtr<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); + + // whether the specified feature will apply to the given character + bool FeatureWillHandleChar(Script aRunScript, uint32_t aFeature, + uint32_t aUnicode); + + // Subclasses may choose to look up glyph ids for characters. + // If they do not override this, gfxHarfBuzzShaper will fetch the cmap + // table and use that. + virtual bool ProvidesGetGlyph() const { return false; } + // Map unicode character to glyph ID. + // Only used if ProvidesGetGlyph() returns true. + virtual uint32_t GetGlyph(uint32_t unicode, uint32_t variation_selector) { + return 0; + } + // Return the horizontal advance of a glyph. + gfxFloat GetGlyphHAdvance(DrawTarget* aDrawTarget, uint16_t aGID); + + gfxFloat SynthesizeSpaceWidth(uint32_t aCh); + + // Work out whether cairo will snap inter-glyph spacing to pixels + // when rendering to the given drawTarget. + RoundingFlags GetRoundOffsetsToPixels(DrawTarget* aDrawTarget); + + virtual bool ShouldHintMetrics() const { return true; } + virtual bool ShouldRoundXOffset(cairo_t* aCairo) const { return true; } + + // Font metrics + struct Metrics { + gfxFloat capHeight; + gfxFloat xHeight; + gfxFloat strikeoutSize; + gfxFloat strikeoutOffset; + gfxFloat underlineSize; + gfxFloat underlineOffset; + + gfxFloat internalLeading; + gfxFloat externalLeading; + + gfxFloat emHeight; + gfxFloat emAscent; + gfxFloat emDescent; + gfxFloat maxHeight; + gfxFloat maxAscent; + gfxFloat maxDescent; + gfxFloat maxAdvance; + + gfxFloat aveCharWidth; + gfxFloat spaceWidth; + gfxFloat zeroWidth; // -1 if there was no zero glyph + + gfxFloat ZeroOrAveCharWidth() const { + return zeroWidth >= 0 ? zeroWidth : aveCharWidth; + } + }; + + typedef nsFontMetrics::FontOrientation Orientation; + + const Metrics& GetMetrics(Orientation aOrientation) { + if (aOrientation == nsFontMetrics::eHorizontal) { + return GetHorizontalMetrics(); + } + if (!mVerticalMetrics) { + mVerticalMetrics = CreateVerticalMetrics(); + } + return *mVerticalMetrics; + } + + /** + * We let layout specify spacing on either side of any + * character. We need to specify both before and after + * spacing so that substring measurement can do the right things. + * These values are in appunits. They're always an integral number of + * appunits, but we specify them in floats in case very large spacing + * values are required. + */ + struct Spacing { + gfxFloat mBefore; + gfxFloat mAfter; + }; + /** + * Metrics for a particular string + */ + struct RunMetrics { + RunMetrics() { mAdvanceWidth = mAscent = mDescent = 0.0; } + + void CombineWith(const RunMetrics& aOther, bool aOtherIsOnLeft); + + // can be negative (partly due to negative spacing). + // Advance widths should be additive: the advance width of the + // (offset1, length1) plus the advance width of (offset1 + length1, + // length2) should be the advance width of (offset1, length1 + length2) + gfxFloat mAdvanceWidth; + + // For zero-width substrings, these must be zero! + gfxFloat mAscent; // always non-negative + gfxFloat mDescent; // always non-negative + + // Bounding box that is guaranteed to include everything drawn. + // If a tight boundingBox was requested when these metrics were + // generated, this will tightly wrap the glyphs, otherwise it is + // "loose" and may be larger than the true bounding box. + // Coordinates are relative to the baseline left origin, so typically + // mBoundingBox.y == -mAscent + gfxRect mBoundingBox; + }; + + /** + * Draw a series of glyphs to aContext. The direction of aTextRun must + * be honoured. + * @param aStart the first character to draw + * @param aEnd draw characters up to here + * @param aPt the baseline origin; the left end of the baseline + * for LTR textruns, the right end for RTL textruns. + * On return, this will be updated to the other end of the baseline. + * In application units, really! + * @param aRunParams record with drawing parameters, see TextRunDrawParams. + * Particular fields of interest include + * .spacing spacing to insert before and after characters (for RTL + * glyphs, before-spacing is inserted to the right of characters). There + * are aEnd - aStart elements in this array, unless it's null to indicate + * that there is no spacing. + * .drawMode specifies whether the fill or stroke of the glyph should be + * drawn, or if it should be drawn into the current path + * .contextPaint information about how to construct the fill and + * stroke pattern. Can be nullptr if we are not stroking the text, which + * indicates that the current source from context should be used for fill + * .context the Thebes graphics context to which we're drawing + * .dt Moz2D DrawTarget to which we're drawing + * + * Callers guarantee: + * -- aStart and aEnd are aligned to cluster and ligature boundaries + * -- all glyphs use this font + */ + void Draw(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd, + mozilla::gfx::Point* aPt, const TextRunDrawParams& aRunParams, + mozilla::gfx::ShapedTextFlags aOrientation); + + /** + * Draw the emphasis marks for the given text run. Its prerequisite + * and output are similiar to the method Draw(). + * @param aPt the baseline origin of the emphasis marks. + * @param aParams some drawing parameters, see EmphasisMarkDrawParams. + */ + void DrawEmphasisMarks(const gfxTextRun* aShapedText, + mozilla::gfx::Point* aPt, uint32_t aOffset, + uint32_t aCount, + const EmphasisMarkDrawParams& aParams); + + /** + * Measure a run of characters. See gfxTextRun::Metrics. + * @param aTight if false, then return the union of the glyph extents + * with the font-box for the characters (the rectangle with x=0,width= + * the advance width for the character run,y=-(font ascent), and height= + * font ascent + font descent). Otherwise, we must return as tight as possible + * an approximation to the area actually painted by glyphs. + * @param aDrawTargetForTightBoundingBox when aTight is true, this must + * be non-null. + * @param aSpacing spacing to insert before and after glyphs. The bounding box + * need not include the spacing itself, but the spacing affects the glyph + * positions. null if there is no spacing. + * + * Callers guarantee: + * -- aStart and aEnd are aligned to cluster and ligature boundaries + * -- all glyphs use this font + * + * The default implementation just uses font metrics and aTextRun's + * advances, and assumes no characters fall outside the font box. In + * general this is insufficient, because that assumption is not always true. + */ + virtual RunMetrics Measure(const gfxTextRun* aTextRun, uint32_t aStart, + uint32_t aEnd, BoundingBoxType aBoundingBoxType, + DrawTarget* aDrawTargetForTightBoundingBox, + Spacing* aSpacing, + mozilla::gfx::ShapedTextFlags aOrientation); + /** + * Line breaks have been changed at the beginning and/or end of a substring + * of the text. Reshaping may be required; glyph updating is permitted. + * @return true if anything was changed, false otherwise + */ + bool NotifyLineBreaksChanged(gfxTextRun* aTextRun, uint32_t aStart, + uint32_t aLength) { + return false; + } + + // Expiration tracking + nsExpirationState* GetExpirationState() { return &mExpirationState; } + + // Get the glyphID of a space + uint16_t GetSpaceGlyph() { return mSpaceGlyph; } + + gfxGlyphExtents* GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit); + + void SetupGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphID, + bool aNeedTight, gfxGlyphExtents* aExtents); + + virtual bool AllowSubpixelAA() { return true; } + + bool IsSyntheticBold() const { return mApplySyntheticBold; } + + float AngleForSyntheticOblique() const; + float SkewForSyntheticOblique() const; + + // Amount by which synthetic bold "fattens" the glyphs: + // For size S up to a threshold size T, we use (0.25 + 3S / 4T), + // so that the result ranges from 0.25 to 1.0; thereafter, + // simply use (S / T). + gfxFloat GetSyntheticBoldOffset() { + gfxFloat size = GetAdjustedSize(); + const gfxFloat threshold = 48.0; + return size < threshold ? (0.25 + 0.75 * size / threshold) + : (size / threshold); + } + + gfxFontEntry* GetFontEntry() const { return mFontEntry.get(); } + bool HasCharacter(uint32_t ch) { + if (!mIsValid || (mUnicodeRangeMap && !mUnicodeRangeMap->test(ch))) { + return false; + } + return mFontEntry->HasCharacter(ch); + } + + const gfxCharacterMap* GetUnicodeRangeMap() const { + return mUnicodeRangeMap.get(); + } + + void SetUnicodeRangeMap(gfxCharacterMap* aUnicodeRangeMap) { + mUnicodeRangeMap = aUnicodeRangeMap; + } + + uint16_t GetUVSGlyph(uint32_t aCh, uint32_t aVS) { + if (!mIsValid) { + return 0; + } + return mFontEntry->GetUVSGlyph(aCh, aVS); + } + + template <typename T> + bool InitFakeSmallCapsRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun, + const T* aText, uint32_t aOffset, uint32_t aLength, + FontMatchType aMatchType, + mozilla::gfx::ShapedTextFlags aOrientation, + Script aScript, nsAtom* aLanguage, + bool aSyntheticLower, bool aSyntheticUpper); + + // call the (virtual) InitTextRun method to do glyph generation/shaping, + // limiting the length of text passed by processing the run in multiple + // segments if necessary + template <typename T> + bool SplitAndInitTextRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun, + const T* aString, uint32_t aRunStart, + uint32_t aRunLength, Script aRunScript, + nsAtom* aLanguage, + mozilla::gfx::ShapedTextFlags aOrientation); + + // Get a ShapedWord representing the given text (either 8- or 16-bit) + // for use in setting up a gfxTextRun. + template <typename T> + gfxShapedWord* GetShapedWord(DrawTarget* aDrawTarget, const T* aText, + uint32_t aLength, uint32_t aHash, + Script aRunScript, nsAtom* aLanguage, + bool aVertical, int32_t aAppUnitsPerDevUnit, + mozilla::gfx::ShapedTextFlags aFlags, + RoundingFlags aRounding, + gfxTextPerfMetrics* aTextPerf); + + // Ensure the ShapedWord cache is initialized. This MUST be called before + // any attempt to use GetShapedWord(). + void InitWordCache() { + if (!mWordCache) { + mWordCache = mozilla::MakeUnique<nsTHashtable<CacheHashEntry>>(); + } + } + + // Called by the gfxFontCache timer to increment the age of all the words, + // so that they'll expire after a sufficient period of non-use + void AgeCachedWords(); + + // Discard all cached word records; called on memory-pressure notification. + void ClearCachedWords() { + if (mWordCache) { + mWordCache->Clear(); + } + } + + // Glyph rendering/geometry has changed, so invalidate data as necessary. + void NotifyGlyphsChanged(); + + virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const; + virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const; + + typedef enum { + FONT_TYPE_DWRITE, + FONT_TYPE_GDI, + FONT_TYPE_FT2, + FONT_TYPE_MAC, + FONT_TYPE_OS2, + FONT_TYPE_CAIRO, + FONT_TYPE_FONTCONFIG + } FontType; + + virtual FontType GetType() const = 0; + + const RefPtr<mozilla::gfx::UnscaledFont>& GetUnscaledFont() const { + return mUnscaledFont; + } + + virtual already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont( + DrawTarget* aTarget) = 0; + + void InitializeScaledFont(); + + 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. This does not add a reference to the returned font. + gfxFont* GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel); + + bool HasColorGlyphFor(uint32_t aCh, uint32_t aNextCh); + + protected: + virtual const Metrics& GetHorizontalMetrics() = 0; + + mozilla::UniquePtr<const Metrics> CreateVerticalMetrics(); + + // Template parameters for DrawGlyphs/DrawOneGlyph, used to select + // simplified versions of the methods in the most common cases. + enum class FontComplexityT { SimpleFont, ComplexFont }; + enum class SpacingT { NoSpacing, HasSpacing }; + + // Output a run of glyphs at *aPt, which is updated to follow the last glyph + // in the run. This method also takes account of any letter-spacing provided + // in aRunParams. + template <FontComplexityT FC, SpacingT S> + bool DrawGlyphs(const gfxShapedText* aShapedText, + uint32_t aOffset, // offset in the textrun + uint32_t aCount, // length of run to draw + mozilla::gfx::Point* aPt, + // transform for mOffset field in DetailedGlyph records, + // to account for rotations (may be null) + const mozilla::gfx::Matrix* aOffsetMatrix, + GlyphBufferAzure& aBuffer); + + // Output a single glyph at *aPt. + // Normal glyphs are simply accumulated in aBuffer until it is full and + // gets flushed, but SVG or color-font glyphs will instead be rendered + // directly to the destination (found from the buffer's parameters). + template <FontComplexityT FC> + void DrawOneGlyph(uint32_t aGlyphID, const mozilla::gfx::Point& aPt, + GlyphBufferAzure& aBuffer, bool* aEmittedGlyphs); + + // Helper for DrawOneGlyph to handle missing glyphs, rendering either + // nothing (for default-ignorables) or a missing-glyph hexbox. + bool DrawMissingGlyph(const TextRunDrawParams& aRunParams, + const FontDrawParams& aFontParams, + const gfxShapedText::DetailedGlyph* aDetails, + const mozilla::gfx::Point& aPt); + + // set the font size and offset used for + // synthetic subscript/superscript glyphs + void CalculateSubSuperSizeAndOffset(int32_t aAppUnitsPerDevPixel, + gfxFloat& aSubSuperSizeRatio, + float& aBaselineOffset); + + // Return a font that is a "clone" of this one, but reduced to 80% size + // (and with variantCaps set to normal). This does not add a reference to + // the returned font. + gfxFont* GetSmallCapsFont(); + + // subclasses may provide (possibly hinted) glyph widths (in font units); + // if they do not override this, harfbuzz will use unhinted widths + // derived from the font tables + virtual bool ProvidesGlyphWidths() const { return false; } + + // The return value is interpreted as a horizontal advance in 16.16 fixed + // point format. + virtual int32_t GetGlyphWidth(uint16_t aGID) { return -1; } + + virtual bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, + bool aTight = false) { + return false; + } + + bool IsSpaceGlyphInvisible(DrawTarget* aRefDrawTarget, + const gfxTextRun* aTextRun); + + void AddGlyphChangeObserver(GlyphChangeObserver* aObserver); + void RemoveGlyphChangeObserver(GlyphChangeObserver* aObserver); + + // whether font contains substitution lookups containing spaces + bool HasSubstitutionRulesWithSpaceLookups(Script aRunScript); + + // do spaces participate in shaping rules? if so, can't used word cache + // Note that this function uses HasGraphiteSpaceContextuals, so it can only + // return a "hint" to the correct answer. The calling code must ensure it + // performs safe actions independent of the value returned. + tainted_boolean_hint SpaceMayParticipateInShaping(Script aRunScript); + + // For 8-bit text, expand to 16-bit and then call the following method. + bool ShapeText(DrawTarget* aContext, const uint8_t* aText, + uint32_t aOffset, // dest offset in gfxShapedText + uint32_t aLength, Script aScript, nsAtom* aLanguage, + bool aVertical, RoundingFlags aRounding, + gfxShapedText* aShapedText); // where to store the result + + // Call the appropriate shaper to generate glyphs for aText and store + // them into aShapedText. + virtual bool ShapeText(DrawTarget* aContext, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, + RoundingFlags aRounding, gfxShapedText* aShapedText); + + // Helper to adjust for synthetic bold and set character-type flags + // in the shaped text; implementations of ShapeText should call this + // after glyph shaping has been completed. + void PostShapingFixup(DrawTarget* aContext, const char16_t* aText, + uint32_t aOffset, // position within aShapedText + uint32_t aLength, bool aVertical, + gfxShapedText* aShapedText); + + // Shape text directly into a range within a textrun, without using the + // font's word cache. Intended for use when the font has layout features + // that involve space, and therefore require shaping complete runs rather + // than isolated words, or for long strings that are inefficient to cache. + // This will split the text on "invalid" characters (tab/newline) that are + // not handled via normal shaping, but does not otherwise divide up the + // text. + template <typename T> + bool ShapeTextWithoutWordCache(DrawTarget* aDrawTarget, const T* aText, + uint32_t aOffset, uint32_t aLength, + Script aScript, nsAtom* aLanguage, + bool aVertical, RoundingFlags aRounding, + gfxTextRun* aTextRun); + + // Shape a fragment of text (a run that is known to contain only + // "valid" characters, no newlines/tabs/other control chars). + // All non-wordcache shaping goes through here; this is the function + // that will ensure we don't pass excessively long runs to the various + // platform shapers. + template <typename T> + bool ShapeFragmentWithoutWordCache(DrawTarget* aDrawTarget, const T* aText, + uint32_t aOffset, uint32_t aLength, + Script aScript, nsAtom* aLanguage, + bool aVertical, RoundingFlags aRounding, + gfxTextRun* aTextRun); + + void CheckForFeaturesInvolvingSpace(); + + // whether a given feature is included in feature settings from both the + // font and the style. aFeatureOn set if resolved feature value is non-zero + bool HasFeatureSet(uint32_t aFeature, bool& aFeatureOn); + + // used when analyzing whether a font has space contextual lookups + static nsDataHashtable<nsUint32HashKey, Script>* sScriptTagToCode; + static nsTHashtable<nsUint32HashKey>* sDefaultFeatures; + + RefPtr<gfxFontEntry> mFontEntry; + + struct CacheHashKey { + union { + const uint8_t* mSingle; + const char16_t* mDouble; + } mText; + uint32_t mLength; + mozilla::gfx::ShapedTextFlags mFlags; + Script mScript; + RefPtr<nsAtom> mLanguage; + int32_t mAppUnitsPerDevUnit; + PLDHashNumber mHashKey; + bool mTextIs8Bit; + RoundingFlags mRounding; + + CacheHashKey(const uint8_t* aText, uint32_t aLength, uint32_t aStringHash, + Script aScriptCode, nsAtom* aLanguage, + int32_t aAppUnitsPerDevUnit, + mozilla::gfx::ShapedTextFlags aFlags, RoundingFlags aRounding) + : mLength(aLength), + mFlags(aFlags), + mScript(aScriptCode), + mLanguage(aLanguage), + mAppUnitsPerDevUnit(aAppUnitsPerDevUnit), + mHashKey(aStringHash + static_cast<int32_t>(aScriptCode) + + aAppUnitsPerDevUnit * 0x100 + uint16_t(aFlags) * 0x10000 + + int(aRounding) + (aLanguage ? aLanguage->hash() : 0)), + mTextIs8Bit(true), + mRounding(aRounding) { + NS_ASSERTION(aFlags & mozilla::gfx::ShapedTextFlags::TEXT_IS_8BIT, + "8-bit flag should have been set"); + mText.mSingle = aText; + } + + CacheHashKey(const char16_t* aText, uint32_t aLength, uint32_t aStringHash, + Script aScriptCode, nsAtom* aLanguage, + int32_t aAppUnitsPerDevUnit, + mozilla::gfx::ShapedTextFlags aFlags, RoundingFlags aRounding) + : mLength(aLength), + mFlags(aFlags), + mScript(aScriptCode), + mLanguage(aLanguage), + mAppUnitsPerDevUnit(aAppUnitsPerDevUnit), + mHashKey(aStringHash + static_cast<int32_t>(aScriptCode) + + aAppUnitsPerDevUnit * 0x100 + uint16_t(aFlags) * 0x10000 + + int(aRounding)), + mTextIs8Bit(false), + mRounding(aRounding) { + // We can NOT assert that TEXT_IS_8BIT is false in aFlags here, + // because this might be an 8bit-only word from a 16-bit textrun, + // in which case the text we're passed is still in 16-bit form, + // and we'll have to use an 8-to-16bit comparison in KeyEquals. + mText.mDouble = aText; + } + }; + + class CacheHashEntry : public PLDHashEntryHdr { + public: + typedef const CacheHashKey& KeyType; + typedef const CacheHashKey* KeyTypePointer; + + // When constructing a new entry in the hashtable, the caller of Put() + // will fill us in. + explicit CacheHashEntry(KeyTypePointer aKey) {} + CacheHashEntry(const CacheHashEntry&) = delete; + CacheHashEntry& operator=(const CacheHashEntry&) = delete; + CacheHashEntry(CacheHashEntry&&) = default; + CacheHashEntry& operator=(CacheHashEntry&&) = default; + + bool KeyEquals(const KeyTypePointer aKey) const; + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + + static PLDHashNumber HashKey(const KeyTypePointer aKey) { + return aKey->mHashKey; + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(mShapedWord.get()); + } + + enum { ALLOW_MEMMOVE = true }; + + mozilla::UniquePtr<gfxShapedWord> mShapedWord; + }; + + mozilla::UniquePtr<nsTHashtable<CacheHashEntry>> mWordCache; + + static const uint32_t kShapedWordCacheMaxAge = 3; + + nsTArray<mozilla::UniquePtr<gfxGlyphExtents>> mGlyphExtentsArray; + mozilla::UniquePtr<nsTHashtable<nsPtrHashKey<GlyphChangeObserver>>> + mGlyphChangeObservers; + + // a copy of the font without antialiasing, if needed for separate + // measurement by mathml code + mozilla::UniquePtr<gfxFont> mNonAAFont; + + // we create either or both of these shapers when needed, depending + // whether the font has graphite tables, and whether graphite shaping + // is actually enabled + mozilla::UniquePtr<gfxFontShaper> mHarfBuzzShaper; + mozilla::UniquePtr<gfxFontShaper> mGraphiteShaper; + + // if a userfont with unicode-range specified, contains map of *possible* + // ranges supported by font + RefPtr<gfxCharacterMap> mUnicodeRangeMap; + + RefPtr<mozilla::gfx::UnscaledFont> mUnscaledFont; + RefPtr<mozilla::gfx::ScaledFont> mAzureScaledFont; + + // For vertical metrics, created on demand. + mozilla::UniquePtr<const Metrics> mVerticalMetrics; + + // Table used for MathML layout. + mozilla::UniquePtr<gfxMathTable> mMathTable; + + gfxFontStyle mStyle; + 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; + + nsExpirationState mExpirationState; + + // Glyph ID of the font's <space> glyph, zero if missing + uint16_t mSpaceGlyph = 0; + + // the AA setting requested for this font - may affect glyph bounds + AntialiasOption mAntialiasOption; + + bool mIsValid; + + // use synthetic bolding for environments where this is not supported + // by the platform + bool mApplySyntheticBold; + + bool mKerningSet; // kerning explicitly set? + bool mKerningEnabled; // if set, on or off? + + bool mMathInitialized; // TryGetMathTable() called? + + // Helper for subclasses that want to initialize standard metrics from the + // tables of sfnt (TrueType/OpenType) fonts. + // This will use mFUnitsConvFactor if it is already set, else compute it + // from mAdjustedSize and the unitsPerEm in the font's 'head' table. + // Returns TRUE and sets mIsValid=TRUE if successful; + // Returns TRUE but leaves mIsValid=FALSE if the font seems to be broken. + // Returns FALSE if the font does not appear to be an sfnt at all, + // and should be handled (if possible) using other APIs. + bool InitMetricsFromSfntTables(Metrics& aMetrics); + + // Helper to calculate various derived metrics from the results of + // InitMetricsFromSfntTables or equivalent platform code + void CalculateDerivedMetrics(Metrics& aMetrics); + + // some fonts have bad metrics, this method sanitize them. + // if this font has bad underline offset, aIsBadUnderlineFont should be true. + void SanitizeMetrics(Metrics* aMetrics, bool aIsBadUnderlineFont); + + bool RenderSVGGlyph(gfxContext* aContext, + mozilla::layout::TextDrawTarget* aTextDrawer, + mozilla::gfx::Point aPoint, uint32_t aGlyphId, + SVGContextPaint* aContextPaint) const; + bool RenderSVGGlyph(gfxContext* aContext, + mozilla::layout::TextDrawTarget* aTextDrawer, + mozilla::gfx::Point aPoint, uint32_t aGlyphId, + SVGContextPaint* aContextPaint, + gfxTextRunDrawCallbacks* aCallbacks, + bool& aEmittedGlyphs) const; + + bool RenderColorGlyph(DrawTarget* aDrawTarget, gfxContext* aContext, + mozilla::layout::TextDrawTarget* aTextDrawer, + mozilla::gfx::ScaledFont* scaledFont, + 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 mozilla::gfx::Float CalcXScale(DrawTarget* aDrawTarget); +}; + +// proportion of ascent used for x-height, if unable to read value from font +#define DEFAULT_XHEIGHT_FACTOR 0.56f + +// Parameters passed to gfxFont methods for drawing glyphs from a textrun. +// The TextRunDrawParams are set up once per textrun; the FontDrawParams +// are dependent on the specific font, so they are set per GlyphRun. + +struct MOZ_STACK_CLASS TextRunDrawParams { + RefPtr<mozilla::gfx::DrawTarget> dt; + gfxContext* context; + gfxFont::Spacing* spacing; + gfxTextRunDrawCallbacks* callbacks; + mozilla::SVGContextPaint* runContextPaint; + mozilla::gfx::Float 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 MOZ_STACK_CLASS FontDrawParams { + RefPtr<mozilla::gfx::ScaledFont> scaledFont; + mozilla::SVGContextPaint* contextPaint; + mozilla::gfx::Float synBoldOnePixelOffset; + mozilla::gfx::Float obliqueSkew; + int32_t extraStrikes; + mozilla::gfx::DrawOptions drawOptions; + gfxFloat advanceDirection; + bool isVerticalFont; + bool haveSVGGlyphs; + bool haveColorGlyphs; +}; + +struct MOZ_STACK_CLASS EmphasisMarkDrawParams { + gfxContext* context; + gfxFont::Spacing* spacing; + gfxTextRun* mark; + gfxFloat advance; + gfxFloat direction; + bool isVertical; +}; + +#endif diff --git a/gfx/thebes/gfxFontConstants.h b/gfx/thebes/gfxFontConstants.h new file mode 100644 index 0000000000..8a816247c5 --- /dev/null +++ b/gfx/thebes/gfxFontConstants.h @@ -0,0 +1,186 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* font constants shared by both thebes and layout */ + +#ifndef GFX_FONT_CONSTANTS_H +#define GFX_FONT_CONSTANTS_H + +/* + * This file is separate from gfxFont.h so that layout can include it + * without bringing in gfxFont.h and everything it includes. + */ + +#define NS_FONT_STYLE_NORMAL 0 +#define NS_FONT_STYLE_ITALIC 1 +#define NS_FONT_STYLE_OBLIQUE 2 + +#define NS_FONT_WEIGHT_NORMAL 400 +#define NS_FONT_WEIGHT_BOLD 700 +#define NS_FONT_WEIGHT_THIN 100 + +#define NS_FONT_STRETCH_ULTRA_CONDENSED 50 +#define NS_FONT_STRETCH_EXTRA_CONDENSED 62 +#define NS_FONT_STRETCH_CONDENSED 75 +#define NS_FONT_STRETCH_SEMI_CONDENSED 87 +#define NS_FONT_STRETCH_NORMAL 100 +#define NS_FONT_STRETCH_SEMI_EXPANDED 112 +#define NS_FONT_STRETCH_EXPANDED 125 +#define NS_FONT_STRETCH_EXTRA_EXPANDED 150 +#define NS_FONT_STRETCH_ULTRA_EXPANDED 200 + +#define NS_FONT_SMOOTHING_AUTO 0 +#define NS_FONT_SMOOTHING_GRAYSCALE 1 + +#define NS_FONT_KERNING_AUTO 0 +#define NS_FONT_KERNING_NONE 1 +#define NS_FONT_KERNING_NORMAL 2 + +#define NS_FONT_SYNTHESIS_WEIGHT 0x1 +#define NS_FONT_SYNTHESIS_STYLE 0x2 + +#define NS_FONT_OPTICAL_SIZING_AUTO 0 +#define NS_FONT_OPTICAL_SIZING_NONE 1 + +#define NS_FONT_VARIANT_ALTERNATES_NORMAL 0 +// alternates - simple enumerated values +#define NS_FONT_VARIANT_ALTERNATES_HISTORICAL (1 << 0) + +// alternates - values that use functional syntax +#define NS_FONT_VARIANT_ALTERNATES_STYLISTIC (1 << 1) +#define NS_FONT_VARIANT_ALTERNATES_STYLESET (1 << 2) +#define NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT (1 << 3) +#define NS_FONT_VARIANT_ALTERNATES_SWASH (1 << 4) +#define NS_FONT_VARIANT_ALTERNATES_ORNAMENTS (1 << 5) +#define NS_FONT_VARIANT_ALTERNATES_ANNOTATION (1 << 6) +#define NS_FONT_VARIANT_ALTERNATES_COUNT 7 + +#define NS_FONT_VARIANT_ALTERNATES_ENUMERATED_MASK \ + NS_FONT_VARIANT_ALTERNATES_HISTORICAL + +#define NS_FONT_VARIANT_ALTERNATES_FUNCTIONAL_MASK \ + (NS_FONT_VARIANT_ALTERNATES_STYLISTIC | \ + NS_FONT_VARIANT_ALTERNATES_STYLESET | \ + NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT | \ + NS_FONT_VARIANT_ALTERNATES_SWASH | NS_FONT_VARIANT_ALTERNATES_ORNAMENTS | \ + NS_FONT_VARIANT_ALTERNATES_ANNOTATION) + +#define NS_FONT_VARIANT_CAPS_NORMAL 0 +#define NS_FONT_VARIANT_CAPS_SMALLCAPS 1 +#define NS_FONT_VARIANT_CAPS_ALLSMALL 2 +#define NS_FONT_VARIANT_CAPS_PETITECAPS 3 +#define NS_FONT_VARIANT_CAPS_ALLPETITE 4 +#define NS_FONT_VARIANT_CAPS_TITLING 5 +#define NS_FONT_VARIANT_CAPS_UNICASE 6 + +#define NS_FONT_VARIANT_EAST_ASIAN_NORMAL 0 +#define NS_FONT_VARIANT_EAST_ASIAN_JIS78 (1 << 0) +#define NS_FONT_VARIANT_EAST_ASIAN_JIS83 (1 << 1) +#define NS_FONT_VARIANT_EAST_ASIAN_JIS90 (1 << 2) +#define NS_FONT_VARIANT_EAST_ASIAN_JIS04 (1 << 3) +#define NS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED (1 << 4) +#define NS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL (1 << 5) +#define NS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH (1 << 6) +#define NS_FONT_VARIANT_EAST_ASIAN_PROP_WIDTH (1 << 7) +#define NS_FONT_VARIANT_EAST_ASIAN_RUBY (1 << 8) +#define NS_FONT_VARIANT_EAST_ASIAN_COUNT 9 + +#define NS_FONT_VARIANT_EAST_ASIAN_VARIANT_MASK \ + (NS_FONT_VARIANT_EAST_ASIAN_JIS78 | NS_FONT_VARIANT_EAST_ASIAN_JIS83 | \ + NS_FONT_VARIANT_EAST_ASIAN_JIS90 | NS_FONT_VARIANT_EAST_ASIAN_JIS04 | \ + NS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED | \ + NS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL) + +#define NS_FONT_VARIANT_EAST_ASIAN_WIDTH_MASK \ + (NS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH | \ + NS_FONT_VARIANT_EAST_ASIAN_PROP_WIDTH) + +#define NS_FONT_VARIANT_LIGATURES_NORMAL 0 +#define NS_FONT_VARIANT_LIGATURES_NONE (1 << 0) +#define NS_FONT_VARIANT_LIGATURES_COMMON (1 << 1) +#define NS_FONT_VARIANT_LIGATURES_NO_COMMON (1 << 2) +#define NS_FONT_VARIANT_LIGATURES_DISCRETIONARY (1 << 3) +#define NS_FONT_VARIANT_LIGATURES_NO_DISCRETIONARY (1 << 4) +#define NS_FONT_VARIANT_LIGATURES_HISTORICAL (1 << 5) +#define NS_FONT_VARIANT_LIGATURES_NO_HISTORICAL (1 << 6) +#define NS_FONT_VARIANT_LIGATURES_CONTEXTUAL (1 << 7) +#define NS_FONT_VARIANT_LIGATURES_NO_CONTEXTUAL (1 << 8) +#define NS_FONT_VARIANT_LIGATURES_COUNT 9 + +#define NS_FONT_VARIANT_LIGATURES_COMMON_MASK \ + (NS_FONT_VARIANT_LIGATURES_COMMON | NS_FONT_VARIANT_LIGATURES_NO_COMMON) + +#define NS_FONT_VARIANT_LIGATURES_DISCRETIONARY_MASK \ + (NS_FONT_VARIANT_LIGATURES_DISCRETIONARY | \ + NS_FONT_VARIANT_LIGATURES_NO_DISCRETIONARY) + +#define NS_FONT_VARIANT_LIGATURES_HISTORICAL_MASK \ + (NS_FONT_VARIANT_LIGATURES_HISTORICAL | \ + NS_FONT_VARIANT_LIGATURES_NO_HISTORICAL) + +#define NS_FONT_VARIANT_LIGATURES_CONTEXTUAL_MASK \ + NS_FONT_VARIANT_LIGATURES_CONTEXTUAL | NS_FONT_VARIANT_LIGATURES_NO_CONTEXTUAL + +#define NS_FONT_VARIANT_NUMERIC_NORMAL 0 +#define NS_FONT_VARIANT_NUMERIC_LINING (1 << 0) +#define NS_FONT_VARIANT_NUMERIC_OLDSTYLE (1 << 1) +#define NS_FONT_VARIANT_NUMERIC_PROPORTIONAL (1 << 2) +#define NS_FONT_VARIANT_NUMERIC_TABULAR (1 << 3) +#define NS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS (1 << 4) +#define NS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS (1 << 5) +#define NS_FONT_VARIANT_NUMERIC_SLASHZERO (1 << 6) +#define NS_FONT_VARIANT_NUMERIC_ORDINAL (1 << 7) +#define NS_FONT_VARIANT_NUMERIC_COUNT 8 + +#define NS_FONT_VARIANT_NUMERIC_FIGURE_MASK \ + NS_FONT_VARIANT_NUMERIC_LINING | NS_FONT_VARIANT_NUMERIC_OLDSTYLE + +#define NS_FONT_VARIANT_NUMERIC_SPACING_MASK \ + NS_FONT_VARIANT_NUMERIC_PROPORTIONAL | NS_FONT_VARIANT_NUMERIC_TABULAR + +#define NS_FONT_VARIANT_NUMERIC_FRACTION_MASK \ + NS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS | \ + NS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS + +#define NS_FONT_VARIANT_POSITION_NORMAL 0 +#define NS_FONT_VARIANT_POSITION_SUPER 1 +#define NS_FONT_VARIANT_POSITION_SUB 2 + +#define NS_FONT_VARIANT_WIDTH_NORMAL 0 +#define NS_FONT_VARIANT_WIDTH_FULL 1 +#define NS_FONT_VARIANT_WIDTH_HALF 2 +#define NS_FONT_VARIANT_WIDTH_THIRD 3 +#define NS_FONT_VARIANT_WIDTH_QUARTER 4 + +// based on fixed offset values used within WebKit +#define NS_FONT_SUBSCRIPT_OFFSET_RATIO (0.20) +#define NS_FONT_SUPERSCRIPT_OFFSET_RATIO (0.34) + +// this roughly corresponds to font-size: smaller behavior +// at smaller sizes <20px the ratio is closer to 0.8 while at +// larger sizes >45px the ratio is closer to 0.667 and in between +// a blend of values is used +#define NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL (0.82) +#define NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE (0.667) +#define NS_FONT_SUB_SUPER_SMALL_SIZE (20.0) +#define NS_FONT_SUB_SUPER_LARGE_SIZE (45.0) + +// pref lang id's for font prefs +enum eFontPrefLang { +#define FONT_PREF_LANG(enum_id_, str_, atom_id_) eFontPrefLang_##enum_id_ +#include "gfxFontPrefLangList.h" +#undef FONT_PREF_LANG + + , + eFontPrefLang_CJKSet // special code for CJK set + , + eFontPrefLang_Emoji // special code for emoji presentation + , + eFontPrefLang_First = eFontPrefLang_Western, + eFontPrefLang_Last = eFontPrefLang_Others, + eFontPrefLang_Count = (eFontPrefLang_Last - eFontPrefLang_First + 1) +}; + +#endif diff --git a/gfx/thebes/gfxFontEntry.cpp b/gfx/thebes/gfxFontEntry.cpp new file mode 100644 index 0000000000..7018d5b09a --- /dev/null +++ b/gfx/thebes/gfxFontEntry.cpp @@ -0,0 +1,2173 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxFontEntry.h" + +#include "GeckoProfiler.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/MathAlgorithms.h" + +#include "mozilla/Logging.h" + +#include "gfxTextRun.h" +#include "gfxPlatform.h" +#include "nsGkAtoms.h" + +#include "gfxTypes.h" +#include "gfxContext.h" +#include "gfxFontConstants.h" +#include "gfxGraphiteShaper.h" +#include "gfxHarfBuzzShaper.h" +#include "gfxUserFontSet.h" +#include "gfxPlatformFontList.h" +#include "nsUnicodeProperties.h" +#include "nsMathUtils.h" +#include "nsBidiUtils.h" +#include "nsStyleConsts.h" +#include "mozilla/AppUnits.h" +#include "mozilla/FloatingPoint.h" +#ifdef MOZ_WASM_SANDBOXING_GRAPHITE +# include "mozilla/ipc/LibrarySandboxPreload.h" +#endif +#include "mozilla/Likely.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Preferences.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/Telemetry.h" +#include "gfxSVGGlyphs.h" +#include "gfx2DGlue.h" + +#include "harfbuzz/hb.h" +#include "harfbuzz/hb-ot.h" +#include "graphite2/Font.h" + +#include "ThebesRLBox.h" + +#include <algorithm> + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::unicode; +using mozilla::services::GetObserverService; + +void gfxCharacterMap::NotifyReleased() { + gfxPlatformFontList* fontlist = gfxPlatformFontList::PlatformFontList(); + if (mShared) { + fontlist->RemoveCmap(this); + } + delete this; +} + +gfxFontEntry::gfxFontEntry() + : mFixedPitch(false), + mIsBadUnderlineFont(false), + mIsUserFontContainer(false), + mIsDataUserFont(false), + mIsLocalUserFont(false), + mStandardFace(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), + mHasGraphiteTables(false), + mCheckedForGraphiteTables(false), + mHasCmapTable(false), + mGrFaceInitialized(false), + mCheckedForColorGlyph(false), + mCheckedForVariationAxes(false), + mHasColorBitmapTable(false), + mCheckedForColorBitmapTables(false) { + memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures)); + memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures)); +} + +gfxFontEntry::gfxFontEntry(const nsACString& aName, bool aIsStandardFace) + : mName(aName), + mFixedPitch(false), + mIsBadUnderlineFont(false), + mIsUserFontContainer(false), + mIsDataUserFont(false), + mIsLocalUserFont(false), + mStandardFace(aIsStandardFace), + mIgnoreGDEF(false), + mIgnoreGSUB(false), + mSVGInitialized(false), + mHasSpaceFeaturesInitialized(false), + mHasSpaceFeatures(false), + mHasSpaceFeaturesKerning(false), + mHasSpaceFeaturesNonKerning(false), + mSkipDefaultFeatureSpaceCheck(false), + mGraphiteSpaceContextualsInitialized(false), + mHasGraphiteSpaceContextuals(false), + mSpaceGlyphIsInvisible(false), + mSpaceGlyphIsInvisibleInitialized(false), + mHasGraphiteTables(false), + mCheckedForGraphiteTables(false), + mHasCmapTable(false), + mGrFaceInitialized(false), + mCheckedForColorGlyph(false), + mCheckedForVariationAxes(false), + mHasColorBitmapTable(false), + mCheckedForColorBitmapTables(false) { + memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures)); + memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures)); +} + +gfxFontEntry::~gfxFontEntry() { + // Should not be dropped by stylo + MOZ_ASSERT(NS_IsMainThread()); + hb_blob_destroy(mCOLR); + hb_blob_destroy(mCPAL); + if (TrakTableInitialized()) { + // Only if it was initialized, so that we don't try to call hb_blob_destroy + // on the kTrakTableUninitialized flag value! + hb_blob_destroy(mTrakTable); + } + + // 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); +} + +void gfxFontEntry::InitializeFrom(fontlist::Face* aFace, + const fontlist::Family* aFamily) { + mStyleRange = aFace->mStyle; + mWeightRange = aFace->mWeight; + mStretchRange = aFace->mStretch; + mFixedPitch = aFace->mFixedPitch; + mIsBadUnderlineFont = aFamily->IsBadUnderlineFamily(); + mShmemFace = aFace; + auto* list = gfxPlatformFontList::PlatformFontList()->SharedFontList(); + mFamilyName = aFamily->DisplayName().AsString(list); + mHasCmapTable = TrySetShmemCharacterMap(); +} + +bool gfxFontEntry::TrySetShmemCharacterMap() { + MOZ_ASSERT(mShmemFace); + auto list = gfxPlatformFontList::PlatformFontList()->SharedFontList(); + mShmemCharacterMap = + static_cast<const SharedBitSet*>(mShmemFace->mCharacterMap.ToPtr(list)); + return mShmemCharacterMap != nullptr; +} + +bool gfxFontEntry::TestCharacterMap(uint32_t aCh) { + if (!mCharacterMap && !mShmemCharacterMap) { + ReadCMAP(); + MOZ_ASSERT(mCharacterMap || mShmemCharacterMap, + "failed to initialize character map"); + } + return mShmemCharacterMap ? mShmemCharacterMap->test(aCh) + : mCharacterMap->test(aCh); +} + +nsresult gfxFontEntry::InitializeUVSMap() { + // mUVSOffset will not be initialized + // until cmap is initialized. + if (!mCharacterMap && !mShmemCharacterMap) { + ReadCMAP(); + NS_ASSERTION(mCharacterMap || mShmemCharacterMap, + "failed to initialize character map"); + } + + if (!mUVSOffset) { + return NS_ERROR_FAILURE; + } + + if (!mUVSData) { + const uint32_t kCmapTag = TRUETYPE_TAG('c', 'm', 'a', 'p'); + AutoTable cmapTable(this, kCmapTag); + if (!cmapTable) { + mUVSOffset = 0; // don't bother to read the table again + return NS_ERROR_FAILURE; + } + + UniquePtr<uint8_t[]> uvsData; + unsigned int cmapLen; + const char* cmapData = hb_blob_get_data(cmapTable, &cmapLen); + nsresult rv = gfxFontUtils::ReadCMAPTableFormat14( + (const uint8_t*)cmapData + mUVSOffset, cmapLen - mUVSOffset, uvsData); + + if (NS_FAILED(rv)) { + mUVSOffset = 0; // don't bother to read the table again + return rv; + } + + mUVSData = std::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, + uint32_t aNumTags) { + hb_face_t* face = GetHBFace(); + if (!face) { + return false; + } + + unsigned int index; + hb_tag_t chosenScript; + bool found = hb_ot_layout_table_select_script( + face, TRUETYPE_TAG('G', 'S', 'U', 'B'), aNumTags, aScriptTags, &index, + &chosenScript); + 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; +} + +nsCString gfxFontEntry::RealFaceName() { + AutoTable nameTable(this, TRUETYPE_TAG('n', 'a', 'm', 'e')); + if (nameTable) { + nsAutoCString name; + nsresult rv = gfxFontUtils::GetFullNameFromTable(nameTable, name); + if (NS_SUCCEEDED(rv)) { + return std::move(name); + } + } + return Name(); +} + +gfxFont* gfxFontEntry::FindOrMakeFont(const gfxFontStyle* aStyle, + gfxCharacterMap* aUnicodeRangeMap) { + // the font entry name is the psname, not the family name + gfxFont* font = + gfxFontCache::GetCache()->Lookup(this, aStyle, aUnicodeRangeMap); + + if (!font) { + gfxFont* newFont = CreateFontInstance(aStyle); + if (!newFont) { + return nullptr; + } + if (!newFont->Valid()) { + delete newFont; + return nullptr; + } + font = newFont; + font->SetUnicodeRangeMap(aUnicodeRangeMap); + gfxFontCache::GetCache()->AddNew(font); + } + return font; +} + +uint16_t gfxFontEntry::UnitsPerEm() { + if (!mUnitsPerEm) { + AutoTable headTable(this, TRUETYPE_TAG('h', 'e', 'a', 'd')); + if (headTable) { + uint32_t len; + const HeadTable* head = + reinterpret_cast<const HeadTable*>(hb_blob_get_data(headTable, &len)); + if (len >= sizeof(HeadTable)) { + mUnitsPerEm = head->unitsPerEm; + } + } + + // if we didn't find a usable 'head' table, or if the value was + // outside the valid range, record it as invalid + if (mUnitsPerEm < kMinUPEM || mUnitsPerEm > kMaxUPEM) { + mUnitsPerEm = kInvalidUPEM; + } + } + return mUnitsPerEm; +} + +bool gfxFontEntry::HasSVGGlyph(uint32_t aGlyphId) { + NS_ASSERTION(mSVGInitialized, + "SVG data has not yet been loaded. TryGetSVGData() first."); + return mSVGGlyphs->HasSVGGlyph(aGlyphId); +} + +bool gfxFontEntry::GetSVGGlyphExtents(DrawTarget* aDrawTarget, + uint32_t aGlyphId, gfxFloat aSize, + gfxRect* aResult) { + MOZ_ASSERT(mSVGInitialized, + "SVG data has not yet been loaded. TryGetSVGData() first."); + MOZ_ASSERT(mUnitsPerEm >= kMinUPEM && mUnitsPerEm <= kMaxUPEM, + "font has invalid unitsPerEm"); + + gfxMatrix svgToApp(aSize / mUnitsPerEm, 0, 0, aSize / mUnitsPerEm, 0, 0); + return mSVGGlyphs->GetGlyphExtents(aGlyphId, svgToApp, aResult); +} + +void gfxFontEntry::RenderSVGGlyph(gfxContext* aContext, uint32_t aGlyphId, + SVGContextPaint* aContextPaint) { + NS_ASSERTION(mSVGInitialized, + "SVG data has not yet been loaded. TryGetSVGData() first."); + mSVGGlyphs->RenderGlyph(aContext, aGlyphId, aContextPaint); +} + +bool gfxFontEntry::TryGetSVGData(gfxFont* aFont) { + if (!gfxPlatform::GetPlatform()->OpenTypeSVGEnabled()) { + return false; + } + + if (!mSVGInitialized) { + mSVGInitialized = true; + + // If UnitsPerEm is not known/valid, we can't use SVG glyphs + if (UnitsPerEm() == kInvalidUPEM) { + return false; + } + + // We don't use AutoTable here because we'll pass ownership of this + // blob to the gfxSVGGlyphs, once we've confirmed the table exists + hb_blob_t* svgTable = GetFontTable(TRUETYPE_TAG('S', 'V', 'G', ' ')); + if (!svgTable) { + return false; + } + + // gfxSVGGlyphs will hb_blob_destroy() the table when it is finished + // with it. + mSVGGlyphs = MakeUnique<gfxSVGGlyphs>(svgTable, this); + } + + if (mSVGGlyphs && !mFontsUsingSVGGlyphs.Contains(aFont)) { + mFontsUsingSVGGlyphs.AppendElement(aFont); + } + + return !!mSVGGlyphs; +} + +void gfxFontEntry::NotifyFontDestroyed(gfxFont* aFont) { + mFontsUsingSVGGlyphs.RemoveElement(aFont); +} + +void gfxFontEntry::NotifyGlyphsChanged() { + for (uint32_t i = 0, count = mFontsUsingSVGGlyphs.Length(); i < count; ++i) { + gfxFont* font = mFontsUsingSVGGlyphs[i]; + font->NotifyGlyphsChanged(); + } +} + +bool gfxFontEntry::TryGetColorGlyphs() { + if (mCheckedForColorGlyph) { + return (mCOLR && mCPAL); + } + + mCheckedForColorGlyph = true; + + mCOLR = GetFontTable(TRUETYPE_TAG('C', 'O', 'L', 'R')); + if (!mCOLR) { + return false; + } + + mCPAL = GetFontTable(TRUETYPE_TAG('C', 'P', 'A', 'L')); + if (!mCPAL) { + hb_blob_destroy(mCOLR); + mCOLR = nullptr; + return false; + } + + // validation COLR and CPAL table + if (gfxFontUtils::ValidateColorGlyphs(mCOLR, mCPAL)) { + return true; + } + + hb_blob_destroy(mCOLR); + hb_blob_destroy(mCPAL); + mCOLR = nullptr; + mCPAL = nullptr; + return false; +} + +/** + * FontTableBlobData + * + * See FontTableHashEntry for the general strategy. + */ + +class gfxFontEntry::FontTableBlobData { + public: + explicit FontTableBlobData(nsTArray<uint8_t>&& aBuffer) + : mTableData(std::move(aBuffer)), mHashtable(nullptr), mHashKey(0) { + MOZ_COUNT_CTOR(FontTableBlobData); + } + + ~FontTableBlobData() { + MOZ_COUNT_DTOR(FontTableBlobData); + if (mHashtable && mHashKey) { + mHashtable->RemoveEntry(mHashKey); + } + } + + // Useful for creating blobs + const char* GetTable() const { + return reinterpret_cast<const char*>(mTableData.Elements()); + } + uint32_t GetTableLength() const { return mTableData.Length(); } + + // Tell this FontTableBlobData to remove the HashEntry when this is + // destroyed. + void ManageHashEntry(nsTHashtable<FontTableHashEntry>* aHashtable, + uint32_t aHashKey) { + mHashtable = aHashtable; + mHashKey = aHashKey; + } + + // Disconnect from the HashEntry (because the blob has already been + // removed from the hashtable). + void ForgetHashEntry() { + mHashtable = nullptr; + mHashKey = 0; + } + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + return mTableData.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + + private: + // The font table data block + nsTArray<uint8_t> mTableData; + + // The blob destroy function needs to know the owning hashtable + // and the hashtable key, so that it can remove the entry. + nsTHashtable<FontTableHashEntry>* mHashtable; + uint32_t mHashKey; + + // not implemented + FontTableBlobData(const FontTableBlobData&); +}; + +hb_blob_t* gfxFontEntry::FontTableHashEntry::ShareTableAndGetBlob( + nsTArray<uint8_t>&& aTable, nsTHashtable<FontTableHashEntry>* aHashtable) { + Clear(); + // adopts elements of aTable + mSharedBlobData = new FontTableBlobData(std::move(aTable)); + + mBlob = hb_blob_create( + mSharedBlobData->GetTable(), mSharedBlobData->GetTableLength(), + HB_MEMORY_MODE_READONLY, mSharedBlobData, DeleteFontTableBlobData); + if (mBlob == hb_blob_get_empty()) { + // The FontTableBlobData was destroyed during hb_blob_create(). + // The (empty) blob is still be held in the hashtable with a strong + // reference. + return hb_blob_reference(mBlob); + } + + // Tell the FontTableBlobData to remove this hash entry when destroyed. + // The hashtable does not keep a strong reference. + mSharedBlobData->ManageHashEntry(aHashtable, GetKey()); + return mBlob; +} + +void gfxFontEntry::FontTableHashEntry::Clear() { + // If the FontTableBlobData is managing the hash entry, then the blob is + // not owned by this HashEntry; otherwise there is strong reference to the + // blob that must be removed. + if (mSharedBlobData) { + mSharedBlobData->ForgetHashEntry(); + mSharedBlobData = nullptr; + } else { + hb_blob_destroy(mBlob); + } + mBlob = nullptr; +} + +// a hb_destroy_func for hb_blob_create + +/* static */ +void gfxFontEntry::FontTableHashEntry::DeleteFontTableBlobData( + void* aBlobData) { + delete static_cast<FontTableBlobData*>(aBlobData); +} + +hb_blob_t* gfxFontEntry::FontTableHashEntry::GetBlob() const { + return hb_blob_reference(mBlob); +} + +bool gfxFontEntry::GetExistingFontTable(uint32_t aTag, hb_blob_t** aBlob) { + if (!mFontTableCache) { + // we do this here rather than on fontEntry construction + // because not all shapers will access the table cache at all + mFontTableCache = MakeUnique<nsTHashtable<FontTableHashEntry>>(8); + } + + FontTableHashEntry* entry = mFontTableCache->GetEntry(aTag); + if (!entry) { + return false; + } + + *aBlob = entry->GetBlob(); + return true; +} + +hb_blob_t* gfxFontEntry::ShareFontTableAndGetBlob(uint32_t aTag, + nsTArray<uint8_t>* aBuffer) { + if (MOZ_UNLIKELY(!mFontTableCache)) { + // we do this here rather than on fontEntry construction + // because not all shapers will access the table cache at all + mFontTableCache = MakeUnique<nsTHashtable<FontTableHashEntry>>(8); + } + + FontTableHashEntry* entry = mFontTableCache->PutEntry(aTag); + if (MOZ_UNLIKELY(!entry)) { // OOM + return nullptr; + } + + if (!aBuffer) { + // ensure the entry is null + entry->Clear(); + return nullptr; + } + + return entry->ShareTableAndGetBlob(std::move(*aBuffer), + mFontTableCache.get()); +} + +already_AddRefed<gfxCharacterMap> gfxFontEntry::GetCMAPFromFontInfo( + FontInfoData* aFontInfoData, uint32_t& aUVSOffset) { + if (!aFontInfoData || !aFontInfoData->mLoadCmaps) { + return nullptr; + } + + return aFontInfoData->GetCMAP(mName, aUVSOffset); +} + +hb_blob_t* gfxFontEntry::GetFontTable(uint32_t aTag) { + hb_blob_t* blob; + if (GetExistingFontTable(aTag, &blob)) { + return blob; + } + + nsTArray<uint8_t> buffer; + bool haveTable = NS_SUCCEEDED(CopyFontTable(aTag, buffer)); + + return ShareFontTableAndGetBlob(aTag, haveTable ? &buffer : nullptr); +} + +// callback for HarfBuzz to get a font table (in hb_blob_t form) +// from the font entry (passed as aUserData) +/*static*/ +hb_blob_t* gfxFontEntry::HBGetTable(hb_face_t* face, uint32_t aTag, + void* aUserData) { + gfxFontEntry* fontEntry = static_cast<gfxFontEntry*>(aUserData); + + // bug 589682 - ignore the GDEF table in buggy fonts (applies to + // Italic and BoldItalic faces of Times New Roman) + if (aTag == TRUETYPE_TAG('G', 'D', 'E', 'F') && fontEntry->IgnoreGDEF()) { + return nullptr; + } + + // bug 721719 - ignore the GSUB table in buggy fonts (applies to Roboto, + // at least on some Android ICS devices; set in gfxFT2FontList.cpp) + if (aTag == TRUETYPE_TAG('G', 'S', 'U', 'B') && fontEntry->IgnoreGSUB()) { + return nullptr; + } + + return fontEntry->GetFontTable(aTag); +} + +/*static*/ +void gfxFontEntry::HBFaceDeletedCallback(void* aUserData) { + gfxFontEntry* fe = static_cast<gfxFontEntry*>(aUserData); + fe->ForgetHBFace(); +} + +void gfxFontEntry::ForgetHBFace() { mHBFace = nullptr; } + +hb_face_t* gfxFontEntry::GetHBFace() { + if (!mHBFace) { + mHBFace = + hb_face_create_for_tables(HBGetTable, this, HBFaceDeletedCallback); + return mHBFace; + } + return hb_face_reference(mHBFace); +} + +struct gfxFontEntry::GrSandboxData { + rlbox_sandbox_gr sandbox; + sandbox_callback_gr<const void* (*)(const void*, unsigned int, size_t*)> + grGetTableCallback; + sandbox_callback_gr<void (*)(const void*, const void*)> + grReleaseTableCallback; + // Text Shapers register a callback to get glyph advances + sandbox_callback_gr<float (*)(const void*, uint16_t)> + grGetGlyphAdvanceCallback; + + GrSandboxData() { +#ifdef MOZ_WASM_SANDBOXING_GRAPHITE + // Firefox preloads the library externally to ensure we won't be stopped by + // the content sandbox + const bool external_loads_exist = true; + // See Bug 1606981: In some environments allowing stdio in the wasm sandbox + // fails as the I/O redirection involves querying meta-data of file + // descriptors. This querying fails in some environments. + const bool allow_stdio = false; + sandbox.create_sandbox(mozilla::ipc::GetSandboxedGraphitePath().get(), + external_loads_exist, allow_stdio); +#else + sandbox.create_sandbox(); +#endif + grGetTableCallback = sandbox.register_callback(GrGetTable); + grReleaseTableCallback = sandbox.register_callback(GrReleaseTable); + grGetGlyphAdvanceCallback = + sandbox.register_callback(gfxGraphiteShaper::GrGetAdvance); + } + + ~GrSandboxData() { + grGetTableCallback.unregister(); + grReleaseTableCallback.unregister(); + grGetGlyphAdvanceCallback.unregister(); + sandbox.destroy_sandbox(); + } +}; + +static thread_local gfxFontEntry* tl_grGetFontTableCallbackData = nullptr; + +/*static*/ +tainted_opaque_gr<const void*> gfxFontEntry::GrGetTable( + rlbox_sandbox_gr& sandbox, + tainted_opaque_gr<const void*> /* aAppFaceHandle */, + tainted_opaque_gr<unsigned int> aName, tainted_opaque_gr<size_t*> aLen) { + gfxFontEntry* fontEntry = tl_grGetFontTableCallbackData; + tainted_gr<size_t*> t_aLen = rlbox::from_opaque(aLen); + *t_aLen = 0; + tainted_gr<const void*> ret = nullptr; + + if (fontEntry) { + unsigned int fontTableKey = + rlbox::from_opaque(aName).unverified_safe_because( + "This is only being used to index into a hashmap, which is robust " + "for any value. No checks needed."); + hb_blob_t* blob = fontEntry->GetFontTable(fontTableKey); + + if (blob) { + unsigned int blobLength; + const void* tableData = hb_blob_get_data(blob, &blobLength); + // tableData is read-only data shared with the sandbox. + // Making a copy in sandbox memory + tainted_gr<void*> t_tableData = rlbox::sandbox_reinterpret_cast<void*>( + sandbox.malloc_in_sandbox<char>(blobLength)); + if (t_tableData) { + rlbox::memcpy(sandbox, t_tableData, tableData, blobLength); + *t_aLen = blobLength; + ret = rlbox::sandbox_const_cast<const void*>(t_tableData); + } + hb_blob_destroy(blob); + } + } + + return ret.to_opaque(); +} + +/*static*/ +void gfxFontEntry::GrReleaseTable( + rlbox_sandbox_gr& sandbox, + tainted_opaque_gr<const void*> /* aAppFaceHandle */, + tainted_opaque_gr<const void*> aTableBuffer) { + sandbox.free_in_sandbox(rlbox::from_opaque(aTableBuffer)); +} + +rlbox_sandbox_gr* gfxFontEntry::GetGrSandbox() { + MOZ_ASSERT(mSandboxData != nullptr); + return &mSandboxData->sandbox; +} + +sandbox_callback_gr<float (*)(const void*, uint16_t)>* +gfxFontEntry::GetGrSandboxAdvanceCallbackHandle() { + MOZ_ASSERT(mSandboxData != nullptr); + return &mSandboxData->grGetGlyphAdvanceCallback; +} + +tainted_opaque_gr<gr_face*> gfxFontEntry::GetGrFace() { + if (!mGrFaceInitialized) { + // When possible, the below code will use WASM as a sandboxing mechanism. + // At this time the wasm sandbox does not support threads. + // If Thebes is updated to make callst to the sandbox on multiple threaads, + // we need to make sure the underlying sandbox supports threading. + MOZ_ASSERT(NS_IsMainThread()); + + mSandboxData = new GrSandboxData(); + + auto p_faceOps = mSandboxData->sandbox.malloc_in_sandbox<gr_face_ops>(); + if (!p_faceOps) { + MOZ_CRASH("Graphite sandbox memory allocation failed"); + } + auto cleanup = MakeScopeExit( + [&] { mSandboxData->sandbox.free_in_sandbox(p_faceOps); }); + p_faceOps->size = sizeof(*p_faceOps); + p_faceOps->get_table = mSandboxData->grGetTableCallback; + p_faceOps->release_table = mSandboxData->grReleaseTableCallback; + + tl_grGetFontTableCallbackData = this; + auto face = sandbox_invoke( + mSandboxData->sandbox, gr_make_face_with_ops, + // For security, we do not pass the callback data to this arg, and use + // a TLS var instead. However, gr_make_face_with_ops expects this to + // be a non null ptr. Therefore, we should pass some dummy non null + // pointer which will be passed to callbacks, but never used. Let's just + // pass p_faceOps again, as this is a non-null tainted pointer. + p_faceOps /* appFaceHandle */, p_faceOps, gr_face_default); + tl_grGetFontTableCallbackData = nullptr; + mGrFace = face.to_opaque(); + mGrFaceInitialized = true; + } + ++mGrFaceRefCnt; + return mGrFace; +} + +void gfxFontEntry::ReleaseGrFace(tainted_opaque_gr<gr_face*> aFace) { + MOZ_ASSERT( + (rlbox::from_opaque(aFace) == rlbox::from_opaque(mGrFace)) + .unverified_safe_because( + "This is safe as the only thing we are doing is comparing " + "addresses of two tainted pointers. Furthermore this is used " + "merely as a debugging aid in the debug builds. This function is " + "called only from the trusted Firefox code rather than the " + "untrusted libGraphite.")); // sanity-check + MOZ_ASSERT(mGrFaceRefCnt > 0); + if (--mGrFaceRefCnt == 0) { + auto t_mGrFace = rlbox::from_opaque(mGrFace); + + tl_grGetFontTableCallbackData = this; + sandbox_invoke(mSandboxData->sandbox, gr_face_destroy, t_mGrFace); + tl_grGetFontTableCallbackData = nullptr; + + t_mGrFace = nullptr; + mGrFace = t_mGrFace.to_opaque(); + + delete mSandboxData; + mSandboxData = nullptr; + + mGrFaceInitialized = false; + } +} + +void gfxFontEntry::DisconnectSVG() { + if (mSVGInitialized && mSVGGlyphs) { + mSVGGlyphs = nullptr; + mSVGInitialized = false; + } +} + +bool gfxFontEntry::HasFontTable(uint32_t aTableTag) { + AutoTable table(this, aTableTag); + return table && hb_blob_get_length(table) > 0; +} + +void gfxFontEntry::CheckForGraphiteTables() { + mHasGraphiteTables = HasFontTable(TRUETYPE_TAG('S', 'i', 'l', 'f')); +} + +tainted_boolean_hint gfxFontEntry::HasGraphiteSpaceContextuals() { + if (!mGraphiteSpaceContextualsInitialized) { + auto face = GetGrFace(); + auto t_face = rlbox::from_opaque(face); + if (t_face) { + tainted_gr<const gr_faceinfo*> faceInfo = + sandbox_invoke(mSandboxData->sandbox, gr_face_info, t_face, 0); + // Comparison with a value in sandboxed memory returns a + // tainted_boolean_hint, i.e. a "hint", since the value could be changed + // maliciously at any moment. + tainted_boolean_hint is_not_none = + faceInfo->space_contextuals != gr_faceinfo::gr_space_none; + mHasGraphiteSpaceContextuals = is_not_none.unverified_safe_because( + "Note ideally mHasGraphiteSpaceContextuals would be " + "tainted_boolean_hint, but RLBox does not yet support bitfields, so " + "it is not wrapped. However, its value is only ever accessed through " + "this function which returns a tainted_boolean_hint, so unwrapping " + "temporarily is safe. We remove the wrapper now and re-add it " + "below."); + } + ReleaseGrFace(face); // always balance GetGrFace, even if face is null + mGraphiteSpaceContextualsInitialized = true; + } + + bool ret = mHasGraphiteSpaceContextuals; + return tainted_boolean_hint(ret); +} + +#define FEATURE_SCRIPT_MASK 0x000000ff // script index replaces low byte of tag + +static_assert(int(Script::NUM_SCRIPT_CODES) <= FEATURE_SCRIPT_MASK, + "Too many script codes"); + +// high-order three bytes of tag with script in low-order byte +#define SCRIPT_FEATURE(s, tag) \ + (((~FEATURE_SCRIPT_MASK) & (tag)) | \ + ((FEATURE_SCRIPT_MASK) & static_cast<uint32_t>(s))) + +bool gfxFontEntry::SupportsOpenTypeFeature(Script aScript, + uint32_t aFeatureTag) { + if (!mSupportedFeatures) { + mSupportedFeatures = MakeUnique<nsDataHashtable<nsUint32HashKey, bool>>(); + } + + // note: high-order three bytes *must* be unique for each feature + // listed below (see SCRIPT_FEATURE macro def'n) + NS_ASSERTION(aFeatureTag == HB_TAG('s', 'm', 'c', 'p') || + aFeatureTag == HB_TAG('c', '2', 's', 'c') || + aFeatureTag == HB_TAG('p', 'c', 'a', 'p') || + aFeatureTag == HB_TAG('c', '2', 'p', 'c') || + aFeatureTag == HB_TAG('s', 'u', 'p', 's') || + aFeatureTag == HB_TAG('s', 'u', 'b', 's') || + aFeatureTag == HB_TAG('v', 'e', 'r', 't'), + "use of unknown feature tag"); + + // note: graphite feature support uses the last script index + NS_ASSERTION(int(aScript) < FEATURE_SCRIPT_MASK - 1, + "need to bump the size of the feature shift"); + + uint32_t scriptFeature = SCRIPT_FEATURE(aScript, aFeatureTag); + bool result; + if (mSupportedFeatures->Get(scriptFeature, &result)) { + return result; + } + + result = false; + + hb_face_t* face = GetHBFace(); + + if (hb_ot_layout_has_substitution(face)) { + hb_script_t hbScript = + gfxHarfBuzzShaper::GetHBScriptUsedForShaping(aScript); + + // Get the OpenType tag(s) that match this script code + unsigned int scriptCount = 4; + hb_tag_t scriptTags[4]; + hb_ot_tags_from_script_and_language(hbScript, HB_LANGUAGE_INVALID, + &scriptCount, scriptTags, nullptr, + nullptr); + + // Append DEFAULT to the returned tags, if room + if (scriptCount < 4) { + scriptTags[scriptCount++] = HB_OT_TAG_DEFAULT_SCRIPT; + } + + // Now check for 'smcp' under the first of those scripts that is present + const hb_tag_t kGSUB = HB_TAG('G', 'S', 'U', 'B'); + for (unsigned int i = 0; i < scriptCount; i++) { + unsigned int scriptIndex; + if (hb_ot_layout_table_find_script(face, kGSUB, scriptTags[i], + &scriptIndex)) { + if (hb_ot_layout_language_find_feature( + face, kGSUB, scriptIndex, HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX, + aFeatureTag, nullptr)) { + result = true; + } + break; + } + } + } + + hb_face_destroy(face); + + mSupportedFeatures->Put(scriptFeature, result); + + return result; +} + +const hb_set_t* gfxFontEntry::InputsForOpenTypeFeature(Script aScript, + uint32_t aFeatureTag) { + if (!mFeatureInputs) { + mFeatureInputs = MakeUnique<nsDataHashtable<nsUint32HashKey, hb_set_t*>>(); + } + + NS_ASSERTION(aFeatureTag == HB_TAG('s', 'u', 'p', 's') || + aFeatureTag == HB_TAG('s', 'u', 'b', 's') || + aFeatureTag == HB_TAG('v', 'e', 'r', 't'), + "use of unknown feature tag"); + + uint32_t scriptFeature = SCRIPT_FEATURE(aScript, aFeatureTag); + hb_set_t* inputGlyphs; + if (mFeatureInputs->Get(scriptFeature, &inputGlyphs)) { + return inputGlyphs; + } + + inputGlyphs = hb_set_create(); + + 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 + unsigned int scriptCount = 4; + hb_tag_t scriptTags[5]; // space for null terminator + hb_ot_tags_from_script_and_language(hbScript, HB_LANGUAGE_INVALID, + &scriptCount, scriptTags, nullptr, + nullptr); + + // Append DEFAULT to the returned tags, if room + if (scriptCount < 4) { + scriptTags[scriptCount++] = HB_OT_TAG_DEFAULT_SCRIPT; + } + scriptTags[scriptCount++] = 0; + + const hb_tag_t kGSUB = HB_TAG('G', 'S', 'U', 'B'); + hb_tag_t features[2] = {aFeatureTag, HB_TAG_NONE}; + hb_set_t* featurelookups = hb_set_create(); + hb_ot_layout_collect_lookups(face, kGSUB, scriptTags, nullptr, features, + featurelookups); + hb_codepoint_t index = -1; + while (hb_set_next(featurelookups, &index)) { + hb_ot_layout_lookup_collect_glyphs(face, kGSUB, index, nullptr, + inputGlyphs, nullptr, nullptr); + } + hb_set_destroy(featurelookups); + } + + hb_face_destroy(face); + + mFeatureInputs->Put(scriptFeature, inputGlyphs); + return inputGlyphs; +} + +bool gfxFontEntry::SupportsGraphiteFeature(uint32_t aFeatureTag) { + if (!mSupportedFeatures) { + mSupportedFeatures = MakeUnique<nsDataHashtable<nsUint32HashKey, bool>>(); + } + + // note: high-order three bytes *must* be unique for each feature + // listed below (see SCRIPT_FEATURE macro def'n) + NS_ASSERTION(aFeatureTag == HB_TAG('s', 'm', 'c', 'p') || + aFeatureTag == HB_TAG('c', '2', 's', 'c') || + aFeatureTag == HB_TAG('p', 'c', 'a', 'p') || + aFeatureTag == HB_TAG('c', '2', 'p', 'c') || + aFeatureTag == HB_TAG('s', 'u', 'p', 's') || + aFeatureTag == HB_TAG('s', 'u', 'b', 's'), + "use of unknown feature tag"); + + // graphite feature check uses the last script slot + uint32_t scriptFeature = SCRIPT_FEATURE(FEATURE_SCRIPT_MASK, aFeatureTag); + bool result; + if (mSupportedFeatures->Get(scriptFeature, &result)) { + return result; + } + + auto face = GetGrFace(); + auto t_face = rlbox::from_opaque(face); + result = t_face ? sandbox_invoke(mSandboxData->sandbox, gr_face_find_fref, + t_face, aFeatureTag) != nullptr + : false; + ReleaseGrFace(face); + + mSupportedFeatures->Put(scriptFeature, result); + + return result; +} + +void gfxFontEntry::GetFeatureInfo(nsTArray<gfxFontFeatureInfo>& aFeatureInfo) { + // TODO: implement alternative code path for graphite fonts + + hb_face_t* face = GetHBFace(); + + // Get the list of features for a specific <script,langSys> pair and + // append them to aFeatureInfo. + auto collectForLang = [=, &aFeatureInfo]( + hb_tag_t aTableTag, unsigned int aScript, + hb_tag_t aScriptTag, unsigned int aLang, + hb_tag_t aLangTag) { + unsigned int featCount = hb_ot_layout_language_get_feature_tags( + face, aTableTag, aScript, aLang, 0, nullptr, nullptr); + AutoTArray<hb_tag_t, 32> featTags; + featTags.SetLength(featCount); + hb_ot_layout_language_get_feature_tags(face, aTableTag, aScript, aLang, 0, + &featCount, featTags.Elements()); + MOZ_ASSERT(featCount <= featTags.Length()); + // Just in case HB didn't fill featTags (i.e. in case it returned fewer + // tags than it promised), we truncate at the length it says it filled: + featTags.SetLength(featCount); + for (hb_tag_t t : featTags) { + aFeatureInfo.AppendElement(gfxFontFeatureInfo{t, aScriptTag, aLangTag}); + } + }; + + // Iterate over the language systems supported by a given script, + // and call collectForLang for each of them. + auto collectForScript = [=](hb_tag_t aTableTag, unsigned int aScript, + hb_tag_t aScriptTag) { + collectForLang(aTableTag, aScript, aScriptTag, + HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX, + HB_TAG('d', 'f', 'l', 't')); + unsigned int langCount = hb_ot_layout_script_get_language_tags( + face, aTableTag, aScript, 0, nullptr, nullptr); + AutoTArray<hb_tag_t, 32> langTags; + langTags.SetLength(langCount); + hb_ot_layout_script_get_language_tags(face, aTableTag, aScript, 0, + &langCount, langTags.Elements()); + MOZ_ASSERT(langCount <= langTags.Length()); + langTags.SetLength(langCount); + for (unsigned int lang = 0; lang < langCount; ++lang) { + collectForLang(aTableTag, aScript, aScriptTag, lang, langTags[lang]); + } + }; + + // Iterate over the scripts supported by a table (GSUB or GPOS), and call + // collectForScript for each of them. + auto collectForTable = [=](hb_tag_t aTableTag) { + unsigned int scriptCount = hb_ot_layout_table_get_script_tags( + face, aTableTag, 0, nullptr, nullptr); + AutoTArray<hb_tag_t, 32> scriptTags; + scriptTags.SetLength(scriptCount); + hb_ot_layout_table_get_script_tags(face, aTableTag, 0, &scriptCount, + scriptTags.Elements()); + MOZ_ASSERT(scriptCount <= scriptTags.Length()); + scriptTags.SetLength(scriptCount); + for (unsigned int script = 0; script < scriptCount; ++script) { + collectForScript(aTableTag, script, scriptTags[script]); + } + }; + + // Collect all OpenType Layout features, both substitution and positioning, + // supported by the font resource. + collectForTable(HB_TAG('G', 'S', 'U', 'B')); + collectForTable(HB_TAG('G', 'P', 'O', 'S')); + + hb_face_destroy(face); +} + +bool gfxFontEntry::GetColorLayersInfo( + uint32_t aGlyphId, const mozilla::gfx::DeviceColor& aDefaultColor, + nsTArray<uint16_t>& aLayerGlyphs, + nsTArray<mozilla::gfx::DeviceColor>& aLayerColors) { + return gfxFontUtils::GetColorGlyphLayers( + mCOLR, mCPAL, aGlyphId, aDefaultColor, aLayerGlyphs, aLayerColors); +} + +typedef struct { + AutoSwap_PRUint32 version; + AutoSwap_PRUint16 format; + AutoSwap_PRUint16 horizOffset; + AutoSwap_PRUint16 vertOffset; + AutoSwap_PRUint16 reserved; + // TrackData horizData; + // TrackData vertData; +} TrakHeader; + +typedef struct { + AutoSwap_PRUint16 nTracks; + AutoSwap_PRUint16 nSizes; + AutoSwap_PRUint32 sizeTableOffset; + // trackTableEntry trackTable[]; + // fixed32 sizeTable[]; +} TrackData; + +typedef struct { + AutoSwap_PRUint32 track; + AutoSwap_PRUint16 nameIndex; + AutoSwap_PRUint16 offset; +} TrackTableEntry; + +bool gfxFontEntry::HasTrackingTable() { + if (!TrakTableInitialized()) { + mTrakTable = GetFontTable(TRUETYPE_TAG('t', 'r', 'a', 'k')); + if (mTrakTable) { + if (!ParseTrakTable()) { + hb_blob_destroy(mTrakTable); + mTrakTable = nullptr; + } + } + } + return mTrakTable != nullptr; +} + +bool gfxFontEntry::ParseTrakTable() { + // Check table validity and set up the subtable pointers we need; + // if 'trak' table is invalid, or doesn't contain a 'normal' track, + // return false to tell the caller not to try using it. + unsigned int len; + const char* data = hb_blob_get_data(mTrakTable, &len); + if (len < sizeof(TrakHeader)) { + return false; + } + auto trak = reinterpret_cast<const TrakHeader*>(data); + uint16_t horizOffset = trak->horizOffset; + if (trak->version != 0x00010000 || uint16_t(trak->format) != 0 || + horizOffset == 0 || uint16_t(trak->reserved) != 0) { + return false; + } + // Find the horizontal trackData, and check it doesn't overrun the buffer. + if (horizOffset > len - sizeof(TrackData)) { + return false; + } + auto trackData = reinterpret_cast<const TrackData*>(data + horizOffset); + uint16_t nTracks = trackData->nTracks; + mNumTrakSizes = trackData->nSizes; + if (nTracks == 0 || mNumTrakSizes < 2) { + return false; + } + uint32_t sizeTableOffset = trackData->sizeTableOffset; + // Find the trackTable, and check it doesn't overrun the buffer. + if (horizOffset > + len - (sizeof(TrackData) + nTracks * sizeof(TrackTableEntry))) { + return false; + } + auto trackTable = reinterpret_cast<const TrackTableEntry*>( + data + horizOffset + sizeof(TrackData)); + // Look for 'normal' tracking, bail out if no such track is present. + unsigned trackIndex; + for (trackIndex = 0; trackIndex < nTracks; ++trackIndex) { + if (trackTable[trackIndex].track == 0x00000000) { + break; + } + } + if (trackIndex == nTracks) { + return false; + } + // Find list of tracking values, and check they won't overrun. + uint16_t offset = trackTable[trackIndex].offset; + if (offset > len - mNumTrakSizes * sizeof(uint16_t)) { + return false; + } + mTrakValues = reinterpret_cast<const AutoSwap_PRInt16*>(data + offset); + // Find the size subtable, and check it doesn't overrun the buffer. + mTrakSizeTable = + reinterpret_cast<const AutoSwap_PRInt32*>(data + sizeTableOffset); + if (mTrakSizeTable + mNumTrakSizes > + reinterpret_cast<const AutoSwap_PRInt32*>(data + len)) { + return false; + } + return true; +} + +float gfxFontEntry::TrackingForCSSPx(float aSize) const { + MOZ_ASSERT(TrakTableInitialized() && mTrakTable && mTrakValues && + mTrakSizeTable); + + // Find index of first sizeTable entry that is >= the requested size. + int32_t fixedSize = int32_t(aSize * 65536.0); // float -> 16.16 fixed-point + unsigned sizeIndex; + for (sizeIndex = 0; sizeIndex < mNumTrakSizes; ++sizeIndex) { + if (mTrakSizeTable[sizeIndex] >= fixedSize) { + break; + } + } + // Return the tracking value for the requested size, or an interpolated + // value if the exact size isn't found. + if (sizeIndex == mNumTrakSizes) { + // Request is larger than last entry in the table, so just use that. + // (We don't attempt to extrapolate more extreme tracking values than + // the largest or smallest present in the table.) + return int16_t(mTrakValues[mNumTrakSizes - 1]); + } + if (sizeIndex == 0 || mTrakSizeTable[sizeIndex] == fixedSize) { + // Found an exact match, or size was smaller than the first entry. + return int16_t(mTrakValues[sizeIndex]); + } + // Requested size falls between two entries: interpolate value. + double s0 = mTrakSizeTable[sizeIndex - 1] / 65536.0; // 16.16 -> float + double s1 = mTrakSizeTable[sizeIndex] / 65536.0; + double t = (aSize - s0) / (s1 - s0); + return (1.0 - t) * int16_t(mTrakValues[sizeIndex - 1]) + + t * int16_t(mTrakValues[sizeIndex]); +} + +void gfxFontEntry::SetupVariationRanges() { + if (!gfxPlatform::GetPlatform()->HasVariationFontSupport() || + !StaticPrefs::layout_css_font_variations_enabled() || !HasVariations() || + IsUserFont()) { + return; + } + AutoTArray<gfxFontVariationAxis, 4> axes; + GetVariationAxes(axes); + for (const auto& axis : axes) { + switch (axis.mTag) { + case HB_TAG('w', 'g', 'h', 't'): + // If the axis range looks like it doesn't fit the CSS font-weight + // scale, we don't hook up the high-level property, and we mark + // the face (in mRangeFlags) as having non-standard weight. This + // means we won't map CSS font-weight to the axis. Setting 'wght' + // with font-variation-settings will still work. + // Strictly speaking, the min value should be checked against 1.0, + // not 0.0, but we'll allow font makers that amount of leeway, as + // in practice a number of fonts seem to use 0..1000. + if (axis.mMinValue >= 0.0f && axis.mMaxValue <= 1000.0f && + // If axis.mMaxValue is less than the default weight we already + // set up, assume the axis has a non-standard range (like Skia) + // and don't try to map it. + Weight().Min() <= FontWeight(axis.mMaxValue)) { + if (FontWeight(axis.mDefaultValue) != Weight().Min()) { + mStandardFace = false; + } + mWeightRange = WeightRange(FontWeight(std::max(1.0f, axis.mMinValue)), + FontWeight(axis.mMaxValue)); + } else { + mRangeFlags |= RangeFlags::eNonCSSWeight; + } + break; + + case HB_TAG('w', 'd', 't', 'h'): + if (axis.mMinValue >= 0.0f && axis.mMaxValue <= 1000.0f && + Stretch().Min() <= FontStretch(axis.mMaxValue)) { + if (FontStretch(axis.mDefaultValue) != Stretch().Min()) { + mStandardFace = false; + } + mStretchRange = StretchRange(FontStretch(axis.mMinValue), + FontStretch(axis.mMaxValue)); + } else { + mRangeFlags |= RangeFlags::eNonCSSStretch; + } + break; + + case HB_TAG('s', 'l', 'n', 't'): + if (axis.mMinValue >= -90.0f && axis.mMaxValue <= 90.0f) { + if (FontSlantStyle::Oblique(axis.mDefaultValue) != + SlantStyle().Min()) { + mStandardFace = false; + } + // OpenType and CSS measure angles in opposite directions, so we + // have to flip signs and swap min/max when setting up the CSS + // font-style range here. + mStyleRange = + SlantStyleRange(FontSlantStyle::Oblique(-axis.mMaxValue), + FontSlantStyle::Oblique(-axis.mMinValue)); + } + break; + + case HB_TAG('i', 't', 'a', 'l'): + if (axis.mMinValue <= 0.0f && axis.mMaxValue >= 1.0f) { + if (axis.mDefaultValue != 0.0f) { + mStandardFace = false; + } + mStyleRange = SlantStyleRange(FontSlantStyle::Normal(), + FontSlantStyle::Italic()); + } + break; + + default: + continue; + } + } +} + +void gfxFontEntry::CheckForVariationAxes() { + if (HasVariations()) { + AutoTArray<gfxFontVariationAxis, 4> axes; + GetVariationAxes(axes); + for (const auto& axis : axes) { + if (axis.mTag == HB_TAG('w', 'g', 'h', 't') && axis.mMaxValue >= 600.0f) { + mRangeFlags |= RangeFlags::eBoldVariableWeight; + } else if (axis.mTag == HB_TAG('i', 't', 'a', 'l') && + axis.mMaxValue >= 1.0f) { + mRangeFlags |= RangeFlags::eItalicVariation; + } + } + } + mCheckedForVariationAxes = true; +} + +bool gfxFontEntry::HasBoldVariableWeight() { + MOZ_ASSERT(!mIsUserFontContainer, + "should not be called for user-font containers!"); + + if (!gfxPlatform::GetPlatform()->HasVariationFontSupport()) { + return false; + } + + if (!mCheckedForVariationAxes) { + CheckForVariationAxes(); + } + + return bool(mRangeFlags & RangeFlags::eBoldVariableWeight); +} + +bool gfxFontEntry::HasItalicVariation() { + MOZ_ASSERT(!mIsUserFontContainer, + "should not be called for user-font containers!"); + + if (!gfxPlatform::GetPlatform()->HasVariationFontSupport()) { + return false; + } + + if (!mCheckedForVariationAxes) { + CheckForVariationAxes(); + } + + return bool(mRangeFlags & RangeFlags::eItalicVariation); +} + +void gfxFontEntry::GetVariationsForStyle(nsTArray<gfxFontVariation>& aResult, + const gfxFontStyle& aStyle) { + if (!gfxPlatform::GetPlatform()->HasVariationFontSupport() || + !StaticPrefs::layout_css_font_variations_enabled()) { + return; + } + + if (!HasVariations()) { + return; + } + + // Resolve high-level CSS properties from the requested style + // (font-{style,weight,stretch}) to the appropriate variations. + // The value used is clamped to the range available in the font face, + // unless the face is a user font where no explicit descriptor was + // given, indicated by the corresponding 'auto' range-flag. + + // We don't do these mappings if the font entry has weight and/or stretch + // ranges that do not appear to use the CSS property scale. Some older + // fonts created for QuickDrawGX/AAT may use "normalized" values where the + // standard variation is 1.0 rather than 400.0 (weight) or 100.0 (stretch). + + if (!(mRangeFlags & RangeFlags::eNonCSSWeight)) { + float weight = (IsUserFont() && (mRangeFlags & RangeFlags::eAutoWeight)) + ? aStyle.weight.ToFloat() + : Weight().Clamp(aStyle.weight).ToFloat(); + aResult.AppendElement(gfxFontVariation{HB_TAG('w', 'g', 'h', 't'), weight}); + } + + if (!(mRangeFlags & RangeFlags::eNonCSSStretch)) { + float stretch = (IsUserFont() && (mRangeFlags & RangeFlags::eAutoStretch)) + ? aStyle.stretch.Percentage() + : Stretch().Clamp(aStyle.stretch).Percentage(); + aResult.AppendElement( + gfxFontVariation{HB_TAG('w', 'd', 't', 'h'), stretch}); + } + + if (aStyle.style.IsItalic() && SupportsItalic()) { + // The 'ital' axis is normally a binary toggle; intermediate values + // can only be set using font-variation-settings. + aResult.AppendElement(gfxFontVariation{HB_TAG('i', 't', 'a', 'l'), 1.0f}); + } else if (SlantStyle().Min().IsOblique()) { + // Figure out what slant angle we should try to match from the + // requested style. + float angle = aStyle.style.IsNormal() ? 0.0f + : aStyle.style.IsItalic() + ? FontSlantStyle::Oblique().ObliqueAngle() + : aStyle.style.ObliqueAngle(); + // Clamp to the available range, unless the face is a user font + // with no explicit descriptor. + if (!(IsUserFont() && (mRangeFlags & RangeFlags::eAutoSlantStyle))) { + angle = SlantStyle().Clamp(FontSlantStyle::Oblique(angle)).ObliqueAngle(); + } + // OpenType and CSS measure angles in opposite directions, so we have to + // invert the sign of the CSS oblique value when setting OpenType 'slnt'. + aResult.AppendElement(gfxFontVariation{HB_TAG('s', 'l', 'n', 't'), -angle}); + } + + auto replaceOrAppend = [&aResult](const gfxFontVariation& aSetting) { + struct TagEquals { + bool Equals(const gfxFontVariation& aIter, uint32_t aTag) const { + return aIter.mTag == aTag; + } + }; + auto index = aResult.IndexOf(aSetting.mTag, 0, TagEquals()); + if (index == aResult.NoIndex) { + aResult.AppendElement(aSetting); + } else { + aResult[index].mValue = aSetting.mValue; + } + }; + + // The low-level font-variation-settings descriptor from @font-face, + // if present, takes precedence over automatic variation settings + // from high-level properties. + for (const auto& v : mVariationSettings) { + replaceOrAppend(v); + } + + // And the low-level font-variation-settings property takes precedence + // over the descriptor. + for (const auto& v : aStyle.variationSettings) { + replaceOrAppend(v); + } +} + +size_t gfxFontEntry::FontTableHashEntry::SizeOfExcludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + if (mBlob) { + n += aMallocSizeOf(mBlob); + } + if (mSharedBlobData) { + n += mSharedBlobData->SizeOfIncludingThis(aMallocSizeOf); + } + return n; +} + +void gfxFontEntry::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + aSizes->mFontListSize += mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + + // cmaps are shared so only non-shared cmaps are included here + if (mCharacterMap && mCharacterMap->mBuildOnTheFly) { + aSizes->mCharMapsSize += mCharacterMap->SizeOfIncludingThis(aMallocSizeOf); + } + if (mFontTableCache) { + aSizes->mFontTableCacheSize += + mFontTableCache->SizeOfIncludingThis(aMallocSizeOf); + } + + // If the font has UVS data, we count that as part of the character map. + if (mUVSData) { + aSizes->mCharMapsSize += aMallocSizeOf(mUVSData.get()); + } + + // The following, if present, are essentially cached forms of font table + // data, so we'll accumulate them together with the basic table cache. + if (mUserFontData) { + aSizes->mFontTableCacheSize += + mUserFontData->SizeOfIncludingThis(aMallocSizeOf); + } + if (mSVGGlyphs) { + aSizes->mFontTableCacheSize += + mSVGGlyphs->SizeOfIncludingThis(aMallocSizeOf); + } + if (mSupportedFeatures) { + aSizes->mFontTableCacheSize += + mSupportedFeatures->ShallowSizeOfIncludingThis(aMallocSizeOf); + } + if (mFeatureInputs) { + aSizes->mFontTableCacheSize += + mFeatureInputs->ShallowSizeOfIncludingThis(aMallocSizeOf); + for (auto iter = mFeatureInputs->ConstIter(); !iter.Done(); iter.Next()) { + // There's no API to get the real size of an hb_set, so we'll use + // an approximation based on knowledge of the implementation. + aSizes->mFontTableCacheSize += 8192; // vector of 64K bits + } + } + // We don't include the size of mCOLR/mCPAL here, because (depending on the + // font backend implementation) they will either wrap blocks of data owned + // by the system (and potentially shared), or tables that are in our font + // table cache and therefore already counted. +} + +void gfxFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +// This is used to report the size of an individual downloaded font in the +// user font cache. (Fonts that are part of the platform font list accumulate +// their sizes to the font list's reporter using the AddSizeOf... methods +// above.) +size_t gfxFontEntry::ComputedSizeOfExcludingThis( + MallocSizeOf aMallocSizeOf) const { + FontListSizes s = {0}; + AddSizeOfExcludingThis(aMallocSizeOf, &s); + + // When reporting memory used for the main platform font list, + // where we're typically summing the totals for a few hundred font faces, + // we report the fields of FontListSizes separately. + // But for downloaded user fonts, the actual resource data (added below) + // will dominate, and the minor overhead of these pieces isn't worth + // splitting out for an individual font. + size_t result = s.mFontListSize + s.mFontTableCacheSize + s.mCharMapsSize; + + if (mIsDataUserFont) { + MOZ_ASSERT(mComputedSizeOfUserFont > 0, "user font with no data?"); + result += mComputedSizeOfUserFont; + } + + return result; +} + +////////////////////////////////////////////////////////////////////////////// +// +// class gfxFontFamily +// +////////////////////////////////////////////////////////////////////////////// + +// we consider faces with mStandardFace == true to be "less than" those with +// false, because during style matching, earlier entries are tried first +class FontEntryStandardFaceComparator { + public: + bool Equals(const RefPtr<gfxFontEntry>& a, + const RefPtr<gfxFontEntry>& b) const { + return a->mStandardFace == b->mStandardFace; + } + bool LessThan(const RefPtr<gfxFontEntry>& a, + const RefPtr<gfxFontEntry>& b) const { + return (a->mStandardFace == true && b->mStandardFace == false); + } +}; + +void gfxFontFamily::SortAvailableFonts() { + mAvailableFonts.Sort(FontEntryStandardFaceComparator()); +} + +bool gfxFontFamily::HasOtherFamilyNames() { + // need to read in other family names to determine this + if (!mOtherFamilyNamesInitialized) { + ReadOtherFamilyNames( + gfxPlatformFontList::PlatformFontList()); // sets mHasOtherFamilyNames + } + return mHasOtherFamilyNames; +} + +gfxFontEntry* gfxFontFamily::FindFontForStyle(const gfxFontStyle& aFontStyle, + bool aIgnoreSizeTolerance) { + AutoTArray<gfxFontEntry*, 4> matched; + FindAllFontsForStyle(aFontStyle, matched, aIgnoreSizeTolerance); + if (!matched.IsEmpty()) { + return matched[0]; + } + return nullptr; +} + +static inline double WeightStyleStretchDistance( + gfxFontEntry* aFontEntry, const gfxFontStyle& aTargetStyle) { + double stretchDist = + StretchDistance(aFontEntry->Stretch(), aTargetStyle.stretch); + double styleDist = + StyleDistance(aFontEntry->SlantStyle(), aTargetStyle.style); + double weightDist = WeightDistance(aFontEntry->Weight(), aTargetStyle.weight); + + // Sanity-check that the distances are within the expected range + // (update if implementation of the distance functions is changed). + MOZ_ASSERT(stretchDist >= 0.0 && stretchDist <= 2000.0); + MOZ_ASSERT(styleDist >= 0.0 && styleDist <= 500.0); + MOZ_ASSERT(weightDist >= 0.0 && weightDist <= 1600.0); + + // weight/style/stretch priority: stretch >> style >> weight + // so we multiply the stretch and style values to make them dominate + // the result + return stretchDist * kStretchFactor + styleDist * kStyleFactor + + weightDist * kWeightFactor; +} + +void gfxFontFamily::FindAllFontsForStyle( + const gfxFontStyle& aFontStyle, nsTArray<gfxFontEntry*>& aFontEntryList, + bool aIgnoreSizeTolerance) { + if (!mHasStyles) { + FindStyleVariations(); // collect faces for the family, if not already done + } + + NS_ASSERTION(mAvailableFonts.Length() > 0, "font family with no faces!"); + NS_ASSERTION(aFontEntryList.IsEmpty(), "non-empty fontlist passed in"); + + gfxFontEntry* fe = nullptr; + + // If the family has only one face, we simply return it; no further + // checking needed + uint32_t count = mAvailableFonts.Length(); + if (count == 1) { + fe = mAvailableFonts[0]; + aFontEntryList.AppendElement(fe); + return; + } + + // Most families are "simple", having just Regular/Bold/Italic/BoldItalic, + // or some subset of these. In this case, we have exactly 4 entries in + // mAvailableFonts, stored in the above order; note that some of the entries + // may be nullptr. We can then pick the required entry based on whether the + // request is for bold or non-bold, italic or non-italic, without running the + // more complex matching algorithm used for larger families with many weights + // and/or widths. + + if (mIsSimpleFamily) { + // Family has no more than the "standard" 4 faces, at fixed indexes; + // calculate which one we want. + // Note that we cannot simply return it as not all 4 faces are necessarily + // present. + bool wantBold = aFontStyle.weight >= FontWeight(600); + bool wantItalic = !aFontStyle.style.IsNormal(); + uint8_t faceIndex = + (wantItalic ? kItalicMask : 0) | (wantBold ? kBoldMask : 0); + + // if the desired style is available, return it directly + fe = mAvailableFonts[faceIndex]; + if (fe) { + aFontEntryList.AppendElement(fe); + return; + } + + // order to check fallback faces in a simple family, depending on requested + // style + static const uint8_t simpleFallbacks[4][3] = { + {kBoldFaceIndex, kItalicFaceIndex, + kBoldItalicFaceIndex}, // fallbacks for Regular + {kRegularFaceIndex, kBoldItalicFaceIndex, kItalicFaceIndex}, // Bold + {kBoldItalicFaceIndex, kRegularFaceIndex, kBoldFaceIndex}, // Italic + {kItalicFaceIndex, kBoldFaceIndex, kRegularFaceIndex} // BoldItalic + }; + const uint8_t* order = simpleFallbacks[faceIndex]; + + for (uint8_t trial = 0; trial < 3; ++trial) { + // check remaining faces in order of preference to find the first that + // actually exists + fe = mAvailableFonts[order[trial]]; + if (fe) { + aFontEntryList.AppendElement(fe); + return; + } + } + + // this can't happen unless we have totally broken the font-list manager! + MOZ_ASSERT_UNREACHABLE("no face found in simple font family!"); + } + + // Pick the font(s) that are closest to the desired weight, style, and + // stretch. Iterate over all fonts, measuring the weight/style distance. + // Because of unicode-range values, there may be more than one font for a + // given but the 99% use case is only a single font entry per + // weight/style/stretch distance value. To optimize this, only add entries + // to the matched font array when another entry already has the same + // weight/style/stretch distance and add the last matched font entry. For + // normal platform fonts with a single font entry for each + // weight/style/stretch combination, only the last matched font entry will + // be added. + + double minDistance = INFINITY; + gfxFontEntry* matched = nullptr; + // iterate in forward order so that faces like 'Bold' are matched before + // matching style distance faces such as 'Bold Outline' (see bug 1185812) + for (uint32_t i = 0; i < count; i++) { + fe = mAvailableFonts[i]; + // weight/style/stretch priority: stretch >> style >> weight + double distance = WeightStyleStretchDistance(fe, aFontStyle); + if (distance < minDistance) { + matched = fe; + if (!aFontEntryList.IsEmpty()) { + aFontEntryList.Clear(); + } + minDistance = distance; + } else if (distance == minDistance) { + if (matched) { + aFontEntryList.AppendElement(matched); + } + matched = fe; + } + } + + NS_ASSERTION(matched, "didn't match a font within a family"); + + if (matched) { + aFontEntryList.AppendElement(matched); + } +} + +void gfxFontFamily::CheckForSimpleFamily() { + // already checked this family + if (mIsSimpleFamily) { + return; + } + + uint32_t count = mAvailableFonts.Length(); + if (count > 4 || count == 0) { + return; // can't be "simple" if there are >4 faces; + // if none then the family is unusable anyway + } + + if (count == 1) { + mIsSimpleFamily = true; + return; + } + + StretchRange firstStretch = mAvailableFonts[0]->Stretch(); + if (!firstStretch.IsSingle()) { + return; // family with variation fonts is not considered "simple" + } + + gfxFontEntry* faces[4] = {0}; + for (uint8_t i = 0; i < count; ++i) { + gfxFontEntry* fe = mAvailableFonts[i]; + if (fe->Stretch() != firstStretch || fe->IsOblique()) { + // simple families don't have varying font-stretch or oblique + return; + } + if (!fe->Weight().IsSingle() || !fe->SlantStyle().IsSingle()) { + return; // family with variation fonts is not considered "simple" + } + uint8_t faceIndex = (fe->IsItalic() ? kItalicMask : 0) | + (fe->SupportsBold() ? kBoldMask : 0); + if (faces[faceIndex]) { + return; // two faces resolve to the same slot; family isn't "simple" + } + faces[faceIndex] = fe; + } + + // we have successfully slotted the available faces into the standard + // 4-face framework + mAvailableFonts.SetLength(4); + for (uint8_t i = 0; i < 4; ++i) { + if (mAvailableFonts[i].get() != faces[i]) { + mAvailableFonts[i].swap(faces[i]); + } + } + + mIsSimpleFamily = true; +} + +#ifdef DEBUG +bool gfxFontFamily::ContainsFace(gfxFontEntry* aFontEntry) { + uint32_t i, numFonts = mAvailableFonts.Length(); + for (i = 0; i < numFonts; i++) { + if (mAvailableFonts[i] == aFontEntry) { + return true; + } + // userfonts contain the actual real font entry + if (mAvailableFonts[i] && mAvailableFonts[i]->mIsUserFontContainer) { + gfxUserFontEntry* ufe = + static_cast<gfxUserFontEntry*>(mAvailableFonts[i].get()); + if (ufe->GetPlatformFontEntry() == aFontEntry) { + return true; + } + } + } + return false; +} +#endif + +void gfxFontFamily::LocalizedName(nsACString& aLocalizedName) { + // just return the primary name; subclasses should override + aLocalizedName = mName; +} + +void gfxFontFamily::FindFontForChar(GlobalFontMatch* aMatchData) { + if (mFamilyCharacterMapInitialized && !TestCharacterMap(aMatchData->mCh)) { + // none of the faces in the family support the required char, + // so bail out immediately + return; + } + +#ifdef MOZ_GECKO_PROFILER + nsCString charAndName; + if (profiler_can_accept_markers()) { + charAndName = nsPrintfCString("\\u%x %s", aMatchData->mCh, mName.get()); + } + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("gfxFontFamily::FindFontForChar", + LAYOUT, charAndName); +#endif + + AutoTArray<gfxFontEntry*, 4> entries; + FindAllFontsForStyle(aMatchData->mStyle, entries, + /*aIgnoreSizeTolerance*/ true); + if (entries.IsEmpty()) { + return; + } + + gfxFontEntry* fe = nullptr; + float distance = INFINITY; + + for (auto e : entries) { + if (e->SkipDuringSystemFallback()) { + continue; + } + + aMatchData->mCmapsTested++; + if (e->HasCharacter(aMatchData->mCh)) { + aMatchData->mCount++; + + LogModule* log = gfxPlatform::GetLog(eGfxLog_textrun); + + if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Debug))) { + Script script = GetScriptCode(aMatchData->mCh); + MOZ_LOG(log, LogLevel::Debug, + ("(textrun-systemfallback-fonts) char: u+%6.6x " + "script: %d match: [%s]\n", + aMatchData->mCh, int(script), e->Name().get())); + } + + fe = e; + distance = WeightStyleStretchDistance(fe, aMatchData->mStyle); + if (aMatchData->mPresentation != eFontPresentation::Any) { + RefPtr<gfxFont> font = fe->FindOrMakeFont(&aMatchData->mStyle); + if (!font) { + continue; + } + bool hasColorGlyph = + font->HasColorGlyphFor(aMatchData->mCh, aMatchData->mNextCh); + if (hasColorGlyph != PrefersColor(aMatchData->mPresentation)) { + distance += kPresentationMismatch; + } + } + break; + } + } + + if (!fe && !aMatchData->mStyle.IsNormalStyle()) { + // If style/weight/stretch was not Normal, see if we can + // fall back to a next-best face (e.g. Arial Black -> Bold, + // or Arial Narrow -> Regular). + GlobalFontMatch data(aMatchData->mCh, aMatchData->mNextCh, + aMatchData->mStyle, aMatchData->mPresentation); + SearchAllFontsForChar(&data); + if (!data.mBestMatch) { + return; + } + fe = data.mBestMatch; + distance = data.mMatchDistance; + } + + if (!fe) { + return; + } + + if (distance < aMatchData->mMatchDistance || + (distance == aMatchData->mMatchDistance && + Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0)) { + aMatchData->mBestMatch = fe; + aMatchData->mMatchedFamily = this; + aMatchData->mMatchDistance = distance; + } +} + +void gfxFontFamily::SearchAllFontsForChar(GlobalFontMatch* aMatchData) { + if (!mFamilyCharacterMapInitialized) { + ReadAllCMAPs(); + } + if (!mFamilyCharacterMap.test(aMatchData->mCh)) { + return; + } + uint32_t i, numFonts = mAvailableFonts.Length(); + for (i = 0; i < numFonts; i++) { + gfxFontEntry* fe = mAvailableFonts[i]; + if (fe && fe->HasCharacter(aMatchData->mCh)) { + float distance = WeightStyleStretchDistance(fe, aMatchData->mStyle); + if (aMatchData->mPresentation != eFontPresentation::Any) { + RefPtr<gfxFont> font = fe->FindOrMakeFont(&aMatchData->mStyle); + if (!font) { + continue; + } + bool hasColorGlyph = + font->HasColorGlyphFor(aMatchData->mCh, aMatchData->mNextCh); + if (hasColorGlyph != PrefersColor(aMatchData->mPresentation)) { + distance += kPresentationMismatch; + } + } + if (distance < aMatchData->mMatchDistance || + (distance == aMatchData->mMatchDistance && + Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0)) { + aMatchData->mBestMatch = fe; + aMatchData->mMatchedFamily = this; + aMatchData->mMatchDistance = distance; + } + } + } +} + +/*virtual*/ +gfxFontFamily::~gfxFontFamily() { + // Should not be dropped by stylo + MOZ_ASSERT(NS_IsMainThread()); +} + +// returns true if other names were found, false otherwise +bool gfxFontFamily::ReadOtherFamilyNamesForFace( + gfxPlatformFontList* aPlatformFontList, hb_blob_t* aNameTable, + bool useFullName) { + uint32_t dataLength; + const char* nameData = hb_blob_get_data(aNameTable, &dataLength); + AutoTArray<nsCString, 4> otherFamilyNames; + + gfxFontUtils::ReadOtherFamilyNamesForFace(mName, nameData, dataLength, + otherFamilyNames, useFullName); + + 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); + } +} + +static bool LookForLegacyFamilyName(const nsACString& aCanonicalName, + const char* aNameData, uint32_t aDataLength, + nsACString& aLegacyName /* outparam */) { + const gfxFontUtils::NameHeader* nameHeader = + reinterpret_cast<const gfxFontUtils::NameHeader*>(aNameData); + + uint32_t nameCount = nameHeader->count; + if (nameCount * sizeof(gfxFontUtils::NameRecord) > aDataLength) { + NS_WARNING("invalid font (name records)"); + return false; + } + + const gfxFontUtils::NameRecord* nameRecord = + reinterpret_cast<const gfxFontUtils::NameRecord*>( + aNameData + sizeof(gfxFontUtils::NameHeader)); + uint32_t stringsBase = uint32_t(nameHeader->stringOffset); + + for (uint32_t i = 0; i < nameCount; i++, nameRecord++) { + uint32_t nameLen = nameRecord->length; + uint32_t nameOff = nameRecord->offset; + + if (stringsBase + nameOff + nameLen > aDataLength) { + NS_WARNING("invalid font (name table strings)"); + return false; + } + + if (uint16_t(nameRecord->nameID) == gfxFontUtils::NAME_ID_FAMILY) { + bool ok = gfxFontUtils::DecodeFontName( + aNameData + stringsBase + nameOff, nameLen, + uint32_t(nameRecord->platformID), uint32_t(nameRecord->encodingID), + uint32_t(nameRecord->languageID), aLegacyName); + // it's only a legacy name if it differs from the canonical name + if (ok && aLegacyName != aCanonicalName) { + return true; + } + } + } + return false; +} + +bool gfxFontFamily::CheckForLegacyFamilyNames(gfxPlatformFontList* aFontList) { + if (mCheckedForLegacyFamilyNames) { + // we already did this, so there's nothing more to add + return false; + } + mCheckedForLegacyFamilyNames = true; + bool added = false; + const uint32_t kNAME = TRUETYPE_TAG('n', 'a', 'm', 'e'); + // Make a local copy of the array of font faces, in case of changes + // during the iteration. + for (auto& fe : + CopyableAutoTArray<RefPtr<gfxFontEntry>, 8>(mAvailableFonts)) { + if (!fe) { + continue; + } + gfxFontEntry::AutoTable nameTable(fe, kNAME); + if (!nameTable) { + continue; + } + nsAutoCString legacyName; + uint32_t dataLength; + const char* nameData = hb_blob_get_data(nameTable, &dataLength); + if (LookForLegacyFamilyName(Name(), nameData, dataLength, legacyName)) { + if (aFontList->AddWithLegacyFamilyName(legacyName, fe, mVisibility)) { + added = true; + } + } + } + return added; +} + +void gfxFontFamily::ReadFaceNames(gfxPlatformFontList* aPlatformFontList, + bool aNeedFullnamePostscriptNames, + FontInfoData* aFontInfoData) { + // if all needed names have already been read, skip + if (mOtherFamilyNamesInitialized && + (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) { + return; + } + + bool asyncFontLoaderDisabled = false; + + if (!mOtherFamilyNamesInitialized && aFontInfoData && + aFontInfoData->mLoadOtherNames && !asyncFontLoaderDisabled) { + const auto* otherFamilyNames = aFontInfoData->GetOtherFamilyNames(mName); + if (otherFamilyNames) { + 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; + } + + nsAutoCString 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 nsACString& 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 0000000000..605878dd72 --- /dev/null +++ b/gfx/thebes/gfxFontEntry.h @@ -0,0 +1,1085 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FONTENTRY_H +#define GFX_FONTENTRY_H + +#include <math.h> +#include <new> +#include <utility> +#include "ThebesRLBoxTypes.h" +#include "gfxFontUtils.h" +#include "gfxFontVariations.h" +#include "gfxRect.h" +#include "gfxTypes.h" +#include "harfbuzz/hb.h" +#include "ipc/EnumSerializer.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TypedEnumBits.h" +#include "mozilla/UniquePtr.h" +#include "nsDataHashtable.h" +#include "nsDebug.h" +#include "nsHashKeys.h" +#include "nsISupports.h" +#include "nsStringFwd.h" +#include "nsTArray.h" +#include "nsUnicodeScriptCodes.h" +#include "nscore.h" + +class FontInfoData; +class gfxContext; +class gfxFont; +class gfxFontFamily; +class gfxPlatformFontList; +class gfxSVGGlyphs; +class gfxUserFontData; +class nsAtom; +struct FontListSizes; +struct gfxFontFeature; +struct gfxFontStyle; +enum class eFontPresentation : uint8_t; + +namespace IPC { +template <class P> +struct ParamTraits; +} + +namespace mozilla { +class SVGContextPaint; +namespace fontlist { +struct Face; +struct Family; +} // namespace fontlist +namespace gfx { +struct DeviceColor; +} +} // namespace mozilla + +typedef struct gr_face gr_face; +typedef struct FT_MM_Var_ FT_MM_Var; + +#define NO_FONT_LANGUAGE_OVERRIDE 0 + +class gfxCharacterMap : public gfxSparseBitSet { + public: + nsrefcnt AddRef() { + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); + ++mRefCnt; + NS_LOG_ADDREF(this, mRefCnt, "gfxCharacterMap", sizeof(*this)); + return mRefCnt; + } + + nsrefcnt Release() { + MOZ_ASSERT(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&); +}; + +// Info on an individual font feature, for reporting available features +// to DevTools via the GetFeatureInfo method. +struct gfxFontFeatureInfo { + uint32_t mTag; + uint32_t mScript; + uint32_t mLangSys; +}; + +class gfxFontEntry { + public: + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::unicode::Script Script; + typedef mozilla::FontWeight FontWeight; + typedef mozilla::FontSlantStyle FontSlantStyle; + typedef mozilla::FontStretch FontStretch; + typedef mozilla::WeightRange WeightRange; + typedef mozilla::SlantStyleRange SlantStyleRange; + typedef mozilla::StretchRange StretchRange; + + // Used by stylo + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxFontEntry) + + explicit gfxFontEntry(const nsACString& aName, bool aIsStandardFace = false); + + // Create a new entry that refers to the same font as this, but without + // additional state that may have been set up (such as family name). + // (This is only to be used for system fonts in the platform font list, + // not user fonts.) + virtual gfxFontEntry* Clone() const = 0; + + // unique name for the face, *not* the family; not necessarily the + // "real" or user-friendly name, may be an internal identifier + const nsCString& Name() const { return mName; } + + // family name + const nsCString& FamilyName() const { return mFamilyName; } + + // The following two methods may be relatively expensive, as they + // will (usually, except on Linux) load and parse the 'name' table; + // they are intended only for the font-inspection API, not for + // perf-critical layout/drawing work. + + // The "real" name of the face, if available from the font resource; + // returns Name() if nothing better is available. + virtual nsCString RealFaceName(); + + WeightRange Weight() const { return mWeightRange; } + StretchRange Stretch() const { return mStretchRange; } + SlantStyleRange SlantStyle() const { return mStyleRange; } + + bool IsUserFont() const { return mIsDataUserFont || mIsLocalUserFont; } + bool IsLocalUserFont() const { return mIsLocalUserFont; } + bool IsFixedPitch() const { return mFixedPitch; } + bool IsItalic() const { return SlantStyle().Min().IsItalic(); } + bool IsOblique() const { return SlantStyle().Min().IsOblique(); } + bool IsUpright() const { return SlantStyle().Min().IsNormal(); } + inline bool SupportsItalic(); + inline bool SupportsBold(); // defined below, because of RangeFlags use + bool IgnoreGDEF() const { return mIgnoreGDEF; } + bool IgnoreGSUB() const { return mIgnoreGSUB; } + + // Return whether the face corresponds to "normal" CSS style properties: + // font-style: normal; + // font-weight: normal; + // font-stretch: normal; + // If this is false, we might want to fall back to a different face and + // possibly apply synthetic styling. + bool IsNormalStyle() const { + return IsUpright() && Weight().Min() <= FontWeight::Normal() && + Weight().Max() >= FontWeight::Normal() && + Stretch().Min() <= FontStretch::Normal() && + Stretch().Max() >= FontStretch::Normal(); + } + + // whether a feature is supported by the font (limited to a small set + // of features for which some form of fallback needs to be implemented) + virtual bool SupportsOpenTypeFeature(Script aScript, uint32_t aFeatureTag); + bool SupportsGraphiteFeature(uint32_t aFeatureTag); + + // returns a set containing all input glyph ids for a given feature + const hb_set_t* InputsForOpenTypeFeature(Script aScript, + uint32_t aFeatureTag); + + virtual bool HasFontTable(uint32_t aTableTag); + + inline bool HasGraphiteTables() { + if (!mCheckedForGraphiteTables) { + CheckForGraphiteTables(); + mCheckedForGraphiteTables = true; + } + return mHasGraphiteTables; + } + + inline bool HasCmapTable() { + if (!mCharacterMap && !mShmemCharacterMap) { + ReadCMAP(); + NS_ASSERTION(mCharacterMap || mShmemCharacterMap, + "failed to initialize character map"); + } + return mHasCmapTable; + } + + inline bool HasCharacter(uint32_t ch) { + if (mShmemCharacterMap) { + return mShmemCharacterMap->test(ch); + } + if (mCharacterMap) { + if (mShmemFace && TrySetShmemCharacterMap()) { + // Forget our temporary local copy, now we can use the shared cmap + mCharacterMap = nullptr; + return mShmemCharacterMap->test(ch); + } + if (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, + gfxFloat aSize, gfxRect* aResult); + void RenderSVGGlyph(gfxContext* aContext, uint32_t aGlyphId, + mozilla::SVGContextPaint* aContextPaint); + // Call this when glyph geometry or rendering has changed + // (e.g. animated SVG glyphs) + void NotifyGlyphsChanged(); + + bool TryGetColorGlyphs(); + bool GetColorLayersInfo(uint32_t aGlyphId, + const mozilla::gfx::DeviceColor& aDefaultColor, + nsTArray<uint16_t>& layerGlyphs, + nsTArray<mozilla::gfx::DeviceColor>& layerColors); + bool HasColorLayersForGlyph(uint32_t aGlyphId) { + MOZ_ASSERT(mCOLR); + return gfxFontUtils::HasColorLayersForGlyph(mCOLR, aGlyphId); + } + + bool HasColorBitmapTable() { + if (!mCheckedForColorBitmapTables) { + mHasColorBitmapTable = HasFontTable(TRUETYPE_TAG('C', 'B', 'D', 'T')) || + HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x')); + mCheckedForColorBitmapTables = true; + } + return mHasColorBitmapTable; + } + + // Access to raw font table data (needed for Harfbuzz): + // returns a pointer to data owned by the fontEntry or the OS, + // which will remain valid until the blob is destroyed. + // The data MUST be treated as read-only; we may be getting a + // reference to a shared system font cache. + // + // The default implementation uses CopyFontTable to get the data + // into a byte array, and maintains a cache of loaded tables. + // + // Subclasses should override this if they can provide more efficient + // access than copying table data into our own buffers. + // + // Get blob that encapsulates a specific font table, or nullptr if + // the table doesn't exist in the font. + // + // Caller is responsible to call hb_blob_destroy() on the returned blob + // (if non-nullptr) when no longer required. For transient access to a + // table, use of AutoTable (below) is generally preferred. + virtual hb_blob_t* GetFontTable(uint32_t aTag); + + // Stack-based utility to return a specified table, automatically releasing + // the blob when the AutoTable goes out of scope. + class AutoTable { + public: + AutoTable(gfxFontEntry* aFontEntry, uint32_t aTag) { + mBlob = aFontEntry->GetFontTable(aTag); + } + ~AutoTable() { hb_blob_destroy(mBlob); } + operator hb_blob_t*() const { return mBlob; } + + private: + hb_blob_t* mBlob; + // not implemented: + AutoTable(const AutoTable&) = delete; + AutoTable& operator=(const AutoTable&) = delete; + }; + + // Return a font instance for a particular style. This may be a newly- + // created instance, or a font already in the global cache. + // We can't return a UniquePtr here, because we may be returning a shared + // cached instance; but we also don't return already_AddRefed, because + // the caller may only need to use the font temporarily and doesn't need + // a strong reference. + gfxFont* FindOrMakeFont(const gfxFontStyle* aStyle, + gfxCharacterMap* aUnicodeRangeMap = nullptr); + + // Get an existing font table cache entry in aBlob if it has been + // registered, or return false if not. Callers must call + // hb_blob_destroy on aBlob if true is returned. + // + // Note that some gfxFont implementations may not call this at all, + // if it is more efficient to get the table from the OS at that level. + bool GetExistingFontTable(uint32_t aTag, hb_blob_t** aBlob); + + // Elements of aTable are transferred (not copied) to and returned in a + // new hb_blob_t which is registered on the gfxFontEntry, but the initial + // reference is owned by the caller. Removing the last reference + // unregisters the table from the font entry. + // + // Pass nullptr for aBuffer to indicate that the table is not present and + // nullptr will be returned. Also returns nullptr on OOM. + hb_blob_t* ShareFontTableAndGetBlob(uint32_t aTag, nsTArray<uint8_t>* aTable); + + // Get the font's unitsPerEm from the 'head' table, in the case of an + // sfnt resource. Will return kInvalidUPEM for non-sfnt fonts, + // if present on the platform. + uint16_t UnitsPerEm(); + enum { + kMinUPEM = 16, // Limits on valid unitsPerEm range, from the + kMaxUPEM = 16384, // OpenType spec + kInvalidUPEM = uint16_t(-1) + }; + + // Shaper face accessors: + // NOTE that harfbuzz and graphite handle ownership/lifetime of the face + // object in completely different ways. + + // 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(); + void ForgetHBFace(); + + // Get the sandbox instance that graphite is running in. + rlbox_sandbox_gr* GetGrSandbox(); + + // Register and get the callback handle for the glyph advance firefox callback + // Since the sandbox instance is shared with multiple test shapers, callback + // registration must be handled centrally to ensure multiple instances don't + // register the same callback. + sandbox_callback_gr<float (*)(const void*, uint16_t)>* + GetGrSandboxAdvanceCallbackHandle(); + + // Get Graphite face corresponding to this font file. + // Caller must call gfxFontEntry::ReleaseGrFace when finished with it. + // Graphite is run in a sandbox + tainted_opaque_gr<gr_face*> GetGrFace(); + void ReleaseGrFace(tainted_opaque_gr<gr_face*> aFace); + + // Does the font have graphite contextuals that involve the space glyph + // (and therefore we should bypass the word cache)? + // Since this function inspects data from libGraphite stored in sandbox memory + // it can only return a "hint" to the correct return value. This is because + // a compromised libGraphite could change the sandbox memory maliciously at + // any moment. The caller must ensure the calling code performs safe actions + // independent of the value returned, to unwrap this return. + tainted_boolean_hint HasGraphiteSpaceContextuals(); + + // Release any SVG-glyphs document this font may have loaded. + void DisconnectSVG(); + + // Called to notify that aFont is being destroyed. Needed when we're tracking + // the fonts belonging to this font entry. + void NotifyFontDestroyed(gfxFont* aFont); + + // For memory reporting of the platform font list. + virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const; + virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const; + + // Used for reporting on individual font entries in the user font cache, + // which are not present in the platform font list. + size_t ComputedSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + // Used when checking for complex script support, to mask off cmap ranges + struct ScriptRange { + uint32_t rangeStart; + uint32_t rangeEnd; + uint32_t numTags; // number of entries in the tags[] array + hb_tag_t tags[3]; // up to three OpenType script tags to check + }; + + bool SupportsScriptInGSUB(const hb_tag_t* aScriptTags, uint32_t aNumTags); + + /** + * Font-variation query methods. + * + * Font backends that don't support variations should provide empty + * implementations. + */ + virtual bool HasVariations() = 0; + + virtual void GetVariationAxes( + nsTArray<gfxFontVariationAxis>& aVariationAxes) = 0; + + virtual void GetVariationInstances( + nsTArray<gfxFontVariationInstance>& aInstances) = 0; + + bool HasBoldVariableWeight(); + bool HasItalicVariation(); + void CheckForVariationAxes(); + + // Set up the entry's weight/stretch/style ranges according to axes found + // by GetVariationAxes (for installed fonts; do NOT call this for user + // fonts, where the ranges are provided by @font-face descriptors). + void SetupVariationRanges(); + + // Get variation axis settings that should be used to implement a particular + // font style using this resource. + void GetVariationsForStyle(nsTArray<gfxFontVariation>& aResult, + const gfxFontStyle& aStyle); + + // Get the font's list of features (if any) for DevTools support. + void GetFeatureInfo(nsTArray<gfxFontFeatureInfo>& aFeatureInfo); + + // This is only called on platforms where we use FreeType. + virtual FT_MM_Var* GetMMVar() { return nullptr; } + + // Return true if the font has a 'trak' table (and we can successfully + // interpret it), otherwise false. This will load and cache the table + // the first time it is called. + bool HasTrackingTable(); + + // Return the tracking (in font units) to be applied for the given size. + // (This is a floating-point number because of possible interpolation.) + float TrackingForCSSPx(float aSize) const; + + nsCString mName; + nsCString mFamilyName; + + RefPtr<gfxCharacterMap> mCharacterMap; + + mozilla::fontlist::Face* mShmemFace = nullptr; + const SharedBitSet* mShmemCharacterMap = nullptr; + + mozilla::UniquePtr<uint8_t[]> mUVSData; + mozilla::UniquePtr<gfxUserFontData> mUserFontData; + mozilla::UniquePtr<gfxSVGGlyphs> mSVGGlyphs; + // list of gfxFonts that are using SVG glyphs + nsTArray<gfxFont*> mFontsUsingSVGGlyphs; + nsTArray<gfxFontFeature> mFeatureSettings; + nsTArray<gfxFontVariation> mVariationSettings; + mozilla::UniquePtr<nsDataHashtable<nsUint32HashKey, bool>> mSupportedFeatures; + mozilla::UniquePtr<nsDataHashtable<nsUint32HashKey, hb_set_t*>> + mFeatureInputs; + + // Color Layer font support + hb_blob_t* mCOLR = nullptr; + hb_blob_t* mCPAL = nullptr; + + // 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]; + + uint32_t mUVSOffset = 0; + + uint32_t mLanguageOverride = NO_FONT_LANGUAGE_OVERRIDE; + + WeightRange mWeightRange = WeightRange(FontWeight(500)); + StretchRange mStretchRange = StretchRange(FontStretch::Normal()); + SlantStyleRange mStyleRange = SlantStyleRange(FontSlantStyle::Normal()); + + // For user fonts (only), we need to record whether or not weight/stretch/ + // slant variations should be clamped to the range specified in the entry + // properties. When the @font-face rule omitted one or more of these + // descriptors, it is treated as the initial value for font-matching (and + // so that is what we record in the font entry), but when rendering the + // range is NOT clamped. + enum class RangeFlags : uint8_t { + eNoFlags = 0, + eAutoWeight = (1 << 0), + eAutoStretch = (1 << 1), + eAutoSlantStyle = (1 << 2), + + // Flag to record whether the face has a variable "wght" axis + // that supports "bold" values, used to disable the application + // of synthetic-bold effects. + eBoldVariableWeight = (1 << 3), + // Whether the face has an 'ital' axis. + eItalicVariation = (1 << 4), + + // Flags to record if the face uses a non-CSS-compatible scale + // for weight and/or stretch, in which case we won't map the + // properties to the variation axes (though they can still be + // explicitly set using font-variation-settings). + eNonCSSWeight = (1 << 5), + eNonCSSStretch = (1 << 6) + }; + RangeFlags mRangeFlags = RangeFlags::eNoFlags; + + bool mFixedPitch : 1; + bool mIsBadUnderlineFont : 1; + bool mIsUserFontContainer : 1; // userfont entry + bool mIsDataUserFont : 1; // platform font entry (data) + bool mIsLocalUserFont : 1; // platform font entry (local) + bool mStandardFace : 1; + bool mIgnoreGDEF : 1; + bool mIgnoreGSUB : 1; + bool 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; + bool mCheckedForVariationAxes : 1; + bool mHasColorBitmapTable : 1; + bool mCheckedForColorBitmapTables : 1; + + protected: + friend class gfxPlatformFontList; + friend class gfxFontFamily; + friend class gfxUserFontEntry; + + gfxFontEntry(); + + // Protected destructor, to discourage deletion outside of Release(): + virtual ~gfxFontEntry(); + + virtual gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle) = 0; + + virtual void CheckForGraphiteTables(); + + // Copy a font table into aBuffer. + // The caller will be responsible for ownership of the data. + virtual nsresult CopyFontTable(uint32_t aTableTag, + nsTArray<uint8_t>& aBuffer) { + MOZ_ASSERT_UNREACHABLE( + "forgot to override either GetFontTable or " + "CopyFontTable?"); + return NS_ERROR_FAILURE; + } + + // Helper for HasTrackingTable; check/parse the table and cache pointers + // to the subtables we need. Returns false on failure, in which case the + // table is unusable. + bool ParseTrakTable(); + + // lookup the cmap in cached font data + virtual already_AddRefed<gfxCharacterMap> GetCMAPFromFontInfo( + FontInfoData* aFontInfoData, uint32_t& aUVSOffset); + + // helper for HasCharacter(), which is what client code should call + virtual bool TestCharacterMap(uint32_t aCh); + + // Try to set mShmemCharacterMap, based on the char map in mShmemFace; + // return true if successful, false if it remains null (maybe the parent + // hasn't handled our SetCharacterMap message yet). + bool TrySetShmemCharacterMap(); + + // Helper for gfxPlatformFontList::CreateFontEntry methods: set properties + // of the gfxFontEntry based on shared Face and Family records. + void InitializeFrom(mozilla::fontlist::Face* aFace, + const mozilla::fontlist::Family* aFamily); + + // Shaper-specific face objects, shared by all instantiations of the same + // physical font, regardless of size. + // Usually, only one of these will actually be created for any given font + // entry, depending on the font tables that are present. + + // hb_face_t is refcounted internally, so each shaper that's using it will + // bump the ref count when it acquires the face, and "destroy" (release) it + // in its destructor. The font entry has only this non-owning reference to + // the face; when the face is deleted, it will tell the font entry to forget + // it, so that a new face will be created next time it is needed. + hb_face_t* mHBFace = nullptr; + + static hb_blob_t* HBGetTable(hb_face_t* face, uint32_t aTag, void* aUserData); + + // Callback that the hb_face will use to tell us when it is being deleted. + static void HBFaceDeletedCallback(void* aUserData); + + // All libGraphite functionality is sandboxed in an rlbox sandbox. This + // contains data for the sandbox instance. + struct GrSandboxData; + GrSandboxData* mSandboxData = nullptr; + + // gr_face is -not- refcounted, so it will be owned directly by the font + // entry, and we'll keep a count of how many references we've handed out; + // each shaper is responsible to call ReleaseGrFace on its entry when + // finished with it, so that we know when it can be deleted. + tainted_opaque_gr<gr_face*> mGrFace; + + // For AAT font, a strong reference to the 'trak' table (if present). + hb_blob_t* const kTrakTableUninitialized = (hb_blob_t*)(intptr_t(-1)); + hb_blob_t* mTrakTable = kTrakTableUninitialized; + bool TrakTableInitialized() const { + return mTrakTable != kTrakTableUninitialized; + } + + // Cached pointers to tables within 'trak', initialized by ParseTrakTable. + const mozilla::AutoSwap_PRInt16* mTrakValues; + const mozilla::AutoSwap_PRInt32* mTrakSizeTable; + + // number of current users of this entry's mGrFace + nsrefcnt mGrFaceRefCnt = 0; + + static tainted_opaque_gr<const void*> GrGetTable( + rlbox_sandbox_gr& sandbox, tainted_opaque_gr<const void*> aAppFaceHandle, + tainted_opaque_gr<unsigned int> aName, tainted_opaque_gr<size_t*> aLen); + static void GrReleaseTable(rlbox_sandbox_gr& sandbox, + tainted_opaque_gr<const void*> aAppFaceHandle, + tainted_opaque_gr<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 = 0; + + // Font's unitsPerEm from the 'head' table, if available (will be set to + // kInvalidUPEM for non-sfnt font formats) + uint16_t mUnitsPerEm = 0; + + uint16_t mNumTrakSizes; + + private: + /** + * Font table hashtable, to support GetFontTable for harfbuzz. + * + * The harfbuzz shaper (and potentially other clients) needs access to raw + * font table data. This needs to be cached so that it can be used + * repeatedly (each time we construct a text run; in some cases, for + * each character/glyph within the run) without re-fetching large tables + * every time. + * + * Because we may instantiate many gfxFonts for the same physical font + * file (at different sizes), we should ensure that they can share a + * single cached copy of the font tables. To do this, we implement table + * access and sharing on the fontEntry rather than the font itself. + * + * The default implementation uses GetFontTable() to read font table + * data into byte arrays, and wraps them in blobs which are registered in + * a hashtable. The hashtable can then return pre-existing blobs to + * harfbuzz. + * + * Harfbuzz will "destroy" the blobs when it is finished with them. When + * the last blob reference is removed, the FontTableBlobData user data + * will remove the blob from the hashtable if still registered. + */ + + class FontTableBlobData; + + /** + * FontTableHashEntry manages the entries of hb_blob_t's containing font + * table data. + * + * This is used to share font tables across fonts with the same + * font entry (but different sizes) for use by HarfBuzz. The hashtable + * does not own a strong reference to the blob, but keeps a weak pointer, + * managed by FontTableBlobData. Similarly FontTableBlobData keeps only a + * weak pointer to the hashtable, managed by FontTableHashEntry. + */ + + class FontTableHashEntry : public nsUint32HashKey { + public: + // Declarations for nsTHashtable + + typedef nsUint32HashKey KeyClass; + typedef KeyClass::KeyType KeyType; + typedef KeyClass::KeyTypePointer KeyTypePointer; + + explicit FontTableHashEntry(KeyTypePointer aTag) + : KeyClass(aTag), mSharedBlobData(nullptr), mBlob(nullptr) {} + + // NOTE: This assumes the new entry belongs to the same hashtable as + // the old, because the mHashtable pointer in mSharedBlobData (if + // present) will not be updated. + FontTableHashEntry(FontTableHashEntry&& toMove) + : KeyClass(std::move(toMove)), + mSharedBlobData(std::move(toMove.mSharedBlobData)), + mBlob(std::move(toMove.mBlob)) { + toMove.mSharedBlobData = nullptr; + toMove.mBlob = nullptr; + } + + ~FontTableHashEntry() { Clear(); } + + // FontTable/Blob API + + // Transfer (not copy) elements of aTable to a new hb_blob_t and + // return ownership to the caller. A weak reference to the blob is + // recorded in the hashtable entry so that others may use the same + // table. + hb_blob_t* ShareTableAndGetBlob( + nsTArray<uint8_t>&& aTable, + nsTHashtable<FontTableHashEntry>* aHashtable); + + // Return a strong reference to the blob. + // Callers must hb_blob_destroy the returned blob. + hb_blob_t* GetBlob() const; + + void Clear(); + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + private: + static void DeleteFontTableBlobData(void* aBlobData); + // not implemented + FontTableHashEntry& operator=(FontTableHashEntry& toCopy); + + FontTableBlobData* mSharedBlobData; + hb_blob_t* mBlob; + }; + + mozilla::UniquePtr<nsTHashtable<FontTableHashEntry>> mFontTableCache; + + gfxFontEntry(const gfxFontEntry&); + gfxFontEntry& operator=(const gfxFontEntry&); +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(gfxFontEntry::RangeFlags) + +inline bool gfxFontEntry::SupportsItalic() { + return SlantStyle().Max().IsItalic() || + ((mRangeFlags & RangeFlags::eAutoSlantStyle) == + RangeFlags::eAutoSlantStyle && + HasItalicVariation()); +} + +inline bool gfxFontEntry::SupportsBold() { + // bold == weights 600 and above + // We return true if the face has a max weight descriptor >= 600, + // OR if it's a user font with auto-weight (no descriptor) and has + // a weight axis that supports values >= 600 + return Weight().Max().IsBold() || + ((mRangeFlags & RangeFlags::eAutoWeight) == RangeFlags::eAutoWeight && + HasBoldVariableWeight()); +} + +// used when iterating over all fonts looking for a match for a given character +struct GlobalFontMatch { + GlobalFontMatch(uint32_t aCharacter, uint32_t aNextCh, + const gfxFontStyle& aStyle, eFontPresentation aPresentation) + : mStyle(aStyle), + mCh(aCharacter), + mNextCh(aNextCh), + mPresentation(aPresentation) {} + + RefPtr<gfxFontEntry> mBestMatch; // current best match + RefPtr<gfxFontFamily> mMatchedFamily; // the family it belongs to + mozilla::fontlist::Family* mMatchedSharedFamily = nullptr; + const gfxFontStyle& mStyle; // style to match + const uint32_t mCh; // codepoint to be matched + const uint32_t mNextCh; // following codepoint (or zero) + eFontPresentation mPresentation; + uint32_t mCount = 0; // number of fonts matched + uint32_t mCmapsTested = 0; // number of cmaps tested + double mMatchDistance = INFINITY; // metric indicating closest match +}; + +// Installation status (base system / langpack / user-installed) may determine +// whether the font is visible to CSS font-family or src:local() lookups. +// (Exactly what these mean and how accurate they are may be vary across +// platforms -- e.g. on Linux there is no clear "base" set of fonts.) +enum class FontVisibility : uint8_t { + Unknown = 0, // No categorization of families available on this system + Base = 1, // Standard part of the base OS installation + LangPack = 2, // From an optional OS component such as language support + User = 3, // User-installed font (or installed by another app, etc) + Hidden = 4, // Internal system font, should never exposed to users + Webfont = 5, // Webfont defined by @font-face + Count = 6, // Count of values, for IPC serialization +}; + +namespace IPC { +template <> +struct ParamTraits<FontVisibility> + : public ContiguousEnumSerializer<FontVisibility, FontVisibility::Unknown, + FontVisibility::Count> {}; +} // namespace IPC + +class gfxFontFamily { + public: + // Used by stylo + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxFontFamily) + + gfxFontFamily(const nsACString& aName, FontVisibility aVisibility) + : mName(aName), + mVisibility(aVisibility), + mOtherFamilyNamesInitialized(false), + mHasOtherFamilyNames(false), + mFaceNamesInitialized(false), + mHasStyles(false), + mIsSimpleFamily(false), + mIsBadUnderlineFamily(false), + mFamilyCharacterMapInitialized(false), + mSkipDefaultFeatureSpaceCheck(false), + mCheckForFallbackFaces(false), + mCheckedForLegacyFamilyNames(false) {} + + const nsCString& Name() { return mName; } + + virtual void LocalizedName(nsACString& aLocalizedName); + virtual bool HasOtherFamilyNames(); + + // See https://bugzilla.mozilla.org/show_bug.cgi?id=835204: + // check the font's 'name' table to see if it has a legacy family name + // that would have been used by GDI (e.g. to split extra-bold or light + // faces in a large family into separate "styled families" because of + // GDI's 4-faces-per-family limitation). If found, the styled family + // name will be added to the font list's "other family names" table. + bool CheckForLegacyFamilyNames(gfxPlatformFontList* aFontList); + + nsTArray<RefPtr<gfxFontEntry>>& GetFontList() { return mAvailableFonts; } + + void AddFontEntry(RefPtr<gfxFontEntry> aFontEntry) { + // bug 589682 - set the IgnoreGDEF flag on entries for Italic faces + // of Times New Roman, because of buggy table in those fonts + if (aFontEntry->IsItalic() && !aFontEntry->IsUserFont() && + Name().EqualsLiteral("Times New Roman")) { + aFontEntry->mIgnoreGDEF = true; + } + if (aFontEntry->mFamilyName.IsEmpty()) { + aFontEntry->mFamilyName = Name(); + } else { + MOZ_ASSERT(aFontEntry->mFamilyName.Equals(Name())); + } + aFontEntry->mSkipDefaultFeatureSpaceCheck = mSkipDefaultFeatureSpaceCheck; + mAvailableFonts.AppendElement(aFontEntry); + + // If we're adding a face to a family that has been marked as "simple", + // we need to ensure any null entries are removed, as well as clearing + // the flag (which may be set again later). + if (mIsSimpleFamily) { + mAvailableFonts.RemoveElementsBy([](const auto& font) { return !font; }); + mIsSimpleFamily = false; + } + } + + // note that the styles for this family have been added + bool HasStyles() { 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). + gfxFontEntry* FindFontForStyle(const gfxFontStyle& aFontStyle, + bool aIgnoreSizeTolerance = false); + + virtual void FindAllFontsForStyle(const gfxFontStyle& aFontStyle, + nsTArray<gfxFontEntry*>& aFontEntryList, + bool aIgnoreSizeTolerance = false); + + // checks for a matching font within the family + // used as part of the font fallback process + void FindFontForChar(GlobalFontMatch* aMatchData); + + // checks all fonts for a matching font within the family + void SearchAllFontsForChar(GlobalFontMatch* aMatchData); + + // read in other family names, if any, and use functor to add each into cache + virtual void ReadOtherFamilyNames(gfxPlatformFontList* aPlatformFontList); + + // set when other family names have been read in + void SetOtherFamilyNamesInitialized() { mOtherFamilyNamesInitialized = true; } + + // read in other localized family names, fullnames and Postscript names + // for all faces and append to lookup tables + 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 nsACString& 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 blocklist + 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; + } + + // Check whether this family is appropriate to include in the Preferences + // font list for the given langGroup and CSS generic, if the platform lets + // us determine this. + // Return true if the family should be included in the list, false to omit. + // Default implementation returns true for everything, so no filtering + // will occur; individual platforms may override. + virtual bool FilterForFontList(nsAtom* aLangGroup, + const nsACString& aGeneric) const { + return true; + } + + FontVisibility Visibility() const { return mVisibility; } + bool IsHidden() const { return Visibility() == FontVisibility::Hidden; } + bool IsWebFontFamily() const { + return Visibility() == FontVisibility::Webfont; + } + + protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~gfxFontFamily(); + + bool ReadOtherFamilyNamesForFace(gfxPlatformFontList* aPlatformFontList, + hb_blob_t* aNameTable, + bool useFullName = false); + + // set whether this font family is in "bad" underline offset blocklist. + void SetBadUnderlineFonts() { + uint32_t i, numFonts = mAvailableFonts.Length(); + for (i = 0; i < numFonts; i++) { + if (mAvailableFonts[i]) { + mAvailableFonts[i]->mIsBadUnderlineFont = true; + } + } + } + + nsCString mName; + nsTArray<RefPtr<gfxFontEntry>> mAvailableFonts; + gfxSparseBitSet mFamilyCharacterMap; + + FontVisibility mVisibility; + + 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 + bool mCheckedForLegacyFamilyNames : 1; + + enum { + // for "simple" families, the faces are stored in mAvailableFonts + // with fixed positions: + kRegularFaceIndex = 0, + kBoldFaceIndex = 1, + kItalicFaceIndex = 2, + kBoldItalicFaceIndex = 3, + // mask values for selecting face with bold and/or italic attributes + kBoldMask = 0x01, + kItalicMask = 0x02 + }; +}; + +// Wrapper for either a mozilla::fontlist::Family in the shared font list or an +// unshared gfxFontFamily that belongs just to the current process. This does +// not own a reference, it just wraps a raw pointer and records the type. +struct FontFamily { + FontFamily() : mUnshared(nullptr), mIsShared(false) {} + + FontFamily(const FontFamily& aOther) = default; + + explicit FontFamily(gfxFontFamily* aFamily) + : mUnshared(aFamily), mIsShared(false) {} + + explicit FontFamily(mozilla::fontlist::Family* aFamily) + : mShared(aFamily), mIsShared(true) {} + + bool operator==(const FontFamily& aOther) const { + return mIsShared == aOther.mIsShared && + (mIsShared ? mShared == aOther.mShared + : mUnshared == aOther.mUnshared); + } + + bool IsNull() const { return mIsShared ? !mShared : !mUnshared; } + + union { + gfxFontFamily* mUnshared; + mozilla::fontlist::Family* mShared; + }; + bool mIsShared; +}; + +// Struct used in the gfxFontGroup font list to keep track of a font family +// together with the CSS generic (if any) that was mapped to it in this +// particular case (so it can be reported to the DevTools font inspector). +struct FamilyAndGeneric final { + FamilyAndGeneric() + : mFamily(), mGeneric(mozilla::StyleGenericFontFamily(0)) {} + FamilyAndGeneric(const FamilyAndGeneric& aOther) = default; + explicit FamilyAndGeneric(gfxFontFamily* aFamily, + mozilla::StyleGenericFontFamily aGeneric = + mozilla::StyleGenericFontFamily(0)) + : mFamily(aFamily), mGeneric(aGeneric) {} + explicit FamilyAndGeneric(mozilla::fontlist::Family* aFamily, + mozilla::StyleGenericFontFamily aGeneric = + mozilla::StyleGenericFontFamily(0)) + : mFamily(aFamily), mGeneric(aGeneric) {} + explicit FamilyAndGeneric(const FontFamily& aFamily, + mozilla::StyleGenericFontFamily aGeneric = + mozilla::StyleGenericFontFamily(0)) + : mFamily(aFamily), mGeneric(aGeneric) {} + + bool operator==(const FamilyAndGeneric& aOther) const { + return mFamily == aOther.mFamily && mGeneric == aOther.mGeneric; + } + + FontFamily mFamily; + mozilla::StyleGenericFontFamily mGeneric; +}; + +#endif diff --git a/gfx/thebes/gfxFontFamilyList.h b/gfx/thebes/gfxFontFamilyList.h new file mode 100644 index 0000000000..5ed650a646 --- /dev/null +++ b/gfx/thebes/gfxFontFamilyList.h @@ -0,0 +1,344 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FONT_FAMILY_LIST_H +#define GFX_FONT_FAMILY_LIST_H + +#include "nsAtom.h" +#include "nsDebug.h" +#include "nsISupportsImpl.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "nsUnicharUtils.h" +#include "nsTArray.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/NotNull.h" +#include "mozilla/ServoStyleConsts.h" +#include "mozilla/StaticPtr.h" + +namespace mozilla { + +/** + * font family name, an Atom for the name if not a generic and + * a font type indicated named family or which generic family + */ + +struct FontFamilyName final { + using Syntax = StyleFontFamilyNameSyntax; + + FontFamilyName() = delete; + + // named font family - e.g. Helvetica + explicit FontFamilyName(nsAtom* aFamilyName, Syntax aSyntax) + : mName(aFamilyName), mSyntax(aSyntax) {} + + explicit FontFamilyName(const nsACString& aFamilyName, Syntax aSyntax) + : mName(NS_Atomize(aFamilyName)), mSyntax(aSyntax) {} + + // generic font family - e.g. sans-serif + explicit FontFamilyName(StyleGenericFontFamily aGeneric) + : mGeneric(aGeneric) { + MOZ_ASSERT(mGeneric != StyleGenericFontFamily::None); + } + + FontFamilyName(const FontFamilyName&) = default; + + bool IsNamed() const { return !!mName; } + + bool IsGeneric() const { return !IsNamed(); } + + bool IsQuoted() const { return mSyntax == StyleFontFamilyNameSyntax::Quoted; } + + void AppendToString(nsACString& aFamilyList, bool aQuotes = true) const { + if (IsNamed()) { + if (mSyntax == Syntax::Identifiers) { + return aFamilyList.Append(nsAtomCString(mName)); + } + if (aQuotes) { + aFamilyList.Append('"'); + } + aFamilyList.Append(nsAtomCString(mName)); + if (aQuotes) { + aFamilyList.Append('"'); + } + return; + } + switch (mGeneric) { + case StyleGenericFontFamily::None: + case StyleGenericFontFamily::MozEmoji: + MOZ_FALLTHROUGH_ASSERT("Should never appear in a font-family name!"); + case StyleGenericFontFamily::Serif: + return aFamilyList.AppendLiteral("serif"); + case StyleGenericFontFamily::SansSerif: + return aFamilyList.AppendLiteral("sans-serif"); + case StyleGenericFontFamily::Monospace: + return aFamilyList.AppendLiteral("monospace"); + case StyleGenericFontFamily::Cursive: + return aFamilyList.AppendLiteral("cursive"); + case StyleGenericFontFamily::Fantasy: + return aFamilyList.AppendLiteral("fantasy"); + } + MOZ_ASSERT_UNREACHABLE("Unknown generic font-family!"); + return aFamilyList.AppendLiteral("serif"); + } + + // helper method that converts generic names to the right enum value + static FontFamilyName Convert(const nsACString& 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"); + + auto genericType = StyleGenericFontFamily::None; + if (aFamilyOrGenericName.LowerCaseEqualsLiteral("serif")) { + genericType = StyleGenericFontFamily::Serif; + } else if (aFamilyOrGenericName.LowerCaseEqualsLiteral("sans-serif")) { + genericType = StyleGenericFontFamily::SansSerif; + } else if (aFamilyOrGenericName.LowerCaseEqualsLiteral("monospace") || + aFamilyOrGenericName.LowerCaseEqualsLiteral("-moz-fixed")) { + genericType = StyleGenericFontFamily::Monospace; + } else if (aFamilyOrGenericName.LowerCaseEqualsLiteral("cursive")) { + genericType = StyleGenericFontFamily::Cursive; + } else if (aFamilyOrGenericName.LowerCaseEqualsLiteral("fantasy")) { + genericType = StyleGenericFontFamily::Fantasy; + } else { + return FontFamilyName(aFamilyOrGenericName, Syntax::Identifiers); + } + + return FontFamilyName(genericType); + } + + bool IsNamedFamily(const nsAString& aFamilyName) const { + if (!IsNamed()) { + return false; + } + nsDependentAtomString name{mName}; + return name.Equals(aFamilyName, nsCaseInsensitiveStringComparator); + } + + RefPtr<nsAtom> mName; // null if mGeneric != Default + StyleFontFamilyNameSyntax mSyntax = StyleFontFamilyNameSyntax::Quoted; + StyleGenericFontFamily mGeneric = StyleGenericFontFamily::None; +}; + +inline bool operator==(const FontFamilyName& a, const FontFamilyName& b) { + return a.mName == b.mName && a.mSyntax == b.mSyntax && + a.mGeneric == b.mGeneric; +} + +/** + * A refcounted array of FontFamilyNames. We use this to store the specified + * and computed value of the font-family property. + * + * TODO(heycam): It might better to define this type (and FontFamilyList and + * FontFamilyName) in Rust. + */ +class SharedFontList { + using Syntax = StyleFontFamilyNameSyntax; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedFontList); + + SharedFontList() = default; + + explicit SharedFontList(StyleGenericFontFamily aGenericType) + : mNames{FontFamilyName(aGenericType)} {} + + SharedFontList(nsAtom* aFamilyName, Syntax aSyntax) + : mNames{FontFamilyName(aFamilyName, aSyntax)} {} + + SharedFontList(const nsACString& aFamilyName, Syntax aSyntax) + : mNames{FontFamilyName(aFamilyName, aSyntax)} {} + + explicit SharedFontList(nsTArray<FontFamilyName>&& aNames) + : mNames(std::move(aNames)) {} + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + n += aMallocSizeOf(this); + n += mNames.ShallowSizeOfExcludingThis(aMallocSizeOf); + return n; + } + + size_t SizeOfIncludingThisIfUnshared(MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + if (mRefCnt.get() == 1) { + n += SizeOfIncludingThis(aMallocSizeOf); + } + return n; + } + + const nsTArray<FontFamilyName> mNames{}; + + static void Initialize(); + static void Shutdown(); + static StaticRefPtr<SharedFontList> sEmpty; + static StaticRefPtr<SharedFontList> + sSingleGenerics[size_t(StyleGenericFontFamily::MozEmoji)]; + + private: + ~SharedFontList() = default; +}; + +/** + * 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 { + using Syntax = StyleFontFamilyNameSyntax; + + public: + FontFamilyList() = default; + + explicit FontFamilyList(StyleGenericFontFamily aGenericType) + : mFontlist(MakeNotNull<SharedFontList*>(aGenericType)) {} + + FontFamilyList(nsAtom* aFamilyName, Syntax aSyntax) + : mFontlist(MakeNotNull<SharedFontList*>(aFamilyName, aSyntax)) {} + + FontFamilyList(const nsACString& aFamilyName, Syntax aSyntax) + : mFontlist(MakeNotNull<SharedFontList*>(aFamilyName, aSyntax)) {} + + explicit FontFamilyList(nsTArray<FontFamilyName>&& aNames) + : mFontlist(MakeNotNull<SharedFontList*>(std::move(aNames))) {} + + FontFamilyList(const FontFamilyList& aOther) = default; + + explicit FontFamilyList(NotNull<SharedFontList*> aFontList) + : mFontlist(aFontList) {} + + void SetFontlist(nsTArray<FontFamilyName>&& aNames) { + mFontlist = MakeNotNull<SharedFontList*>(std::move(aNames)); + } + + void SetFontlist(NotNull<SharedFontList*> aFontlist) { + mFontlist = aFontlist; + } + + uint32_t Length() const { return mFontlist->mNames.Length(); } + + bool IsEmpty() const { return mFontlist->mNames.IsEmpty(); } + + NotNull<SharedFontList*> GetFontlist() const { return mFontlist; } + + bool Equals(const FontFamilyList& aFontlist) const { + return (mFontlist == aFontlist.mFontlist || + mFontlist->mNames == aFontlist.mFontlist->mNames) && + mDefaultFontType == aFontlist.mDefaultFontType; + } + + bool HasDefaultGeneric() const { + if (mDefaultFontType == StyleGenericFontFamily::None) { + return false; + } + for (const FontFamilyName& name : mFontlist->mNames) { + if (name.mGeneric == 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->mNames.Length(); + for (uint32_t i = 0; i < len; i++) { + const FontFamilyName name = mFontlist->mNames[i]; + if (name.IsGeneric()) { + if (name.mGeneric == StyleGenericFontFamily::Cursive || + name.mGeneric == StyleGenericFontFamily::Fantasy) { + continue; + } + if (i > 0) { + nsTArray<FontFamilyName> names; + names.AppendElements(mFontlist->mNames); + names.RemoveElementAt(i); + names.InsertElementAt(0, name); + SetFontlist(std::move(names)); + } + return true; + } + } + return false; + } + + void PrependGeneric(StyleGenericFontFamily aGeneric) { + nsTArray<FontFamilyName> names; + names.AppendElements(mFontlist->mNames); + names.InsertElementAt(0, FontFamilyName(aGeneric)); + SetFontlist(std::move(names)); + } + + void ToString(nsACString& aFamilyList, bool aQuotes = true, + bool aIncludeDefault = false) const { + aFamilyList = + StringJoin(","_ns, mFontlist->mNames, + [aQuotes](nsACString& dst, const FontFamilyName& name) { + name.AppendToString(dst, aQuotes); + }); + if (aIncludeDefault && mDefaultFontType != StyleGenericFontFamily::None) { + if (!aFamilyList.IsEmpty()) { + aFamilyList.Append(','); + } + if (mDefaultFontType == StyleGenericFontFamily::Serif) { + aFamilyList.AppendLiteral("serif"); + } else { + aFamilyList.AppendLiteral("sans-serif"); + } + } + } + + // searches for a specific non-generic name, case-insensitive comparison + bool Contains(const nsAString& aFamilyName) const { + for (const FontFamilyName& name : mFontlist->mNames) { + if (name.IsNamedFamily(aFamilyName)) { + return true; + } + } + return false; + } + + StyleGenericFontFamily GetDefaultFontType() const { return mDefaultFontType; } + void SetDefaultFontType(StyleGenericFontFamily aType) { + NS_ASSERTION(aType == StyleGenericFontFamily::None || + aType == StyleGenericFontFamily::Serif || + aType == StyleGenericFontFamily::SansSerif, + "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->SizeOfIncludingThisIfUnshared(aMallocSizeOf); + return n; + } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + + protected: + NotNull<RefPtr<SharedFontList>> mFontlist{ + WrapNotNull(SharedFontList::sEmpty.get())}; + StyleGenericFontFamily mDefaultFontType = + StyleGenericFontFamily::None; // or serif, or sans-serif +}; + +inline bool operator==(const FontFamilyList& a, const FontFamilyList& b) { + return a.Equals(b); +} + +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 0000000000..4d1d82927a --- /dev/null +++ b/gfx/thebes/gfxFontFeatures.cpp @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxFontFeatures.h" +#include "nsAtom.h" +#include "nsUnicharUtils.h" +#include "nsHashKeys.h" + +using namespace mozilla; + +gfxFontFeatureValueSet::gfxFontFeatureValueSet() : mFontFeatureValues(8) {} + +Span<const uint32_t> gfxFontFeatureValueSet::GetFontFeatureValuesFor( + const nsACString& aFamily, uint32_t aVariantProperty, nsAtom* aName) const { + nsAutoCString family(aFamily); + ToLowerCase(family); + FeatureValueHashKey key(family, aVariantProperty, aName); + FeatureValueHashEntry* entry = mFontFeatureValues.GetEntry(key); + if (!entry) { + return {}; + } + NS_ASSERTION(entry->mValues.Length() > 0, + "null array of font feature values"); + return {entry->mValues}; +} + +nsTArray<uint32_t>* gfxFontFeatureValueSet::AppendFeatureValueHashEntry( + const nsACString& aFamily, nsAtom* aName, uint32_t aAlternate) { + FeatureValueHashKey key(aFamily, aAlternate, aName); + FeatureValueHashEntry* entry = mFontFeatureValues.PutEntry(key); + entry->mKey = key; + return &entry->mValues; +} + +bool gfxFontFeatureValueSet::FeatureValueHashEntry::KeyEquals( + const KeyTypePointer aKey) const { + return aKey->mPropVal == mKey.mPropVal && aKey->mName == mKey.mName && + aKey->mFamily.Equals(mKey.mFamily); +} + +PLDHashNumber gfxFontFeatureValueSet::FeatureValueHashEntry::HashKey( + const KeyTypePointer aKey) { + return HashString(aKey->mFamily) + aKey->mName->hash() + + aKey->mPropVal * uint32_t(0xdeadbeef); +} diff --git a/gfx/thebes/gfxFontFeatures.h b/gfx/thebes/gfxFontFeatures.h new file mode 100644 index 0000000000..24ece02d36 --- /dev/null +++ b/gfx/thebes/gfxFontFeatures.h @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FONT_FEATURES_H +#define GFX_FONT_FEATURES_H + +#include "nsAtom.h" +#include "nsTHashtable.h" +#include "nsTArray.h" +#include "nsString.h" + +// An OpenType feature tag and value pair +struct gfxFontFeature { + uint32_t + mTag; // see http://www.microsoft.com/typography/otspec/featuretags.htm + uint32_t mValue; // 0 = off, 1 = on, larger values may be used as parameters + // to features that select among multiple alternatives +}; + +inline bool operator<(const gfxFontFeature& a, const gfxFontFeature& b) { + return (a.mTag < b.mTag) || ((a.mTag == b.mTag) && (a.mValue < b.mValue)); +} + +inline bool operator==(const gfxFontFeature& a, const gfxFontFeature& b) { + return (a.mTag == b.mTag) && (a.mValue == b.mValue); +} + +class nsAtom; + +class gfxFontFeatureValueSet final { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxFontFeatureValueSet) + + gfxFontFeatureValueSet(); + + struct ValueList { + ValueList(const nsAString& aName, const nsTArray<uint32_t>& aSelectors) + : name(aName), featureSelectors(aSelectors.Clone()) {} + nsString name; + nsTArray<uint32_t> featureSelectors; + }; + + struct FeatureValues { + uint32_t alternate; + nsTArray<ValueList> valuelist; + }; + + mozilla::Span<const uint32_t> GetFontFeatureValuesFor( + const nsACString& aFamily, uint32_t aVariantProperty, + nsAtom* aName) const; + + // Appends a new hash entry with given key values and returns a pointer to + // mValues array to fill. This should be filled first. + nsTArray<uint32_t>* AppendFeatureValueHashEntry(const nsACString& aFamily, + nsAtom* aName, + uint32_t aAlternate); + + private: + // Private destructor, to discourage deletion outside of Release(): + ~gfxFontFeatureValueSet() = default; + + struct FeatureValueHashKey { + nsCString mFamily; + uint32_t mPropVal; + RefPtr<nsAtom> mName; + + FeatureValueHashKey() : mPropVal(0) {} + FeatureValueHashKey(const nsACString& aFamily, uint32_t aPropVal, + nsAtom* aName) + : mFamily(aFamily), mPropVal(aPropVal), mName(aName) {} + FeatureValueHashKey(const FeatureValueHashKey& aKey) = default; + }; + + class FeatureValueHashEntry : public PLDHashEntryHdr { + public: + typedef const FeatureValueHashKey& KeyType; + typedef const FeatureValueHashKey* KeyTypePointer; + + explicit FeatureValueHashEntry(KeyTypePointer aKey) {} + FeatureValueHashEntry(FeatureValueHashEntry&& other) + : PLDHashEntryHdr(std::move(other)), + mKey(std::move(other.mKey)), + mValues(std::move(other.mValues)) { + NS_ERROR("Should not be called"); + } + ~FeatureValueHashEntry() = default; + + bool KeyEquals(const KeyTypePointer aKey) const; + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(const KeyTypePointer aKey); + enum { ALLOW_MEMMOVE = true }; + + FeatureValueHashKey mKey; + nsTArray<uint32_t> mValues; + }; + + nsTHashtable<FeatureValueHashEntry> mFontFeatureValues; +}; + +#endif diff --git a/gfx/thebes/gfxFontInfoLoader.cpp b/gfx/thebes/gfxFontInfoLoader.cpp new file mode 100644 index 0000000000..a245644ae5 --- /dev/null +++ b/gfx/thebes/gfxFontInfoLoader.cpp @@ -0,0 +1,323 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxFontInfoLoader.h" +#include "mozilla/gfx/Logging.h" +#include "nsCRT.h" +#include "nsIObserverService.h" +#include "nsXPCOM.h" // for gXPCOMThreadsShutDown +#include "nsThreadUtils.h" // for nsRunnable +#include "gfxPlatformFontList.h" + +#ifdef XP_WIN +# include <windows.h> +#endif + +using namespace mozilla; +using services::GetObserverService; + +#define LOG_FONTINIT(args) \ + MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug, args) +#define LOG_FONTINIT_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug) + +void FontInfoData::Load() { + TimeStamp start = TimeStamp::Now(); + + uint32_t i, n = mFontFamiliesToLoad.Length(); + mLoadStats.families = n; + for (i = 0; i < n && !mCanceled; i++) { + // font file memory mapping sometimes causes exceptions - bug 1100949 + MOZ_SEH_TRY { LoadFontFamilyData(mFontFamiliesToLoad[i]); } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + gfxCriticalError() << "Exception occurred reading font data for " + << mFontFamiliesToLoad[i].get(); + } + } + + mLoadTime = TimeStamp::Now() - start; +} + +class FontInfoLoadCompleteEvent : public Runnable { + virtual ~FontInfoLoadCompleteEvent() = default; + + public: + NS_INLINE_DECL_REFCOUNTING_INHERITED(FontInfoLoadCompleteEvent, Runnable) + + explicit FontInfoLoadCompleteEvent(FontInfoData* aFontInfo) + : mozilla::Runnable("FontInfoLoadCompleteEvent"), mFontInfo(aFontInfo) {} + + NS_IMETHOD Run() override; + + private: + RefPtr<FontInfoData> mFontInfo; +}; + +class AsyncFontInfoLoader : public Runnable { + virtual ~AsyncFontInfoLoader() = default; + + public: + NS_INLINE_DECL_REFCOUNTING_INHERITED(AsyncFontInfoLoader, Runnable) + + explicit AsyncFontInfoLoader(FontInfoData* aFontInfo) + : mozilla::Runnable("AsyncFontInfoLoader"), mFontInfo(aFontInfo) { + mCompleteEvent = new FontInfoLoadCompleteEvent(aFontInfo); + } + + NS_IMETHOD Run() override; + + private: + RefPtr<FontInfoData> mFontInfo; + RefPtr<FontInfoLoadCompleteEvent> mCompleteEvent; +}; + +class ShutdownThreadEvent : public Runnable { + virtual ~ShutdownThreadEvent() = default; + + public: + NS_INLINE_DECL_REFCOUNTING_INHERITED(ShutdownThreadEvent, Runnable) + + explicit ShutdownThreadEvent(nsIThread* aThread) + : mozilla::Runnable("ShutdownThreadEvent"), mThread(aThread) {} + NS_IMETHOD Run() override { + mThread->Shutdown(); + return NS_OK; + } + + private: + nsCOMPtr<nsIThread> mThread; +}; + +// runs on main thread after async font info loading is done +nsresult FontInfoLoadCompleteEvent::Run() { + gfxFontInfoLoader* loader = + static_cast<gfxFontInfoLoader*>(gfxPlatformFontList::PlatformFontList()); + + loader->FinalizeLoader(mFontInfo); + + return NS_OK; +} + +// runs on separate thread +nsresult AsyncFontInfoLoader::Run() { + // load platform-specific font info + mFontInfo->Load(); + + // post a completion event that transfer the data to the fontlist + NS_DispatchToMainThread(mCompleteEvent); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(gfxFontInfoLoader::ShutdownObserver, nsIObserver) + +static bool sFontLoaderShutdownObserved = false; + +NS_IMETHODIMP +gfxFontInfoLoader::ShutdownObserver::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* someData) { + if (!nsCRT::strcmp(aTopic, "quit-application") || + !nsCRT::strcmp(aTopic, "xpcom-shutdown")) { + mLoader->CancelLoader(); + sFontLoaderShutdownObserved = true; + } else { + MOZ_ASSERT_UNREACHABLE("unexpected notification topic"); + } + return NS_OK; +} + +// StartLoader is usually called at startup with a (prefs-derived) delay value, +// so that the async loader runs shortly after startup, to avoid competing for +// disk i/o etc with other more critical operations. +// However, it may be called with aDelay=0 if we find that the font info (e.g. +// localized names) is needed for layout. In this case we start the loader +// immediately; however, it is still an async process and we may use fallback +// fonts to satisfy layout until it completes. +void gfxFontInfoLoader::StartLoader(uint32_t aDelay) { + if (aDelay == 0 && (mState == stateTimerOff || mState == stateAsyncLoad)) { + // We were asked to load (async) without delay, but have already started, + // so just return and let the loader proceed. + return; + } + + NS_ASSERTION(!mFontInfo, "fontinfo should be null when starting font loader"); + + // sanity check + if (mState != stateInitial && mState != stateTimerOff && + mState != stateTimerOnDelay) { + CancelLoader(); + } + + AddShutdownObserver(); + + // Caller asked for a delay? ==> start async thread after a delay + if (aDelay) { + NS_ASSERTION(!sFontLoaderShutdownObserved, + "Bug 1508626 - Setting delay timer for font loader after " + "shutdown observed"); + NS_ASSERTION(!gXPCOMThreadsShutDown, + "Bug 1508626 - Setting delay timer for font loader after " + "shutdown but before observer"); + // Set up delay timer. We don't expect to be called with a delay once + // the timer already exists, but if that happens we'll just ignore the + // extra call and leave the existing timer to do its thing. + MOZ_ASSERT(!mTimer, "duplicate use of StartLoader() with delay?"); + if (mTimer) { + return; + } + mTimer = NS_NewTimer(); + mTimer->InitWithNamedFuncCallback(DelayedStartCallback, this, aDelay, + nsITimer::TYPE_ONE_SHOT, + "gfxFontInfoLoader::StartLoader"); + mState = stateTimerOnDelay; + return; + } + + // Either we've been called back by the DelayedStartCallback when its timer + // fired, or a layout caller has passed aDelay=0 to ask the loader to run + // without further delay. + + // Cancel the delay timer, if any. + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + + NS_ASSERTION( + !sFontLoaderShutdownObserved, + "Bug 1508626 - Initializing font loader after shutdown observed"); + NS_ASSERTION(!gXPCOMThreadsShutDown, + "Bug 1508626 - Initializing font loader after shutdown but " + "before observer"); + + mFontInfo = CreateFontInfoData(); + if (!mFontInfo) { + // The platform doesn't want anything loaded, so just bail out. + mState = stateTimerOff; + return; + } + + // initialize + InitLoader(); + + // start async load + nsresult rv = NS_NewNamedThread("Font Loader", + getter_AddRefs(mFontLoaderThread), nullptr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + PRThread* prThread; + if (NS_SUCCEEDED(mFontLoaderThread->GetPRThread(&prThread))) { + PR_SetThreadPriority(prThread, PR_PRIORITY_LOW); + } + + mState = stateAsyncLoad; + + nsCOMPtr<nsIRunnable> loadEvent = new AsyncFontInfoLoader(mFontInfo); + + mFontLoaderThread->Dispatch(loadEvent.forget(), NS_DISPATCH_NORMAL); + + if (LOG_FONTINIT_ENABLED()) { + LOG_FONTINIT( + ("(fontinit) fontloader started (fontinfo: %p)\n", mFontInfo.get())); + } +} + +class FinalizeLoaderRunnable : public Runnable { + virtual ~FinalizeLoaderRunnable() = default; + + public: + NS_INLINE_DECL_REFCOUNTING_INHERITED(FinalizeLoaderRunnable, Runnable) + + explicit FinalizeLoaderRunnable(gfxFontInfoLoader* aLoader) + : mozilla::Runnable("FinalizeLoaderRunnable"), mLoader(aLoader) {} + + NS_IMETHOD Run() override { + nsresult rv; + if (mLoader->LoadFontInfo()) { + mLoader->CancelLoader(); + rv = NS_OK; + } else { + nsCOMPtr<nsIRunnable> runnable = this; + rv = NS_DispatchToCurrentThreadQueue( + runnable.forget(), PR_INTERVAL_NO_TIMEOUT, EventQueuePriority::Idle); + } + return rv; + } + + private: + gfxFontInfoLoader* mLoader; +}; + +void gfxFontInfoLoader::FinalizeLoader(FontInfoData* aFontInfo) { + // Avoid loading data if loader has already been canceled. + // This should mean that CancelLoader() ran and the Load + // thread has already Shutdown(), and likely before processing + // the Shutdown event it handled the load event and sent back + // our Completion event, thus we end up here. + if (mState != stateAsyncLoad || mFontInfo != aFontInfo) { + return; + } + + mLoadTime = mFontInfo->mLoadTime; + + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIRunnable> runnable = new FinalizeLoaderRunnable(this); + if (NS_FAILED(NS_DispatchToCurrentThreadQueue(runnable.forget(), + PR_INTERVAL_NO_TIMEOUT, + EventQueuePriority::Idle))) { + NS_WARNING("Failed to finalize async font info"); + } +} + +void gfxFontInfoLoader::CancelLoader() { + if (mState == stateInitial) { + return; + } + mState = stateTimerOff; + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + if (mFontInfo) // null during any initial delay + mFontInfo->mCanceled = true; + if (mFontLoaderThread) { + NS_DispatchToMainThread(new ShutdownThreadEvent(mFontLoaderThread)); + mFontLoaderThread = nullptr; + } + RemoveShutdownObserver(); + CleanupLoader(); +} + +gfxFontInfoLoader::~gfxFontInfoLoader() { + RemoveShutdownObserver(); + MOZ_COUNT_DTOR(gfxFontInfoLoader); +} + +void gfxFontInfoLoader::AddShutdownObserver() { + if (mObserver) { + return; + } + + nsCOMPtr<nsIObserverService> obs = GetObserverService(); + if (obs) { + mObserver = new ShutdownObserver(this); + obs->AddObserver(mObserver, "quit-application", false); + obs->AddObserver(mObserver, "xpcom-shutdown", false); + } +} + +void gfxFontInfoLoader::RemoveShutdownObserver() { + if (mObserver) { + nsCOMPtr<nsIObserverService> obs = GetObserverService(); + if (obs) { + obs->RemoveObserver(mObserver, "quit-application"); + obs->RemoveObserver(mObserver, "xpcom-shutdown"); + mObserver = nullptr; + } + } +} diff --git a/gfx/thebes/gfxFontInfoLoader.h b/gfx/thebes/gfxFontInfoLoader.h new file mode 100644 index 0000000000..2f0b053a90 --- /dev/null +++ b/gfx/thebes/gfxFontInfoLoader.h @@ -0,0 +1,215 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FONT_INFO_LOADER_H +#define GFX_FONT_INFO_LOADER_H + +#include "nsCOMPtr.h" +#include "nsIObserver.h" +#include "nsITimer.h" +#include "nsIThread.h" +#include "nsString.h" +#include "gfxFontEntry.h" +#include "mozilla/Atomics.h" +#include "mozilla/TimeStamp.h" +#include "nsISupports.h" + +// data retrieved for a given face + +struct FontFaceData { + nsCString mFullName; + nsCString mPostscriptName; + RefPtr<gfxCharacterMap> mCharacterMap; + uint32_t mUVSOffset = 0; +}; + +// base class used to contain cached system-wide font info. +// methods in this class are called on off-main threads so +// all methods use only static methods or other thread-safe +// font data access API's. specifically, no use is made of +// gfxPlatformFontList, gfxFontFamily, gfxFamily or any +// harfbuzz API methods within FontInfoData subclasses. + +class FontInfoData { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FontInfoData) + + FontInfoData(bool aLoadOtherNames, bool aLoadFaceNames, bool aLoadCmaps) + : mCanceled(false), + mLoadOtherNames(aLoadOtherNames), + mLoadFaceNames(aLoadFaceNames), + mLoadCmaps(aLoadCmaps) { + MOZ_COUNT_CTOR(FontInfoData); + } + + protected: + // Protected destructor, to discourage deletion outside of Release(): + MOZ_COUNTED_DTOR_VIRTUAL(FontInfoData) + + public: + virtual void Load(); + + // loads font data for all fonts of a given family + // (called on async thread) + virtual void LoadFontFamilyData(const nsACString& aFamilyName) = 0; + + // -- methods overriden by platform-specific versions -- + + // fetches cmap data for a particular font from cached font data + virtual already_AddRefed<gfxCharacterMap> GetCMAP(const nsACString& aFontName, + uint32_t& aUVSOffset) { + FontFaceData faceData; + if (!mFontFaceData.Get(aFontName, &faceData) || !faceData.mCharacterMap) { + return nullptr; + } + + aUVSOffset = faceData.mUVSOffset; + RefPtr<gfxCharacterMap> cmap = faceData.mCharacterMap; + return cmap.forget(); + } + + // fetches fullname/postscript names from cached font data + virtual void GetFaceNames(const nsACString& aFontName, nsACString& aFullName, + nsACString& aPostscriptName) { + FontFaceData faceData; + if (!mFontFaceData.Get(aFontName, &faceData)) { + return; + } + + aFullName = faceData.mFullName; + aPostscriptName = faceData.mPostscriptName; + } + + // fetches localized family name data from cached font data + const nsTArray<nsCString>* GetOtherFamilyNames( + const nsACString& aFamilyName) { + return mOtherFamilyNames.GetValue(aFamilyName); + } + + nsTArray<nsCString> mFontFamiliesToLoad; + + // currently non-issue but beware, + // this is also set during cleanup after finishing + mozilla::Atomic<bool> mCanceled; + + // time spent on the loader thread + mozilla::TimeDuration mLoadTime; + + struct FontCounts { + uint32_t families; + uint32_t fonts; + uint32_t cmaps; + uint32_t facenames; + uint32_t othernames; + }; + + FontCounts mLoadStats; + + bool mLoadOtherNames; + bool mLoadFaceNames; + bool mLoadCmaps; + + // face name ==> per-face data + nsDataHashtable<nsCStringHashKey, FontFaceData> mFontFaceData; + + // canonical family name ==> array of localized family names + nsDataHashtable<nsCStringHashKey, CopyableTArray<nsCString> > + mOtherFamilyNames; +}; + +// gfxFontInfoLoader - helper class for loading font info on async thread +// For large, "all fonts on system" data, data needed on a given platform +// (e.g. localized names, face names, cmaps) are loaded async. + +// helper class for loading in font info on a separate async thread +// once async thread completes, completion process is run on the main +// thread's idle queue in short slices + +class gfxFontInfoLoader { + public: + // state transitions: + // initial ---StartLoader with delay---> timer on delay + // initial ---StartLoader without delay---> timer off + // timer on delay ---LoaderTimerFire---> timer off + // timer on delay ---CancelLoader---> timer off + // timer off ---StartLoader with delay---> timer on delay + // timer off ---StartLoader without delay---> timer off + typedef enum { + stateInitial, + stateTimerOnDelay, + stateAsyncLoad, + stateTimerOff + } TimerState; + + gfxFontInfoLoader() : mState(stateInitial) { + MOZ_COUNT_CTOR(gfxFontInfoLoader); + } + + virtual ~gfxFontInfoLoader(); + + // start timer with an initial delay + void StartLoader(uint32_t aDelay); + + // Finalize - async load complete, transfer data (on idle) + virtual void FinalizeLoader(FontInfoData* aFontInfo); + + // cancel the timer and cleanup + void CancelLoader(); + + protected: + friend class FinalizeLoaderRunnable; + + class ShutdownObserver : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + explicit ShutdownObserver(gfxFontInfoLoader* aLoader) : mLoader(aLoader) {} + + protected: + virtual ~ShutdownObserver() = default; + + gfxFontInfoLoader* mLoader; + }; + + // CreateFontInfo - create platform-specific object used + // to load system-wide font info + virtual already_AddRefed<FontInfoData> CreateFontInfoData() { + return nullptr; + } + + // Init - initialization before async loader thread runs + virtual void InitLoader() = 0; + + // LoadFontInfo - transfer font info data within a time limit, return + // true when done + virtual bool LoadFontInfo() = 0; + + // Cleanup - finish and cleanup after done, including possible reflows + virtual void CleanupLoader() { mFontInfo = nullptr; } + + static void DelayedStartCallback(nsITimer* aTimer, void* aThis) { + gfxFontInfoLoader* loader = static_cast<gfxFontInfoLoader*>(aThis); + loader->StartLoader(0); + } + + void LoadFontInfoTimerFire(); + + void AddShutdownObserver(); + void RemoveShutdownObserver(); + + nsCOMPtr<nsITimer> mTimer; + nsCOMPtr<nsIObserver> mObserver; + nsCOMPtr<nsIThread> mFontLoaderThread; + TimerState mState; + + // after async font loader completes, data is stored here + RefPtr<FontInfoData> mFontInfo; + + // time spent on the loader thread + mozilla::TimeDuration mLoadTime; +}; + +#endif /* GFX_FONT_INFO_LOADER_H */ diff --git a/gfx/thebes/gfxFontMissingGlyphs.cpp b/gfx/thebes/gfxFontMissingGlyphs.cpp new file mode 100644 index 0000000000..be6ef83fcb --- /dev/null +++ b/gfx/thebes/gfxFontMissingGlyphs.cpp @@ -0,0 +1,501 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxFontMissingGlyphs.h" + +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Helpers.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/LinkedList.h" +#include "mozilla/RefPtr.h" +#include "nsDeviceContext.h" +#include "nsLayoutUtils.h" +#include "TextDrawTarget.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +#define X 255 +static const uint8_t gMiniFontData[] = { + 0, X, 0, 0, X, 0, X, X, X, X, X, X, X, 0, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, 0, 0, X, X, X, X, 0, X, X, X, X, X, X, + X, 0, X, 0, X, 0, 0, 0, X, 0, 0, X, X, 0, X, X, 0, 0, X, 0, 0, 0, 0, X, + X, 0, X, X, 0, X, X, 0, X, X, 0, X, X, 0, 0, X, 0, X, X, 0, 0, X, 0, 0, + X, 0, X, 0, X, 0, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, 0, 0, X, + X, X, X, X, X, X, X, X, X, X, X, 0, X, 0, 0, X, 0, X, X, X, X, X, X, X, + X, 0, X, 0, X, 0, X, 0, 0, 0, 0, X, 0, 0, X, 0, 0, X, X, 0, X, 0, 0, X, + X, 0, X, 0, 0, X, X, 0, X, X, 0, X, X, 0, 0, X, 0, X, X, 0, 0, X, 0, 0, + 0, X, 0, 0, X, 0, X, X, X, X, X, X, 0, 0, X, X, X, X, X, X, X, 0, 0, X, + X, X, X, 0, 0, X, X, 0, X, X, X, 0, 0, X, X, X, X, 0, X, X, X, X, 0, 0, +}; +#undef X + +/* Parameters that control the rendering of hexboxes. They look like this: + + BMP codepoints non-BMP codepoints + (U+0000 - U+FFFF) (U+10000 - U+10FFFF) + + +---------+ +-------------+ + | | | | + | HHH HHH | | HHH HHH HHH | + | HHH HHH | | HHH HHH HHH | + | HHH HHH | | HHH HHH HHH | + | HHH HHH | | HHH HHH HHH | + | HHH HHH | | HHH HHH HHH | + | | | | + | HHH HHH | | HHH HHH HHH | + | HHH HHH | | HHH HHH HHH | + | HHH HHH | | HHH HHH HHH | + | HHH HHH | | HHH HHH HHH | + | HHH HHH | | HHH HHH HHH | + | | | | + +---------+ +-------------+ +*/ + +/** Width of a minifont glyph (see above) */ +static const int MINIFONT_WIDTH = 3; +/** Height of a minifont glyph (see above) */ +static const int MINIFONT_HEIGHT = 5; +/** + * Gap between minifont glyphs (both horizontal and vertical) and also + * the minimum desired gap between the box border and the glyphs + */ +static const int HEX_CHAR_GAP = 1; +/** + * The amount of space between the vertical edge of the glyphbox and the + * box border. We make this nonzero so that when multiple missing glyphs + * occur consecutively there's a gap between their rendered boxes. + */ +static const int BOX_HORIZONTAL_INSET = 1; +/** The width of the border */ +static const int BOX_BORDER_WIDTH = 1; +/** + * The scaling factor for the border opacity; this is multiplied by the current + * opacity being used to draw the text. + */ +static const Float BOX_BORDER_OPACITY = 0.5; + +#ifndef MOZ_GFX_OPTIMIZE_MOBILE + +static RefPtr<DrawTarget> gGlyphDrawTarget; +static RefPtr<SourceSurface> gGlyphMask; +static RefPtr<SourceSurface> gGlyphAtlas; +static DeviceColor gGlyphColor; + +/** + * Generates a new colored mini-font atlas from the mini-font mask. + */ +static bool MakeGlyphAtlas(const DeviceColor& aColor) { + gGlyphAtlas = nullptr; + if (!gGlyphDrawTarget) { + gGlyphDrawTarget = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( + IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT), + SurfaceFormat::B8G8R8A8); + if (!gGlyphDrawTarget) { + return false; + } + } + if (!gGlyphMask) { + gGlyphMask = gGlyphDrawTarget->CreateSourceSurfaceFromData( + const_cast<uint8_t*>(gMiniFontData), + IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT), MINIFONT_WIDTH * 16, + SurfaceFormat::A8); + if (!gGlyphMask) { + return false; + } + } + gGlyphDrawTarget->MaskSurface(ColorPattern(aColor), gGlyphMask, Point(0, 0), + DrawOptions(1.0f, CompositionOp::OP_SOURCE)); + gGlyphAtlas = gGlyphDrawTarget->Snapshot(); + if (!gGlyphAtlas) { + return false; + } + gGlyphColor = aColor; + return true; +} + +/** + * Reuse the current mini-font atlas if the color matches, otherwise regenerate + * it. + */ +static inline already_AddRefed<SourceSurface> GetGlyphAtlas( + const DeviceColor& aColor) { + // Get the opaque color, ignoring any transparency which will be handled + // later. + DeviceColor color(aColor.r, aColor.g, aColor.b); + if ((gGlyphAtlas && gGlyphColor == color) || MakeGlyphAtlas(color)) { + return do_AddRef(gGlyphAtlas); + } + return nullptr; +} + +/** + * Clear any cached glyph atlas resources. + */ +static void PurgeGlyphAtlas() { + gGlyphAtlas = nullptr; + gGlyphDrawTarget = nullptr; + gGlyphMask = nullptr; +} + +// WebRender layer manager user data that will get signaled when the layer +// manager is destroyed. +class WRUserData : public layers::LayerUserData, + public LinkedListElement<WRUserData> { + public: + explicit WRUserData(layers::WebRenderLayerManager* aManager); + + ~WRUserData(); + + static void Assign(layers::WebRenderLayerManager* aManager) { + if (!aManager->HasUserData(&sWRUserDataKey)) { + aManager->SetUserData(&sWRUserDataKey, new WRUserData(aManager)); + } + } + + void Remove() { mManager->RemoveUserData(&sWRUserDataKey); } + + layers::WebRenderLayerManager* mManager; + + static UserDataKey sWRUserDataKey; +}; + +static RefPtr<SourceSurface> gWRGlyphAtlas[8]; +static LinkedList<WRUserData> gWRUsers; +UserDataKey WRUserData::sWRUserDataKey; + +/** + * Generates a transformed WebRender mini-font atlas for a given orientation. + */ +static already_AddRefed<SourceSurface> MakeWRGlyphAtlas(const Matrix* aMat) { + IntSize size(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT); + // If the orientation is transposed, width/height are swapped. + if (aMat && aMat->_11 == 0) { + std::swap(size.width, size.height); + } + RefPtr<DrawTarget> ref = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + RefPtr<DrawTarget> dt = + gfxPlatform::GetPlatform()->CreateSimilarSoftwareDrawTarget( + ref, size, SurfaceFormat::B8G8R8A8); + if (!dt) { + return nullptr; + } + if (aMat) { + // Select appropriate transform matrix based on whether the + // orientation is transposed. + dt->SetTransform(aMat->_11 == 0 + ? Matrix(0.0f, copysign(1.0f, aMat->_12), + copysign(1.0f, aMat->_21), 0.0f, + aMat->_21 < 0 ? MINIFONT_HEIGHT : 0.0f, + aMat->_12 < 0 ? MINIFONT_WIDTH * 16 : 0.0f) + : Matrix(copysign(1.0f, aMat->_11), 0.0f, 0.0f, + copysign(1.0f, aMat->_22), + aMat->_11 < 0 ? MINIFONT_WIDTH * 16 : 0.0f, + aMat->_22 < 0 ? MINIFONT_HEIGHT : 0.0f)); + } + RefPtr<SourceSurface> mask = dt->CreateSourceSurfaceFromData( + const_cast<uint8_t*>(gMiniFontData), + IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT), MINIFONT_WIDTH * 16, + SurfaceFormat::A8); + if (!mask) { + return nullptr; + } + dt->MaskSurface(ColorPattern(DeviceColor::MaskOpaqueWhite()), mask, + Point(0, 0)); + return dt->Snapshot(); +} + +/** + * Clear any cached WebRender glyph atlas resources. + */ +static void PurgeWRGlyphAtlas() { + // For each WR layer manager, we need go through each atlas orientation + // and see if it has a stashed image key. If it does, remove the image + // from the layer manager. + for (WRUserData* user : gWRUsers) { + auto* manager = user->mManager; + for (size_t i = 0; i < 8; i++) { + if (gWRGlyphAtlas[i]) { + uint32_t handle = (uint32_t)(uintptr_t)gWRGlyphAtlas[i]->GetUserData( + reinterpret_cast<UserDataKey*>(manager)); + if (handle) { + manager->GetRenderRootStateManager()->AddImageKeyForDiscard( + wr::ImageKey{manager->WrBridge()->GetNamespace(), handle}); + } + } + } + } + // Remove the layer managers' destroy notifications only after processing + // so as not to mess up gWRUsers iteration. + while (!gWRUsers.isEmpty()) { + gWRUsers.popFirst()->Remove(); + } + // Finally, clear out the atlases. + for (size_t i = 0; i < 8; i++) { + gWRGlyphAtlas[i] = nullptr; + } +} + +WRUserData::WRUserData(layers::WebRenderLayerManager* aManager) + : mManager(aManager) { + gWRUsers.insertFront(this); +} + +WRUserData::~WRUserData() { + // When the layer manager is destroyed, we need go through each + // atlas and remove any assigned image keys. + if (isInList()) { + for (size_t i = 0; i < 8; i++) { + if (gWRGlyphAtlas[i]) { + gWRGlyphAtlas[i]->RemoveUserData( + reinterpret_cast<UserDataKey*>(mManager)); + } + } + } +} + +static already_AddRefed<SourceSurface> GetWRGlyphAtlas(DrawTarget& aDrawTarget, + const Matrix* aMat) { + uint32_t key = 0; + // Encode orientation in the key. + if (aMat) { + if (aMat->_11 == 0) { + key |= 4 | (aMat->_12 < 0 ? 1 : 0) | (aMat->_21 < 0 ? 2 : 0); + } else { + key |= (aMat->_11 < 0 ? 1 : 0) | (aMat->_22 < 0 ? 2 : 0); + } + } + + // Check if an atlas was already created, or create one if necessary. + RefPtr<SourceSurface> atlas = gWRGlyphAtlas[key]; + if (!atlas) { + atlas = MakeWRGlyphAtlas(aMat); + gWRGlyphAtlas[key] = atlas; + } + + // The atlas may exist, but an image key may not be assigned for it to + // the given layer manager. + auto* tdt = static_cast<layout::TextDrawTarget*>(&aDrawTarget); + auto* manager = tdt->WrLayerManager(); + if (!atlas->GetUserData(reinterpret_cast<UserDataKey*>(manager))) { + // No image key, so we need to map the atlas' data for transfer to WR. + RefPtr<DataSourceSurface> dataSurface = atlas->GetDataSurface(); + if (!dataSurface) { + return nullptr; + } + DataSourceSurface::ScopedMap map(dataSurface, DataSourceSurface::READ); + if (!map.IsMapped()) { + return nullptr; + } + // Transfer the data and get an image key for it. + Maybe<wr::ImageKey> result = tdt->DefineImage( + atlas->GetSize(), map.GetStride(), atlas->GetFormat(), map.GetData()); + if (!result.isSome()) { + return nullptr; + } + // Assign the image key to the atlas. + atlas->AddUserData(reinterpret_cast<UserDataKey*>(manager), + (void*)(uintptr_t)result.value().mHandle, nullptr); + // Create a user data notification for when the layer manager is + // destroyed so we can clean up any assigned image keys. + WRUserData::Assign(manager); + } + return atlas.forget(); +} + +static void DrawHexChar(uint32_t aDigit, Float aLeft, Float aTop, + DrawTarget& aDrawTarget, SourceSurface* aAtlas, + const DeviceColor& aColor, + const Matrix* aMat = nullptr) { + Rect dest(aLeft, aTop, MINIFONT_WIDTH, MINIFONT_HEIGHT); + if (aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT) { + // For WR, we need to get the image key assigned to the given WR layer + // manager for referencing the image. + auto* tdt = static_cast<layout::TextDrawTarget*>(&aDrawTarget); + auto* manager = tdt->WrLayerManager(); + wr::ImageKey key = {manager->WrBridge()->GetNamespace(), + (uint32_t)(uintptr_t)aAtlas->GetUserData( + reinterpret_cast<UserDataKey*>(manager))}; + // Transform the bounds of the atlas into the given orientation, and then + // also transform a small clip rect which will be used to select the given + // digit from the atlas. + Rect bounds(aLeft - aDigit * MINIFONT_WIDTH, aTop, MINIFONT_WIDTH * 16, + MINIFONT_HEIGHT); + if (aMat) { + // Width and height may be negative after the transform, so move the rect + // if necessary and fix size. + bounds = aMat->TransformRect(bounds); + bounds.x += std::min(bounds.width, 0.0f); + bounds.y += std::min(bounds.height, 0.0f); + bounds.width = fabs(bounds.width); + bounds.height = fabs(bounds.height); + dest = aMat->TransformRect(dest); + dest.x += std::min(dest.width, 0.0f); + dest.y += std::min(dest.height, 0.0f); + dest.width = fabs(dest.width); + dest.height = fabs(dest.height); + } + // Finally, push the colored image with point filtering. + tdt->PushImage(key, bounds, dest, wr::ImageRendering::Pixelated, + wr::ToColorF(aColor)); + } else { + // For the normal case, just draw the given digit from the atlas. Point + // filtering is used to ensure the mini-font rectangles stay sharp with any + // scaling. Handle any transparency here as well. + aDrawTarget.DrawSurface( + aAtlas, dest, + Rect(aDigit * MINIFONT_WIDTH, 0, MINIFONT_WIDTH, MINIFONT_HEIGHT), + DrawSurfaceOptions(SamplingFilter::POINT), + DrawOptions(aColor.a, CompositionOp::OP_OVER, AntialiasMode::NONE)); + } +} + +void gfxFontMissingGlyphs::Purge() { + PurgeGlyphAtlas(); + PurgeWRGlyphAtlas(); +} + +#else // MOZ_GFX_OPTIMIZE_MOBILE + +void gfxFontMissingGlyphs::Purge() {} + +#endif + +void gfxFontMissingGlyphs::Shutdown() { Purge(); } + +void gfxFontMissingGlyphs::DrawMissingGlyph(uint32_t aChar, const Rect& aRect, + DrawTarget& aDrawTarget, + const Pattern& aPattern, + uint32_t aAppUnitsPerDevPixel, + const Matrix* aMat) { + Rect rect(aRect); + // If there is an orientation transform, reorient the bounding rect. + if (aMat) { + rect.MoveBy(-aRect.BottomLeft()); + rect = aMat->TransformBounds(rect); + rect.MoveBy(aRect.BottomLeft()); + } + + // If we're currently drawing with some kind of pattern, we just draw the + // missing-glyph data in black. + DeviceColor color = aPattern.GetType() == PatternType::COLOR + ? static_cast<const ColorPattern&>(aPattern).mColor + : ToDeviceColor(sRGBColor::OpaqueBlack()); + + // Stroke a rectangle so that the stroke's left edge is inset one pixel + // from the left edge of the glyph box and the stroke's right edge + // is inset one pixel from the right edge of the glyph box. + Float halfBorderWidth = BOX_BORDER_WIDTH / 2.0; + Float borderLeft = rect.X() + BOX_HORIZONTAL_INSET + halfBorderWidth; + Float borderRight = rect.XMost() - BOX_HORIZONTAL_INSET - halfBorderWidth; + Rect borderStrokeRect(borderLeft, rect.Y() + halfBorderWidth, + borderRight - borderLeft, + rect.Height() - 2.0 * halfBorderWidth); + if (!borderStrokeRect.IsEmpty()) { + ColorPattern adjustedColor(color); + adjustedColor.mColor.a *= BOX_BORDER_OPACITY; +#ifdef MOZ_GFX_OPTIMIZE_MOBILE + aDrawTarget.FillRect(borderStrokeRect, adjustedColor); +#else + StrokeOptions strokeOptions(BOX_BORDER_WIDTH); + aDrawTarget.StrokeRect(borderStrokeRect, adjustedColor, strokeOptions); +#endif + } + +#ifndef MOZ_GFX_OPTIMIZE_MOBILE + RefPtr<SourceSurface> atlas = + aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT + ? GetWRGlyphAtlas(aDrawTarget, aMat) + : GetGlyphAtlas(color); + if (!atlas) { + return; + } + + Point center = rect.Center(); + Float halfGap = HEX_CHAR_GAP / 2.f; + Float top = -(MINIFONT_HEIGHT + halfGap); + // We always want integer scaling, otherwise the "bitmap" glyphs will look + // even uglier than usual when zoomed + int32_t devPixelsPerCSSPx = + std::max<int32_t>(1, AppUnitsPerCSSPixel() / aAppUnitsPerDevPixel); + + Matrix tempMat; + if (aMat) { + // If there is an orientation transform, since draw target transforms may + // not be supported, scale and translate it so that it can be directly used + // for rendering the mini font without changing the draw target transform. + tempMat = Matrix(*aMat) + .PostScale(devPixelsPerCSSPx, devPixelsPerCSSPx) + .PostTranslate(center); + aMat = &tempMat; + } else { + // Otherwise, scale and translate the draw target transform assuming it + // supports that. + tempMat = aDrawTarget.GetTransform(); + aDrawTarget.SetTransform(Matrix(tempMat).PreTranslate(center).PreScale( + devPixelsPerCSSPx, devPixelsPerCSSPx)); + } + + if (aChar < 0x10000) { + if (rect.Width() >= 2 * (MINIFONT_WIDTH + HEX_CHAR_GAP) && + rect.Height() >= 2 * MINIFONT_HEIGHT + HEX_CHAR_GAP) { + // Draw 4 digits for BMP + Float left = -(MINIFONT_WIDTH + halfGap); + DrawHexChar((aChar >> 12) & 0xF, left, top, aDrawTarget, atlas, color, + aMat); + DrawHexChar((aChar >> 8) & 0xF, halfGap, top, aDrawTarget, atlas, color, + aMat); + DrawHexChar((aChar >> 4) & 0xF, left, halfGap, aDrawTarget, atlas, color, + aMat); + DrawHexChar(aChar & 0xF, halfGap, halfGap, aDrawTarget, atlas, color, + aMat); + } + } else { + if (rect.Width() >= 3 * (MINIFONT_WIDTH + HEX_CHAR_GAP) && + rect.Height() >= 2 * MINIFONT_HEIGHT + HEX_CHAR_GAP) { + // Draw 6 digits for non-BMP + Float first = -(MINIFONT_WIDTH * 1.5 + HEX_CHAR_GAP); + Float second = -(MINIFONT_WIDTH / 2.0); + Float third = (MINIFONT_WIDTH / 2.0 + HEX_CHAR_GAP); + DrawHexChar((aChar >> 20) & 0xF, first, top, aDrawTarget, atlas, color, + aMat); + DrawHexChar((aChar >> 16) & 0xF, second, top, aDrawTarget, atlas, color, + aMat); + DrawHexChar((aChar >> 12) & 0xF, third, top, aDrawTarget, atlas, color, + aMat); + DrawHexChar((aChar >> 8) & 0xF, first, halfGap, aDrawTarget, atlas, color, + aMat); + DrawHexChar((aChar >> 4) & 0xF, second, halfGap, aDrawTarget, atlas, + color, aMat); + DrawHexChar(aChar & 0xF, third, halfGap, aDrawTarget, atlas, color, aMat); + } + } + + if (!aMat) { + // The draw target transform was changed, so it must be restored to + // the original value. + aDrawTarget.SetTransform(tempMat); + } +#endif +} + +Float gfxFontMissingGlyphs::GetDesiredMinWidth(uint32_t aChar, + uint32_t aAppUnitsPerDevPixel) { + /** + * The minimum desired width for a missing-glyph glyph box. I've laid it out + * like this so you can see what goes where. + */ + Float width = BOX_HORIZONTAL_INSET + BOX_BORDER_WIDTH + HEX_CHAR_GAP + + MINIFONT_WIDTH + HEX_CHAR_GAP + MINIFONT_WIDTH + + ((aChar < 0x10000) ? 0 : HEX_CHAR_GAP + MINIFONT_WIDTH) + + HEX_CHAR_GAP + BOX_BORDER_WIDTH + BOX_HORIZONTAL_INSET; + // Note that this will give us floating-point division, so the width will + // -not- be snapped to integer multiples of its basic pixel value + width *= Float(AppUnitsPerCSSPixel()) / aAppUnitsPerDevPixel; + return width; +} diff --git a/gfx/thebes/gfxFontMissingGlyphs.h b/gfx/thebes/gfxFontMissingGlyphs.h new file mode 100644 index 0000000000..b51740ecf4 --- /dev/null +++ b/gfx/thebes/gfxFontMissingGlyphs.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FONTMISSINGGLYPHS_H +#define GFX_FONTMISSINGGLYPHS_H + +#include "mozilla/Attributes.h" +#include "mozilla/gfx/MatrixFwd.h" +#include "mozilla/gfx/Rect.h" + +namespace mozilla { +namespace gfx { +class DrawTarget; +class Pattern; +} // namespace gfx +} // namespace mozilla + +/** + * This class should not be instantiated. It's just a container + * for some helper functions. + */ +class gfxFontMissingGlyphs final { + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::Float Float; + typedef mozilla::gfx::Matrix Matrix; + typedef mozilla::gfx::Pattern Pattern; + typedef mozilla::gfx::Rect Rect; + + gfxFontMissingGlyphs() = delete; // prevent instantiation + + public: + /** + * Draw hexboxes for a missing glyph. + * @param aChar the UTF16 codepoint for the character + * @param aRect the glyph-box for the glyph that is missing + * @param aDrawTarget the DrawTarget to draw to + * @param aPattern the pattern currently being used to paint + * @param aAppUnitsPerDevPixel the appUnits to devPixel ratio we're using, + * (so we can scale glyphs to a sensible size) + * @param aMat optional local-space orientation matrix + */ + static void DrawMissingGlyph(uint32_t aChar, const Rect& aRect, + DrawTarget& aDrawTarget, const Pattern& aPattern, + uint32_t aAppUnitsPerDevPixel, + const Matrix* aMat = nullptr); + /** + * @return the desired minimum width for a glyph-box that will allow + * the hexboxes to be drawn reasonably. + */ + static Float GetDesiredMinWidth(uint32_t aChar, uint32_t aAppUnitsPerDevUnit); + + static void Purge(); + + static void Shutdown(); +}; + +#endif diff --git a/gfx/thebes/gfxFontPrefLangList.h b/gfx/thebes/gfxFontPrefLangList.h new file mode 100644 index 0000000000..5ca1311cb0 --- /dev/null +++ b/gfx/thebes/gfxFontPrefLangList.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// this needs to match the list of pref font.default.xx entries listed in +// all.js! + +FONT_PREF_LANG(Western, "x-western", x_western), + FONT_PREF_LANG(Japanese, "ja", Japanese), + FONT_PREF_LANG(ChineseTW, "zh-TW", Taiwanese), + FONT_PREF_LANG(ChineseCN, "zh-CN", Chinese), + FONT_PREF_LANG(ChineseHK, "zh-HK", HongKongChinese), + FONT_PREF_LANG(Korean, "ko", ko), + FONT_PREF_LANG(Cyrillic, "x-cyrillic", x_cyrillic), + FONT_PREF_LANG(Greek, "el", el), FONT_PREF_LANG(Thai, "th", th), + FONT_PREF_LANG(Hebrew, "he", he), FONT_PREF_LANG(Arabic, "ar", ar), + FONT_PREF_LANG(Devanagari, "x-devanagari", x_devanagari), + FONT_PREF_LANG(Tamil, "x-tamil", x_tamil), + FONT_PREF_LANG(Armenian, "x-armn", x_armn), + FONT_PREF_LANG(Bengali, "x-beng", x_beng), + FONT_PREF_LANG(Canadian, "x-cans", x_cans), + FONT_PREF_LANG(Ethiopic, "x-ethi", x_ethi), + FONT_PREF_LANG(Georgian, "x-geor", x_geor), + FONT_PREF_LANG(Gujarati, "x-gujr", x_gujr), + FONT_PREF_LANG(Gurmukhi, "x-guru", x_guru), + FONT_PREF_LANG(Khmer, "x-khmr", x_khmr), + FONT_PREF_LANG(Malayalam, "x-mlym", x_mlym), + FONT_PREF_LANG(Mathematics, "x-math", x_math), + FONT_PREF_LANG(Oriya, "x-orya", x_orya), + FONT_PREF_LANG(Telugu, "x-telu", x_telu), + FONT_PREF_LANG(Kannada, "x-knda", x_knda), + FONT_PREF_LANG(Sinhala, "x-sinh", x_sinh), + FONT_PREF_LANG(Tibetan, "x-tibt", x_tibt), + FONT_PREF_LANG(Others, "x-unicode", Unicode) diff --git a/gfx/thebes/gfxFontSrcPrincipal.cpp b/gfx/thebes/gfxFontSrcPrincipal.cpp new file mode 100644 index 0000000000..3e4c2b9ef3 --- /dev/null +++ b/gfx/thebes/gfxFontSrcPrincipal.cpp @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxFontSrcPrincipal.h" + +#include "nsProxyRelease.h" +#include "nsURIHashKey.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/HashFunctions.h" + +using mozilla::BasePrincipal; + +gfxFontSrcPrincipal::gfxFontSrcPrincipal(nsIPrincipal* aNodePrincipal, + nsIPrincipal* aStoragePrincipal) + : mNodePrincipal(aNodePrincipal), + mStoragePrincipal(mozilla::StaticPrefs::privacy_partition_network_state() + ? aStoragePrincipal + : aNodePrincipal) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aNodePrincipal); + MOZ_ASSERT(aStoragePrincipal); + + nsAutoCString suffix; + mStoragePrincipal->GetOriginSuffix(suffix); + + mHash = mozilla::AddToHash(mStoragePrincipal->GetHashValue(), + mozilla::HashString(suffix)); +} + +gfxFontSrcPrincipal::~gfxFontSrcPrincipal() { + NS_ReleaseOnMainThread("gfxFontSrcPrincipal::mNodePrincipal", + mNodePrincipal.forget()); + NS_ReleaseOnMainThread("gfxFontSrcPrincipal::mStoragePrincipal", + mStoragePrincipal.forget()); +} + +bool gfxFontSrcPrincipal::Equals(gfxFontSrcPrincipal* aOther) { + return BasePrincipal::Cast(mStoragePrincipal) + ->FastEquals(BasePrincipal::Cast(aOther->mStoragePrincipal)); +} diff --git a/gfx/thebes/gfxFontSrcPrincipal.h b/gfx/thebes/gfxFontSrcPrincipal.h new file mode 100644 index 0000000000..406d26915d --- /dev/null +++ b/gfx/thebes/gfxFontSrcPrincipal.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_FONTSRCPRINCIPAL_H +#define MOZILLA_GFX_FONTSRCPRINCIPAL_H + +#include "nsCOMPtr.h" +#include "PLDHashTable.h" + +class nsIPrincipal; + +namespace mozilla { +namespace net { +class nsSimpleURI; +} // namespace net +} // namespace mozilla + +/** + * A wrapper for an nsIPrincipal that can be used OMT, which has cached + * information useful for the gfxUserFontSet. + */ +class gfxFontSrcPrincipal { + public: + explicit gfxFontSrcPrincipal(nsIPrincipal* aNodePrincipal, + nsIPrincipal* aStoragePrincipal); + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxFontSrcPrincipal) + + nsIPrincipal* get() const { return NodePrincipal(); } + + nsIPrincipal* NodePrincipal() const { return mNodePrincipal; } + + nsIPrincipal* StoragePrincipal() const { return mStoragePrincipal; } + + bool Equals(gfxFontSrcPrincipal* aOther); + + PLDHashNumber Hash() const { return mHash; } + + private: + ~gfxFontSrcPrincipal(); + + // The principal of the node. + nsCOMPtr<nsIPrincipal> mNodePrincipal; + + // The principal used for storage. + nsCOMPtr<nsIPrincipal> mStoragePrincipal; + + // Precomputed hash for mStoragePrincipal. + PLDHashNumber mHash; +}; + +#endif // MOZILLA_GFX_FONTSRCPRINCIPAL_H diff --git a/gfx/thebes/gfxFontSrcURI.cpp b/gfx/thebes/gfxFontSrcURI.cpp new file mode 100644 index 0000000000..6e281a77d0 --- /dev/null +++ b/gfx/thebes/gfxFontSrcURI.cpp @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxFontSrcURI.h" + +#include "nsIProtocolHandler.h" +#include "nsProxyRelease.h" +#include "nsNetUtil.h" +#include "nsSimpleURI.h" +#include "nsURIHashKey.h" + +static bool HasFlag(nsIURI* aURI, uint32_t aFlag) { + nsresult rv; + bool value = false; + rv = NS_URIChainHasFlags(aURI, aFlag, &value); + return NS_SUCCEEDED(rv) && value; +} + +gfxFontSrcURI::gfxFontSrcURI(nsIURI* aURI) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aURI); + + mURI = aURI; + + // If we have a data: URI, we know that it is backed by an nsSimpleURI, + // and that we don't need to serialize it ahead of time. + nsCString scheme; + mURI->GetScheme(scheme); + + if (scheme.EqualsLiteral("data")) { + // We know that nsSimpleURI::From returns us a pointer to the same object, + // and we hold a strong reference to the object in mURI, so no need to + // hold it strongly here as well. (And we'd have to + // NS_ReleaseOnMainThread it in our destructor anyway.) + RefPtr<mozilla::net::nsSimpleURI> simpleURI = + mozilla::net::nsSimpleURI::From(aURI); + mSimpleURI = simpleURI; + + NS_ASSERTION(mSimpleURI, + "Why aren't our data: URLs backed by nsSimpleURI?"); + } else { + mSimpleURI = nullptr; + } + + if (!mSimpleURI) { + mURI->GetSpec(mSpec); + } + + mHash = nsURIHashKey::HashKey(mURI); + + mInheritsSecurityContext = + HasFlag(aURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT); + mSyncLoadIsOK = HasFlag(aURI, nsIProtocolHandler::URI_SYNC_LOAD_IS_OK); +} + +gfxFontSrcURI::~gfxFontSrcURI() { + NS_ReleaseOnMainThread("gfxFontSrcURI::mURI", mURI.forget()); +} + +bool gfxFontSrcURI::Equals(gfxFontSrcURI* aOther) { + if (mSimpleURI) { + if (aOther->mSimpleURI) { + return mSimpleURI->Equals(aOther->mSimpleURI); + } + + // The two URIs are probably different. Do a quick check on the + // schemes before deciding to serialize mSimpleURI (which might be + // quite large). + { + nsCString thisScheme; + mSimpleURI->GetScheme(thisScheme); + + nsCString otherScheme; + if (!StringBeginsWith(aOther->mSpec, thisScheme)) { + return false; + } + } + + nsCString thisSpec; + mSimpleURI->GetSpec(thisSpec); + return thisSpec == aOther->mSpec; + } + + if (aOther->mSimpleURI) { + return aOther->Equals(this); + } + + return mSpec == aOther->mSpec; +} + +nsresult gfxFontSrcURI::GetSpec(nsACString& aResult) { + if (mSimpleURI) { + return mSimpleURI->GetSpec(aResult); + } + + aResult = mSpec; + return NS_OK; +} + +nsCString gfxFontSrcURI::GetSpecOrDefault() { + if (mSimpleURI) { + return mSimpleURI->GetSpecOrDefault(); + } + + return mSpec; +} diff --git a/gfx/thebes/gfxFontSrcURI.h b/gfx/thebes/gfxFontSrcURI.h new file mode 100644 index 0000000000..226341e5fd --- /dev/null +++ b/gfx/thebes/gfxFontSrcURI.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_FONTSRCURI_H +#define MOZILLA_GFX_FONTSRCURI_H + +#include "nsCOMPtr.h" +#include "nsTString.h" +#include "PLDHashTable.h" + +class nsIURI; + +namespace mozilla { +namespace net { +class nsSimpleURI; +} // namespace net +} // namespace mozilla + +/** + * A wrapper for an nsIURI that can be used OMT, which has cached information + * useful for the gfxUserFontSet. + */ +class gfxFontSrcURI { + public: + explicit gfxFontSrcURI(nsIURI* aURI); + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxFontSrcURI) + + nsIURI* get() { return mURI; } + + bool Equals(gfxFontSrcURI* aOther); + nsresult GetSpec(nsACString& aResult); + nsCString GetSpecOrDefault(); + + PLDHashNumber Hash() const { return mHash; } + bool InheritsSecurityContext() const { return mInheritsSecurityContext; } + bool SyncLoadIsOK() const { return mSyncLoadIsOK; } + + private: + ~gfxFontSrcURI(); + + // The URI. + nsCOMPtr<nsIURI> mURI; + + // If the nsIURI is an nsSimpleURI for a data: URL, this is a pointer to it. + // (Just a weak reference since mURI holds the strong reference.) + // + // We store this so that we don't duplicate the URL spec for data: URLs, + // which can be much larger than other URLs. + mozilla::net::nsSimpleURI* mSimpleURI; + + // If the nsIURI is not an nsSimpleURI, this is its spec. + nsCString mSpec; + + // Precomputed hash for mURI. + PLDHashNumber mHash; + + // Whether the nsIURI's protocol handler has the URI_INHERITS_SECURITY_CONTEXT + // flag. + bool mInheritsSecurityContext; + + // Whether the nsIURI's protocol handler has teh URI_SYNC_LOAD_IS_OK flag. + bool mSyncLoadIsOK; +}; + +#endif // MOZILLA_GFX_FONTSRCURI_H diff --git a/gfx/thebes/gfxFontUtils.cpp b/gfx/thebes/gfxFontUtils.cpp new file mode 100644 index 0000000000..f5d1d4011a --- /dev/null +++ b/gfx/thebes/gfxFontUtils.cpp @@ -0,0 +1,1950 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/BinarySearch.h" + +#include "gfxFontUtils.h" +#include "gfxFontEntry.h" +#include "gfxFontVariations.h" +#include "gfxUtils.h" + +#include "nsServiceManagerUtils.h" + +#include "mozilla/Preferences.h" +#include "mozilla/BinarySearch.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Unused.h" + +#include "nsCOMPtr.h" +#include "nsIUUIDGenerator.h" +#include "mozilla/Encoding.h" + +#include "harfbuzz/hb.h" + +#include "plbase64.h" +#include "mozilla/Logging.h" + +#ifdef XP_MACOSX +# include <CoreFoundation/CoreFoundation.h> +#endif + +#define LOG(log, args) MOZ_LOG(gfxPlatform::GetLog(log), LogLevel::Debug, args) + +#define UNICODE_BMP_LIMIT 0x10000 + +using namespace mozilla; + +#pragma pack(1) + +typedef struct { + AutoSwap_PRUint16 format; + AutoSwap_PRUint16 reserved; + AutoSwap_PRUint32 length; + AutoSwap_PRUint32 language; + AutoSwap_PRUint32 startCharCode; + AutoSwap_PRUint32 numChars; +} Format10CmapHeader; + +typedef struct { + AutoSwap_PRUint16 format; + AutoSwap_PRUint16 reserved; + AutoSwap_PRUint32 length; + AutoSwap_PRUint32 language; + AutoSwap_PRUint32 numGroups; +} Format12CmapHeader; + +typedef struct { + AutoSwap_PRUint32 startCharCode; + AutoSwap_PRUint32 endCharCode; + AutoSwap_PRUint32 startGlyphId; +} Format12Group; + +#pragma pack() + +void gfxSparseBitSet::Dump(const char* aPrefix, eGfxLog aWhichLog) const { + uint32_t numBlocks = mBlockIndex.Length(); + + for (uint32_t b = 0; b < numBlocks; b++) { + if (mBlockIndex[b] == NO_BLOCK) { + continue; + } + const Block* block = &mBlocks[mBlockIndex[b]]; + const int BUFSIZE = 256; + char outStr[BUFSIZE]; + int index = 0; + index += snprintf(&outStr[index], BUFSIZE - index, "%s u+%6.6x [", aPrefix, + (b * BLOCK_SIZE_BITS)); + for (int i = 0; i < 32; i += 4) { + for (int j = i; j < i + 4; j++) { + uint8_t bits = block->mBits[j]; + uint8_t flip1 = ((bits & 0xaa) >> 1) | ((bits & 0x55) << 1); + uint8_t flip2 = ((flip1 & 0xcc) >> 2) | ((flip1 & 0x33) << 2); + uint8_t flipped = ((flip2 & 0xf0) >> 4) | ((flip2 & 0x0f) << 4); + + index += snprintf(&outStr[index], BUFSIZE - index, "%2.2x", flipped); + } + if (i + 4 != 32) index += snprintf(&outStr[index], BUFSIZE - index, " "); + } + Unused << snprintf(&outStr[index], BUFSIZE - index, "]"); + LOG(aWhichLog, ("%s", outStr)); + } +} + +nsresult gfxFontUtils::ReadCMAPTableFormat10(const uint8_t* aBuf, + uint32_t aLength, + gfxSparseBitSet& aCharacterMap) { + // Ensure table is large enough that we can safely read the header + NS_ENSURE_TRUE(aLength >= sizeof(Format10CmapHeader), + NS_ERROR_GFX_CMAP_MALFORMED); + + // Sanity-check header fields + const Format10CmapHeader* cmap10 = + reinterpret_cast<const Format10CmapHeader*>(aBuf); + NS_ENSURE_TRUE(uint16_t(cmap10->format) == 10, NS_ERROR_GFX_CMAP_MALFORMED); + NS_ENSURE_TRUE(uint16_t(cmap10->reserved) == 0, NS_ERROR_GFX_CMAP_MALFORMED); + + uint32_t tablelen = cmap10->length; + NS_ENSURE_TRUE(tablelen >= sizeof(Format10CmapHeader) && tablelen <= aLength, + NS_ERROR_GFX_CMAP_MALFORMED); + + NS_ENSURE_TRUE(cmap10->language == 0, NS_ERROR_GFX_CMAP_MALFORMED); + + uint32_t numChars = cmap10->numChars; + NS_ENSURE_TRUE( + tablelen == sizeof(Format10CmapHeader) + numChars * sizeof(uint16_t), + NS_ERROR_GFX_CMAP_MALFORMED); + + uint32_t charCode = cmap10->startCharCode; + NS_ENSURE_TRUE(charCode <= CMAP_MAX_CODEPOINT && + charCode + numChars <= CMAP_MAX_CODEPOINT, + NS_ERROR_GFX_CMAP_MALFORMED); + + // glyphs[] array immediately follows the subtable header + const AutoSwap_PRUint16* glyphs = + reinterpret_cast<const AutoSwap_PRUint16*>(cmap10 + 1); + + for (uint32_t i = 0; i < numChars; ++i) { + if (uint16_t(*glyphs) != 0) { + aCharacterMap.set(charCode); + } + ++charCode; + ++glyphs; + } + + aCharacterMap.Compact(); + + return NS_OK; +} + +nsresult gfxFontUtils::ReadCMAPTableFormat12or13( + const uint8_t* aBuf, uint32_t aLength, gfxSparseBitSet& aCharacterMap) { + // Format 13 has the same structure as format 12, the only difference is + // the interpretation of the glyphID field. So we can share the code here + // that reads the table and just records character coverage. + + // Ensure table is large enough that we can safely read the header + NS_ENSURE_TRUE(aLength >= sizeof(Format12CmapHeader), + NS_ERROR_GFX_CMAP_MALFORMED); + + // Sanity-check header fields + const Format12CmapHeader* cmap12 = + reinterpret_cast<const Format12CmapHeader*>(aBuf); + NS_ENSURE_TRUE( + uint16_t(cmap12->format) == 12 || uint16_t(cmap12->format) == 13, + NS_ERROR_GFX_CMAP_MALFORMED); + NS_ENSURE_TRUE(uint16_t(cmap12->reserved) == 0, NS_ERROR_GFX_CMAP_MALFORMED); + + uint32_t tablelen = cmap12->length; + NS_ENSURE_TRUE(tablelen >= sizeof(Format12CmapHeader) && tablelen <= aLength, + NS_ERROR_GFX_CMAP_MALFORMED); + + NS_ENSURE_TRUE(cmap12->language == 0, NS_ERROR_GFX_CMAP_MALFORMED); + + // Check that the table is large enough for the group array + const uint32_t numGroups = cmap12->numGroups; + NS_ENSURE_TRUE( + (tablelen - sizeof(Format12CmapHeader)) / sizeof(Format12Group) >= + numGroups, + NS_ERROR_GFX_CMAP_MALFORMED); + + // The array of groups immediately follows the subtable header. + const Format12Group* group = + reinterpret_cast<const Format12Group*>(aBuf + sizeof(Format12CmapHeader)); + + // Check that groups are in correct order and do not overlap, + // and record character coverage in aCharacterMap. + uint32_t prevEndCharCode = 0; + for (uint32_t i = 0; i < numGroups; i++, group++) { + uint32_t startCharCode = group->startCharCode; + const uint32_t endCharCode = group->endCharCode; + NS_ENSURE_TRUE((prevEndCharCode < startCharCode || i == 0) && + startCharCode <= endCharCode && + endCharCode <= CMAP_MAX_CODEPOINT, + NS_ERROR_GFX_CMAP_MALFORMED); + // don't include a character that maps to glyph ID 0 (.notdef) + if (group->startGlyphId == 0) { + startCharCode++; + } + if (startCharCode <= endCharCode) { + aCharacterMap.SetRange(startCharCode, endCharCode); + } + prevEndCharCode = endCharCode; + } + + aCharacterMap.Compact(); + + return NS_OK; +} + +nsresult gfxFontUtils::ReadCMAPTableFormat4(const uint8_t* aBuf, + uint32_t aLength, + gfxSparseBitSet& aCharacterMap) { + enum { + OffsetFormat = 0, + OffsetLength = 2, + OffsetLanguage = 4, + OffsetSegCountX2 = 6 + }; + + NS_ENSURE_TRUE(ReadShortAt(aBuf, OffsetFormat) == 4, + NS_ERROR_GFX_CMAP_MALFORMED); + uint16_t tablelen = ReadShortAt(aBuf, OffsetLength); + NS_ENSURE_TRUE(tablelen <= aLength, NS_ERROR_GFX_CMAP_MALFORMED); + NS_ENSURE_TRUE(tablelen > 16, NS_ERROR_GFX_CMAP_MALFORMED); + + // This field should normally (except for Mac platform subtables) be zero + // according to the OT spec, but some buggy fonts have lang = 1 (which would + // be English for MacOS). E.g. Arial Narrow Bold, v. 1.1 (Tiger), Arial + // Unicode MS (see bug 530614). So accept either zero or one here; the error + // should be harmless. + NS_ENSURE_TRUE((ReadShortAt(aBuf, OffsetLanguage) & 0xfffe) == 0, + NS_ERROR_GFX_CMAP_MALFORMED); + + uint16_t segCountX2 = ReadShortAt(aBuf, OffsetSegCountX2); + NS_ENSURE_TRUE(tablelen >= 16 + (segCountX2 * 4), + NS_ERROR_GFX_CMAP_MALFORMED); + + const uint16_t segCount = segCountX2 / 2; + + const uint16_t* endCounts = reinterpret_cast<const uint16_t*>(aBuf + 14); + const uint16_t* startCounts = + endCounts + 1 /* skip one uint16_t for reservedPad */ + segCount; + const uint16_t* idDeltas = startCounts + segCount; + const uint16_t* idRangeOffsets = idDeltas + segCount; + uint16_t prevEndCount = 0; + for (uint16_t i = 0; i < segCount; i++) { + const uint16_t endCount = ReadShortAt16(endCounts, i); + const uint16_t startCount = ReadShortAt16(startCounts, i); + const uint16_t idRangeOffset = ReadShortAt16(idRangeOffsets, i); + + // sanity-check range + // This permits ranges to overlap by 1 character, which is strictly + // incorrect but occurs in Baskerville on OS X 10.7 (see bug 689087), + // and appears to be harmless in practice + NS_ENSURE_TRUE(startCount >= prevEndCount && startCount <= endCount, + NS_ERROR_GFX_CMAP_MALFORMED); + prevEndCount = endCount; + + if (idRangeOffset == 0) { + // figure out if there's a code in the range that would map to + // glyph ID 0 (.notdef); if so, we need to skip setting that + // character code in the map + const uint16_t skipCode = 65536 - ReadShortAt16(idDeltas, i); + if (startCount < skipCode) { + aCharacterMap.SetRange(startCount, + std::min<uint16_t>(skipCode - 1, endCount)); + } + if (skipCode < endCount) { + aCharacterMap.SetRange(std::max<uint16_t>(startCount, skipCode + 1), + endCount); + } + } else { + // Unused: self-documenting. + // const uint16_t idDelta = ReadShortAt16(idDeltas, i); + for (uint32_t c = startCount; c <= endCount; ++c) { + if (c == 0xFFFF) break; + + const uint16_t* gdata = + (idRangeOffset / 2 + (c - startCount) + &idRangeOffsets[i]); + + NS_ENSURE_TRUE( + (uint8_t*)gdata > aBuf && (uint8_t*)gdata < aBuf + aLength, + NS_ERROR_GFX_CMAP_MALFORMED); + + // make sure we have a glyph + if (*gdata != 0) { + // The glyph index at this point is: + uint16_t glyph = ReadShortAt16(idDeltas, i) + *gdata; + if (glyph) { + aCharacterMap.set(c); + } + } + } + } + } + + aCharacterMap.Compact(); + + return NS_OK; +} + +nsresult gfxFontUtils::ReadCMAPTableFormat14(const uint8_t* aBuf, + uint32_t aLength, + UniquePtr<uint8_t[]>& aTable) { + enum { + OffsetFormat = 0, + OffsetTableLength = 2, + OffsetNumVarSelectorRecords = 6, + OffsetVarSelectorRecords = 10, + + SizeOfVarSelectorRecord = 11, + VSRecOffsetVarSelector = 0, + VSRecOffsetDefUVSOffset = 3, + VSRecOffsetNonDefUVSOffset = 7, + + SizeOfDefUVSTable = 4, + DefUVSOffsetStartUnicodeValue = 0, + DefUVSOffsetAdditionalCount = 3, + + SizeOfNonDefUVSTable = 5, + NonDefUVSOffsetUnicodeValue = 0, + NonDefUVSOffsetGlyphID = 3 + }; + NS_ENSURE_TRUE(aLength >= OffsetVarSelectorRecords, + NS_ERROR_GFX_CMAP_MALFORMED); + + NS_ENSURE_TRUE(ReadShortAt(aBuf, OffsetFormat) == 14, + NS_ERROR_GFX_CMAP_MALFORMED); + + uint32_t tablelen = ReadLongAt(aBuf, OffsetTableLength); + NS_ENSURE_TRUE(tablelen <= aLength, NS_ERROR_GFX_CMAP_MALFORMED); + NS_ENSURE_TRUE(tablelen >= OffsetVarSelectorRecords, + NS_ERROR_GFX_CMAP_MALFORMED); + + const uint32_t numVarSelectorRecords = + ReadLongAt(aBuf, OffsetNumVarSelectorRecords); + NS_ENSURE_TRUE( + (tablelen - OffsetVarSelectorRecords) / SizeOfVarSelectorRecord >= + numVarSelectorRecords, + NS_ERROR_GFX_CMAP_MALFORMED); + + const uint8_t* records = aBuf + OffsetVarSelectorRecords; + for (uint32_t i = 0; i < numVarSelectorRecords; + i++, records += SizeOfVarSelectorRecord) { + const uint32_t varSelector = ReadUint24At(records, VSRecOffsetVarSelector); + const uint32_t defUVSOffset = ReadLongAt(records, VSRecOffsetDefUVSOffset); + const uint32_t nonDefUVSOffset = + ReadLongAt(records, VSRecOffsetNonDefUVSOffset); + NS_ENSURE_TRUE(varSelector <= CMAP_MAX_CODEPOINT && + defUVSOffset <= tablelen - 4 && + nonDefUVSOffset <= tablelen - 4, + NS_ERROR_GFX_CMAP_MALFORMED); + + if (defUVSOffset) { + const uint32_t numUnicodeValueRanges = ReadLongAt(aBuf, defUVSOffset); + NS_ENSURE_TRUE((tablelen - defUVSOffset) / SizeOfDefUVSTable >= + numUnicodeValueRanges, + NS_ERROR_GFX_CMAP_MALFORMED); + const uint8_t* tables = aBuf + defUVSOffset + 4; + uint32_t prevEndUnicode = 0; + for (uint32_t j = 0; j < numUnicodeValueRanges; + j++, tables += SizeOfDefUVSTable) { + const uint32_t startUnicode = + ReadUint24At(tables, DefUVSOffsetStartUnicodeValue); + const uint32_t endUnicode = + startUnicode + tables[DefUVSOffsetAdditionalCount]; + NS_ENSURE_TRUE((prevEndUnicode < startUnicode || j == 0) && + endUnicode <= CMAP_MAX_CODEPOINT, + NS_ERROR_GFX_CMAP_MALFORMED); + prevEndUnicode = endUnicode; + } + } + + if (nonDefUVSOffset) { + const uint32_t numUVSMappings = ReadLongAt(aBuf, nonDefUVSOffset); + NS_ENSURE_TRUE( + (tablelen - nonDefUVSOffset) / SizeOfNonDefUVSTable >= numUVSMappings, + NS_ERROR_GFX_CMAP_MALFORMED); + const uint8_t* tables = aBuf + nonDefUVSOffset + 4; + uint32_t prevUnicode = 0; + for (uint32_t j = 0; j < numUVSMappings; + j++, tables += SizeOfNonDefUVSTable) { + const uint32_t unicodeValue = + ReadUint24At(tables, NonDefUVSOffsetUnicodeValue); + NS_ENSURE_TRUE((prevUnicode < unicodeValue || j == 0) && + unicodeValue <= CMAP_MAX_CODEPOINT, + NS_ERROR_GFX_CMAP_MALFORMED); + prevUnicode = unicodeValue; + } + } + } + + aTable = MakeUnique<uint8_t[]>(tablelen); + memcpy(aTable.get(), aBuf, tablelen); + + return NS_OK; +} + +// For fonts with two format-4 tables, the first one (Unicode platform) is +// preferred on the Mac; on other platforms we allow the Microsoft-platform +// subtable to replace it. + +#if defined(XP_MACOSX) +# define acceptableFormat4(p, e, k) \ + (((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDMicrosoft && !(k)) || \ + ((p) == PLATFORM_ID_UNICODE)) + +# define acceptableUCS4Encoding(p, e, k) \ + (((p) == PLATFORM_ID_MICROSOFT && \ + (e) == EncodingIDUCS4ForMicrosoftPlatform) && \ + (k) != 12 || \ + ((p) == PLATFORM_ID_UNICODE && ((e) != EncodingIDUVSForUnicodePlatform))) +#else +# define acceptableFormat4(p, e, k) \ + (((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDMicrosoft) || \ + ((p) == PLATFORM_ID_UNICODE)) + +# define acceptableUCS4Encoding(p, e, k) \ + ((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDUCS4ForMicrosoftPlatform) +#endif + +#define acceptablePlatform(p) \ + ((p) == PLATFORM_ID_UNICODE || (p) == PLATFORM_ID_MICROSOFT) +#define isSymbol(p, e) ((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDSymbol) +#define isUVSEncoding(p, e) \ + ((p) == PLATFORM_ID_UNICODE && (e) == EncodingIDUVSForUnicodePlatform) + +uint32_t gfxFontUtils::FindPreferredSubtable(const uint8_t* aBuf, + uint32_t aBufLength, + uint32_t* aTableOffset, + uint32_t* aUVSTableOffset) { + 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; + break; + } else if (format == 4 && + acceptableFormat4(platformID, encodingID, keepFormat)) { + keepFormat = format; + *aTableOffset = offset; + } else if ((format == 10 || format == 12 || format == 13) && + acceptableUCS4Encoding(platformID, encodingID, keepFormat)) { + keepFormat = format; + *aTableOffset = offset; + if (platformID > PLATFORM_ID_UNICODE || !aUVSTableOffset || + *aUVSTableOffset) { + break; // we don't want to try anything else when this format is + // available. + } + } else if (format == 14 && isUVSEncoding(platformID, encodingID) && + aUVSTableOffset) { + *aUVSTableOffset = offset; + if (keepFormat == 10 || keepFormat == 12) { + break; + } + } + } + + return keepFormat; +} + +nsresult gfxFontUtils::ReadCMAP(const uint8_t* aBuf, uint32_t aBufLength, + gfxSparseBitSet& aCharacterMap, + uint32_t& aUVSOffset) { + uint32_t offset; + uint32_t format = + FindPreferredSubtable(aBuf, aBufLength, &offset, &aUVSOffset); + + switch (format) { + case 4: + return ReadCMAPTableFormat4(aBuf + offset, aBufLength - offset, + aCharacterMap); + + case 10: + return ReadCMAPTableFormat10(aBuf + offset, aBufLength - offset, + aCharacterMap); + + case 12: + case 13: + return ReadCMAPTableFormat12or13(aBuf + offset, aBufLength - offset, + aCharacterMap); + + default: + break; + } + + return NS_ERROR_FAILURE; +} + +#pragma pack(1) + +typedef struct { + AutoSwap_PRUint16 format; + AutoSwap_PRUint16 length; + AutoSwap_PRUint16 language; + AutoSwap_PRUint16 segCountX2; + AutoSwap_PRUint16 searchRange; + AutoSwap_PRUint16 entrySelector; + AutoSwap_PRUint16 rangeShift; + + AutoSwap_PRUint16 arrays[1]; +} Format4Cmap; + +typedef struct Format14Cmap { + AutoSwap_PRUint16 format; + AutoSwap_PRUint32 length; + AutoSwap_PRUint32 numVarSelectorRecords; + + typedef struct { + AutoSwap_PRUint24 varSelector; + AutoSwap_PRUint32 defaultUVSOffset; + AutoSwap_PRUint32 nonDefaultUVSOffset; + } VarSelectorRecord; + + VarSelectorRecord varSelectorRecords[1]; +} Format14Cmap; + +typedef struct NonDefUVSTable { + AutoSwap_PRUint32 numUVSMappings; + + typedef struct { + AutoSwap_PRUint24 unicodeValue; + AutoSwap_PRUint16 glyphID; + } UVSMapping; + + UVSMapping uvsMappings[1]; +} NonDefUVSTable; + +#pragma pack() + +uint32_t gfxFontUtils::MapCharToGlyphFormat4(const uint8_t* aBuf, + uint32_t aLength, char16_t aCh) { + const Format4Cmap* cmap4 = reinterpret_cast<const Format4Cmap*>(aBuf); + + uint16_t segCount = (uint16_t)(cmap4->segCountX2) / 2; + + const AutoSwap_PRUint16* endCodes = &cmap4->arrays[0]; + const AutoSwap_PRUint16* startCodes = &cmap4->arrays[segCount + 1]; + const AutoSwap_PRUint16* idDelta = &startCodes[segCount]; + const AutoSwap_PRUint16* idRangeOffset = &idDelta[segCount]; + + // Sanity-check that the fixed-size arrays don't exceed the buffer. + const uint8_t* const limit = aBuf + aLength; + if ((const uint8_t*)(&idRangeOffset[segCount]) > limit) { + return 0; // broken font, just bail out safely + } + + // For most efficient binary search, we want to work on a range of segment + // indexes that is a power of 2 so that we can always halve it by shifting. + // So we find the largest power of 2 that is <= segCount. + // We will offset this range by segOffset so as to reach the end + // of the table, provided that doesn't put us beyond the target + // value from the outset. + uint32_t powerOf2 = mozilla::FindHighestBit(segCount); + uint32_t segOffset = segCount - powerOf2; + uint32_t idx = 0; + + if (uint16_t(startCodes[segOffset]) <= aCh) { + idx = segOffset; + } + + // Repeatedly halve the size of the range until we find the target group + while (powerOf2 > 1) { + powerOf2 >>= 1; + if (uint16_t(startCodes[idx + powerOf2]) <= aCh) { + idx += powerOf2; + } + } + + if (aCh >= uint16_t(startCodes[idx]) && aCh <= uint16_t(endCodes[idx])) { + uint16_t result; + if (uint16_t(idRangeOffset[idx]) == 0) { + result = aCh; + } else { + uint16_t offset = aCh - uint16_t(startCodes[idx]); + const AutoSwap_PRUint16* glyphIndexTable = + (const AutoSwap_PRUint16*)((const char*)&idRangeOffset[idx] + + uint16_t(idRangeOffset[idx])); + if ((const uint8_t*)(glyphIndexTable + offset + 1) > limit) { + return 0; // broken font, just bail out safely + } + result = glyphIndexTable[offset]; + } + + // Note that this is unsigned 16-bit arithmetic, and may wrap around + // (which is required behavior per spec) + result += uint16_t(idDelta[idx]); + return result; + } + + return 0; +} + +uint32_t gfxFontUtils::MapCharToGlyphFormat10(const uint8_t* aBuf, + uint32_t aCh) { + const Format10CmapHeader* cmap10 = + reinterpret_cast<const Format10CmapHeader*>(aBuf); + + uint32_t startChar = cmap10->startCharCode; + uint32_t numChars = cmap10->numChars; + + if (aCh < startChar || aCh >= startChar + numChars) { + return 0; + } + + const AutoSwap_PRUint16* glyphs = + reinterpret_cast<const AutoSwap_PRUint16*>(cmap10 + 1); + + uint16_t glyph = glyphs[aCh - startChar]; + return glyph; +} + +uint32_t gfxFontUtils::MapCharToGlyphFormat12or13(const uint8_t* aBuf, + uint32_t aCh) { + // The only difference between formats 12 and 13 is the interpretation of + // the glyphId field. So the code here uses the same "Format12" structures, + // etc., to handle both subtable formats. + + const Format12CmapHeader* cmap12 = + reinterpret_cast<const Format12CmapHeader*>(aBuf); + + // We know that numGroups is within range for the subtable size + // because it was checked by ReadCMAPTableFormat12or13. + uint32_t numGroups = cmap12->numGroups; + + // The array of groups immediately follows the subtable header. + const Format12Group* groups = + reinterpret_cast<const Format12Group*>(aBuf + sizeof(Format12CmapHeader)); + + // For most efficient binary search, we want to work on a range that + // is a power of 2 so that we can always halve it by shifting. + // So we find the largest power of 2 that is <= numGroups. + // We will offset this range by rangeOffset so as to reach the end + // of the table, provided that doesn't put us beyond the target + // value from the outset. + uint32_t powerOf2 = mozilla::FindHighestBit(numGroups); + uint32_t rangeOffset = numGroups - powerOf2; + uint32_t range = 0; + uint32_t startCharCode; + + if (groups[rangeOffset].startCharCode <= aCh) { + range = rangeOffset; + } + + // Repeatedly halve the size of the range until we find the target group + while (powerOf2 > 1) { + powerOf2 >>= 1; + if (groups[range + powerOf2].startCharCode <= aCh) { + range += powerOf2; + } + } + + // Check if the character is actually present in the range and return + // the corresponding glyph ID. Here is where formats 12 and 13 interpret + // the startGlyphId (12) or glyphId (13) field differently + startCharCode = groups[range].startCharCode; + if (startCharCode <= aCh && groups[range].endCharCode >= aCh) { + return uint16_t(cmap12->format) == 12 + ? uint16_t(groups[range].startGlyphId) + aCh - startCharCode + : uint16_t(groups[range].startGlyphId); + } + + // Else it's not present, so return the .notdef glyph + return 0; +} + +namespace { + +struct Format14CmapWrapper { + const Format14Cmap& mCmap14; + explicit Format14CmapWrapper(const Format14Cmap& cmap14) : mCmap14(cmap14) {} + uint32_t operator[](size_t index) const { + return mCmap14.varSelectorRecords[index].varSelector; + } +}; + +struct NonDefUVSTableWrapper { + const NonDefUVSTable& mTable; + explicit NonDefUVSTableWrapper(const NonDefUVSTable& table) : mTable(table) {} + uint32_t operator[](size_t index) const { + return mTable.uvsMappings[index].unicodeValue; + } +}; + +} // namespace + +uint16_t gfxFontUtils::MapUVSToGlyphFormat14(const uint8_t* aBuf, uint32_t aCh, + uint32_t aVS) { + using mozilla::BinarySearch; + const Format14Cmap* cmap14 = reinterpret_cast<const Format14Cmap*>(aBuf); + + size_t index; + if (!BinarySearch(Format14CmapWrapper(*cmap14), 0, + cmap14->numVarSelectorRecords, aVS, &index)) { + return 0; + } + + const uint32_t nonDefUVSOffset = + cmap14->varSelectorRecords[index].nonDefaultUVSOffset; + if (!nonDefUVSOffset) { + return 0; + } + + const NonDefUVSTable* table = + reinterpret_cast<const NonDefUVSTable*>(aBuf + nonDefUVSOffset); + + if (BinarySearch(NonDefUVSTableWrapper(*table), 0, table->numUVSMappings, aCh, + &index)) { + return table->uvsMappings[index].glyphID; + } + + return 0; +} + +uint32_t gfxFontUtils::MapCharToGlyph(const uint8_t* aCmapBuf, + uint32_t aBufLength, uint32_t aUnicode, + uint32_t aVarSelector) { + uint32_t offset, uvsOffset; + uint32_t format = + FindPreferredSubtable(aCmapBuf, aBufLength, &offset, &uvsOffset); + + uint32_t gid; + switch (format) { + case 4: + gid = aUnicode < UNICODE_BMP_LIMIT + ? MapCharToGlyphFormat4(aCmapBuf + offset, aBufLength - offset, + char16_t(aUnicode)) + : 0; + break; + case 10: + gid = MapCharToGlyphFormat10(aCmapBuf + offset, aUnicode); + break; + case 12: + case 13: + gid = MapCharToGlyphFormat12or13(aCmapBuf + offset, aUnicode); + break; + default: + NS_WARNING("unsupported cmap format, glyphs will be missing"); + gid = 0; + } + + if (aVarSelector && uvsOffset && gid) { + uint32_t varGID = gfxFontUtils::MapUVSToGlyphFormat14( + aCmapBuf + uvsOffset, aUnicode, aVarSelector); + if (!varGID) { + aUnicode = gfxFontUtils::GetUVSFallback(aUnicode, aVarSelector); + if (aUnicode) { + switch (format) { + case 4: + if (aUnicode < UNICODE_BMP_LIMIT) { + varGID = MapCharToGlyphFormat4( + aCmapBuf + offset, aBufLength - offset, char16_t(aUnicode)); + } + break; + case 10: + varGID = MapCharToGlyphFormat10(aCmapBuf + offset, aUnicode); + break; + case 12: + case 13: + varGID = MapCharToGlyphFormat12or13(aCmapBuf + offset, aUnicode); + break; + } + } + } + if (varGID) { + gid = varGID; + } + + // else the variation sequence was not supported, use default mapping + // of the character code alone + } + + return gid; +} + +void gfxFontUtils::ParseFontList(const nsACString& aFamilyList, + nsTArray<nsCString>& aFontList) { + const char kComma = ','; + + // append each font name to the list + nsAutoCString fontname; + const char *p, *p_end; + aFamilyList.BeginReading(p); + aFamilyList.EndReading(p_end); + + while (p < p_end) { + const char* nameStart = p; + while (++p != p_end && *p != kComma) /* nothing */ + ; + + // pull out a single name and clean out leading/trailing whitespace + fontname = Substring(nameStart, p); + fontname.CompressWhitespace(true, true); + + // append it to the list if it's not empty + if (!fontname.IsEmpty()) { + aFontList.AppendElement(fontname); + } + ++p; + } +} + +void gfxFontUtils::AppendPrefsFontList(const char* aPrefName, + nsTArray<nsCString>& aFontList, + bool aLocalized) { + // get the list of single-face font families + nsAutoCString fontlistValue; + nsresult rv = aLocalized + ? Preferences::GetLocalizedCString(aPrefName, fontlistValue) + : Preferences::GetCString(aPrefName, fontlistValue); + if (NS_FAILED(rv)) { + return; + } + + ParseFontList(fontlistValue, aFontList); +} + +void gfxFontUtils::GetPrefsFontList(const char* aPrefName, + nsTArray<nsCString>& aFontList, + bool aLocalized) { + aFontList.Clear(); + AppendPrefsFontList(aPrefName, aFontList, aLocalized); +} + +// produce a unique font name that is (1) a valid Postscript name and (2) less +// than 31 characters in length. Using AddFontMemResourceEx on Windows fails +// for names longer than 30 characters in length. + +#define MAX_B64_LEN 32 + +nsresult gfxFontUtils::MakeUniqueUserFontName(nsAString& aName) { + nsCOMPtr<nsIUUIDGenerator> uuidgen = + do_GetService("@mozilla.org/uuid-generator;1"); + NS_ENSURE_TRUE(uuidgen, NS_ERROR_OUT_OF_MEMORY); + + nsID guid; + + NS_ASSERTION(sizeof(guid) * 2 <= MAX_B64_LEN, "size of nsID has changed!"); + + nsresult rv = uuidgen->GenerateUUIDInPlace(&guid); + NS_ENSURE_SUCCESS(rv, rv); + + char guidB64[MAX_B64_LEN] = {0}; + + if (!PL_Base64Encode(reinterpret_cast<char*>(&guid), sizeof(guid), guidB64)) + return NS_ERROR_FAILURE; + + // all b64 characters except for '/' are allowed in Postscript names, so + // convert / ==> - + char* p; + for (p = guidB64; *p; p++) { + if (*p == '/') *p = '-'; + } + + aName.AssignLiteral(u"uf"); + aName.AppendASCII(guidB64); + return NS_OK; +} + +// TrueType/OpenType table handling code + +// need byte aligned structs +#pragma pack(1) + +// name table stores set of name record structures, followed by +// large block containing all the strings. name record offset and length +// indicates the offset and length within that block. +// http://www.microsoft.com/typography/otspec/name.htm +struct NameRecordData { + uint32_t offset; + uint32_t length; +}; + +#pragma pack() + +static bool IsValidSFNTVersion(uint32_t version) { + // normally 0x00010000, CFF-style OT fonts == 'OTTO' and Apple TT fonts = + // 'true' 'typ1' is also possible for old Type 1 fonts in a SFNT container but + // not supported + return version == 0x10000 || version == TRUETYPE_TAG('O', 'T', 'T', 'O') || + version == TRUETYPE_TAG('t', 'r', 'u', 'e'); +} + +gfxUserFontType gfxFontUtils::DetermineFontDataType(const uint8_t* aFontData, + uint32_t aFontDataLength) { + // test for OpenType font data + // problem: EOT-Lite with 0x10000 length will look like TrueType! + if (aFontDataLength >= sizeof(SFNTHeader)) { + const SFNTHeader* sfntHeader = + reinterpret_cast<const SFNTHeader*>(aFontData); + uint32_t sfntVersion = sfntHeader->sfntVersion; + if (IsValidSFNTVersion(sfntVersion)) { + return GFX_USERFONT_OPENTYPE; + } + } + + // test for WOFF or WOFF2 + if (aFontDataLength >= sizeof(AutoSwap_PRUint32)) { + const AutoSwap_PRUint32* version = + reinterpret_cast<const AutoSwap_PRUint32*>(aFontData); + if (uint32_t(*version) == TRUETYPE_TAG('w', 'O', 'F', 'F')) { + return GFX_USERFONT_WOFF; + } + if (uint32_t(*version) == TRUETYPE_TAG('w', 'O', 'F', '2')) { + return GFX_USERFONT_WOFF2; + } + } + + // tests for other formats here + + return GFX_USERFONT_UNKNOWN; +} + +static int DirEntryCmp(const void* aKey, const void* aItem) { + int32_t tag = *static_cast<const int32_t*>(aKey); + const TableDirEntry* entry = static_cast<const TableDirEntry*>(aItem); + return tag - int32_t(entry->tag); +} + +/* static */ +TableDirEntry* gfxFontUtils::FindTableDirEntry(const void* aFontData, + uint32_t aTableTag) { + const SFNTHeader* header = reinterpret_cast<const SFNTHeader*>(aFontData); + const TableDirEntry* dir = reinterpret_cast<const TableDirEntry*>(header + 1); + return static_cast<TableDirEntry*>( + bsearch(&aTableTag, dir, uint16_t(header->numTables), + sizeof(TableDirEntry), DirEntryCmp)); +} + +/* static */ +hb_blob_t* gfxFontUtils::GetTableFromFontData(const void* aFontData, + uint32_t aTableTag) { + const TableDirEntry* dir = FindTableDirEntry(aFontData, aTableTag); + if (dir) { + return hb_blob_create( + reinterpret_cast<const char*>(aFontData) + dir->offset, dir->length, + HB_MEMORY_MODE_READONLY, nullptr, nullptr); + } + return nullptr; +} + +nsresult gfxFontUtils::RenameFont(const nsAString& aName, + const uint8_t* aFontData, + uint32_t aFontDataLength, + FallibleTArray<uint8_t>* aNewFont) { + NS_ASSERTION(aNewFont, "null font data array"); + + uint64_t dataLength(aFontDataLength); + + // new name table + static const uint32_t neededNameIDs[] = {NAME_ID_FAMILY, NAME_ID_STYLE, + NAME_ID_UNIQUE, NAME_ID_FULL, + NAME_ID_POSTSCRIPT}; + + // calculate new name table size + uint16_t nameCount = ArrayLength(neededNameIDs); + + // leave room for null-terminator + uint32_t nameStrLength = (aName.Length() + 1) * sizeof(char16_t); + if (nameStrLength > 65535) { + // The name length _in bytes_ must fit in an unsigned short field; + // therefore, a name longer than this cannot be used. + return NS_ERROR_FAILURE; + } + + // round name table size up to 4-byte multiple + uint32_t nameTableSize = + (sizeof(NameHeader) + sizeof(NameRecord) * nameCount + nameStrLength + + 3) & + ~3; + + if (dataLength + nameTableSize > UINT32_MAX) return NS_ERROR_FAILURE; + + // bug 505386 - need to handle unpadded font length + uint32_t paddedFontDataSize = (aFontDataLength + 3) & ~3; + uint32_t adjFontDataSize = paddedFontDataSize + nameTableSize; + + // create new buffer: old font data plus new name table + if (!aNewFont->AppendElements(adjFontDataSize, fallible)) + return NS_ERROR_OUT_OF_MEMORY; + + // copy the old font data + uint8_t* newFontData = reinterpret_cast<uint8_t*>(aNewFont->Elements()); + + // null the last four bytes in case the font length is not a multiple of 4 + memset(newFontData + aFontDataLength, 0, + paddedFontDataSize - aFontDataLength); + + // copy font data + memcpy(newFontData, aFontData, aFontDataLength); + + // null out the last 4 bytes for checksum calculations + memset(newFontData + adjFontDataSize - 4, 0, 4); + + NameHeader* nameHeader = + reinterpret_cast<NameHeader*>(newFontData + paddedFontDataSize); + + // -- name header + nameHeader->format = 0; + nameHeader->count = nameCount; + nameHeader->stringOffset = + sizeof(NameHeader) + nameCount * sizeof(NameRecord); + + // -- name records + uint32_t i; + NameRecord* nameRecord = reinterpret_cast<NameRecord*>(nameHeader + 1); + + for (i = 0; i < nameCount; i++, nameRecord++) { + nameRecord->platformID = PLATFORM_ID_MICROSOFT; + nameRecord->encodingID = ENCODING_ID_MICROSOFT_UNICODEBMP; + nameRecord->languageID = LANG_ID_MICROSOFT_EN_US; + nameRecord->nameID = neededNameIDs[i]; + nameRecord->offset = 0; + nameRecord->length = nameStrLength; + } + + // -- string data, located after the name records, stored in big-endian form + char16_t* strData = reinterpret_cast<char16_t*>(nameRecord); + + mozilla::NativeEndian::copyAndSwapToBigEndian(strData, aName.BeginReading(), + aName.Length()); + strData[aName.Length()] = 0; // add null termination + + // adjust name table header to point to the new name table + SFNTHeader* sfntHeader = reinterpret_cast<SFNTHeader*>(newFontData); + + // table directory entries begin immediately following SFNT header + TableDirEntry* dirEntry = + FindTableDirEntry(newFontData, TRUETYPE_TAG('n', 'a', 'm', 'e')); + // function only called if font validates, so this should always be true + MOZ_ASSERT(dirEntry, "attempt to rename font with no name table"); + + uint32_t numTables = sfntHeader->numTables; + + // note: dirEntry now points to 'name' table record + + // recalculate name table checksum + uint32_t checkSum = 0; + AutoSwap_PRUint32* nameData = + reinterpret_cast<AutoSwap_PRUint32*>(nameHeader); + AutoSwap_PRUint32* nameDataEnd = nameData + (nameTableSize >> 2); + + while (nameData < nameDataEnd) checkSum = checkSum + *nameData++; + + // adjust name table entry to point to new name table + dirEntry->offset = paddedFontDataSize; + dirEntry->length = nameTableSize; + dirEntry->checkSum = checkSum; + + // fix up checksums + uint32_t checksum = 0; + + // checksum for font = (checksum of header) + (checksum of tables) + uint32_t headerLen = sizeof(SFNTHeader) + sizeof(TableDirEntry) * numTables; + const AutoSwap_PRUint32* headerData = + reinterpret_cast<const AutoSwap_PRUint32*>(newFontData); + + // header length is in bytes, checksum calculated in longwords + for (i = 0; i < (headerLen >> 2); i++, headerData++) { + checksum += *headerData; + } + + uint32_t headOffset = 0; + dirEntry = reinterpret_cast<TableDirEntry*>(newFontData + sizeof(SFNTHeader)); + + for (i = 0; i < numTables; i++, dirEntry++) { + if (dirEntry->tag == TRUETYPE_TAG('h', 'e', 'a', 'd')) { + headOffset = dirEntry->offset; + } + checksum += dirEntry->checkSum; + } + + NS_ASSERTION(headOffset != 0, "no head table for font"); + + HeadTable* headData = reinterpret_cast<HeadTable*>(newFontData + headOffset); + + headData->checkSumAdjustment = HeadTable::HEAD_CHECKSUM_CALC_CONST - checksum; + + return NS_OK; +} + +// This is only called after the basic validity of the downloaded sfnt +// data has been checked, so it should never fail to find the name table +// (though it might fail to read it, if memory isn't available); +// other checks here are just for extra paranoia. +nsresult gfxFontUtils::GetFullNameFromSFNT(const uint8_t* aFontData, + uint32_t aLength, + nsACString& aFullName) { + aFullName = "(MISSING NAME)"; // should always get replaced + + const TableDirEntry* dirEntry = + FindTableDirEntry(aFontData, TRUETYPE_TAG('n', 'a', 'm', 'e')); + + // should never fail, as we're only called after font validation succeeded + NS_ENSURE_TRUE(dirEntry, NS_ERROR_NOT_AVAILABLE); + + uint32_t len = dirEntry->length; + NS_ENSURE_TRUE(aLength > len && aLength - len >= dirEntry->offset, + NS_ERROR_UNEXPECTED); + + 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, + nsACString& aFullName) { + nsAutoCString name; + nsresult rv = gfxFontUtils::ReadCanonicalName( + aNameTable, gfxFontUtils::NAME_ID_FULL, name); + if (NS_SUCCEEDED(rv) && !name.IsEmpty()) { + aFullName = name; + return NS_OK; + } + rv = gfxFontUtils::ReadCanonicalName(aNameTable, gfxFontUtils::NAME_ID_FAMILY, + name); + if (NS_SUCCEEDED(rv) && !name.IsEmpty()) { + nsAutoCString styleName; + rv = gfxFontUtils::ReadCanonicalName( + aNameTable, gfxFontUtils::NAME_ID_STYLE, styleName); + if (NS_SUCCEEDED(rv) && !styleName.IsEmpty()) { + name.Append(' '); + name.Append(styleName); + aFullName = name; + } + return NS_OK; + } + + return NS_ERROR_NOT_AVAILABLE; +} + +nsresult gfxFontUtils::GetFamilyNameFromTable(hb_blob_t* aNameTable, + nsACString& aFamilyName) { + nsAutoCString name; + nsresult rv = gfxFontUtils::ReadCanonicalName( + aNameTable, gfxFontUtils::NAME_ID_FAMILY, name); + if (NS_SUCCEEDED(rv) && !name.IsEmpty()) { + aFamilyName = name; + return NS_OK; + } + return NS_ERROR_NOT_AVAILABLE; +} + +enum { +#if defined(XP_MACOSX) + CANONICAL_LANG_ID = gfxFontUtils::LANG_ID_MAC_ENGLISH, + PLATFORM_ID = gfxFontUtils::PLATFORM_ID_MAC +#else + CANONICAL_LANG_ID = gfxFontUtils::LANG_ID_MICROSOFT_EN_US, + PLATFORM_ID = gfxFontUtils::PLATFORM_ID_MICROSOFT +#endif +}; + +nsresult gfxFontUtils::ReadNames(const char* aNameData, uint32_t aDataLen, + uint32_t aNameID, int32_t aPlatformID, + nsTArray<nsCString>& aNames) { + return ReadNames(aNameData, aDataLen, aNameID, LANG_ALL, aPlatformID, aNames); +} + +nsresult gfxFontUtils::ReadCanonicalName(hb_blob_t* aNameTable, + uint32_t aNameID, nsCString& aName) { + uint32_t nameTableLen; + const char* nameTable = hb_blob_get_data(aNameTable, &nameTableLen); + return ReadCanonicalName(nameTable, nameTableLen, aNameID, aName); +} + +nsresult gfxFontUtils::ReadCanonicalName(const char* aNameData, + uint32_t aDataLen, uint32_t aNameID, + nsCString& aName) { + nsresult rv; + + nsTArray<nsCString> names; + + // first, look for the English name (this will succeed 99% of the time) + rv = ReadNames(aNameData, aDataLen, aNameID, CANONICAL_LANG_ID, PLATFORM_ID, + names); + NS_ENSURE_SUCCESS(rv, rv); + + // otherwise, grab names for all languages + if (names.Length() == 0) { + rv = ReadNames(aNameData, aDataLen, aNameID, LANG_ALL, PLATFORM_ID, names); + NS_ENSURE_SUCCESS(rv, rv); + } + +#if defined(XP_MACOSX) + // may be dealing with font that only has Microsoft name entries + if (names.Length() == 0) { + rv = ReadNames(aNameData, aDataLen, aNameID, LANG_ID_MICROSOFT_EN_US, + PLATFORM_ID_MICROSOFT, names); + NS_ENSURE_SUCCESS(rv, rv); + + // getting really desperate now, take anything! + if (names.Length() == 0) { + rv = ReadNames(aNameData, aDataLen, aNameID, LANG_ALL, + PLATFORM_ID_MICROSOFT, names); + NS_ENSURE_SUCCESS(rv, rv); + } + } +#endif + + // return the first name (99.9% of the time names will + // contain a single English name) + if (names.Length()) { + aName.Assign(names[0]); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +// Charsets to use for decoding Mac platform font names. +// This table is sorted by {encoding, language}, with the wildcard "ANY" being +// greater than any defined values for each field; we use a binary search on +// both fields, and fall back to matching only encoding if necessary + +// Some "redundant" entries for specific combinations are included such as +// encoding=roman, lang=english, in order that common entries will be found +// on the first search. + +const uint16_t ANY = 0xffff; +const gfxFontUtils::MacFontNameCharsetMapping + gfxFontUtils::gMacFontNameCharsets[] = { + {ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_ENGLISH, MACINTOSH_ENCODING}, + {ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_ICELANDIC, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_TURKISH, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_POLISH, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_ROMANIAN, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_CZECH, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_SLOVAK, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_ROMAN, ANY, MACINTOSH_ENCODING}, + {ENCODING_ID_MAC_JAPANESE, LANG_ID_MAC_JAPANESE, SHIFT_JIS_ENCODING}, + {ENCODING_ID_MAC_JAPANESE, ANY, SHIFT_JIS_ENCODING}, + {ENCODING_ID_MAC_TRAD_CHINESE, LANG_ID_MAC_TRAD_CHINESE, BIG5_ENCODING}, + {ENCODING_ID_MAC_TRAD_CHINESE, ANY, BIG5_ENCODING}, + {ENCODING_ID_MAC_KOREAN, LANG_ID_MAC_KOREAN, EUC_KR_ENCODING}, + {ENCODING_ID_MAC_KOREAN, ANY, EUC_KR_ENCODING}, + {ENCODING_ID_MAC_ARABIC, LANG_ID_MAC_ARABIC, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_ARABIC, LANG_ID_MAC_URDU, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_ARABIC, LANG_ID_MAC_FARSI, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_ARABIC, ANY, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_HEBREW, LANG_ID_MAC_HEBREW, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_HEBREW, ANY, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_GREEK, ANY, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_CYRILLIC, ANY, X_MAC_CYRILLIC_ENCODING}, + {ENCODING_ID_MAC_DEVANAGARI, ANY, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_GURMUKHI, ANY, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_GUJARATI, ANY, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_SIMP_CHINESE, LANG_ID_MAC_SIMP_CHINESE, + GB18030_ENCODING}, + {ENCODING_ID_MAC_SIMP_CHINESE, ANY, GB18030_ENCODING}}; + +const Encoding* gfxFontUtils::gISOFontNameCharsets[] = { + /* 0 */ WINDOWS_1252_ENCODING, /* US-ASCII */ + /* 1 */ nullptr, /* spec says "ISO 10646" but does not specify encoding + form! */ + /* 2 */ WINDOWS_1252_ENCODING /* ISO-8859-1 */ +}; + +const Encoding* gfxFontUtils::gMSFontNameCharsets[] = { + /* [0] ENCODING_ID_MICROSOFT_SYMBOL */ UTF_16BE_ENCODING, + /* [1] ENCODING_ID_MICROSOFT_UNICODEBMP */ UTF_16BE_ENCODING, + /* [2] ENCODING_ID_MICROSOFT_SHIFTJIS */ SHIFT_JIS_ENCODING, + /* [3] ENCODING_ID_MICROSOFT_PRC */ nullptr, + /* [4] ENCODING_ID_MICROSOFT_BIG5 */ BIG5_ENCODING, + /* [5] ENCODING_ID_MICROSOFT_WANSUNG */ nullptr, + /* [6] ENCODING_ID_MICROSOFT_JOHAB */ nullptr, + /* [7] reserved */ nullptr, + /* [8] reserved */ nullptr, + /* [9] reserved */ nullptr, + /*[10] ENCODING_ID_MICROSOFT_UNICODEFULL */ UTF_16BE_ENCODING}; + +struct MacCharsetMappingComparator { + typedef gfxFontUtils::MacFontNameCharsetMapping MacFontNameCharsetMapping; + const MacFontNameCharsetMapping& mSearchValue; + explicit MacCharsetMappingComparator( + const MacFontNameCharsetMapping& aSearchValue) + : mSearchValue(aSearchValue) {} + int operator()(const MacFontNameCharsetMapping& aEntry) const { + if (mSearchValue < aEntry) { + return -1; + } + if (aEntry < mSearchValue) { + return 1; + } + return 0; + } +}; + +// Return the Encoding object we should use to decode a font name +// given the name table attributes. +// Special return values: +// X_USER_DEFINED_ENCODING One of Mac legacy encodings that is not a part +// of Encoding Standard +// nullptr unknown charset, do not attempt conversion +const Encoding* gfxFontUtils::GetCharsetForFontName(uint16_t aPlatform, + uint16_t aScript, + uint16_t aLanguage) { + switch (aPlatform) { + case PLATFORM_ID_UNICODE: + return UTF_16BE_ENCODING; + + case PLATFORM_ID_MAC: { + MacFontNameCharsetMapping searchValue = {aScript, aLanguage, nullptr}; + for (uint32_t i = 0; i < 2; ++i) { + size_t idx; + if (BinarySearchIf(gMacFontNameCharsets, 0, + ArrayLength(gMacFontNameCharsets), + MacCharsetMappingComparator(searchValue), &idx)) { + return gMacFontNameCharsets[idx].mEncoding; + } + + // no match, so try again finding one in any language + searchValue.mLanguage = ANY; + } + } break; + + case PLATFORM_ID_ISO: + if (aScript < ArrayLength(gISOFontNameCharsets)) { + return gISOFontNameCharsets[aScript]; + } + break; + + case PLATFORM_ID_MICROSOFT: + if (aScript < ArrayLength(gMSFontNameCharsets)) { + return gMSFontNameCharsets[aScript]; + } + break; + } + + return nullptr; +} + +template <int N> +static bool StartsWith(const nsACString& string, const char (&prefix)[N]) { + if (N - 1 > string.Length()) { + return false; + } + return memcmp(string.Data(), prefix, N - 1) == 0; +} + +// convert a raw name from the name table to an nsString, if possible; +// return value indicates whether conversion succeeded +bool gfxFontUtils::DecodeFontName(const char* aNameData, int32_t aByteLen, + uint32_t aPlatformCode, uint32_t aScriptCode, + uint32_t aLangCode, nsACString& aName) { + if (aByteLen <= 0) { + NS_WARNING("empty font name"); + aName.SetLength(0); + return true; + } + + auto encoding = GetCharsetForFontName(aPlatformCode, aScriptCode, aLangCode); + + if (!encoding) { + // nullptr -> unknown charset +#ifdef DEBUG + char warnBuf[128]; + if (aByteLen > 64) aByteLen = 64; + SprintfLiteral(warnBuf, + "skipping font name, unknown charset %d:%d:%d for <%.*s>", + aPlatformCode, aScriptCode, aLangCode, aByteLen, aNameData); + NS_WARNING(warnBuf); +#endif + return false; + } + + if (encoding == X_USER_DEFINED_ENCODING) { +#ifdef XP_MACOSX + // Special case for macOS only: support legacy Mac encodings + // that aren't part of the Encoding Standard. + if (aPlatformCode == PLATFORM_ID_MAC) { + CFStringRef str = + CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*)aNameData, + aByteLen, aScriptCode, false); + if (str) { + CFIndex length = CFStringGetLength(str); + nsAutoString name16; + name16.SetLength(length); + CFStringGetCharacters(str, CFRangeMake(0, length), + (UniChar*)name16.BeginWriting()); + CFRelease(str); + CopyUTF16toUTF8(name16, aName); + return true; + } + } +#endif + NS_WARNING("failed to get the decoder for a font name string"); + return false; + } + + auto rv = encoding->DecodeWithoutBOMHandling( + nsDependentCSubstring(aNameData, aByteLen), aName); + return NS_SUCCEEDED(rv); +} + +nsresult gfxFontUtils::ReadNames(const char* aNameData, uint32_t aDataLen, + uint32_t aNameID, int32_t aLangID, + int32_t aPlatformID, + nsTArray<nsCString>& aNames) { + NS_ASSERTION(aDataLen != 0, "null name table"); + + if (!aDataLen) { + return NS_ERROR_FAILURE; + } + + // -- name table data + const NameHeader* nameHeader = reinterpret_cast<const NameHeader*>(aNameData); + + uint32_t nameCount = nameHeader->count; + + // -- sanity check the number of name records + if (uint64_t(nameCount) * sizeof(NameRecord) > aDataLen) { + NS_WARNING("invalid font (name table data)"); + return NS_ERROR_FAILURE; + } + + // -- iterate through name records + const NameRecord* nameRecord = + reinterpret_cast<const NameRecord*>(aNameData + sizeof(NameHeader)); + uint64_t nameStringsBase = uint64_t(nameHeader->stringOffset); + + uint32_t i; + for (i = 0; i < nameCount; i++, nameRecord++) { + uint32_t platformID; + + // skip over unwanted nameID's + if (uint32_t(nameRecord->nameID) != aNameID) { + continue; + } + + // skip over unwanted platform data + platformID = nameRecord->platformID; + if (aPlatformID != PLATFORM_ALL && platformID != uint32_t(aPlatformID)) { + continue; + } + + // skip over unwanted languages + if (aLangID != LANG_ALL && + uint32_t(nameRecord->languageID) != uint32_t(aLangID)) { + continue; + } + + // add name to names array + + // -- calculate string location + uint32_t namelen = nameRecord->length; + uint32_t nameoff = + nameRecord->offset; // offset from base of string storage + + if (nameStringsBase + uint64_t(nameoff) + uint64_t(namelen) > aDataLen) { + NS_WARNING("invalid font (name table strings)"); + return NS_ERROR_FAILURE; + } + + // -- decode if necessary and make nsString + nsAutoCString name; + + DecodeFontName(aNameData + nameStringsBase + nameoff, namelen, platformID, + uint32_t(nameRecord->encodingID), + uint32_t(nameRecord->languageID), name); + + uint32_t k, numNames; + bool foundName = false; + + numNames = aNames.Length(); + for (k = 0; k < numNames; k++) { + if (name.Equals(aNames[k])) { + foundName = true; + break; + } + } + + if (!foundName) aNames.AppendElement(name); + } + + return NS_OK; +} + +#pragma pack(1) + +struct COLRBaseGlyphRecord { + AutoSwap_PRUint16 glyphId; + AutoSwap_PRUint16 firstLayerIndex; + AutoSwap_PRUint16 numLayers; +}; + +struct COLRLayerRecord { + AutoSwap_PRUint16 glyphId; + AutoSwap_PRUint16 paletteEntryIndex; +}; + +// sRGB color space +struct CPALColorRecord { + uint8_t blue; + uint8_t green; + uint8_t red; + uint8_t alpha; +}; + +#pragma pack() + +bool gfxFontUtils::ValidateColorGlyphs(hb_blob_t* aCOLR, hb_blob_t* aCPAL) { + unsigned int colrLength; + const COLRHeader* colr = + reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR, &colrLength)); + unsigned int cpalLength; + const CPALHeaderVersion0* cpal = reinterpret_cast<const CPALHeaderVersion0*>( + hb_blob_get_data(aCPAL, &cpalLength)); + + if (!colr || !cpal || !colrLength || !cpalLength) { + return false; + } + + if (uint16_t(colr->version) != 0 || uint16_t(cpal->version) != 0) { + // We only support version 0 headers. + return false; + } + + const uint32_t offsetBaseGlyphRecord = colr->offsetBaseGlyphRecord; + const uint16_t numBaseGlyphRecord = colr->numBaseGlyphRecord; + const uint32_t offsetLayerRecord = colr->offsetLayerRecord; + const uint16_t numLayerRecords = colr->numLayerRecords; + + const uint32_t offsetFirstColorRecord = cpal->offsetFirstColorRecord; + const uint16_t numColorRecords = cpal->numColorRecords; + const uint32_t numPaletteEntries = cpal->numPaletteEntries; + + if (offsetBaseGlyphRecord >= colrLength) { + return false; + } + + if (offsetLayerRecord >= colrLength) { + return false; + } + + if (offsetFirstColorRecord >= cpalLength) { + return false; + } + + if (!numPaletteEntries) { + return false; + } + + if (sizeof(COLRBaseGlyphRecord) * numBaseGlyphRecord > + colrLength - offsetBaseGlyphRecord) { + // COLR base glyph record will be overflow + return false; + } + + if (sizeof(COLRLayerRecord) * numLayerRecords > + colrLength - offsetLayerRecord) { + // COLR layer record will be overflow + return false; + } + + if (sizeof(CPALColorRecord) * numColorRecords > + cpalLength - offsetFirstColorRecord) { + // CPAL color record will be overflow + return false; + } + + if (numPaletteEntries * uint16_t(cpal->numPalettes) != numColorRecords) { + // palette of CPAL color record will be overflow. + return false; + } + + uint16_t lastGlyphId = 0; + const COLRBaseGlyphRecord* baseGlyph = + reinterpret_cast<const COLRBaseGlyphRecord*>( + reinterpret_cast<const uint8_t*>(colr) + offsetBaseGlyphRecord); + + for (uint16_t i = 0; i < numBaseGlyphRecord; i++, baseGlyph++) { + const uint32_t firstLayerIndex = baseGlyph->firstLayerIndex; + const uint16_t numLayers = baseGlyph->numLayers; + const uint16_t glyphId = baseGlyph->glyphId; + + if (lastGlyphId && lastGlyphId >= glyphId) { + // glyphId must be sorted + return false; + } + lastGlyphId = glyphId; + + if (!numLayers) { + // no layer + return false; + } + if (firstLayerIndex + numLayers > numLayerRecords) { + // layer length of target glyph is overflow + return false; + } + } + + const COLRLayerRecord* layer = reinterpret_cast<const COLRLayerRecord*>( + reinterpret_cast<const uint8_t*>(colr) + offsetLayerRecord); + + for (uint16_t i = 0; i < numLayerRecords; i++, layer++) { + if (uint16_t(layer->paletteEntryIndex) >= numPaletteEntries && + uint16_t(layer->paletteEntryIndex) != 0xFFFF) { + // CPAL palette entry record is overflow + return false; + } + } + + return true; +} + +static int CompareBaseGlyph(const void* key, const void* data) { + uint32_t glyphId = (uint32_t)(uintptr_t)key; + const COLRBaseGlyphRecord* baseGlyph = + reinterpret_cast<const COLRBaseGlyphRecord*>(data); + uint32_t baseGlyphId = uint16_t(baseGlyph->glyphId); + + if (baseGlyphId == glyphId) { + return 0; + } + + return baseGlyphId > glyphId ? -1 : 1; +} + +static COLRBaseGlyphRecord* LookForBaseGlyphRecord(const COLRHeader* aCOLR, + uint32_t aGlyphId) { + const uint8_t* baseGlyphRecords = reinterpret_cast<const uint8_t*>(aCOLR) + + uint32_t(aCOLR->offsetBaseGlyphRecord); + // BaseGlyphRecord is sorted by glyphId + return reinterpret_cast<COLRBaseGlyphRecord*>( + bsearch((void*)(uintptr_t)aGlyphId, baseGlyphRecords, + uint16_t(aCOLR->numBaseGlyphRecord), sizeof(COLRBaseGlyphRecord), + CompareBaseGlyph)); +} + +bool gfxFontUtils::GetColorGlyphLayers( + hb_blob_t* aCOLR, hb_blob_t* aCPAL, uint32_t aGlyphId, + const mozilla::gfx::DeviceColor& aDefaultColor, nsTArray<uint16_t>& aGlyphs, + nsTArray<mozilla::gfx::DeviceColor>& aColors) { + unsigned int blobLength; + const COLRHeader* colr = + reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR, &blobLength)); + MOZ_ASSERT(colr, "Cannot get COLR raw data"); + MOZ_ASSERT(blobLength, "Found COLR data, but length is 0"); + + COLRBaseGlyphRecord* baseGlyph = LookForBaseGlyphRecord(colr, aGlyphId); + if (!baseGlyph) { + return false; + } + + const CPALHeaderVersion0* cpal = reinterpret_cast<const CPALHeaderVersion0*>( + hb_blob_get_data(aCPAL, &blobLength)); + MOZ_ASSERT(cpal, "Cannot get CPAL raw data"); + MOZ_ASSERT(blobLength, "Found CPAL data, but length is 0"); + + const COLRLayerRecord* layer = reinterpret_cast<const COLRLayerRecord*>( + reinterpret_cast<const uint8_t*>(colr) + + uint32_t(colr->offsetLayerRecord) + + sizeof(COLRLayerRecord) * uint16_t(baseGlyph->firstLayerIndex)); + const uint16_t numLayers = baseGlyph->numLayers; + const uint32_t offsetFirstColorRecord = cpal->offsetFirstColorRecord; + + for (uint16_t layerIndex = 0; layerIndex < numLayers; layerIndex++) { + aGlyphs.AppendElement(uint16_t(layer->glyphId)); + if (uint16_t(layer->paletteEntryIndex) == 0xFFFF) { + aColors.AppendElement(aDefaultColor); + } else { + const CPALColorRecord* color = reinterpret_cast<const CPALColorRecord*>( + reinterpret_cast<const uint8_t*>(cpal) + offsetFirstColorRecord + + sizeof(CPALColorRecord) * uint16_t(layer->paletteEntryIndex)); + aColors.AppendElement( + mozilla::gfx::ToDeviceColor(mozilla::gfx::sRGBColor::FromU8( + color->red, color->green, color->blue, color->alpha))); + } + layer++; + } + return true; +} + +bool gfxFontUtils::HasColorLayersForGlyph(hb_blob_t* aCOLR, uint32_t aGlyphId) { + unsigned int blobLength; + const COLRHeader* colr = + reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR, &blobLength)); + MOZ_ASSERT(colr, "Cannot get COLR raw data"); + MOZ_ASSERT(blobLength, "Found COLR data, but length is 0"); + + return LookForBaseGlyphRecord(colr, aGlyphId); +} + +void gfxFontUtils::GetVariationData( + gfxFontEntry* aFontEntry, nsTArray<gfxFontVariationAxis>* aAxes, + nsTArray<gfxFontVariationInstance>* aInstances) { + MOZ_ASSERT(!aAxes || aAxes->IsEmpty()); + MOZ_ASSERT(!aInstances || aInstances->IsEmpty()); + + if (!aFontEntry->HasVariations()) { + return; + } + + // Some platforms don't offer a simple API to return the list of instances, + // so we have to interpret the 'fvar' table ourselves. + + // https://www.microsoft.com/typography/otspec/fvar.htm#fvarHeader + struct FvarHeader { + AutoSwap_PRUint16 majorVersion; + AutoSwap_PRUint16 minorVersion; + AutoSwap_PRUint16 axesArrayOffset; + AutoSwap_PRUint16 reserved; + AutoSwap_PRUint16 axisCount; + AutoSwap_PRUint16 axisSize; + AutoSwap_PRUint16 instanceCount; + AutoSwap_PRUint16 instanceSize; + }; + + // https://www.microsoft.com/typography/otspec/fvar.htm#variationAxisRecord + struct AxisRecord { + AutoSwap_PRUint32 axisTag; + AutoSwap_PRInt32 minValue; + AutoSwap_PRInt32 defaultValue; + AutoSwap_PRInt32 maxValue; + AutoSwap_PRUint16 flags; + AutoSwap_PRUint16 axisNameID; + }; + const uint16_t HIDDEN_AXIS = 0x0001; // AxisRecord flags value + + // https://www.microsoft.com/typography/otspec/fvar.htm#instanceRecord + struct InstanceRecord { + AutoSwap_PRUint16 subfamilyNameID; + AutoSwap_PRUint16 flags; + AutoSwap_PRInt32 coordinates[1]; // variable-size array [axisCount] + // The variable-length 'coordinates' array may be followed by an + // optional extra field 'postScriptNameID'. We can't directly + // represent this in the struct, because its offset varies depending + // on the number of axes present. + // (Not currently used by our code here anyhow.) + // AutoSwap_PRUint16 postScriptNameID; + }; + + // Helper to ensure we free a font table when we return. + class AutoHBBlob { + public: + explicit AutoHBBlob(hb_blob_t* aBlob) : mBlob(aBlob) {} + + ~AutoHBBlob() { hb_blob_destroy(mBlob); } + + operator hb_blob_t*() { return mBlob; } + + private: + hb_blob_t* const mBlob; + }; + + // Load the two font tables we need as harfbuzz blobs; if either is absent, + // just bail out. + AutoHBBlob fvarTable( + aFontEntry->GetFontTable(TRUETYPE_TAG('f', 'v', 'a', 'r'))); + AutoHBBlob nameTable( + aFontEntry->GetFontTable(TRUETYPE_TAG('n', 'a', 'm', 'e'))); + if (!fvarTable || !nameTable) { + return; + } + unsigned int len; + const char* data = hb_blob_get_data(fvarTable, &len); + if (len < sizeof(FvarHeader)) { + return; + } + // Read the fields of the table header; bail out if it looks broken. + auto fvar = reinterpret_cast<const FvarHeader*>(data); + if (uint16_t(fvar->majorVersion) != 1 || uint16_t(fvar->minorVersion) != 0 || + uint16_t(fvar->reserved) != 2) { + return; + } + uint16_t axisCount = fvar->axisCount; + uint16_t axisSize = fvar->axisSize; + uint16_t instanceCount = fvar->instanceCount; + uint16_t instanceSize = fvar->instanceSize; + if (axisCount == + 0 || // no axes? + // https://www.microsoft.com/typography/otspec/fvar.htm#axisSize + axisSize != 20 || // required value for current table version + // https://www.microsoft.com/typography/otspec/fvar.htm#instanceSize + (instanceSize != axisCount * sizeof(int32_t) + 4 && + instanceSize != axisCount * sizeof(int32_t) + 6)) { + return; + } + // Check that axis array will not exceed table size + uint16_t axesOffset = fvar->axesArrayOffset; + if (axesOffset + uint32_t(axisCount) * axisSize > len) { + return; + } + // Get pointer to the array of axis records + auto axes = reinterpret_cast<const AxisRecord*>(data + axesOffset); + // Get address of instance array, and check it doesn't overflow table size. + // https://www.microsoft.com/typography/otspec/fvar.htm#axisAndInstanceArrays + auto instData = data + axesOffset + axisCount * axisSize; + if (instData + uint32_t(instanceCount) * instanceSize > data + len) { + return; + } + if (aInstances) { + aInstances->SetCapacity(instanceCount); + for (unsigned i = 0; i < instanceCount; ++i, instData += instanceSize) { + // Typed pointer to the current instance record, to read its fields. + auto inst = reinterpret_cast<const InstanceRecord*>(instData); + // Pointer to the coordinates array within the instance record. + // This array has axisCount elements, and is included in instanceSize + // (which depends on axisCount, and was validated above) so we know + // access to coords[j] below will not be outside the table bounds. + auto coords = &inst->coordinates[0]; + gfxFontVariationInstance instance; + uint16_t nameID = inst->subfamilyNameID; + nsresult rv = ReadCanonicalName(nameTable, nameID, instance.mName); + if (NS_FAILED(rv)) { + // If no name was available for the instance, ignore it. + continue; + } + instance.mValues.SetCapacity(axisCount); + for (unsigned j = 0; j < axisCount; ++j) { + gfxFontVariationValue value = {axes[j].axisTag, + int32_t(coords[j]) / 65536.0f}; + instance.mValues.AppendElement(value); + } + aInstances->AppendElement(std::move(instance)); + } + } + if (aAxes) { + aAxes->SetCapacity(axisCount); + for (unsigned i = 0; i < axisCount; ++i) { + if (uint16_t(axes[i].flags) & HIDDEN_AXIS) { + continue; + } + gfxFontVariationAxis axis; + axis.mTag = axes[i].axisTag; + uint16_t nameID = axes[i].axisNameID; + nsresult rv = ReadCanonicalName(nameTable, nameID, axis.mName); + if (NS_FAILED(rv)) { + axis.mName.Truncate(0); + } + // Convert values from 16.16 fixed-point to float + axis.mMinValue = int32_t(axes[i].minValue) / 65536.0f; + axis.mDefaultValue = int32_t(axes[i].defaultValue) / 65536.0f; + axis.mMaxValue = int32_t(axes[i].maxValue) / 65536.0f; + aAxes->AppendElement(axis); + } + } +} + +void gfxFontUtils::ReadOtherFamilyNamesForFace( + const nsACString& aFamilyName, const char* aNameData, uint32_t aDataLength, + nsTArray<nsCString>& aOtherFamilyNames, bool useFullName) { + const NameHeader* nameHeader = reinterpret_cast<const NameHeader*>(aNameData); + + uint32_t nameCount = nameHeader->count; + if (nameCount * sizeof(NameRecord) > aDataLength) { + NS_WARNING("invalid font (name records)"); + return; + } + + const NameRecord* nameRecord = + reinterpret_cast<const NameRecord*>(aNameData + sizeof(NameHeader)); + uint32_t stringsBase = uint32_t(nameHeader->stringOffset); + + for (uint32_t i = 0; i < nameCount; i++, nameRecord++) { + uint32_t nameLen = nameRecord->length; + uint32_t nameOff = + nameRecord->offset; // offset from base of string storage + + if (stringsBase + nameOff + nameLen > aDataLength) { + NS_WARNING("invalid font (name table strings)"); + return; + } + + uint16_t nameID = nameRecord->nameID; + if ((useFullName && nameID == NAME_ID_FULL) || + (!useFullName && + (nameID == NAME_ID_FAMILY || nameID == NAME_ID_PREFERRED_FAMILY))) { + nsAutoCString otherFamilyName; + bool ok = DecodeFontName( + aNameData + stringsBase + nameOff, nameLen, + uint32_t(nameRecord->platformID), uint32_t(nameRecord->encodingID), + uint32_t(nameRecord->languageID), otherFamilyName); + // add if not same as canonical family name + if (ok && otherFamilyName != aFamilyName && + !aOtherFamilyNames.Contains(otherFamilyName)) { + aOtherFamilyNames.AppendElement(otherFamilyName); + } + } + } +} + +#ifdef XP_WIN + +/* static */ +bool gfxFontUtils::IsCffFont(const uint8_t* aFontData) { + // this is only called after aFontData has passed basic validation, + // so we know there is enough data present to allow us to read the version! + const SFNTHeader* sfntHeader = reinterpret_cast<const SFNTHeader*>(aFontData); + return (sfntHeader->sfntVersion == TRUETYPE_TAG('O', 'T', 'T', 'O')); +} + +#endif + +#undef acceptablePlatform +#undef isSymbol +#undef isUVSEncoding +#undef LOG +#undef LOG_ENABLED diff --git a/gfx/thebes/gfxFontUtils.h b/gfx/thebes/gfxFontUtils.h new file mode 100644 index 0000000000..3496a877e9 --- /dev/null +++ b/gfx/thebes/gfxFontUtils.h @@ -0,0 +1,1474 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FONT_UTILS_H +#define GFX_FONT_UTILS_H + +#include <string.h> +#include <algorithm> +#include <new> +#include <utility> +#include "gfxPlatform.h" +#include "ipc/IPCMessageUtils.h" +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/Casting.h" +#include "mozilla/EndianUtils.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/UniquePtr.h" +#include "nsStringFwd.h" +#include "nsTArray.h" +#include "nscore.h" +#include "zlib.h" + +class PickleIterator; +class gfxFontEntry; +struct gfxFontVariationAxis; +struct gfxFontVariationInstance; + +namespace mozilla { +class Encoding; +namespace gfx { +struct DeviceColor; +} +} // namespace mozilla + +/* Bug 341128 - w32api defines min/max which causes problems with <bitset> */ +#ifdef __MINGW32__ +# undef min +# undef max +#endif + +#undef ERROR /* defined by Windows.h, conflicts with some generated bindings \ + code when this gets indirectly included via shared font list \ + */ + +typedef struct hb_blob_t hb_blob_t; + +class SharedBitSet; + +class gfxSparseBitSet { + private: + friend class SharedBitSet; + + enum { BLOCK_SIZE = 32 }; // ==> 256 codepoints per block + enum { BLOCK_SIZE_BITS = BLOCK_SIZE * 8 }; + enum { NO_BLOCK = 0xffff }; // index value indicating missing (empty) block + + struct Block { + explicit Block(unsigned char memsetValue = 0) { + memset(mBits, memsetValue, BLOCK_SIZE); + } + uint8_t mBits[BLOCK_SIZE]; + }; + + public: + gfxSparseBitSet() = default; + + bool Equals(const gfxSparseBitSet* aOther) const { + if (mBlockIndex.Length() != aOther->mBlockIndex.Length()) { + return false; + } + size_t n = mBlockIndex.Length(); + for (size_t i = 0; i < n; ++i) { + uint32_t b1 = mBlockIndex[i]; + uint32_t b2 = aOther->mBlockIndex[i]; + if ((b1 == NO_BLOCK) != (b2 == NO_BLOCK)) { + return false; + } + if (b1 == NO_BLOCK) { + continue; + } + if (memcmp(&mBlocks[b1].mBits, &aOther->mBlocks[b2].mBits, BLOCK_SIZE) != + 0) { + return false; + } + } + return true; + } + + bool test(uint32_t aIndex) const { + uint32_t i = aIndex / BLOCK_SIZE_BITS; + if (i >= mBlockIndex.Length() || mBlockIndex[i] == NO_BLOCK) { + return false; + } + const Block& block = mBlocks[mBlockIndex[i]]; + return ((block.mBits[(aIndex >> 3) & (BLOCK_SIZE - 1)]) & + (1 << (aIndex & 0x7))) != 0; + } + + // dump out contents of bitmap + void Dump(const char* aPrefix, eGfxLog aWhichLog) const; + + bool TestRange(uint32_t aStart, uint32_t aEnd) { + // start point is beyond the end of the block array? return false + // immediately + uint32_t startBlock = aStart / BLOCK_SIZE_BITS; + uint32_t blockLen = mBlockIndex.Length(); + if (startBlock >= blockLen) { + return false; + } + + // check for blocks in range, if none, return false + bool hasBlocksInRange = false; + uint32_t endBlock = aEnd / BLOCK_SIZE_BITS; + for (uint32_t bi = startBlock; bi <= endBlock; bi++) { + if (bi < blockLen && mBlockIndex[bi] != NO_BLOCK) { + hasBlocksInRange = true; + break; + } + } + if (!hasBlocksInRange) { + return false; + } + + // first block, check bits + if (mBlockIndex[startBlock] != NO_BLOCK) { + const Block& block = mBlocks[mBlockIndex[startBlock]]; + uint32_t start = aStart; + uint32_t end = std::min(aEnd, ((startBlock + 1) * BLOCK_SIZE_BITS) - 1); + for (uint32_t i = start; i <= end; i++) { + if ((block.mBits[(i >> 3) & (BLOCK_SIZE - 1)]) & (1 << (i & 0x7))) { + return true; + } + } + } + if (endBlock == startBlock) { + return false; + } + + // [2..n-1] blocks check bytes + for (uint32_t i = startBlock + 1; i < endBlock; i++) { + if (i >= blockLen || mBlockIndex[i] == NO_BLOCK) { + continue; + } + const Block& block = mBlocks[mBlockIndex[i]]; + for (uint32_t index = 0; index < BLOCK_SIZE; index++) { + if (block.mBits[index]) { + return true; + } + } + } + + // last block, check bits + if (endBlock < blockLen && mBlockIndex[endBlock] != NO_BLOCK) { + const Block& block = mBlocks[mBlockIndex[endBlock]]; + uint32_t start = endBlock * BLOCK_SIZE_BITS; + uint32_t end = aEnd; + for (uint32_t i = start; i <= end; i++) { + if ((block.mBits[(i >> 3) & (BLOCK_SIZE - 1)]) & (1 << (i & 0x7))) { + return true; + } + } + } + + return false; + } + + void set(uint32_t aIndex) { + uint32_t i = aIndex / BLOCK_SIZE_BITS; + while (i >= mBlockIndex.Length()) { + mBlockIndex.AppendElement(NO_BLOCK); + } + if (mBlockIndex[i] == NO_BLOCK) { + mBlocks.AppendElement(); + MOZ_ASSERT(mBlocks.Length() < 0xffff, "block index overflow!"); + mBlockIndex[i] = static_cast<uint16_t>(mBlocks.Length() - 1); + } + Block& block = mBlocks[mBlockIndex[i]]; + block.mBits[(aIndex >> 3) & (BLOCK_SIZE - 1)] |= 1 << (aIndex & 0x7); + } + + void set(uint32_t aIndex, bool aValue) { + if (aValue) { + set(aIndex); + } else { + clear(aIndex); + } + } + + void SetRange(uint32_t aStart, uint32_t aEnd) { + const uint32_t startIndex = aStart / BLOCK_SIZE_BITS; + const uint32_t endIndex = aEnd / BLOCK_SIZE_BITS; + + while (endIndex >= mBlockIndex.Length()) { + mBlockIndex.AppendElement(NO_BLOCK); + } + + for (uint32_t i = startIndex; i <= endIndex; ++i) { + const uint32_t blockFirstBit = i * BLOCK_SIZE_BITS; + const uint32_t blockLastBit = blockFirstBit + BLOCK_SIZE_BITS - 1; + + if (mBlockIndex[i] == NO_BLOCK) { + bool fullBlock = (aStart <= blockFirstBit && aEnd >= blockLastBit); + mBlocks.AppendElement(Block(fullBlock ? 0xFF : 0)); + MOZ_ASSERT(mBlocks.Length() < 0xffff, "block index overflow!"); + mBlockIndex[i] = static_cast<uint16_t>(mBlocks.Length() - 1); + if (fullBlock) { + continue; + } + } + + Block& block = mBlocks[mBlockIndex[i]]; + const uint32_t start = + aStart > blockFirstBit ? aStart - blockFirstBit : 0; + const uint32_t end = + std::min<uint32_t>(aEnd - blockFirstBit, BLOCK_SIZE_BITS - 1); + + for (uint32_t bit = start; bit <= end; ++bit) { + block.mBits[bit >> 3] |= 1 << (bit & 0x7); + } + } + } + + void clear(uint32_t aIndex) { + uint32_t i = aIndex / BLOCK_SIZE_BITS; + if (i >= mBlockIndex.Length()) { + return; + } + if (mBlockIndex[i] == NO_BLOCK) { + mBlocks.AppendElement(); + MOZ_ASSERT(mBlocks.Length() < 0xffff, "block index overflow!"); + mBlockIndex[i] = static_cast<uint16_t>(mBlocks.Length() - 1); + } + Block& block = mBlocks[mBlockIndex[i]]; + block.mBits[(aIndex >> 3) & (BLOCK_SIZE - 1)] &= ~(1 << (aIndex & 0x7)); + } + + void ClearRange(uint32_t aStart, uint32_t aEnd) { + const uint32_t startIndex = aStart / BLOCK_SIZE_BITS; + const uint32_t endIndex = aEnd / BLOCK_SIZE_BITS; + + for (uint32_t i = startIndex; i <= endIndex; ++i) { + if (i >= mBlockIndex.Length()) { + return; + } + if (mBlockIndex[i] == NO_BLOCK) { + continue; + } + + const uint32_t blockFirstBit = i * BLOCK_SIZE_BITS; + Block& block = mBlocks[mBlockIndex[i]]; + + const uint32_t start = + aStart > blockFirstBit ? aStart - blockFirstBit : 0; + const uint32_t end = + std::min<uint32_t>(aEnd - blockFirstBit, BLOCK_SIZE_BITS - 1); + + for (uint32_t bit = start; bit <= end; ++bit) { + block.mBits[bit >> 3] &= ~(1 << (bit & 0x7)); + } + } + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return mBlocks.ShallowSizeOfExcludingThis(aMallocSizeOf) + + mBlockIndex.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + + // clear out all blocks in the array + void reset() { + mBlocks.Clear(); + mBlockIndex.Clear(); + } + + // set this bitset to the union of its current contents and another + void Union(const gfxSparseBitSet& aBitset) { + // ensure mBlocks is large enough + uint32_t blockCount = aBitset.mBlockIndex.Length(); + while (blockCount > mBlockIndex.Length()) { + mBlockIndex.AppendElement(NO_BLOCK); + } + // for each block that may be present in aBitset... + for (uint32_t i = 0; i < blockCount; ++i) { + // if it is missing (implicitly empty), just skip + if (aBitset.mBlockIndex[i] == NO_BLOCK) { + continue; + } + // if the block is missing in this set, just copy the other + if (mBlockIndex[i] == NO_BLOCK) { + mBlocks.AppendElement(aBitset.mBlocks[aBitset.mBlockIndex[i]]); + MOZ_ASSERT(mBlocks.Length() < 0xffff, "block index overflow!"); + mBlockIndex[i] = static_cast<uint16_t>(mBlocks.Length() - 1); + continue; + } + // else set existing block to the union of both + uint32_t* dst = + reinterpret_cast<uint32_t*>(&mBlocks[mBlockIndex[i]].mBits); + const uint32_t* src = reinterpret_cast<const uint32_t*>( + &aBitset.mBlocks[aBitset.mBlockIndex[i]].mBits); + for (uint32_t j = 0; j < BLOCK_SIZE / 4; ++j) { + dst[j] |= src[j]; + } + } + } + + inline void Union(const SharedBitSet& aBitset); + + void Compact() { + // TODO: Discard any empty blocks, and adjust index accordingly. + // (May not be worth doing, though, because we so rarely clear bits + // that were previously set.) + mBlocks.Compact(); + mBlockIndex.Compact(); + } + + uint32_t GetChecksum() const { + uint32_t check = + adler32(0, reinterpret_cast<const uint8_t*>(mBlockIndex.Elements()), + mBlockIndex.Length() * sizeof(uint16_t)); + check = adler32(check, reinterpret_cast<const uint8_t*>(mBlocks.Elements()), + mBlocks.Length() * sizeof(Block)); + return check; + } + + private: + friend struct IPC::ParamTraits<gfxSparseBitSet>; + friend struct IPC::ParamTraits<gfxSparseBitSet::Block>; + CopyableTArray<uint16_t> mBlockIndex; + CopyableTArray<Block> mBlocks; +}; + +namespace IPC { +template <> +struct ParamTraits<gfxSparseBitSet> { + typedef gfxSparseBitSet paramType; + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mBlockIndex); + WriteParam(aMsg, aParam.mBlocks); + } + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mBlockIndex) && + ReadParam(aMsg, aIter, &aResult->mBlocks); + } +}; + +template <> +struct ParamTraits<gfxSparseBitSet::Block> { + typedef gfxSparseBitSet::Block paramType; + static void Write(Message* aMsg, const paramType& aParam) { + aMsg->WriteBytes(&aParam, sizeof(aParam)); + } + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return aMsg->ReadBytesInto(aIter, aResult, sizeof(*aResult)); + } +}; +} // namespace IPC + +/** + * SharedBitSet is a version of gfxSparseBitSet that is intended to be used + * in a shared-memory block, and can be used regardless of the address at which + * the block has been mapped. The SharedBitSet cannot be modified once it has + * been created. + * + * Max size of a SharedBitSet = 4352 * 32 ; blocks + * + 4352 * 2 ; index + * + 4 ; counts + * = 147972 bytes + * + * Therefore, SharedFontList must be able to allocate a contiguous block of at + * least this size. + */ +class SharedBitSet { + private: + // We use the same Block type as gfxSparseBitSet. + typedef gfxSparseBitSet::Block Block; + + enum { BLOCK_SIZE = gfxSparseBitSet::BLOCK_SIZE }; + enum { BLOCK_SIZE_BITS = gfxSparseBitSet::BLOCK_SIZE_BITS }; + enum { NO_BLOCK = gfxSparseBitSet::NO_BLOCK }; + + public: + static const size_t kMaxSize = 147972; // see above + + // Returns the size needed for a SharedBitSet version of the given + // gfxSparseBitSet. + static size_t RequiredSize(const gfxSparseBitSet& aBitset) { + size_t total = sizeof(SharedBitSet); + size_t len = aBitset.mBlockIndex.Length(); + total += len * sizeof(uint16_t); // add size for index array + // add size for blocks, excluding any missing ones + for (uint16_t i = 0; i < len; i++) { + if (aBitset.mBlockIndex[i] != NO_BLOCK) { + total += sizeof(Block); + } + } + MOZ_ASSERT(total <= kMaxSize); + return total; + } + + // Create a SharedBitSet in the provided buffer, initializing it with the + // contents of aBitset. + static SharedBitSet* Create(void* aBuffer, size_t aBufSize, + const gfxSparseBitSet& aBitset) { + MOZ_ASSERT(aBufSize >= RequiredSize(aBitset)); + return new (aBuffer) SharedBitSet(aBitset); + } + + bool test(uint32_t aIndex) const { + const auto i = static_cast<uint16_t>(aIndex / BLOCK_SIZE_BITS); + if (i >= mBlockIndexCount) { + return false; + } + const uint16_t* const blockIndex = + reinterpret_cast<const uint16_t*>(this + 1); + if (blockIndex[i] == NO_BLOCK) { + return false; + } + const Block* const blocks = + reinterpret_cast<const Block*>(blockIndex + mBlockIndexCount); + const Block& block = blocks[blockIndex[i]]; + return ((block.mBits[(aIndex >> 3) & (BLOCK_SIZE - 1)]) & + (1 << (aIndex & 0x7))) != 0; + } + + bool Equals(const gfxSparseBitSet* aOther) const { + if (mBlockIndexCount != aOther->mBlockIndex.Length()) { + return false; + } + const uint16_t* const blockIndex = + reinterpret_cast<const uint16_t*>(this + 1); + const Block* const blocks = + reinterpret_cast<const Block*>(blockIndex + mBlockIndexCount); + for (uint16_t i = 0; i < mBlockIndexCount; ++i) { + uint16_t index = blockIndex[i]; + uint16_t otherIndex = aOther->mBlockIndex[i]; + if ((index == NO_BLOCK) != (otherIndex == NO_BLOCK)) { + return false; + } + if (index == NO_BLOCK) { + continue; + } + const Block& b1 = blocks[index]; + const Block& b2 = aOther->mBlocks[otherIndex]; + if (memcmp(&b1.mBits, &b2.mBits, BLOCK_SIZE) != 0) { + return false; + } + } + return true; + } + + private: + friend class gfxSparseBitSet; + SharedBitSet() = delete; + + explicit SharedBitSet(const gfxSparseBitSet& aBitset) + : mBlockIndexCount( + mozilla::AssertedCast<uint16_t>(aBitset.mBlockIndex.Length())), + mBlockCount(0) { + uint16_t* blockIndex = reinterpret_cast<uint16_t*>(this + 1); + Block* blocks = reinterpret_cast<Block*>(blockIndex + mBlockIndexCount); + for (uint16_t i = 0; i < mBlockIndexCount; i++) { + if (aBitset.mBlockIndex[i] != NO_BLOCK) { + const Block& srcBlock = aBitset.mBlocks[aBitset.mBlockIndex[i]]; + std::memcpy(&blocks[mBlockCount], &srcBlock, sizeof(Block)); + blockIndex[i] = mBlockCount; + mBlockCount++; + } else { + blockIndex[i] = NO_BLOCK; + } + } + } + + // We never manage SharedBitSet as a "normal" object, it's a view onto a + // buffer of shared memory. So we should never be trying to call this. + ~SharedBitSet() = delete; + + uint16_t mBlockIndexCount; + uint16_t mBlockCount; + + // After the two "header" fields above, we have a block index array + // of uint16_t[mBlockIndexCount], followed by mBlockCount Block records. +}; + +// Union the contents of a SharedBitSet with the target gfxSparseBitSet +inline void gfxSparseBitSet::Union(const SharedBitSet& aBitset) { + // ensure mBlockIndex is large enough + while (mBlockIndex.Length() < aBitset.mBlockIndexCount) { + mBlockIndex.AppendElement(NO_BLOCK); + } + auto blockIndex = reinterpret_cast<const uint16_t*>(&aBitset + 1); + auto blocks = + reinterpret_cast<const Block*>(blockIndex + aBitset.mBlockIndexCount); + for (uint32_t i = 0; i < aBitset.mBlockIndexCount; ++i) { + // if it is missing (implicitly empty) in source, just skip + if (blockIndex[i] == NO_BLOCK) { + continue; + } + // if the block is missing, just copy from source bitset + if (mBlockIndex[i] == NO_BLOCK) { + mBlocks.AppendElement(blocks[blockIndex[i]]); + MOZ_ASSERT(mBlocks.Length() < 0xffff, "block index overflow"); + mBlockIndex[i] = uint16_t(mBlocks.Length() - 1); + continue; + } + // else set existing target block to the union of both + uint32_t* dst = reinterpret_cast<uint32_t*>(&mBlocks[mBlockIndex[i]].mBits); + const uint32_t* src = + reinterpret_cast<const uint32_t*>(&blocks[blockIndex[i]].mBits); + for (uint32_t j = 0; j < BLOCK_SIZE / 4; ++j) { + dst[j] |= src[j]; + } + } +} + +#define TRUETYPE_TAG(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d)) + +namespace mozilla { + +// Byte-swapping types and name table structure definitions moved from +// gfxFontUtils.cpp to .h file so that gfxFont.cpp can also refer to them +#pragma pack(1) + +struct AutoSwap_PRUint16 { +#ifdef __SUNPRO_CC + AutoSwap_PRUint16& operator=(const uint16_t aValue) { + this->value = mozilla::NativeEndian::swapToBigEndian(aValue); + return *this; + } +#else + MOZ_IMPLICIT AutoSwap_PRUint16(uint16_t aValue) { + value = mozilla::NativeEndian::swapToBigEndian(aValue); + } +#endif + operator uint16_t() const { + return mozilla::NativeEndian::swapFromBigEndian(value); + } + + operator uint32_t() const { + return mozilla::NativeEndian::swapFromBigEndian(value); + } + + operator uint64_t() const { + return mozilla::NativeEndian::swapFromBigEndian(value); + } + + private: + uint16_t value; +}; + +struct AutoSwap_PRInt16 { +#ifdef __SUNPRO_CC + AutoSwap_PRInt16& operator=(const int16_t aValue) { + this->value = mozilla::NativeEndian::swapToBigEndian(aValue); + return *this; + } +#else + MOZ_IMPLICIT AutoSwap_PRInt16(int16_t aValue) { + value = mozilla::NativeEndian::swapToBigEndian(aValue); + } +#endif + operator int16_t() const { + return mozilla::NativeEndian::swapFromBigEndian(value); + } + + operator uint32_t() const { + return mozilla::NativeEndian::swapFromBigEndian(value); + } + + private: + int16_t value; +}; + +struct AutoSwap_PRUint32 { +#ifdef __SUNPRO_CC + AutoSwap_PRUint32& operator=(const uint32_t aValue) { + this->value = mozilla::NativeEndian::swapToBigEndian(aValue); + return *this; + } +#else + MOZ_IMPLICIT AutoSwap_PRUint32(uint32_t aValue) { + value = mozilla::NativeEndian::swapToBigEndian(aValue); + } +#endif + operator uint32_t() const { + return mozilla::NativeEndian::swapFromBigEndian(value); + } + + private: + uint32_t value; +}; + +struct AutoSwap_PRInt32 { +#ifdef __SUNPRO_CC + AutoSwap_PRInt32& operator=(const int32_t aValue) { + this->value = mozilla::NativeEndian::swapToBigEndian(aValue); + return *this; + } +#else + MOZ_IMPLICIT AutoSwap_PRInt32(int32_t aValue) { + value = mozilla::NativeEndian::swapToBigEndian(aValue); + } +#endif + operator int32_t() const { + return mozilla::NativeEndian::swapFromBigEndian(value); + } + + private: + int32_t value; +}; + +struct AutoSwap_PRUint64 { +#ifdef __SUNPRO_CC + AutoSwap_PRUint64& operator=(const uint64_t aValue) { + this->value = mozilla::NativeEndian::swapToBigEndian(aValue); + return *this; + } +#else + MOZ_IMPLICIT AutoSwap_PRUint64(uint64_t aValue) { + value = mozilla::NativeEndian::swapToBigEndian(aValue); + } +#endif + operator uint64_t() const { + return mozilla::NativeEndian::swapFromBigEndian(value); + } + + private: + uint64_t value; +}; + +struct AutoSwap_PRUint24 { + operator uint32_t() const { + return value[0] << 16 | value[1] << 8 | value[2]; + } + + private: + AutoSwap_PRUint24() = default; + uint8_t value[3]; +}; + +struct SFNTHeader { + AutoSwap_PRUint32 sfntVersion; // Fixed, 0x00010000 for version 1.0. + AutoSwap_PRUint16 numTables; // Number of tables. + AutoSwap_PRUint16 searchRange; // (Maximum power of 2 <= numTables) x 16. + AutoSwap_PRUint16 entrySelector; // Log2(maximum power of 2 <= numTables). + AutoSwap_PRUint16 rangeShift; // NumTables x 16-searchRange. +}; + +struct TTCHeader { + AutoSwap_PRUint32 ttcTag; // 4 -byte identifier 'ttcf'. + AutoSwap_PRUint16 majorVersion; + AutoSwap_PRUint16 minorVersion; + AutoSwap_PRUint32 numFonts; + // followed by: + // AutoSwap_PRUint32 offsetTable[numFonts] +}; + +struct TableDirEntry { + AutoSwap_PRUint32 tag; // 4 -byte identifier. + AutoSwap_PRUint32 checkSum; // CheckSum for this table. + AutoSwap_PRUint32 offset; // Offset from beginning of TrueType font file. + AutoSwap_PRUint32 length; // Length of this table. +}; + +struct HeadTable { + enum { + HEAD_VERSION = 0x00010000, + HEAD_MAGIC_NUMBER = 0x5F0F3CF5, + HEAD_CHECKSUM_CALC_CONST = 0xB1B0AFBA + }; + + AutoSwap_PRUint32 tableVersionNumber; // Fixed, 0x00010000 for version 1.0. + AutoSwap_PRUint32 fontRevision; // Set by font manufacturer. + AutoSwap_PRUint32 + checkSumAdjustment; // To compute: set it to 0, sum the entire font as + // ULONG, then store 0xB1B0AFBA - sum. + AutoSwap_PRUint32 magicNumber; // Set to 0x5F0F3CF5. + AutoSwap_PRUint16 flags; + AutoSwap_PRUint16 + unitsPerEm; // Valid range is from 16 to 16384. This value should be a + // power of 2 for fonts that have TrueType outlines. + AutoSwap_PRUint64 created; // Number of seconds since 12:00 midnight, January + // 1, 1904. 64-bit integer + AutoSwap_PRUint64 modified; // Number of seconds since 12:00 midnight, + // January 1, 1904. 64-bit integer + AutoSwap_PRInt16 xMin; // For all glyph bounding boxes. + AutoSwap_PRInt16 yMin; // For all glyph bounding boxes. + AutoSwap_PRInt16 xMax; // For all glyph bounding boxes. + AutoSwap_PRInt16 yMax; // For all glyph bounding boxes. + AutoSwap_PRUint16 macStyle; // Bit 0: Bold (if set to 1); + AutoSwap_PRUint16 lowestRecPPEM; // Smallest readable size in pixels. + AutoSwap_PRInt16 fontDirectionHint; + AutoSwap_PRInt16 indexToLocFormat; + AutoSwap_PRInt16 glyphDataFormat; +}; + +struct OS2Table { + AutoSwap_PRUint16 version; // 0004 = OpenType 1.5 + AutoSwap_PRInt16 xAvgCharWidth; + AutoSwap_PRUint16 usWeightClass; + AutoSwap_PRUint16 usWidthClass; + AutoSwap_PRUint16 fsType; + AutoSwap_PRInt16 ySubscriptXSize; + AutoSwap_PRInt16 ySubscriptYSize; + AutoSwap_PRInt16 ySubscriptXOffset; + AutoSwap_PRInt16 ySubscriptYOffset; + AutoSwap_PRInt16 ySuperscriptXSize; + AutoSwap_PRInt16 ySuperscriptYSize; + AutoSwap_PRInt16 ySuperscriptXOffset; + AutoSwap_PRInt16 ySuperscriptYOffset; + AutoSwap_PRInt16 yStrikeoutSize; + AutoSwap_PRInt16 yStrikeoutPosition; + AutoSwap_PRInt16 sFamilyClass; + uint8_t panose[10]; + AutoSwap_PRUint32 unicodeRange1; + AutoSwap_PRUint32 unicodeRange2; + AutoSwap_PRUint32 unicodeRange3; + AutoSwap_PRUint32 unicodeRange4; + uint8_t achVendID[4]; + AutoSwap_PRUint16 fsSelection; + AutoSwap_PRUint16 usFirstCharIndex; + AutoSwap_PRUint16 usLastCharIndex; + AutoSwap_PRInt16 sTypoAscender; + AutoSwap_PRInt16 sTypoDescender; + AutoSwap_PRInt16 sTypoLineGap; + AutoSwap_PRUint16 usWinAscent; + AutoSwap_PRUint16 usWinDescent; + AutoSwap_PRUint32 codePageRange1; + AutoSwap_PRUint32 codePageRange2; + AutoSwap_PRInt16 sxHeight; + AutoSwap_PRInt16 sCapHeight; + AutoSwap_PRUint16 usDefaultChar; + AutoSwap_PRUint16 usBreakChar; + AutoSwap_PRUint16 usMaxContext; +}; + +struct PostTable { + AutoSwap_PRUint32 version; + AutoSwap_PRInt32 italicAngle; + AutoSwap_PRInt16 underlinePosition; + AutoSwap_PRUint16 underlineThickness; + AutoSwap_PRUint32 isFixedPitch; + AutoSwap_PRUint32 minMemType42; + AutoSwap_PRUint32 maxMemType42; + AutoSwap_PRUint32 minMemType1; + AutoSwap_PRUint32 maxMemType1; +}; + +// This structure is used for both 'hhea' and 'vhea' tables. +// The field names here are those of the horizontal version; the +// vertical table just exchanges vertical and horizontal coordinates. +struct MetricsHeader { + AutoSwap_PRUint32 version; + AutoSwap_PRInt16 ascender; + AutoSwap_PRInt16 descender; + AutoSwap_PRInt16 lineGap; + AutoSwap_PRUint16 advanceWidthMax; + AutoSwap_PRInt16 minLeftSideBearing; + AutoSwap_PRInt16 minRightSideBearing; + AutoSwap_PRInt16 xMaxExtent; + AutoSwap_PRInt16 caretSlopeRise; + AutoSwap_PRInt16 caretSlopeRun; + AutoSwap_PRInt16 caretOffset; + AutoSwap_PRInt16 reserved1; + AutoSwap_PRInt16 reserved2; + AutoSwap_PRInt16 reserved3; + AutoSwap_PRInt16 reserved4; + AutoSwap_PRInt16 metricDataFormat; + AutoSwap_PRUint16 numOfLongMetrics; +}; + +struct MaxpTableHeader { + AutoSwap_PRUint32 version; // CFF: 0x00005000; TrueType: 0x00010000 + AutoSwap_PRUint16 numGlyphs; + // truetype version has additional fields that we don't currently use +}; + +// old 'kern' table, supported on Windows +// see http://www.microsoft.com/typography/otspec/kern.htm +struct KernTableVersion0 { + AutoSwap_PRUint16 version; // 0x0000 + AutoSwap_PRUint16 nTables; +}; + +struct KernTableSubtableHeaderVersion0 { + AutoSwap_PRUint16 version; + AutoSwap_PRUint16 length; + AutoSwap_PRUint16 coverage; +}; + +// newer Mac-only 'kern' table, ignored by Windows +// see http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6kern.html +struct KernTableVersion1 { + AutoSwap_PRUint32 version; // 0x00010000 + AutoSwap_PRUint32 nTables; +}; + +struct KernTableSubtableHeaderVersion1 { + AutoSwap_PRUint32 length; + AutoSwap_PRUint16 coverage; + AutoSwap_PRUint16 tupleIndex; +}; + +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 +}; + +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 static_cast<uint16_t>(aBuf[aIndex] << 8) | aBuf[aIndex + 1]; + } + + static inline uint16_t ReadShortAt16(const uint16_t* aBuf, uint32_t aIndex) { + const uint8_t* buf = reinterpret_cast<const uint8_t*>(aBuf); + uint32_t index = aIndex << 1; + return static_cast<uint16_t>(buf[index] << 8) | buf[index + 1]; + } + + static inline uint32_t ReadUint24At(const uint8_t* aBuf, uint32_t aIndex) { + return ((aBuf[aIndex] << 16) | (aBuf[aIndex + 1] << 8) | + (aBuf[aIndex + 2])); + } + + static inline uint32_t ReadLongAt(const uint8_t* aBuf, uint32_t aIndex) { + return ((aBuf[aIndex] << 24) | (aBuf[aIndex + 1] << 16) | + (aBuf[aIndex + 2] << 8) | (aBuf[aIndex + 3])); + } + + static nsresult ReadCMAPTableFormat10(const uint8_t* aBuf, uint32_t aLength, + gfxSparseBitSet& aCharacterMap); + + static nsresult ReadCMAPTableFormat12or13(const uint8_t* aBuf, + uint32_t aLength, + gfxSparseBitSet& aCharacterMap); + + static nsresult ReadCMAPTableFormat4(const uint8_t* aBuf, uint32_t aLength, + gfxSparseBitSet& aCharacterMap); + + static nsresult ReadCMAPTableFormat14(const uint8_t* aBuf, uint32_t aLength, + mozilla::UniquePtr<uint8_t[]>& aTable); + + static uint32_t FindPreferredSubtable(const uint8_t* aBuf, + uint32_t aBufLength, + uint32_t* aTableOffset, + uint32_t* aUVSTableOffset); + + static nsresult ReadCMAP(const uint8_t* aBuf, uint32_t aBufLength, + gfxSparseBitSet& aCharacterMap, + uint32_t& aUVSOffset); + + static uint32_t MapCharToGlyphFormat4(const uint8_t* aBuf, uint32_t aLength, + char16_t aCh); + + static uint32_t MapCharToGlyphFormat10(const uint8_t* aBuf, uint32_t aCh); + + static uint32_t MapCharToGlyphFormat12or13(const uint8_t* aBuf, uint32_t aCh); + + static uint16_t MapUVSToGlyphFormat14(const uint8_t* aBuf, uint32_t aCh, + uint32_t aVS); + + // sCJKCompatSVSTable is a 'cmap' format 14 subtable that maps + // <char + var-selector> pairs to the corresponding Unicode + // compatibility ideograph codepoints. + static MOZ_ALWAYS_INLINE uint32_t GetUVSFallback(uint32_t aCh, uint32_t aVS) { + aCh = MapUVSToGlyphFormat14(sCJKCompatSVSTable, aCh, aVS); + return aCh >= 0xFB00 ? aCh + (0x2F800 - 0xFB00) : aCh; + } + + static uint32_t MapCharToGlyph(const uint8_t* aCmapBuf, uint32_t aBufLength, + uint32_t aUnicode, uint32_t aVarSelector = 0); + +#ifdef XP_WIN + // determine whether a font (which has already been sanitized, so is known + // to be a valid sfnt) is CFF format rather than TrueType + static bool IsCffFont(const uint8_t* aFontData); +#endif + + // determine the format of font data + static gfxUserFontType DetermineFontDataType(const uint8_t* aFontData, + uint32_t aFontDataLength); + + // Read the fullname from the sfnt data (used to save the original name + // prior to renaming the font for installation). + // This is called with sfnt data that has already been validated, + // so it should always succeed in finding the name table. + static nsresult GetFullNameFromSFNT(const uint8_t* aFontData, + uint32_t aLength, nsACString& aFullName); + + // helper to get fullname from name table, constructing from family+style + // if no explicit fullname is present + static nsresult GetFullNameFromTable(hb_blob_t* aNameTable, + nsACString& aFullName); + + // helper to get family name from name table + static nsresult GetFamilyNameFromTable(hb_blob_t* aNameTable, + nsACString& aFamilyName); + + // Find the table directory entry for a given table tag, in a (validated) + // buffer of 'sfnt' data. Returns null if the tag is not present. + static mozilla::TableDirEntry* FindTableDirEntry(const void* aFontData, + uint32_t aTableTag); + + // Return a blob that wraps a table found within a buffer of font data. + // The blob does NOT own its data; caller guarantees that the buffer + // will remain valid at least as long as the blob. + // Returns null if the specified table is not found. + // This method assumes aFontData is valid 'sfnt' data; before using this, + // caller is responsible to do any sanitization/validation necessary. + static hb_blob_t* GetTableFromFontData(const void* aFontData, + uint32_t aTableTag); + + // create a new name table and build a new font with that name table + // appended on the end, returns true on success + static nsresult RenameFont(const nsAString& aName, const uint8_t* aFontData, + uint32_t aFontDataLength, + FallibleTArray<uint8_t>* aNewFont); + + // read all names matching aNameID, returning in aNames array + static nsresult ReadNames(const char* aNameData, uint32_t aDataLen, + uint32_t aNameID, int32_t aPlatformID, + nsTArray<nsCString>& aNames); + + // reads English or first name matching aNameID, returning in aName + // platform based on OS + static nsresult ReadCanonicalName(hb_blob_t* aNameTable, uint32_t aNameID, + nsCString& aName); + + static nsresult ReadCanonicalName(const char* aNameData, uint32_t aDataLen, + uint32_t aNameID, nsCString& aName); + + // convert a name from the raw name table data into an nsString, + // provided we know how; return true if successful, or false + // if we can't handle the encoding + static bool DecodeFontName(const char* aBuf, int32_t aLength, + uint32_t aPlatformCode, uint32_t aScriptCode, + uint32_t aLangCode, nsACString& dest); + + static inline bool IsJoinCauser(uint32_t ch) { return (ch == 0x200D); } + + // We treat Combining Grapheme Joiner (U+034F) together with the join + // controls (ZWJ, ZWNJ) here, because (like them) it is an invisible + // char that will be handled by the shaper even if not explicitly + // supported by the font. (See bug 1408366.) + static inline bool IsJoinControl(uint32_t ch) { + return (ch == 0x200C || ch == 0x200D || ch == 0x034f); + } + + enum { + kUnicodeVS1 = 0xFE00, + kUnicodeVS16 = 0xFE0F, + kUnicodeVS17 = 0xE0100, + kUnicodeVS256 = 0xE01EF + }; + + static inline bool IsVarSelector(uint32_t ch) { + return (ch >= kUnicodeVS1 && ch <= kUnicodeVS16) || + (ch >= kUnicodeVS17 && ch <= kUnicodeVS256); + } + + enum { + kUnicodeRegionalIndicatorA = 0x1F1E6, + kUnicodeRegionalIndicatorZ = 0x1F1FF + }; + + static inline bool IsRegionalIndicator(uint32_t aCh) { + return aCh >= kUnicodeRegionalIndicatorA && + aCh <= kUnicodeRegionalIndicatorZ; + } + + static inline bool IsInvalid(uint32_t ch) { return (ch == 0xFFFD); } + + // Font code may want to know if there is the potential for bidi behavior + // to be triggered by any of the characters in a text run; this can be + // used to test that possibility. + enum { + kUnicodeBidiScriptsStart = 0x0590, + kUnicodeBidiScriptsEnd = 0x08FF, + kUnicodeBidiPresentationStart = 0xFB1D, + kUnicodeBidiPresentationEnd = 0xFEFC, + kUnicodeFirstHighSurrogateBlock = 0xD800, + kUnicodeRLM = 0x200F, + kUnicodeRLE = 0x202B, + kUnicodeRLO = 0x202E + }; + + static inline bool PotentialRTLChar(char16_t aCh) { + if (aCh >= kUnicodeBidiScriptsStart && aCh <= kUnicodeBidiScriptsEnd) + // bidi scripts Hebrew, Arabic, Syriac, Thaana, N'Ko are all encoded + // together + return true; + + if (aCh == kUnicodeRLM || aCh == kUnicodeRLE || aCh == kUnicodeRLO) + // directional controls that trigger bidi layout + return true; + + if (aCh >= kUnicodeBidiPresentationStart && + aCh <= kUnicodeBidiPresentationEnd) + // presentation forms of Arabic and Hebrew letters + return true; + + if ((aCh & 0xFF00) == kUnicodeFirstHighSurrogateBlock) + // surrogate that could be part of a bidi supplementary char + // (Cypriot, Aramaic, Phoenecian, etc) + return true; + + // otherwise we know this char cannot trigger bidi reordering + return false; + } + + // parse a simple list of font family names into + // an array of strings + static void ParseFontList(const nsACString& aFamilyList, + nsTArray<nsCString>& aFontList); + + // for a given font list pref name, append list of font names + static void AppendPrefsFontList(const char* aPrefName, + nsTArray<nsCString>& aFontList, + bool aLocalized = false); + + // for a given font list pref name, initialize a list of font names + static void GetPrefsFontList(const char* aPrefName, + nsTArray<nsCString>& aFontList, + bool aLocalized = false); + + // generate a unique font name + static nsresult MakeUniqueUserFontName(nsAString& aName); + + // 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::DeviceColor& aDefaultColor, + nsTArray<uint16_t>& aGlyphs, + nsTArray<mozilla::gfx::DeviceColor>& aColors); + static bool HasColorLayersForGlyph(hb_blob_t* aCOLR, uint32_t aGlyphId); + + // Helper used to implement gfxFontEntry::GetVariation{Axes,Instances} for + // platforms where the native font APIs don't provide the info we want + // in a convenient form, or when native APIs are too expensive. + // (Not used on platforms -- currently, freetype -- where the font APIs + // expose variation instance details directly.) + static void GetVariationData(gfxFontEntry* aFontEntry, + nsTArray<gfxFontVariationAxis>* aAxes, + nsTArray<gfxFontVariationInstance>* aInstances); + + // Helper method for reading localized family names from the name table + // of a single face. + static void ReadOtherFamilyNamesForFace( + const nsACString& aFamilyName, const char* aNameData, + uint32_t aDataLength, nsTArray<nsCString>& aOtherFamilyNames, + bool useFullName); + + protected: + friend struct MacCharsetMappingComparator; + + static nsresult ReadNames(const char* aNameData, uint32_t aDataLen, + uint32_t aNameID, int32_t aLangID, + int32_t aPlatformID, nsTArray<nsCString>& aNames); + + // convert opentype name-table platform/encoding/language values to an + // Encoding object we can use to convert the name data to unicode + static const mozilla::Encoding* GetCharsetForFontName(uint16_t aPlatform, + uint16_t aScript, + uint16_t aLanguage); + + struct MacFontNameCharsetMapping { + uint16_t mScript; + uint16_t mLanguage; + const mozilla::Encoding* mEncoding; + + bool operator<(const MacFontNameCharsetMapping& rhs) const { + return (mScript < rhs.mScript) || + ((mScript == rhs.mScript) && (mLanguage < rhs.mLanguage)); + } + }; + static const MacFontNameCharsetMapping gMacFontNameCharsets[]; + static const mozilla::Encoding* gISOFontNameCharsets[]; + static const mozilla::Encoding* gMSFontNameCharsets[]; +}; + +// Factors used to weight the distances between the available and target font +// properties during font-matching. These ensure that we respect the CSS-fonts +// requirement that font-stretch >> font-style >> font-weight; and in addition, +// a mismatch between the desired and actual glyph presentation (emoji vs text) +// will take precedence over any of the style attributes. +constexpr double kPresentationMismatch = 1.0e12; +constexpr double kStretchFactor = 1.0e8; +constexpr double kStyleFactor = 1.0e4; +constexpr double kWeightFactor = 1.0e0; + +// style distance ==> [0,500] +static inline double StyleDistance(const mozilla::SlantStyleRange& aRange, + mozilla::FontSlantStyle aTargetStyle) { + const mozilla::FontSlantStyle minStyle = aRange.Min(); + if (aTargetStyle == minStyle) { + return 0.0; // styles match exactly ==> 0 + } + + // bias added to angle difference when searching in the non-preferred + // direction from a target angle + const double kReverse = 100.0; + + // bias added when we've crossed from positive to negative angles or + // vice versa + const double kNegate = 200.0; + + if (aTargetStyle.IsNormal()) { + if (minStyle.IsOblique()) { + // to distinguish oblique 0deg from normal, we add 1.0 to the angle + const double minAngle = minStyle.ObliqueAngle(); + if (minAngle >= 0.0) { + return 1.0 + minAngle; + } + const mozilla::FontSlantStyle maxStyle = aRange.Max(); + const double maxAngle = maxStyle.ObliqueAngle(); + if (maxAngle >= 0.0) { + // [min,max] range includes 0.0, so just return our minimum + return 1.0; + } + // negative oblique is even worse than italic + return kNegate - maxAngle; + } + // must be italic, which is worse than any non-negative oblique; + // treat as a match in the wrong search direction + MOZ_ASSERT(minStyle.IsItalic()); + return kReverse; + } + + const double kDefaultAngle = + mozilla::FontSlantStyle::Oblique().ObliqueAngle(); + + if (aTargetStyle.IsItalic()) { + if (minStyle.IsOblique()) { + const double minAngle = minStyle.ObliqueAngle(); + if (minAngle >= kDefaultAngle) { + return 1.0 + (minAngle - kDefaultAngle); + } + const mozilla::FontSlantStyle maxStyle = aRange.Max(); + const double maxAngle = maxStyle.ObliqueAngle(); + if (maxAngle >= kDefaultAngle) { + return 1.0; + } + if (maxAngle > 0.0) { + // wrong direction but still > 0, add bias of 100 + return kReverse + (kDefaultAngle - maxAngle); + } + // negative oblique angle, add bias of 300 + return kReverse + kNegate + (kDefaultAngle - maxAngle); + } + // normal is worse than oblique > 0, but better than oblique <= 0 + MOZ_ASSERT(minStyle.IsNormal()); + return kNegate; + } + + // target is oblique <angle>: four different cases depending on + // the value of the <angle>, which determines the preferred direction + // of search + const double targetAngle = aTargetStyle.ObliqueAngle(); + if (targetAngle >= kDefaultAngle) { + if (minStyle.IsOblique()) { + const double minAngle = minStyle.ObliqueAngle(); + if (minAngle >= targetAngle) { + return minAngle - targetAngle; + } + const mozilla::FontSlantStyle maxStyle = aRange.Max(); + const double maxAngle = maxStyle.ObliqueAngle(); + if (maxAngle >= targetAngle) { + return 0.0; + } + if (maxAngle > 0.0) { + return kReverse + (targetAngle - maxAngle); + } + return kReverse + kNegate + (targetAngle - maxAngle); + } + if (minStyle.IsItalic()) { + return kReverse + kNegate; + } + return kReverse + kNegate + 1.0; + } + + if (targetAngle <= -kDefaultAngle) { + if (minStyle.IsOblique()) { + const mozilla::FontSlantStyle maxStyle = aRange.Max(); + const double maxAngle = maxStyle.ObliqueAngle(); + if (maxAngle <= targetAngle) { + return targetAngle - maxAngle; + } + const double minAngle = minStyle.ObliqueAngle(); + if (minAngle <= targetAngle) { + return 0.0; + } + if (minAngle < 0.0) { + return kReverse + (minAngle - targetAngle); + } + return kReverse + kNegate + (minAngle - targetAngle); + } + if (minStyle.IsItalic()) { + return kReverse + kNegate; + } + return kReverse + kNegate + 1.0; + } + + if (targetAngle >= 0.0) { + if (minStyle.IsOblique()) { + const double minAngle = minStyle.ObliqueAngle(); + if (minAngle > targetAngle) { + return kReverse + (minAngle - targetAngle); + } + const mozilla::FontSlantStyle maxStyle = aRange.Max(); + const double maxAngle = maxStyle.ObliqueAngle(); + if (maxAngle >= targetAngle) { + return 0.0; + } + if (maxAngle > 0.0) { + return targetAngle - maxAngle; + } + return kReverse + kNegate + (targetAngle - maxAngle); + } + if (minStyle.IsItalic()) { + return kReverse + kNegate - 2.0; + } + return kReverse + kNegate - 1.0; + } + + // last case: (targetAngle < 0.0 && targetAngle > kDefaultAngle) + if (minStyle.IsOblique()) { + const mozilla::FontSlantStyle maxStyle = aRange.Max(); + const double maxAngle = maxStyle.ObliqueAngle(); + if (maxAngle < targetAngle) { + return kReverse + (targetAngle - maxAngle); + } + const double minAngle = minStyle.ObliqueAngle(); + if (minAngle <= targetAngle) { + return 0.0; + } + if (minAngle < 0.0) { + return minAngle - targetAngle; + } + return kReverse + kNegate + (minAngle - targetAngle); + } + if (minStyle.IsItalic()) { + return kReverse + kNegate - 2.0; + } + return kReverse + kNegate - 1.0; +} + +// stretch distance ==> [0,2000] +static inline double StretchDistance(const mozilla::StretchRange& aRange, + mozilla::FontStretch aTargetStretch) { + const double kReverseDistance = 1000.0; + + mozilla::FontStretch minStretch = aRange.Min(); + mozilla::FontStretch maxStretch = aRange.Max(); + + // The stretch value is a (non-negative) percentage; currently we support + // values in the range 0 .. 1000. (If the upper limit is ever increased, + // the kReverseDistance value used here may need to be adjusted.) + // If aTargetStretch is >100, we prefer larger values if available; + // if <=100, we prefer smaller values if available. + if (aTargetStretch < minStretch) { + if (aTargetStretch > mozilla::FontStretch::Normal()) { + return minStretch - aTargetStretch; + } + return (minStretch - aTargetStretch) + kReverseDistance; + } + if (aTargetStretch > maxStretch) { + if (aTargetStretch <= mozilla::FontStretch::Normal()) { + return aTargetStretch - maxStretch; + } + return (aTargetStretch - maxStretch) + kReverseDistance; + } + return 0.0; +} + +// Calculate weight distance with values in the range (0..1000). In general, +// heavier weights match towards even heavier weights while lighter weights +// match towards even lighter weights. Target weight values in the range +// [400..500] are special, since they will first match up to 500, then down +// towards 0, then up again towards 999. +// +// Example: with target 600 and font weight 800, distance will be 200. With +// target 300 and font weight 600, distance will be 900, since heavier +// weights are farther away than lighter weights. If the target is 5 and the +// font weight 995, the distance would be 1590 for the same reason. + +// weight distance ==> [0,1600] +static inline double WeightDistance(const mozilla::WeightRange& aRange, + mozilla::FontWeight aTargetWeight) { + const double kNotWithinCentralRange = 100.0; + const double kReverseDistance = 600.0; + + mozilla::FontWeight minWeight = aRange.Min(); + mozilla::FontWeight maxWeight = aRange.Max(); + + if (aTargetWeight >= minWeight && aTargetWeight <= maxWeight) { + // Target is within the face's range, so it's a perfect match + return 0.0; + } + + if (aTargetWeight < mozilla::FontWeight(400)) { + // Requested a lighter-than-400 weight + if (maxWeight < aTargetWeight) { + return aTargetWeight - maxWeight; + } + // Add reverse-search penalty for bolder faces + return (minWeight - aTargetWeight) + kReverseDistance; + } + + if (aTargetWeight > mozilla::FontWeight(500)) { + // Requested a bolder-than-500 weight + if (minWeight > aTargetWeight) { + return minWeight - aTargetWeight; + } + // Add reverse-search penalty for lighter faces + return (aTargetWeight - maxWeight) + kReverseDistance; + } + + // Special case for requested weight in the [400..500] range + if (minWeight > aTargetWeight) { + if (minWeight <= mozilla::FontWeight(500)) { + // Bolder weight up to 500 is first choice + return minWeight - aTargetWeight; + } + // Other bolder weights get a reverse-search penalty + return (minWeight - aTargetWeight) + kReverseDistance; + } + // Lighter weights are not as good as bolder ones within [400..500] + return (aTargetWeight - maxWeight) + kNotWithinCentralRange; +} + +#endif /* GFX_FONT_UTILS_H */ diff --git a/gfx/thebes/gfxFontVariations.h b/gfx/thebes/gfxFontVariations.h new file mode 100644 index 0000000000..e589ffc8cb --- /dev/null +++ b/gfx/thebes/gfxFontVariations.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FONT_VARIATIONS_H +#define GFX_FONT_VARIATIONS_H + +#include "mozilla/gfx/FontVariation.h" +#include "nsString.h" +#include "nsTArray.h" + +typedef mozilla::gfx::FontVariation gfxFontVariation; + +// Structure that describes a single axis of variation in an +// OpenType Variation or Multiple-Master font. +struct gfxFontVariationAxis { + uint32_t mTag; + nsCString mName; // may be empty + float mMinValue; + float mMaxValue; + float mDefaultValue; +}; + +// A single <axis, value> pair that may be applied to a variation font. +struct gfxFontVariationValue { + uint32_t mAxis; + float mValue; +}; + +// Structure that describes a named instance of a variation font: +// a name like "Light Condensed" or "Black Ultra Extended" etc., +// and a list of the corresponding <variation-axis, value> pairs +// to be used. +struct gfxFontVariationInstance { + nsCString mName; + CopyableTArray<gfxFontVariationValue> mValues; +}; + +#endif diff --git a/gfx/thebes/gfxGDIFont.cpp b/gfx/thebes/gfxGDIFont.cpp new file mode 100644 index 0000000000..4d6574cadb --- /dev/null +++ b/gfx/thebes/gfxGDIFont.cpp @@ -0,0 +1,497 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxGDIFont.h" + +#include "mozilla/MemoryReporting.h" +#include "mozilla/Sprintf.h" +#include "mozilla/WindowsVersion.h" + +#include <algorithm> +#include "gfxWindowsPlatform.h" +#include "gfxContext.h" +#include "mozilla/Preferences.h" +#include "nsUnicodeProperties.h" +#include "gfxFontConstants.h" +#include "gfxTextRun.h" + +#include "cairo-win32.h" + +#define ROUND(x) floor((x) + 0.5) + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::unicode; + +gfxGDIFont::gfxGDIFont(GDIFontEntry* aFontEntry, const gfxFontStyle* aFontStyle, + AntialiasOption anAAOption) + : gfxFont(nullptr, aFontEntry, aFontStyle, anAAOption), + mFont(nullptr), + mMetrics(nullptr), + mIsBitmap(false), + mScriptCache(nullptr) { + mNeedsSyntheticBold = aFontStyle->NeedsSyntheticBold(aFontEntry); + + Initialize(); + + if (mFont) { + mUnscaledFont = aFontEntry->LookupUnscaledFont(mFont); + } +} + +gfxGDIFont::~gfxGDIFont() { + if (mFont) { + ::DeleteObject(mFont); + } + if (mScriptCache) { + ScriptFreeCache(&mScriptCache); + } + delete mMetrics; +} + +UniquePtr<gfxFont> gfxGDIFont::CopyWithAntialiasOption( + AntialiasOption anAAOption) { + auto entry = static_cast<GDIFontEntry*>(mFontEntry.get()); + return MakeUnique<gfxGDIFont>(entry, &mStyle, anAAOption); +} + +bool gfxGDIFont::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, + RoundingFlags aRounding, + gfxShapedText* aShapedText) { + if (!mIsValid) { + NS_WARNING("invalid font! expect incorrect text rendering"); + return false; + } + + return gfxFont::ShapeText(aDrawTarget, aText, aOffset, aLength, aScript, + aLanguage, aVertical, aRounding, aShapedText); +} + +const gfxFont::Metrics& gfxGDIFont::GetHorizontalMetrics() { return *mMetrics; } + +already_AddRefed<ScaledFont> gfxGDIFont::GetScaledFont(DrawTarget* aTarget) { + if (!mAzureScaledFont) { + LOGFONT lf; + GetObject(GetHFONT(), sizeof(LOGFONT), &lf); + + mAzureScaledFont = Factory::CreateScaledFontForGDIFont( + &lf, GetUnscaledFont(), GetAdjustedSize()); + InitializeScaledFont(); + } + + RefPtr<ScaledFont> scaledFont(mAzureScaledFont); + return scaledFont.forget(); +} + +gfxFont::RunMetrics gfxGDIFont::Measure(const gfxTextRun* aTextRun, + uint32_t aStart, uint32_t aEnd, + BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, + Spacing* aSpacing, + gfx::ShapedTextFlags aOrientation) { + gfxFont::RunMetrics metrics = + gfxFont::Measure(aTextRun, aStart, aEnd, aBoundingBoxType, aRefDrawTarget, + aSpacing, aOrientation); + + // if aBoundingBoxType is LOOSE_INK_EXTENTS + // and the underlying cairo font may be antialiased, + // we can't trust Windows to have considered all the pixels + // so we need to add "padding" to the bounds. + // (see bugs 475968, 439831, compare also bug 445087) + if (aBoundingBoxType == LOOSE_INK_EXTENTS && + mAntialiasOption != kAntialiasNone && metrics.mBoundingBox.Width() > 0) { + metrics.mBoundingBox.MoveByX(-aTextRun->GetAppUnitsPerDevUnit()); + metrics.mBoundingBox.SetWidth(metrics.mBoundingBox.Width() + + aTextRun->GetAppUnitsPerDevUnit() * 3); + } + + return metrics; +} + +void gfxGDIFont::Initialize() { + NS_ASSERTION(!mMetrics, "re-creating metrics? this will leak"); + + LOGFONTW logFont; + + if (mAdjustedSize == 0.0) { + mAdjustedSize = mStyle.size; + if (mStyle.sizeAdjust > 0.0 && mAdjustedSize > 0.0) { + // to implement font-size-adjust, we first create the "unadjusted" font + FillLogFont(logFont, mAdjustedSize); + mFont = ::CreateFontIndirectW(&logFont); + + // initialize its metrics so we can calculate size adjustment + Initialize(); + + // Unless the font was so small that GDI metrics rounded to zero, + // calculate the properly adjusted size, and then proceed + // to recreate mFont and recalculate metrics + if (mMetrics->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 (mNeedsSyntheticBold && GetFontEntry()->IsLocalUserFont()) { + mApplySyntheticBold = true; + } + + // this may end up being zero + mAdjustedSize = ROUND(mAdjustedSize); + FillLogFont(logFont, mAdjustedSize); + mFont = ::CreateFontIndirectW(&logFont); + + mMetrics = new gfxFont::Metrics; + ::memset(mMetrics, 0, sizeof(*mMetrics)); + + if (!mFont) { + NS_WARNING("Failed creating GDI font"); + mIsValid = false; + return; + } + + AutoDC dc; + SetGraphicsMode(dc.GetDC(), GM_ADVANCED); + AutoSelectFont selectFont(dc.GetDC(), mFont); + + // Get font metrics if size > 0 + if (mAdjustedSize > 0.0) { + OUTLINETEXTMETRIC oMetrics; + TEXTMETRIC& metrics = oMetrics.otmTextMetrics; + + if (0 < GetOutlineTextMetrics(dc.GetDC(), sizeof(oMetrics), &oMetrics)) { + mMetrics->strikeoutSize = (double)oMetrics.otmsStrikeoutSize; + mMetrics->strikeoutOffset = (double)oMetrics.otmsStrikeoutPosition; + mMetrics->underlineSize = (double)oMetrics.otmsUnderscoreSize; + mMetrics->underlineOffset = (double)oMetrics.otmsUnderscorePosition; + + const MAT2 kIdentityMatrix = {{0, 1}, {0, 0}, {0, 0}, {0, 1}}; + GLYPHMETRICS gm; + DWORD len = GetGlyphOutlineW(dc.GetDC(), char16_t('x'), GGO_METRICS, &gm, + 0, nullptr, &kIdentityMatrix); + if (len == GDI_ERROR || gm.gmptGlyphOrigin.y <= 0) { + // 56% of ascent, best guess for true type + mMetrics->xHeight = + ROUND((double)metrics.tmAscent * DEFAULT_XHEIGHT_FACTOR); + } else { + mMetrics->xHeight = gm.gmptGlyphOrigin.y; + } + len = GetGlyphOutlineW(dc.GetDC(), char16_t('H'), GGO_METRICS, &gm, 0, + nullptr, &kIdentityMatrix); + if (len == GDI_ERROR || gm.gmptGlyphOrigin.y <= 0) { + mMetrics->capHeight = metrics.tmAscent - metrics.tmInternalLeading; + } else { + mMetrics->capHeight = gm.gmptGlyphOrigin.y; + } + mMetrics->emHeight = metrics.tmHeight - metrics.tmInternalLeading; + gfxFloat typEmHeight = + (double)oMetrics.otmAscent - (double)oMetrics.otmDescent; + mMetrics->emAscent = + ROUND(mMetrics->emHeight * (double)oMetrics.otmAscent / typEmHeight); + mMetrics->emDescent = mMetrics->emHeight - mMetrics->emAscent; + if (oMetrics.otmEMSquare > 0) { + mFUnitsConvFactor = float(mAdjustedSize / oMetrics.otmEMSquare); + } + } else { + // Make a best-effort guess at extended metrics + // this is based on general typographic guidelines + + // GetTextMetrics can fail if the font file has been removed + // or corrupted recently. + BOOL result = GetTextMetrics(dc.GetDC(), &metrics); + if (!result) { + NS_WARNING("Missing or corrupt font data, fasten your seatbelt"); + mIsValid = false; + memset(mMetrics, 0, sizeof(*mMetrics)); + return; + } + + mMetrics->xHeight = + ROUND((float)metrics.tmAscent * DEFAULT_XHEIGHT_FACTOR); + mMetrics->strikeoutSize = 1; + mMetrics->strikeoutOffset = + ROUND(mMetrics->xHeight * 0.5f); // 50% of xHeight + mMetrics->underlineSize = 1; + mMetrics->underlineOffset = + -ROUND((float)metrics.tmDescent * 0.30f); // 30% of descent + mMetrics->emHeight = metrics.tmHeight - metrics.tmInternalLeading; + mMetrics->emAscent = metrics.tmAscent - metrics.tmInternalLeading; + mMetrics->emDescent = metrics.tmDescent; + mMetrics->capHeight = mMetrics->emAscent; + } + + mMetrics->internalLeading = metrics.tmInternalLeading; + mMetrics->externalLeading = metrics.tmExternalLeading; + mMetrics->maxHeight = metrics.tmHeight; + mMetrics->maxAscent = metrics.tmAscent; + mMetrics->maxDescent = metrics.tmDescent; + mMetrics->maxAdvance = metrics.tmMaxCharWidth; + mMetrics->aveCharWidth = std::max<gfxFloat>(1, metrics.tmAveCharWidth); + // The font is monospace when TMPF_FIXED_PITCH is *not* set! + // See http://msdn2.microsoft.com/en-us/library/ms534202(VS.85).aspx + if (!(metrics.tmPitchAndFamily & TMPF_FIXED_PITCH)) { + mMetrics->maxAdvance = mMetrics->aveCharWidth; + } + + mIsBitmap = !(metrics.tmPitchAndFamily & TMPF_VECTOR); + + // For fonts with USE_TYPO_METRICS set in the fsSelection field, + // let the OS/2 sTypo* metrics override the previous values. + // (see http://www.microsoft.com/typography/otspec/os2.htm#fss) + // Using the equivalent values from oMetrics provides inconsistent + // results with CFF fonts, so we instead rely on OS2Table. + gfxFontEntry::AutoTable os2Table(mFontEntry, + TRUETYPE_TAG('O', 'S', '/', '2')); + if (os2Table) { + uint32_t len; + const OS2Table* os2 = + reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len)); + if (len >= offsetof(OS2Table, sTypoLineGap) + sizeof(int16_t)) { + const uint16_t kUseTypoMetricsMask = 1 << 7; + if ((uint16_t(os2->fsSelection) & kUseTypoMetricsMask)) { + double ascent = int16_t(os2->sTypoAscender); + double descent = int16_t(os2->sTypoDescender); + double lineGap = int16_t(os2->sTypoLineGap); + mMetrics->maxAscent = ROUND(ascent * mFUnitsConvFactor); + mMetrics->maxDescent = -ROUND(descent * mFUnitsConvFactor); + mMetrics->maxHeight = mMetrics->maxAscent + mMetrics->maxDescent; + mMetrics->internalLeading = mMetrics->maxHeight - mMetrics->emHeight; + gfxFloat lineHeight = + ROUND((ascent - descent + lineGap) * mFUnitsConvFactor); + lineHeight = std::max(lineHeight, mMetrics->maxHeight); + mMetrics->externalLeading = lineHeight - mMetrics->maxHeight; + } + } + // although sxHeight and sCapHeight are signed fields, we consider + // negative values to be erroneous and just ignore them + if (uint16_t(os2->version) >= 2) { + // version 2 and later includes the x-height and cap-height fields + if (len >= offsetof(OS2Table, sxHeight) + sizeof(int16_t) && + int16_t(os2->sxHeight) > 0) { + mMetrics->xHeight = ROUND(int16_t(os2->sxHeight) * mFUnitsConvFactor); + } + if (len >= offsetof(OS2Table, sCapHeight) + sizeof(int16_t) && + int16_t(os2->sCapHeight) > 0) { + mMetrics->capHeight = + ROUND(int16_t(os2->sCapHeight) * mFUnitsConvFactor); + } + } + } + + WORD glyph; + SIZE size; + DWORD ret = GetGlyphIndicesW(dc.GetDC(), L" ", 1, &glyph, + GGI_MARK_NONEXISTING_GLYPHS); + if (ret != GDI_ERROR && glyph != 0xFFFF) { + mSpaceGlyph = glyph; + // Cache the width of a single space. + GetTextExtentPoint32W(dc.GetDC(), L" ", 1, &size); + mMetrics->spaceWidth = ROUND(size.cx); + } else { + mMetrics->spaceWidth = mMetrics->aveCharWidth; + } + + // Cache the width of digit zero, if available. + ret = GetGlyphIndicesW(dc.GetDC(), L"0", 1, &glyph, + GGI_MARK_NONEXISTING_GLYPHS); + if (ret != GDI_ERROR && glyph != 0xFFFF) { + GetTextExtentPoint32W(dc.GetDC(), L"0", 1, &size); + mMetrics->zeroWidth = ROUND(size.cx); + } else { + mMetrics->zeroWidth = -1.0; // indicates not found + } + + SanitizeMetrics(mMetrics, GetFontEntry()->mIsBadUnderlineFont); + } else { + mFUnitsConvFactor = 0.0; // zero-sized font: all values scale to zero + } + + if (IsSyntheticBold()) { + mMetrics->aveCharWidth += GetSyntheticBoldOffset(); + mMetrics->maxAdvance += GetSyntheticBoldOffset(); + } + +#if 0 + printf("Font: %p (%s) size: %f adjusted size: %f valid: %s\n", this, + GetName().get(), mStyle.size, mAdjustedSize, (mIsValid ? "yes" : "no")); + printf(" emHeight: %f emAscent: %f emDescent: %f\n", mMetrics->emHeight, mMetrics->emAscent, mMetrics->emDescent); + printf(" maxAscent: %f maxDescent: %f maxAdvance: %f\n", mMetrics->maxAscent, mMetrics->maxDescent, mMetrics->maxAdvance); + printf(" internalLeading: %f externalLeading: %f\n", mMetrics->internalLeading, mMetrics->externalLeading); + printf(" spaceWidth: %f aveCharWidth: %f\n", mMetrics->spaceWidth, mMetrics->aveCharWidth); + printf(" xHeight: %f capHeight: %f\n", mMetrics->xHeight, mMetrics->capHeight); + printf(" uOff: %f uSize: %f stOff: %f stSize: %f\n", + mMetrics->underlineOffset, mMetrics->underlineSize, mMetrics->strikeoutOffset, mMetrics->strikeoutSize); +#endif +} + +void gfxGDIFont::FillLogFont(LOGFONTW& aLogFont, gfxFloat aSize) { + GDIFontEntry* fe = static_cast<GDIFontEntry*>(GetFontEntry()); + + // Figure out the lfWeight value to use for GDI font selection, + // or zero to use the entry's current LOGFONT value. + LONG weight; + if (fe->IsUserFont()) { + if (fe->IsLocalUserFont()) { + // for local user fonts, don't change the original weight + // in the entry's logfont, because that could alter the + // choice of actual face used (bug 724231) + weight = 0; + } else { + // avoid GDI synthetic bold which occurs when weight + // specified is >= font data weight + 200 + weight = mNeedsSyntheticBold ? 700 : 200; + } + } else { + // GDI doesn't support variation fonts, so for system fonts we know + // that the entry has only a single weight, not a range. + MOZ_ASSERT(fe->Weight().IsSingle()); + weight = mNeedsSyntheticBold ? 700 : fe->Weight().Min().ToIntRounded(); + } + + fe->FillLogFont(&aLogFont, weight, aSize); +} + +uint32_t gfxGDIFont::GetGlyph(uint32_t aUnicode, uint32_t aVarSelector) { + // Callback used only for fonts that lack a 'cmap' table. + + // We don't support variation selector sequences or non-BMP characters + // in the legacy bitmap, vector or postscript fonts that might use + // this code path. + if (aUnicode > 0xffff || aVarSelector) { + return 0; + } + + if (!mGlyphIDs) { + mGlyphIDs = MakeUnique<nsDataHashtable<nsUint32HashKey, uint32_t>>(64); + } + + uint32_t gid; + if (mGlyphIDs->Get(aUnicode, &gid)) { + return gid; + } + + wchar_t ch = aUnicode; + WORD glyph; + DWORD ret = ScriptGetCMap(nullptr, &mScriptCache, &ch, 1, 0, &glyph); + if (ret != S_OK) { + AutoDC dc; + AutoSelectFont fs(dc.GetDC(), GetHFONT()); + if (ret == E_PENDING) { + // Try ScriptGetCMap again now that we've set up the font. + ret = ScriptGetCMap(dc.GetDC(), &mScriptCache, &ch, 1, 0, &glyph); + } + if (ret != S_OK) { + // If ScriptGetCMap still failed, fall back to GetGlyphIndicesW + // (see bug 1105807). + ret = GetGlyphIndicesW(dc.GetDC(), &ch, 1, &glyph, + GGI_MARK_NONEXISTING_GLYPHS); + if (ret == GDI_ERROR || glyph == 0xFFFF) { + glyph = 0; + } + } + } + + mGlyphIDs->Put(aUnicode, glyph); + return glyph; +} + +int32_t gfxGDIFont::GetGlyphWidth(uint16_t aGID) { + if (!mGlyphWidths) { + mGlyphWidths = MakeUnique<nsDataHashtable<nsUint32HashKey, int32_t>>(128); + } + + int32_t width; + if (mGlyphWidths->Get(aGID, &width)) { + return width; + } + + DCForMetrics dc; + 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; +} + +bool gfxGDIFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) { + DCForMetrics dc; + AutoSelectFont fs(dc, GetHFONT()); + + if (mIsBitmap) { + int devWidth; + if (!GetCharWidthI(dc, aGID, 1, nullptr, &devWidth)) { + return false; + } + devWidth = std::min(std::max(0, devWidth), 0x7fff); + + *aBounds = gfxRect(0, -mMetrics->maxAscent, devWidth, + mMetrics->maxAscent + mMetrics->maxDescent); + return true; + } + + const MAT2 kIdentityMatrix = {{0, 1}, {0, 0}, {0, 0}, {0, 1}}; + GLYPHMETRICS gm; + if (GetGlyphOutlineW(dc, aGID, GGO_METRICS | GGO_GLYPH_INDEX, &gm, 0, nullptr, + &kIdentityMatrix) == GDI_ERROR) { + return false; + } + + if (gm.gmBlackBoxX == 1 && gm.gmBlackBoxY == 1 && + !GetGlyphOutlineW(dc, aGID, GGO_NATIVE | GGO_GLYPH_INDEX, &gm, 0, nullptr, + &kIdentityMatrix)) { + // Workaround for GetGlyphOutline returning 1x1 bounding box + // for <space> glyph that is in fact empty. + gm.gmBlackBoxX = 0; + gm.gmBlackBoxY = 0; + } else if (gm.gmBlackBoxX > 0 && !aTight) { + // The bounding box reported by Windows supposedly contains the glyph's + // "black" area; however, antialiasing (especially with ClearType) means + // that the actual image that needs to be rendered may "bleed" into the + // adjacent pixels, mainly on the right side. + gm.gmptGlyphOrigin.x -= 1; + gm.gmBlackBoxX += 3; + } + + *aBounds = gfxRect(gm.gmptGlyphOrigin.x, -gm.gmptGlyphOrigin.y, + gm.gmBlackBoxX, gm.gmBlackBoxY); + return true; +} + +void gfxGDIFont::AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const { + gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); + aSizes->mFontInstances += aMallocSizeOf(mMetrics); + if (mGlyphWidths) { + aSizes->mFontInstances += + mGlyphWidths->ShallowSizeOfIncludingThis(aMallocSizeOf); + } +} + +void gfxGDIFont::AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const { + aSizes->mFontInstances += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} diff --git a/gfx/thebes/gfxGDIFont.h b/gfx/thebes/gfxGDIFont.h new file mode 100644 index 0000000000..b5ccb8940f --- /dev/null +++ b/gfx/thebes/gfxGDIFont.h @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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 "usp10.h" + +class gfxGDIFont : public gfxFont { + public: + gfxGDIFont(GDIFontEntry* aFontEntry, const gfxFontStyle* aFontStyle, + AntialiasOption anAAOption = kAntialiasDefault); + + virtual ~gfxGDIFont(); + + HFONT GetHFONT() { return mFont; } + + already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont( + DrawTarget* aTarget) override; + + /* override Measure to add padding for antialiasing */ + RunMetrics Measure(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd, + BoundingBoxType aBoundingBoxType, + DrawTarget* aDrawTargetForTightBoundingBox, + Spacing* aSpacing, + mozilla::gfx::ShapedTextFlags aOrientation) override; + + /* required for MathML to suppress effects of ClearType "padding" */ + mozilla::UniquePtr<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. + bool ProvidesGetGlyph() const override { return !mFontEntry->HasCmapTable(); } + + uint32_t GetGlyph(uint32_t aUnicode, uint32_t aVarSelector) override; + + bool ProvidesGlyphWidths() const override { return true; } + + // get hinted glyph width in pixels as 16.16 fixed-point value + int32_t GetGlyphWidth(uint16_t aGID) override; + + bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override; + + void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const; + void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const; + + FontType GetType() const override { return FONT_TYPE_GDI; } + + protected: + const Metrics& GetHorizontalMetrics() override; + + bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, RoundingFlags aRounding, + gfxShapedText* aShapedText) override; + + void Initialize(); // creates metrics and Cairo fonts + + // Fill the given LOGFONT record according to our size. + // (Synthetic italic is *not* handled here, because GDI may not reliably + // use the face we expect if we tweak the lfItalic field, and because we + // have generic support for this in gfxFont::Draw instead.) + void FillLogFont(LOGFONTW& aLogFont, gfxFloat aSize); + + HFONT mFont; + + Metrics* mMetrics; + bool mIsBitmap; + + bool mNeedsSyntheticBold; + + // cache of glyph IDs (used for non-sfnt fonts only) + mozilla::UniquePtr<nsDataHashtable<nsUint32HashKey, uint32_t> > mGlyphIDs; + SCRIPT_CACHE mScriptCache; + + // cache of glyph widths in 16.16 fixed-point pixels + mozilla::UniquePtr<nsDataHashtable<nsUint32HashKey, int32_t> > mGlyphWidths; +}; + +#endif /* GFX_GDIFONT_H */ diff --git a/gfx/thebes/gfxGDIFontList.cpp b/gfx/thebes/gfxGDIFontList.cpp new file mode 100644 index 0000000000..c7e54d623b --- /dev/null +++ b/gfx/thebes/gfxGDIFontList.cpp @@ -0,0 +1,1099 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/DebugOnly.h" +#include <algorithm> + +#include "mozilla/Logging.h" +#include "mozilla/TextUtils.h" +#include "mozilla/Sprintf.h" + +#include "gfxGDIFontList.h" +#include "gfxWindowsPlatform.h" +#include "gfxUserFontSet.h" +#include "gfxFontUtils.h" +#include "gfxGDIFont.h" + +#include "nsServiceManagerUtils.h" +#include "nsTArray.h" +#include "nsUnicharUtils.h" + +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsAppDirectoryServiceDefs.h" +#include "gfxFontConstants.h" +#include "GeckoProfiler.h" + +#include "mozilla/MemoryReporting.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/Telemetry.h" + +#include <usp10.h> + +using namespace mozilla; +using namespace mozilla::gfx; + +#define ROUND(x) floor((x) + 0.5) + +#define LOG_FONTLIST(args) \ + MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug, args) +#define LOG_FONTLIST_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug) + +#define LOG_CMAPDATA_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_cmapdata), LogLevel::Debug) + +static __inline void BuildKeyNameFromFontName(nsAString& aName) { + if (aName.Length() >= LF_FACESIZE) aName.Truncate(LF_FACESIZE - 1); + ToLowerCase(aName); +} + +// Implementation of gfxPlatformFontList for Win32 GDI, +// using GDI font enumeration APIs to get the list of fonts + +class WinUserFontData : public gfxUserFontData { + public: + explicit WinUserFontData(HANDLE aFontRef) : mFontRef(aFontRef) {} + + virtual ~WinUserFontData() { + DebugOnly<BOOL> success; + success = RemoveFontMemResourceEx(mFontRef); +#if DEBUG + if (!success) { + char buf[256]; + SprintfLiteral( + buf, + "error deleting font handle (%p) - RemoveFontMemResourceEx failed", + mFontRef); + NS_ASSERTION(success, buf); + } +#endif + } + + HANDLE mFontRef; +}; + +BYTE FontTypeToOutPrecision(uint8_t fontType) { + BYTE ret; + switch (fontType) { + case GFX_FONT_TYPE_TT_OPENTYPE: + case GFX_FONT_TYPE_TRUETYPE: + ret = OUT_TT_ONLY_PRECIS; + break; + case GFX_FONT_TYPE_PS_OPENTYPE: + ret = OUT_PS_ONLY_PRECIS; + break; + case GFX_FONT_TYPE_TYPE1: + ret = OUT_OUTLINE_PRECIS; + break; + case GFX_FONT_TYPE_RASTER: + ret = OUT_RASTER_PRECIS; + break; + case GFX_FONT_TYPE_DEVICE: + ret = OUT_DEVICE_PRECIS; + break; + default: + ret = OUT_DEFAULT_PRECIS; + } + return ret; +} + +/*************************************************************** + * + * GDIFontEntry + * + */ + +GDIFontEntry::GDIFontEntry(const nsACString& aFaceName, + gfxWindowsFontType aFontType, SlantStyleRange aStyle, + WeightRange aWeight, StretchRange aStretch, + gfxUserFontData* aUserFontData) + : gfxFontEntry(aFaceName), + mFontType(aFontType), + mForceGDI(false), + mUnicodeRanges() { + mUserFontData.reset(aUserFontData); + mStyleRange = aStyle; + mWeightRange = aWeight; + mStretchRange = aStretch; + if (IsType1()) mForceGDI = true; + mIsDataUserFont = aUserFontData != nullptr; + + InitLogFont(aFaceName, aFontType); +} + +gfxFontEntry* GDIFontEntry::Clone() const { + MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!"); + return new GDIFontEntry(Name(), mFontType, SlantStyle(), Weight(), Stretch(), + nullptr); +} + +nsresult GDIFontEntry::ReadCMAP(FontInfoData* aFontInfoData) { + AUTO_PROFILER_LABEL("GDIFontEntry::ReadCMAP", OTHER); + + // attempt this once, if errors occur leave a blank cmap + if (mCharacterMap) { + return NS_OK; + } + + // skip non-SFNT fonts completely + if (mFontType != GFX_FONT_TYPE_PS_OPENTYPE && + mFontType != GFX_FONT_TYPE_TT_OPENTYPE && + mFontType != GFX_FONT_TYPE_TRUETYPE) { + mCharacterMap = new gfxCharacterMap(); + mCharacterMap->mBuildOnTheFly = true; + return NS_ERROR_FAILURE; + } + + RefPtr<gfxCharacterMap> charmap; + nsresult rv; + + if (aFontInfoData && + (charmap = GetCMAPFromFontInfo(aFontInfoData, mUVSOffset))) { + rv = NS_OK; + } else { + uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p'); + charmap = new gfxCharacterMap(); + AutoTArray<uint8_t, 16384> cmap; + rv = CopyFontTable(kCMAP, cmap); + + if (NS_SUCCEEDED(rv)) { + rv = gfxFontUtils::ReadCMAP(cmap.Elements(), cmap.Length(), *charmap, + mUVSOffset); + } + } + + 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", + mName.get(), charmap->SizeOfIncludingThis(moz_malloc_size_of), + charmap->mHash, mCharacterMap == charmap ? " new" : "")); + if (LOG_CMAPDATA_ENABLED()) { + char prefix[256]; + SprintfLiteral(prefix, "(cmapdata) name: %.220s", mName.get()); + charmap->Dump(prefix, eGfxLog_cmapdata); + } + + return rv; +} + +gfxFont* GDIFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle) { + return new gfxGDIFont(this, aFontStyle); +} + +nsresult GDIFontEntry::CopyFontTable(uint32_t aTableTag, + nsTArray<uint8_t>& aBuffer) { + if (!IsTrueType()) { + return NS_ERROR_FAILURE; + } + + AutoDC dc; + AutoSelectFont font(dc.GetDC(), &mLogFont); + if (font.IsValid()) { + uint32_t tableSize = ::GetFontData( + dc.GetDC(), NativeEndian::swapToBigEndian(aTableTag), 0, nullptr, 0); + if (tableSize != GDI_ERROR) { + if (aBuffer.SetLength(tableSize, fallible)) { + ::GetFontData(dc.GetDC(), NativeEndian::swapToBigEndian(aTableTag), 0, + aBuffer.Elements(), tableSize); + return NS_OK; + } + return NS_ERROR_OUT_OF_MEMORY; + } + } + return NS_ERROR_FAILURE; +} + +already_AddRefed<UnscaledFontGDI> GDIFontEntry::LookupUnscaledFont( + HFONT aFont) { + RefPtr<UnscaledFontGDI> unscaledFont(mUnscaledFont); + if (!unscaledFont) { + LOGFONT lf; + GetObject(aFont, sizeof(LOGFONT), &lf); + unscaledFont = new UnscaledFontGDI(lf); + mUnscaledFont = unscaledFont; + } + + return unscaledFont.forget(); +} + +void GDIFontEntry::FillLogFont(LOGFONTW* aLogFont, LONG aWeight, + gfxFloat aSize) { + memcpy(aLogFont, &mLogFont, sizeof(LOGFONTW)); + + aLogFont->lfHeight = (LONG)-ROUND(aSize); + + if (aLogFont->lfHeight == 0) { + aLogFont->lfHeight = -1; + } + + // If a non-zero weight is passed in, use this to override the original + // weight in the entry's logfont. This is used to control synthetic bolding + // for installed families with no bold face, and for downloaded fonts + // (but NOT for local user fonts, because it could cause a different, + // glyph-incompatible face to be used) + if (aWeight != 0) { + aLogFont->lfWeight = aWeight; + } + + // for non-local() user fonts, we never want to apply italics here; + // if the face is described as italic, we should use it as-is, + // and if it's not, but then the element is styled italic, we'll use + // a cairo transform to create fake italic (oblique) + if (mIsDataUserFont) { + aLogFont->lfItalic = 0; + } +} + +#define MISSING_GLYPH \ + 0x1F // glyph index returned for missing characters + // on WinXP with .fon fonts, but not Type1 (.pfb) + +bool GDIFontEntry::TestCharacterMap(uint32_t aCh) { + if (!mCharacterMap) { + ReadCMAP(); + NS_ASSERTION(mCharacterMap, "failed to initialize a character map"); + } + + if (mCharacterMap->mBuildOnTheFly) { + if (aCh > 0xFFFF) return false; + + // previous code was using the group style + gfxFontStyle fakeStyle; + if (!IsUpright()) { + fakeStyle.style = FontSlantStyle::Italic(); + } + fakeStyle.weight = Weight().Min(); + + RefPtr<gfxFont> tempFont = FindOrMakeFont(&fakeStyle, nullptr); + if (!tempFont || !tempFont->Valid()) return false; + gfxGDIFont* font = static_cast<gfxGDIFont*>(tempFont.get()); + + HDC dc = GetDC((HWND) nullptr); + SetGraphicsMode(dc, GM_ADVANCED); + HFONT hfont = font->GetHFONT(); + HFONT oldFont = (HFONT)SelectObject(dc, hfont); + + wchar_t str[1] = {(wchar_t)aCh}; + WORD glyph[1]; + + bool hasGlyph = false; + + // Bug 573038 - in some cases GetGlyphIndicesW returns 0xFFFF for a + // missing glyph or 0x1F in other cases to indicate the "invalid" + // glyph. Map both cases to "not found" + if (IsType1() || mForceGDI) { + // Type1 fonts and uniscribe APIs don't get along. + // ScriptGetCMap will return E_HANDLE + DWORD ret = + GetGlyphIndicesW(dc, str, 1, glyph, GGI_MARK_NONEXISTING_GLYPHS); + if (ret != GDI_ERROR && glyph[0] != 0xFFFF && + (IsType1() || glyph[0] != MISSING_GLYPH)) { + hasGlyph = true; + } + } else { + // ScriptGetCMap works better than GetGlyphIndicesW + // for things like bitmap/vector fonts + SCRIPT_CACHE sc = nullptr; + HRESULT rv = ScriptGetCMap(dc, &sc, str, 1, 0, glyph); + if (rv == S_OK) hasGlyph = true; + } + + SelectObject(dc, oldFont); + ReleaseDC(nullptr, dc); + + if (hasGlyph) { + mCharacterMap->set(aCh); + return true; + } + } else { + // font had a cmap so simply check that + return mCharacterMap->test(aCh); + } + + return false; +} + +void GDIFontEntry::InitLogFont(const nsACString& aName, + gfxWindowsFontType aFontType) { +#define CLIP_TURNOFF_FONTASSOCIATION 0x40 + + mLogFont.lfHeight = -1; + + // Fill in logFont structure + mLogFont.lfWidth = 0; + mLogFont.lfEscapement = 0; + mLogFont.lfOrientation = 0; + mLogFont.lfUnderline = FALSE; + mLogFont.lfStrikeOut = FALSE; + mLogFont.lfCharSet = DEFAULT_CHARSET; + mLogFont.lfOutPrecision = FontTypeToOutPrecision(aFontType); + mLogFont.lfClipPrecision = CLIP_TURNOFF_FONTASSOCIATION; + mLogFont.lfQuality = DEFAULT_QUALITY; + mLogFont.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE; + // always force lfItalic if we want it. Font selection code will + // do its best to give us an italic font entry, but if no face exists + // it may give us a regular one based on weight. Windows should + // do fake italic for us in that case. + mLogFont.lfItalic = !IsUpright(); + mLogFont.lfWeight = Weight().Min().ToIntRounded(); + + NS_ConvertUTF8toUTF16 name(aName); + int len = std::min<int>(name.Length(), LF_FACESIZE - 1); + memcpy(&mLogFont.lfFaceName, name.BeginReading(), len * sizeof(char16_t)); + mLogFont.lfFaceName[len] = '\0'; +} + +GDIFontEntry* GDIFontEntry::CreateFontEntry(const nsACString& aName, + gfxWindowsFontType aFontType, + SlantStyleRange aStyle, + WeightRange aWeight, + StretchRange aStretch, + gfxUserFontData* aUserFontData) { + // jtdfix - need to set charset, unicode ranges, pitch/family + + GDIFontEntry* fe = new GDIFontEntry(aName, aFontType, aStyle, aWeight, + aStretch, aUserFontData); + + return fe; +} + +void GDIFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +/*************************************************************** + * + * GDIFontFamily + * + */ + +static bool ShouldIgnoreItalicStyle(const nsACString& aName) { + // Ignore italic style's "Meiryo" because "Meiryo (Bold) Italic" has + // non-italic style glyphs as Japanese characters. However, using it + // causes serious problem if web pages wants some elements to be + // different style from others only with font-style. For example, + // <em> and <i> should be rendered as italic in the default style. + return aName.EqualsLiteral("Meiryo") || + aName.EqualsLiteral( + "\xe3\x83\xa1\xe3\x82\xa4\xe3\x83\xaa\xe3\x82\xaa"); +} + +int CALLBACK GDIFontFamily::FamilyAddStylesProc( + const ENUMLOGFONTEXW* lpelfe, const NEWTEXTMETRICEXW* nmetrics, + DWORD fontType, LPARAM data) { + const NEWTEXTMETRICW& metrics = nmetrics->ntmTm; + LOGFONTW logFont = lpelfe->elfLogFont; + GDIFontFamily* ff = reinterpret_cast<GDIFontFamily*>(data); + + if (logFont.lfItalic && ShouldIgnoreItalicStyle(ff->mName)) { + return 1; + } + + // Some fonts claim to support things > 900, but we don't so clamp the sizes + logFont.lfWeight = clamped(logFont.lfWeight, LONG(100), LONG(900)); + + gfxWindowsFontType feType = + GDIFontEntry::DetermineFontType(metrics, fontType); + + GDIFontEntry* fe = nullptr; + for (uint32_t i = 0; i < ff->mAvailableFonts.Length(); ++i) { + fe = static_cast<GDIFontEntry*>(ff->mAvailableFonts[i].get()); + if (feType > fe->mFontType) { + // if the new type is better than the old one, remove the old entries + ff->mAvailableFonts.RemoveElementAt(i); + --i; + } else if (feType < fe->mFontType) { + // otherwise if the new type is worse, skip it + return 1; + } + } + + for (uint32_t i = 0; i < ff->mAvailableFonts.Length(); ++i) { + fe = static_cast<GDIFontEntry*>(ff->mAvailableFonts[i].get()); + // check if we already know about this face + if (fe->Weight().Min() == FontWeight(int32_t(logFont.lfWeight)) && + fe->IsItalic() == (logFont.lfItalic == 0xFF)) { + // update the charset bit here since this could be different + // XXX Can we still do this now that we store mCharset + // on the font family rather than the font entry? + ff->mCharset.set(metrics.tmCharSet); + return 1; + } + } + + // We can't set the hasItalicFace flag correctly here, + // because we might not have seen the family's italic face(s) yet. + // So we'll set that flag for all members after loading all the faces. + auto italicStyle = (logFont.lfItalic == 0xFF ? FontSlantStyle::Italic() + : FontSlantStyle::Normal()); + fe = GDIFontEntry::CreateFontEntry( + NS_ConvertUTF16toUTF8(lpelfe->elfFullName), feType, + SlantStyleRange(italicStyle), + WeightRange(FontWeight(int32_t(logFont.lfWeight))), + StretchRange(FontStretch::Normal()), nullptr); + if (!fe) return 1; + + ff->AddFontEntry(fe); + + 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: normal", + fe->Name().get(), ff->Name().get(), + (logFont.lfItalic == 0xff) ? "italic" : "normal", logFont.lfWeight)); + } + 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; + NS_ConvertUTF8toUTF16 name(mName); + uint32_t l = std::min<uint32_t>(name.Length(), LF_FACESIZE - 1); + memcpy(logFont.lfFaceName, name.get(), l * sizeof(char16_t)); + + EnumFontFamiliesExW(hdc, &logFont, + (FONTENUMPROCW)GDIFontFamily::FamilyAddStylesProc, + (LPARAM)this, 0); + if (LOG_FONTLIST_ENABLED() && mAvailableFonts.Length() == 0) { + LOG_FONTLIST( + ("(fontlist) no styles available in family \"%s\"", mName.get())); + } + + ReleaseDC(nullptr, hdc); + + if (mIsBadUnderlineFamily) { + SetBadUnderlineFonts(); + } + + CheckForSimpleFamily(); +} + +/*************************************************************** + * + * gfxGDIFontList + * + */ + +gfxGDIFontList::gfxGDIFontList() : mFontSubstitutes(32) { +#ifdef MOZ_BUNDLED_FONTS + if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) { + 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; + NS_ConvertUTF16toUTF8 substitute(substituteName); + NS_ConvertUTF16toUTF8 actual(actualFontName); + if (!actual.IsEmpty() && (ff = mFontFamilies.GetWeak(actual))) { + mFontSubstitutes.Put(substitute, RefPtr{ff}); + } else { + mNonExistingFonts.AppendElement(substitute); + } + } + + // "Courier" on a default Windows install is an ugly bitmap font. + // If there is no substitution for Courier in the registry + // substitute "Courier" with "Courier New". + nsAutoString substituteName; + substituteName.AssignLiteral("Courier"); + BuildKeyNameFromFontName(substituteName); + NS_ConvertUTF16toUTF8 substitute(substituteName); + if (!mFontSubstitutes.GetWeak(substitute)) { + gfxFontFamily* ff; + nsAutoString actualFontName; + actualFontName.AssignLiteral("Courier New"); + BuildKeyNameFromFontName(actualFontName); + NS_ConvertUTF16toUTF8 actual(actualFontName); + ff = mFontFamilies.GetWeak(actual); + if (ff) { + mFontSubstitutes.Put(substitute, RefPtr{ff}); + } + } + return NS_OK; +} + +nsresult gfxGDIFontList::InitFontListForPlatform() { + Telemetry::AutoTimer<Telemetry::GDI_INITFONTLIST_TOTAL> timer; + + mFontSubstitutes.Clear(); + mNonExistingFonts.Clear(); + + // iterate over available families + LOGFONTW logfont; + memset(&logfont, 0, sizeof(logfont)); + logfont.lfCharSet = DEFAULT_CHARSET; + + AutoDC hdc; + (void)EnumFontFamiliesExW(hdc.GetDC(), &logfont, + (FONTENUMPROCW)&EnumFontFamExProc, 0, 0); + + GetFontSubstitutes(); + + GetPrefsAndStartLoader(); + + return NS_OK; +} + +int CALLBACK gfxGDIFontList::EnumFontFamExProc(ENUMLOGFONTEXW* lpelfe, + NEWTEXTMETRICEXW* lpntme, + DWORD fontType, LPARAM lParam) { + const NEWTEXTMETRICW& metrics = lpntme->ntmTm; + const LOGFONTW& lf = lpelfe->elfLogFont; + + if (lf.lfFaceName[0] == '@') { + return 1; + } + + nsAutoString name(lf.lfFaceName); + BuildKeyNameFromFontName(name); + + NS_ConvertUTF16toUTF8 key(name); + + gfxGDIFontList* fontList = PlatformFontList(); + + if (!fontList->mFontFamilies.GetWeak(key)) { + NS_ConvertUTF16toUTF8 faceName(lf.lfFaceName); + FontVisibility visibility = FontVisibility::Unknown; // TODO + RefPtr<GDIFontFamily> family = new GDIFontFamily(faceName, visibility); + fontList->mFontFamilies.Put(key, RefPtr{family}); + + // if locale is such that CJK font names are the default coming from + // GDI, then if a family name is non-ASCII immediately read in other + // family names. This assures that MS Gothic, MS Mincho are all found + // before lookups begin. + if (!IsAscii(faceName)) { + family->ReadOtherFamilyNames(gfxPlatformFontList::PlatformFontList()); + } + + if (fontList->mBadUnderlineFamilyNames.ContainsSorted(key)) { + family->SetBadUnderlineFamily(); + } + + family->mWindowsFamily = lf.lfPitchAndFamily & 0xF0; + family->mWindowsPitch = lf.lfPitchAndFamily & 0x0F; + + // mark the charset bit + family->mCharset.set(metrics.tmCharSet); + } + + return 1; +} + +gfxFontEntry* gfxGDIFontList::LookupLocalFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) { + 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->SlantStyle(), lookup->Weight(), aStretchForEntry, nullptr); + + if (!fe) return nullptr; + + fe->mIsLocalUserFont = true; + + // make the new font entry match the userfont entry style characteristics + fe->mWeightRange = aWeightForEntry; + fe->mStyleRange = aStyleForEntry; + fe->mStretchRange = aStretchForEntry; + + return fe; +} + +// If aFontData contains only a MS/Symbol cmap subtable, not MS/Unicode, +// we modify the subtable header to mark it as Unicode instead, because +// otherwise GDI will refuse to load the font. +// NOTE that this function does not bounds-check every access to the font data. +// This is OK because we only use it on data that has already been validated +// by OTS, and therefore we will not hit out-of-bounds accesses here. +static bool FixupSymbolEncodedFont(uint8_t* aFontData, uint32_t aLength) { + struct CmapHeader { + AutoSwap_PRUint16 version; + AutoSwap_PRUint16 numTables; + }; + struct CmapEncodingRecord { + AutoSwap_PRUint16 platformID; + AutoSwap_PRUint16 encodingID; + AutoSwap_PRUint32 offset; + }; + const uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p'); + const TableDirEntry* dir = gfxFontUtils::FindTableDirEntry(aFontData, kCMAP); + if (dir && uint32_t(dir->length) >= sizeof(CmapHeader)) { + CmapHeader* cmap = + reinterpret_cast<CmapHeader*>(aFontData + uint32_t(dir->offset)); + CmapEncodingRecord* encRec = + reinterpret_cast<CmapEncodingRecord*>(cmap + 1); + int32_t symbolSubtable = -1; + for (uint32_t i = 0; i < (uint16_t)cmap->numTables; ++i) { + if (uint16_t(encRec[i].platformID) != + gfxFontUtils::PLATFORM_ID_MICROSOFT) { + continue; // only interested in MS platform + } + if (uint16_t(encRec[i].encodingID) == + gfxFontUtils::ENCODING_ID_MICROSOFT_UNICODEBMP) { + // We've got a Microsoft/Unicode table, so don't interfere. + symbolSubtable = -1; + break; + } + if (uint16_t(encRec[i].encodingID) == + gfxFontUtils::ENCODING_ID_MICROSOFT_SYMBOL) { + // Found a symbol subtable; remember it for possible fixup, + // but if we subsequently find a Microsoft/Unicode subtable, + // we'll cancel this. + symbolSubtable = i; + } + } + if (symbolSubtable != -1) { + // We found a windows/symbol cmap table, and no windows/unicode one; + // change the encoding ID so that AddFontMemResourceEx will accept it + encRec[symbolSubtable].encodingID = + gfxFontUtils::ENCODING_ID_MICROSOFT_UNICODEBMP; + return true; + } + } + return false; +} + +gfxFontEntry* gfxGDIFontList::MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, + uint32_t aLength) { + // MakePlatformFont is responsible for deleting the font data with free + // so we set up a stack object to ensure it is freed even if we take an + // early exit + struct FontDataDeleter { + explicit FontDataDeleter(const uint8_t* aFontData) : mFontData(aFontData) {} + ~FontDataDeleter() { free((void*)mFontData); } + const uint8_t* mFontData; + }; + FontDataDeleter autoDelete(aFontData); + + bool isCFF = gfxFontUtils::IsCffFont(aFontData); + + nsresult rv; + HANDLE fontRef = nullptr; + + nsAutoString uniqueName; + rv = gfxFontUtils::MakeUniqueUserFontName(uniqueName); + if (NS_FAILED(rv)) return nullptr; + + FallibleTArray<uint8_t> newFontData; + + rv = gfxFontUtils::RenameFont(uniqueName, aFontData, aLength, &newFontData); + + if (NS_FAILED(rv)) return nullptr; + + DWORD numFonts = 0; + + uint8_t* fontData = reinterpret_cast<uint8_t*>(newFontData.Elements()); + uint32_t fontLength = newFontData.Length(); + NS_ASSERTION(fontData, "null font data after renaming"); + + // http://msdn.microsoft.com/en-us/library/ms533942(VS.85).aspx + // "A font that is added by AddFontMemResourceEx is always private + // to the process that made the call and is not enumerable." + fontRef = + AddFontMemResourceEx(fontData, fontLength, 0 /* reserved */, &numFonts); + if (!fontRef) { + if (FixupSymbolEncodedFont(fontData, fontLength)) { + fontRef = AddFontMemResourceEx(fontData, fontLength, 0, &numFonts); + } + } + if (!fontRef) { + return nullptr; + } + + // only load fonts with a single face contained in the data + // AddFontMemResourceEx generates an additional face name for + // vertical text if the font supports vertical writing but since + // the font is referenced via the name this can be ignored + if (fontRef && numFonts > 2) { + RemoveFontMemResourceEx(fontRef); + return nullptr; + } + + // make a new font entry using the unique name + WinUserFontData* winUserFontData = new WinUserFontData(fontRef); + GDIFontEntry* fe = GDIFontEntry::CreateFontEntry( + NS_ConvertUTF16toUTF8(uniqueName), + gfxWindowsFontType(isCFF ? GFX_FONT_TYPE_PS_OPENTYPE + : GFX_FONT_TYPE_TRUETYPE) /*type*/, + aStyleForEntry, aWeightForEntry, aStretchForEntry, winUserFontData); + + if (fe) { + fe->mIsDataUserFont = true; + } + + return fe; +} + +bool gfxGDIFontList::FindAndAddFamilies(StyleGenericFontFamily aGeneric, + const nsACString& aFamily, + nsTArray<FamilyAndGeneric>* aOutput, + FindFamiliesFlags aFlags, + gfxFontStyle* aStyle, nsAtom* aLanguage, + gfxFloat aDevToCssSize) { + NS_ConvertUTF8toUTF16 key16(aFamily); + BuildKeyNameFromFontName(key16); + NS_ConvertUTF16toUTF8 keyName(key16); + + gfxFontFamily* ff = mFontSubstitutes.GetWeak(keyName); + if (ff) { + aOutput->AppendElement(FamilyAndGeneric(ff, aGeneric)); + return true; + } + + if (mNonExistingFonts.Contains(keyName)) { + return false; + } + + return gfxPlatformFontList::FindAndAddFamilies( + aGeneric, aFamily, aOutput, aFlags, aStyle, aLanguage, aDevToCssSize); +} + +FontFamily gfxGDIFontList::GetDefaultFontForPlatform(const gfxFontStyle* aStyle, + nsAtom* aLanguage) { + FontFamily ff; + + // this really shouldn't fail to find a font.... + NONCLIENTMETRICSW ncm; + ncm.cbSize = sizeof(ncm); + BOOL status = + ::SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0); + if (status) { + ff = FindFamily(NS_ConvertUTF16toUTF8(ncm.lfMessageFont.lfFaceName)); + if (!ff.IsNull()) { + return ff; + } + } + + // ...but just in case, try another (long-deprecated) approach as well + HGDIOBJ hGDI = ::GetStockObject(DEFAULT_GUI_FONT); + LOGFONTW logFont; + if (hGDI && ::GetObjectW(hGDI, sizeof(logFont), &logFont)) { + ff = FindFamily(NS_ConvertUTF16toUTF8(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() = default; + + virtual void Load() { + mHdc = GetDC(nullptr); + SetGraphicsMode(mHdc, GM_ADVANCED); + FontInfoData::Load(); + ReleaseDC(nullptr, mHdc); + } + + // loads font data for all members of a given family + virtual void LoadFontFamilyData(const nsACString& aFamilyName); + + // callback for GDI EnumFontFamiliesExW call + static int CALLBACK EnumerateFontsForFamily(const ENUMLOGFONTEXW* lpelfe, + const NEWTEXTMETRICEXW* nmetrics, + DWORD fontType, LPARAM data); + + HDC mHdc; +}; + +struct EnumerateFontsForFamilyData { + EnumerateFontsForFamilyData(const nsACString& aFamilyName, + GDIFontInfo& aFontInfo) + : mFamilyName(aFamilyName), mFontInfo(aFontInfo) {} + + nsCString mFamilyName; + nsTArray<nsCString> mOtherFamilyNames; + GDIFontInfo& mFontInfo; + nsCString mPreviousFontName; +}; + +int CALLBACK GDIFontInfo::EnumerateFontsForFamily( + const ENUMLOGFONTEXW* lpelfe, const NEWTEXTMETRICEXW* nmetrics, + DWORD fontType, LPARAM data) { + EnumerateFontsForFamilyData* famData = + reinterpret_cast<EnumerateFontsForFamilyData*>(data); + HDC hdc = famData->mFontInfo.mHdc; + LOGFONTW logFont = lpelfe->elfLogFont; + const NEWTEXTMETRICW& metrics = nmetrics->ntmTm; + + AutoSelectFont font(hdc, &logFont); + if (!font.IsValid()) { + return 1; + } + + FontFaceData fontData; + NS_ConvertUTF16toUTF8 fontName(lpelfe->elfFullName); + + // callback called for each style-charset so return if style already seen + if (fontName.Equals(famData->mPreviousFontName)) { + return 1; + } + famData->mPreviousFontName = fontName; + famData->mFontInfo.mLoadStats.fonts++; + + // read name table info + bool nameDataLoaded = false; + if (famData->mFontInfo.mLoadFaceNames || famData->mFontInfo.mLoadOtherNames) { + uint32_t kNAME = + NativeEndian::swapToBigEndian(TRUETYPE_TAG('n', 'a', 'm', 'e')); + uint32_t nameSize; + AutoTArray<uint8_t, 1024> nameData; + + nameSize = ::GetFontData(hdc, kNAME, 0, nullptr, 0); + if (nameSize != GDI_ERROR && nameSize > 0 && + nameData.SetLength(nameSize, fallible)) { + ::GetFontData(hdc, kNAME, 0, nameData.Elements(), nameSize); + + // face names + if (famData->mFontInfo.mLoadFaceNames) { + gfxFontUtils::ReadCanonicalName((const char*)(nameData.Elements()), + nameSize, gfxFontUtils::NAME_ID_FULL, + fontData.mFullName); + gfxFontUtils::ReadCanonicalName( + (const char*)(nameData.Elements()), nameSize, + gfxFontUtils::NAME_ID_POSTSCRIPT, fontData.mPostscriptName); + nameDataLoaded = true; + famData->mFontInfo.mLoadStats.facenames++; + } + + // other family names + if (famData->mFontInfo.mLoadOtherNames) { + gfxFontUtils::ReadOtherFamilyNamesForFace( + famData->mFamilyName, (const char*)(nameData.Elements()), nameSize, + famData->mOtherFamilyNames, false); + } + } + } + + // read cmap + bool cmapLoaded = false; + gfxWindowsFontType feType = + GDIFontEntry::DetermineFontType(metrics, fontType); + if (famData->mFontInfo.mLoadCmaps && (feType == GFX_FONT_TYPE_PS_OPENTYPE || + feType == GFX_FONT_TYPE_TT_OPENTYPE || + feType == GFX_FONT_TYPE_TRUETYPE)) { + uint32_t kCMAP = + NativeEndian::swapToBigEndian(TRUETYPE_TAG('c', 'm', 'a', 'p')); + uint32_t cmapSize; + AutoTArray<uint8_t, 1024> cmapData; + + cmapSize = ::GetFontData(hdc, kCMAP, 0, nullptr, 0); + if (cmapSize != GDI_ERROR && cmapSize > 0 && + cmapData.SetLength(cmapSize, fallible)) { + ::GetFontData(hdc, kCMAP, 0, cmapData.Elements(), cmapSize); + bool cmapLoaded = false; + RefPtr<gfxCharacterMap> charmap = new gfxCharacterMap(); + uint32_t offset; + + if (NS_SUCCEEDED(gfxFontUtils::ReadCMAP(cmapData.Elements(), cmapSize, + *charmap, offset))) { + fontData.mCharacterMap = charmap; + fontData.mUVSOffset = offset; + cmapLoaded = true; + famData->mFontInfo.mLoadStats.cmaps++; + } + } + } + + if (cmapLoaded || nameDataLoaded) { + famData->mFontInfo.mFontFaceData.Put(fontName, fontData); + } + + return famData->mFontInfo.mCanceled ? 0 : 1; +} + +void GDIFontInfo::LoadFontFamilyData(const nsACString& aFamilyName) { + // iterate over the family + LOGFONTW logFont; + memset(&logFont, 0, sizeof(LOGFONTW)); + logFont.lfCharSet = DEFAULT_CHARSET; + logFont.lfPitchAndFamily = 0; + NS_ConvertUTF8toUTF16 name(aFamilyName); + uint32_t l = std::min<uint32_t>(name.Length(), LF_FACESIZE - 1); + memcpy(logFont.lfFaceName, name.BeginReading(), l * sizeof(char16_t)); + + EnumerateFontsForFamilyData data(aFamilyName, *this); + + EnumFontFamiliesExW(mHdc, &logFont, + (FONTENUMPROCW)GDIFontInfo::EnumerateFontsForFamily, + (LPARAM)(&data), 0); + + // if found other names, insert them + if (data.mOtherFamilyNames.Length() != 0) { + mOtherFamilyNames.Put(aFamilyName, data.mOtherFamilyNames); + mLoadStats.othernames += data.mOtherFamilyNames.Length(); + } +} + +already_AddRefed<FontInfoData> gfxGDIFontList::CreateFontInfoData() { + bool loadCmaps = !UsesSystemFallback() || + gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback(); + + RefPtr<GDIFontInfo> fi = + new GDIFontInfo(true, NeedFullnamePostscriptNames(), loadCmaps); + + return fi.forget(); +} + +gfxFontFamily* gfxGDIFontList::CreateFontFamily( + const nsACString& aName, FontVisibility aVisibility) const { + return new GDIFontFamily(aName, aVisibility); +} + +#ifdef MOZ_BUNDLED_FONTS + +void gfxGDIFontList::ActivateBundledFonts() { + nsCOMPtr<nsIFile> localDir; + nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(localDir)); + if (NS_FAILED(rv)) { + return; + } + if (NS_FAILED(localDir->Append(u"fonts"_ns))) { + return; + } + bool isDir; + if (NS_FAILED(localDir->IsDirectory(&isDir)) || !isDir) { + return; + } + + nsCOMPtr<nsIDirectoryEnumerator> e; + rv = localDir->GetDirectoryEntries(getter_AddRefs(e)); + if (NS_FAILED(rv)) { + return; + } + + nsCOMPtr<nsIFile> file; + while (NS_SUCCEEDED(e->GetNextFile(getter_AddRefs(file))) && file) { + nsAutoString path; + if (NS_FAILED(file->GetPath(path))) { + continue; + } + AddFontResourceExW(path.get(), FR_PRIVATE, nullptr); + } +} + +#endif diff --git a/gfx/thebes/gfxGDIFontList.h b/gfx/thebes/gfxGDIFontList.h new file mode 100644 index 0000000000..70a2357e17 --- /dev/null +++ b/gfx/thebes/gfxGDIFontList.h @@ -0,0 +1,356 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_GDIFONTLIST_H +#define GFX_GDIFONTLIST_H + +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/MemoryReporting.h" +#include "gfxWindowsPlatform.h" +#include "gfxPlatformFontList.h" +#include "nsGkAtoms.h" +#include "mozilla/gfx/UnscaledFontGDI.h" + +#include <windows.h> + +class AutoDC // get the global device context, and auto-release it on + // destruction +{ + public: + AutoDC() { mDC = ::GetDC(nullptr); } + + ~AutoDC() { ::ReleaseDC(nullptr, mDC); } + + HDC GetDC() { return mDC; } + + private: + HDC mDC; +}; + +class AutoSelectFont // select a font into the given DC, and auto-restore +{ + public: + AutoSelectFont(HDC aDC, LOGFONTW* aLogFont) : mOwnsFont(false) { + mFont = ::CreateFontIndirectW(aLogFont); + if (mFont) { + mOwnsFont = true; + mDC = aDC; + mOldFont = (HFONT)::SelectObject(aDC, mFont); + } else { + mOldFont = nullptr; + } + } + + AutoSelectFont(HDC aDC, HFONT aFont) : mOwnsFont(false) { + mDC = aDC; + mFont = aFont; + mOldFont = (HFONT)::SelectObject(aDC, aFont); + } + + ~AutoSelectFont() { + if (mOldFont) { + ::SelectObject(mDC, mOldFont); + if (mOwnsFont) { + ::DeleteObject(mFont); + } + } + } + + bool IsValid() const { return mFont != nullptr; } + + HFONT GetFont() const { return mFont; } + + private: + HDC mDC; + HFONT mFont; + HFONT mOldFont; + bool mOwnsFont; +}; + +/** + * List of different types of fonts we support on Windows. + * These can generally be lumped in to 3 categories where we have to + * do special things: Really old fonts bitmap and vector fonts (device + * and raster), Type 1 fonts, and TrueType/OpenType fonts. + * + * This list is sorted in order from least prefered to most prefered. + * We prefer Type1 fonts over OpenType fonts to avoid falling back to + * things like Arial (opentype) when you ask for Helvetica (type1) + **/ +enum gfxWindowsFontType { + GFX_FONT_TYPE_UNKNOWN = 0, + GFX_FONT_TYPE_DEVICE, + GFX_FONT_TYPE_RASTER, + GFX_FONT_TYPE_TRUETYPE, + GFX_FONT_TYPE_PS_OPENTYPE, + GFX_FONT_TYPE_TT_OPENTYPE, + GFX_FONT_TYPE_TYPE1 +}; + +// A single member of a font family (i.e. a single face, such as Times Italic) +// represented as a LOGFONT that will resolve to the correct face. +// This replaces FontEntry from gfxWindowsFonts.h/cpp. +class GDIFontEntry final : public gfxFontEntry { + public: + LPLOGFONTW GetLogFont() { return &mLogFont; } + + nsresult ReadCMAP(FontInfoData* aFontInfoData = nullptr) override; + + void FillLogFont(LOGFONTW* aLogFont, LONG aWeight, gfxFloat aSize); + + static gfxWindowsFontType DetermineFontType(const NEWTEXTMETRICW& metrics, + DWORD fontType) { + gfxWindowsFontType feType; + if (metrics.ntmFlags & NTM_TYPE1) + feType = GFX_FONT_TYPE_TYPE1; + else if (metrics.ntmFlags & NTM_PS_OPENTYPE) + feType = GFX_FONT_TYPE_PS_OPENTYPE; + else if (metrics.ntmFlags & NTM_TT_OPENTYPE) + feType = GFX_FONT_TYPE_TT_OPENTYPE; + else if (fontType == TRUETYPE_FONTTYPE) + feType = GFX_FONT_TYPE_TRUETYPE; + else if (fontType == RASTER_FONTTYPE) + feType = GFX_FONT_TYPE_RASTER; + else if (fontType == DEVICE_FONTTYPE) + feType = GFX_FONT_TYPE_DEVICE; + else + feType = GFX_FONT_TYPE_UNKNOWN; + + return feType; + } + + bool IsType1() const { return (mFontType == GFX_FONT_TYPE_TYPE1); } + + bool IsTrueType() const { + return (mFontType == GFX_FONT_TYPE_TRUETYPE || + mFontType == GFX_FONT_TYPE_PS_OPENTYPE || + mFontType == GFX_FONT_TYPE_TT_OPENTYPE); + } + + 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; + + gfxFontEntry* Clone() const override; + + // GDI backend doesn't support font variations: + bool HasVariations() override { return false; } + void GetVariationAxes(nsTArray<gfxFontVariationAxis>&) override {} + void GetVariationInstances(nsTArray<gfxFontVariationInstance>&) override {} + + // create a font entry for a font with a given name + static GDIFontEntry* CreateFontEntry(const nsACString& aName, + gfxWindowsFontType aFontType, + SlantStyleRange aStyle, + WeightRange aWeight, + StretchRange aStretch, + gfxUserFontData* aUserFontData); + + gfxWindowsFontType mFontType; + bool mForceGDI; + + gfxSparseBitSet mUnicodeRanges; + + protected: + friend class gfxGDIFont; + + GDIFontEntry(const nsACString& aFaceName, gfxWindowsFontType aFontType, + SlantStyleRange aStyle, WeightRange aWeight, + StretchRange aStretch, gfxUserFontData* aUserFontData); + + void InitLogFont(const nsACString& aName, gfxWindowsFontType aFontType); + + gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle) override; + + virtual nsresult CopyFontTable(uint32_t aTableTag, + nsTArray<uint8_t>& aBuffer) override; + + already_AddRefed<mozilla::gfx::UnscaledFontGDI> LookupUnscaledFont( + HFONT aFont); + + LOGFONTW mLogFont; + + mozilla::ThreadSafeWeakPtr<mozilla::gfx::UnscaledFontGDI> mUnscaledFont; +}; + +// a single font family, referencing one or more faces +class GDIFontFamily final : public gfxFontFamily { + public: + GDIFontFamily(const nsACString& aName, FontVisibility aVisibility) + : gfxFontFamily(aName, aVisibility), + mWindowsFamily(0), + mWindowsPitch(0), + mCharset() {} + + virtual void FindStyleVariations(FontInfoData* aFontInfoData = nullptr); + + bool FilterForFontList(nsAtom* aLangGroup, + const nsACString& aGeneric) const final { + return !IsSymbolFontFamily() && SupportsLangGroup(aLangGroup) && + MatchesGenericFamily(aGeneric); + } + + protected: + friend class gfxGDIFontList; + + // helpers for FilterForFontList + bool IsSymbolFontFamily() const { return mCharset.test(SYMBOL_CHARSET); } + + bool MatchesGenericFamily(const nsACString& aGeneric) const { + if (aGeneric.IsEmpty()) { + return true; + } + + // Japanese 'Mincho' fonts do not belong to FF_MODERN even if + // they are fixed pitch because they have variable stroke width. + if (mWindowsFamily == FF_ROMAN && mWindowsPitch & FIXED_PITCH) { + return aGeneric.EqualsLiteral("monospace"); + } + + // Japanese 'Gothic' fonts do not belong to FF_SWISS even if + // they are variable pitch because they have constant stroke width. + if (mWindowsFamily == FF_MODERN && mWindowsPitch & VARIABLE_PITCH) { + return aGeneric.EqualsLiteral("sans-serif"); + } + + // All other fonts will be grouped correctly using family... + switch (mWindowsFamily) { + case FF_DONTCARE: + return false; + case FF_ROMAN: + return aGeneric.EqualsLiteral("serif"); + case FF_SWISS: + return aGeneric.EqualsLiteral("sans-serif"); + case FF_MODERN: + return aGeneric.EqualsLiteral("monospace"); + case FF_SCRIPT: + return aGeneric.EqualsLiteral("cursive"); + case FF_DECORATIVE: + return aGeneric.EqualsLiteral("fantasy"); + } + + return false; + } + + bool SupportsLangGroup(nsAtom* aLangGroup) const { + if (!aLangGroup || aLangGroup == nsGkAtoms::Unicode) { + return true; + } + + int16_t bit = -1; + + /* map our langgroup names in to Windows charset bits */ + if (aLangGroup == nsGkAtoms::x_western) { + bit = ANSI_CHARSET; + } else if (aLangGroup == nsGkAtoms::Japanese) { + bit = SHIFTJIS_CHARSET; + } else if (aLangGroup == nsGkAtoms::ko) { + bit = HANGEUL_CHARSET; + } else if (aLangGroup == nsGkAtoms::zh_cn) { + bit = GB2312_CHARSET; + } else if (aLangGroup == nsGkAtoms::zh_tw) { + bit = CHINESEBIG5_CHARSET; + } else if (aLangGroup == nsGkAtoms::el) { + bit = GREEK_CHARSET; + } else if (aLangGroup == nsGkAtoms::he) { + bit = HEBREW_CHARSET; + } else if (aLangGroup == nsGkAtoms::ar) { + bit = ARABIC_CHARSET; + } else if (aLangGroup == nsGkAtoms::x_cyrillic) { + bit = RUSSIAN_CHARSET; + } else if (aLangGroup == nsGkAtoms::th) { + bit = THAI_CHARSET; + } + + if (bit != -1) { + return mCharset.test(bit); + } + + return false; + } + + uint8_t mWindowsFamily; + uint8_t mWindowsPitch; + + gfxSparseBitSet mCharset; + + private: + static int CALLBACK FamilyAddStylesProc(const ENUMLOGFONTEXW* lpelfe, + const NEWTEXTMETRICEXW* nmetrics, + DWORD fontType, LPARAM data); +}; + +class gfxGDIFontList final : public gfxPlatformFontList { + public: + static gfxGDIFontList* PlatformFontList() { + return static_cast<gfxGDIFontList*>(sPlatformFontList); + } + + // initialize font lists + virtual nsresult InitFontListForPlatform() override; + + gfxFontFamily* CreateFontFamily(const nsACString& aName, + FontVisibility aVisibility) const override; + + bool FindAndAddFamilies(mozilla::StyleGenericFontFamily aGeneric, + const nsACString& aFamily, + nsTArray<FamilyAndGeneric>* aOutput, + FindFamiliesFlags aFlags, + gfxFontStyle* aStyle = nullptr, + nsAtom* aLanguage = nullptr, + gfxFloat aDevToCssSize = 1.0) override; + + virtual gfxFontEntry* LookupLocalFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry); + + virtual gfxFontEntry* MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + 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: + FontFamily GetDefaultFontForPlatform(const gfxFontStyle* aStyle, + nsAtom* aLanguage = nullptr) override; + + private: + friend class gfxWindowsPlatform; + + gfxGDIFontList(); + + nsresult GetFontSubstitutes(); + + static int CALLBACK EnumFontFamExProc(ENUMLOGFONTEXW* lpelfe, + NEWTEXTMETRICEXW* lpntme, + DWORD fontType, LPARAM lParam); + + virtual already_AddRefed<FontInfoData> CreateFontInfoData(); + +#ifdef MOZ_BUNDLED_FONTS + void ActivateBundledFonts(); +#endif + + FontFamilyTable mFontSubstitutes; + nsTArray<nsCString> mNonExistingFonts; +}; + +#endif /* GFX_GDIFONTLIST_H */ diff --git a/gfx/thebes/gfxGdkNativeRenderer.cpp b/gfx/thebes/gfxGdkNativeRenderer.cpp new file mode 100644 index 0000000000..49f3afdb59 --- /dev/null +++ b/gfx/thebes/gfxGdkNativeRenderer.cpp @@ -0,0 +1,15 @@ +/* -*- 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 "gfxGdkNativeRenderer.h" +#include "gfxContext.h" +#include "gfxPlatformGtk.h" + +#ifdef MOZ_X11 +# include <gdk/gdkx.h> +# include "cairo-xlib.h" +# include "gfxXlibSurface.h" + +#endif diff --git a/gfx/thebes/gfxGdkNativeRenderer.h b/gfx/thebes/gfxGdkNativeRenderer.h new file mode 100644 index 0000000000..e11a3ba5d1 --- /dev/null +++ b/gfx/thebes/gfxGdkNativeRenderer.h @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFXGDKNATIVERENDER_H_ +#define GFXGDKNATIVERENDER_H_ + +#include <gdk/gdk.h> +#include "nsSize.h" +#ifdef MOZ_X11 +# include "gfxXlibNativeRenderer.h" +#endif + +class gfxContext; + +/** + * This class lets us take code that draws into an GDK drawable and lets us + * use it to draw into any Thebes context. The user should subclass this class, + * override DrawWithGDK, and then call Draw(). The drawing will be subjected + * to all Thebes transformations, clipping etc. + */ +class gfxGdkNativeRenderer +#ifdef MOZ_X11 + : private gfxXlibNativeRenderer +#endif +{ + public: + /** + * Perform the native drawing. + * @param offsetX draw at this offset into the given drawable + * @param offsetY draw at this offset into the given drawable + * @param clipRects an array of rects; clip to the union + * @param numClipRects the number of rects in the array, or zero if + * no clipping is required + */ + + 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 + */ + + private: +#ifdef MOZ_X11 + // for gfxXlibNativeRenderer: + virtual nsresult DrawWithXlib(cairo_surface_t* surface, nsIntPoint offset, + mozilla::gfx::IntRect* clipRects, + uint32_t numClipRects) override; + +#endif +}; + +#endif /*GFXGDKNATIVERENDER_H_*/ diff --git a/gfx/thebes/gfxGlyphExtents.cpp b/gfx/thebes/gfxGlyphExtents.cpp new file mode 100644 index 0000000000..3f7e3b5aaa --- /dev/null +++ b/gfx/thebes/gfxGlyphExtents.cpp @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxGlyphExtents.h" +#include "gfxTextRun.h" + +using namespace mozilla; + +#ifdef DEBUG_roc +# define DEBUG_TEXT_RUN_STORAGE_METRICS +#endif + +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS +extern uint32_t gTextRunStorageHighWaterMark; +extern uint32_t gTextRunStorage; +extern uint32_t gFontCount; +extern uint32_t gGlyphExtentsCount; +extern uint32_t gGlyphExtentsWidthsTotalSize; +extern uint32_t gGlyphExtentsSetupEagerSimple; +extern uint32_t gGlyphExtentsSetupEagerTight; +extern uint32_t gGlyphExtentsSetupLazyTight; +extern uint32_t gGlyphExtentsSetupFallBackToTight; +#endif + +gfxGlyphExtents::~gfxGlyphExtents() { +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + gGlyphExtentsWidthsTotalSize += + mContainedGlyphWidths.SizeOfExcludingThis(&FontCacheMallocSizeOf); + gGlyphExtentsCount++; +#endif + MOZ_COUNT_DTOR(gfxGlyphExtents); +} + +bool gfxGlyphExtents::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; + } + +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + ++gGlyphExtentsSetupLazyTight; +#endif + aFont->SetupGlyphExtents(aDrawTarget, aGlyphID, true, this); + entry = mTightGlyphExtents.GetEntry(aGlyphID); + if (!entry) { + NS_WARNING("Could not get glyph extents"); + return false; + } + } + + *aExtents = gfxRect(entry->x, entry->y, entry->width, entry->height); + return true; +} + +gfxGlyphExtents::GlyphWidths::~GlyphWidths() { + uint32_t i, count = mBlocks.Length(); + for (i = 0; i < count; ++i) { + uintptr_t bits = mBlocks[i]; + if (bits && !(bits & 0x1)) { + delete[] reinterpret_cast<uint16_t*>(bits); + } + } +} + +uint32_t gfxGlyphExtents::GlyphWidths::SizeOfExcludingThis( + MallocSizeOf aMallocSizeOf) const { + uint32_t i; + uint32_t size = mBlocks.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (i = 0; i < mBlocks.Length(); ++i) { + uintptr_t bits = mBlocks[i]; + if (bits && !(bits & 0x1)) { + size += aMallocSizeOf(reinterpret_cast<void*>(bits)); + } + } + return size; +} + +void gfxGlyphExtents::GlyphWidths::Set(uint32_t aGlyphID, uint16_t aWidth) { + uint32_t block = aGlyphID >> BLOCK_SIZE_BITS; + uint32_t len = mBlocks.Length(); + if (block >= len) { + uintptr_t* elems = mBlocks.AppendElements(block + 1 - len); + if (!elems) return; + memset(elems, 0, sizeof(uintptr_t) * (block + 1 - len)); + } + + uintptr_t bits = mBlocks[block]; + uint32_t glyphOffset = aGlyphID & (BLOCK_SIZE - 1); + if (!bits) { + mBlocks[block] = MakeSingle(glyphOffset, aWidth); + return; + } + + uint16_t* newBlock; + if (bits & 0x1) { + // Expand the block to a real block. We could avoid this by checking + // glyphOffset == GetGlyphOffset(bits), but that never happens so don't + // bother + newBlock = new uint16_t[BLOCK_SIZE]; + if (!newBlock) return; + uint32_t i; + for (i = 0; i < BLOCK_SIZE; ++i) { + newBlock[i] = INVALID_WIDTH; + } + newBlock[GetGlyphOffset(bits)] = GetWidth(bits); + mBlocks[block] = reinterpret_cast<uintptr_t>(newBlock); + } else { + newBlock = reinterpret_cast<uint16_t*>(bits); + } + newBlock[glyphOffset] = aWidth; +} + +void gfxGlyphExtents::SetTightGlyphExtents(uint32_t aGlyphID, + const gfxRect& aExtentsAppUnits) { + HashEntry* entry = mTightGlyphExtents.PutEntry(aGlyphID); + if (!entry) return; + entry->x = aExtentsAppUnits.X(); + entry->y = aExtentsAppUnits.Y(); + entry->width = aExtentsAppUnits.Width(); + entry->height = aExtentsAppUnits.Height(); +} + +size_t gfxGlyphExtents::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + return mContainedGlyphWidths.SizeOfExcludingThis(aMallocSizeOf) + + mTightGlyphExtents.ShallowSizeOfExcludingThis(aMallocSizeOf); +} + +size_t gfxGlyphExtents::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} diff --git a/gfx/thebes/gfxGlyphExtents.h b/gfx/thebes/gfxGlyphExtents.h new file mode 100644 index 0000000000..38e495f95f --- /dev/null +++ b/gfx/thebes/gfxGlyphExtents.h @@ -0,0 +1,153 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_GLYPHEXTENTS_H +#define GFX_GLYPHEXTENTS_H + +#include "gfxFont.h" +#include "gfxRect.h" +#include "nsTHashtable.h" +#include "nsHashKeys.h" +#include "nsTArray.h" +#include "mozilla/MemoryReporting.h" + +class gfxContext; + +namespace mozilla { +namespace gfx { +class DrawTarget; +} // namespace gfx +} // namespace mozilla + +/** + * This stores glyph bounds information for a particular gfxFont, at + * a particular appunits-per-dev-pixel ratio (because the compressed glyph + * width array is stored in appunits). + * + * We store a hashtable from glyph IDs to float bounding rects. For the + * common case where the glyph has no horizontal left bearing, and no + * y overflow above the font ascent or below the font descent, and tight + * bounding boxes are not required, we avoid storing the glyph ID in the + * hashtable and instead consult an array of 16-bit glyph XMost values (in + * appunits). This array always has an entry for the font's space glyph --- the + * width is assumed to be zero. + */ +class gfxGlyphExtents { + typedef mozilla::gfx::DrawTarget DrawTarget; + + public: + explicit gfxGlyphExtents(int32_t aAppUnitsPerDevUnit) + : mAppUnitsPerDevUnit(aAppUnitsPerDevUnit) { + 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), x(0.0), y(0.0), width(0.0), height(0.0) {} + HashEntry(HashEntry&& aOther) + : nsUint32HashKey(std::move(aOther)), + x(aOther.x), + y(aOther.y), + width(aOther.width), + height(aOther.height) {} + + float x, y, width, height; + }; + + enum { + BLOCK_SIZE_BITS = 7, + BLOCK_SIZE = 1 << BLOCK_SIZE_BITS + }; // 128-glyph blocks + + class GlyphWidths { + public: + void Set(uint32_t aIndex, uint16_t aValue); + uint16_t Get(uint32_t aIndex) const { + uint32_t block = aIndex >> BLOCK_SIZE_BITS; + if (block >= mBlocks.Length()) return INVALID_WIDTH; + uintptr_t bits = mBlocks[block]; + if (!bits) return INVALID_WIDTH; + uint32_t indexInBlock = aIndex & (BLOCK_SIZE - 1); + if (bits & 0x1) { + if (GetGlyphOffset(bits) != indexInBlock) return INVALID_WIDTH; + return GetWidth(bits); + } + uint16_t* widths = reinterpret_cast<uint16_t*>(bits); + return widths[indexInBlock]; + } + + uint32_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + ~GlyphWidths(); + + private: + static uint32_t GetGlyphOffset(uintptr_t aBits) { + NS_ASSERTION(aBits & 0x1, "This is really a pointer..."); + return (aBits >> 1) & ((1 << BLOCK_SIZE_BITS) - 1); + } + static uint32_t GetWidth(uintptr_t aBits) { + NS_ASSERTION(aBits & 0x1, "This is really a pointer..."); + return aBits >> (1 + BLOCK_SIZE_BITS); + } + static uintptr_t MakeSingle(uint32_t aGlyphOffset, uint16_t aWidth) { + return (aWidth << (1 + BLOCK_SIZE_BITS)) + (aGlyphOffset << 1) + 1; + } + + nsTArray<uintptr_t> mBlocks; + }; + + GlyphWidths mContainedGlyphWidths; + nsTHashtable<HashEntry> mTightGlyphExtents; + int32_t mAppUnitsPerDevUnit; + + private: + // not implemented: + gfxGlyphExtents(const gfxGlyphExtents& aOther) = delete; + gfxGlyphExtents& operator=(const gfxGlyphExtents& aOther) = delete; +}; + +#endif diff --git a/gfx/thebes/gfxGradientCache.cpp b/gfx/thebes/gfxGradientCache.cpp new file mode 100644 index 0000000000..2faf7b9a1f --- /dev/null +++ b/gfx/thebes/gfxGradientCache.cpp @@ -0,0 +1,214 @@ +/* -*- 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 "gfxGradientCache.h" +#include <time.h> + +namespace mozilla { +namespace gfx { + +using namespace mozilla; + +struct GradientCacheKey : public PLDHashEntryHdr { + typedef const GradientCacheKey& KeyType; + typedef const GradientCacheKey* KeyTypePointer; + enum { ALLOW_MEMMOVE = true }; + const CopyableTArray<GradientStop> mStops; + ExtendMode mExtend; + BackendType mBackendType; + + GradientCacheKey(const nsTArray<GradientStop>& aStops, ExtendMode aExtend, + BackendType aBackendType) + : mStops(aStops), mExtend(aExtend), mBackendType(aBackendType) {} + + explicit GradientCacheKey(const GradientCacheKey* aOther) + : mStops(aOther->mStops), + mExtend(aOther->mExtend), + mBackendType(aOther->mBackendType) {} + + GradientCacheKey(GradientCacheKey&& aOther) = default; + + union FloatUint32 { + float f; + uint32_t u; + }; + + static PLDHashNumber HashKey(const KeyTypePointer aKey) { + PLDHashNumber hash = 0; + FloatUint32 convert; + hash = AddToHash(hash, int(aKey->mBackendType)); + hash = AddToHash(hash, int(aKey->mExtend)); + for (uint32_t i = 0; i < aKey->mStops.Length(); i++) { + hash = AddToHash(hash, aKey->mStops[i].color.ToABGR()); + // Use the float bits as hash, except for the cases of 0.0 and -0.0 which + // both map to 0 + convert.f = aKey->mStops[i].offset; + hash = AddToHash(hash, convert.f ? convert.u : 0); + } + return hash; + } + + bool KeyEquals(KeyTypePointer aKey) const { + bool sameStops = true; + if (aKey->mStops.Length() != mStops.Length()) { + sameStops = false; + } else { + for (uint32_t i = 0; i < mStops.Length(); i++) { + if (mStops[i].color.ToABGR() != aKey->mStops[i].color.ToABGR() || + mStops[i].offset != aKey->mStops[i].offset) { + sameStops = false; + break; + } + } + } + + return sameStops && (aKey->mBackendType == mBackendType) && + (aKey->mExtend == mExtend); + } + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } +}; + +/** + * This class is what is cached. It need to be allocated in an object separated + * to the cache entry to be able to be tracked by the nsExpirationTracker. + * */ +struct GradientCacheData { + GradientCacheData(GradientStops* aStops, GradientCacheKey&& aKey) + : mStops(aStops), mKey(std::move(aKey)) {} + + GradientCacheData(GradientCacheData&& aOther) = default; + + nsExpirationState* GetExpirationState() { return &mExpirationState; } + + nsExpirationState mExpirationState; + const RefPtr<GradientStops> mStops; + GradientCacheKey mKey; +}; + +/** + * This class implements a cache with no maximum size, that retains the + * gfxPatterns used to draw the gradients. + * + * The key is the nsStyleGradient that defines the gradient, and the size of the + * gradient. + * + * The value is the gfxPattern, and whether or not we perform an optimization + * based on the actual gradient property. + * + * An entry stays in the cache as long as it is used often. As long as a cache + * entry is in the cache, all the references it has are guaranteed to be valid: + * the nsStyleRect for the key, the gfxPattern for the value. + */ +class GradientCache final : public nsExpirationTracker<GradientCacheData, 4> { + public: + GradientCache() + : nsExpirationTracker<GradientCacheData, 4>(MAX_GENERATION_MS, + "GradientCache") { + srand(time(nullptr)); + } + + virtual void NotifyExpired(GradientCacheData* aObject) override { + // This will free the gfxPattern. + RemoveObject(aObject); + mHashEntries.Remove(aObject->mKey); + } + + GradientCacheData* Lookup(const nsTArray<GradientStop>& aStops, + ExtendMode aExtend, BackendType aBackendType) { + GradientCacheData* gradient = + mHashEntries.Get(GradientCacheKey(aStops, aExtend, aBackendType)); + + if (gradient) { + MarkUsed(gradient); + } + + return gradient; + } + + // Returns true if we successfully register the gradient in the cache, false + // otherwise. + bool RegisterEntry(GradientCacheData* aValue) { + nsresult rv = AddObject(aValue); + if (NS_FAILED(rv)) { + // We are OOM, and we cannot track this object. We don't want stall + // entries in the hash table (since the expiration tracker is responsible + // for removing the cache entries), so we avoid putting that entry in the + // table, which is a good things considering we are short on memory + // anyway, we probably don't want to retain things. + return false; + } + mHashEntries.Put(aValue->mKey, aValue); + return true; + } + + protected: + static const uint32_t MAX_GENERATION_MS = 10000; + /** + * FIXME use nsTHashtable to avoid duplicating the GradientCacheKey. + * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47 + */ + nsClassHashtable<GradientCacheKey, GradientCacheData> mHashEntries; +}; + +static GradientCache* gGradientCache = nullptr; + +GradientStops* gfxGradientCache::GetGradientStops( + const DrawTarget* aDT, nsTArray<GradientStop>& aStops, ExtendMode aExtend) { + if (!gGradientCache) { + gGradientCache = new GradientCache(); + } + GradientCacheData* cached = + gGradientCache->Lookup(aStops, aExtend, aDT->GetBackendType()); + if (cached && cached->mStops) { + if (!cached->mStops->IsValid()) { + gGradientCache->NotifyExpired(cached); + } else { + return cached->mStops; + } + } + + return nullptr; +} + +already_AddRefed<GradientStops> gfxGradientCache::GetOrCreateGradientStops( + const DrawTarget* aDT, nsTArray<GradientStop>& aStops, ExtendMode aExtend) { + if (aDT->IsRecording()) { + return aDT->CreateGradientStops(aStops.Elements(), aStops.Length(), + aExtend); + } + + RefPtr<GradientStops> gs = GetGradientStops(aDT, aStops, aExtend); + if (!gs) { + gs = aDT->CreateGradientStops(aStops.Elements(), aStops.Length(), aExtend); + if (!gs) { + return nullptr; + } + GradientCacheData* cached = new GradientCacheData( + gs, GradientCacheKey(aStops, aExtend, aDT->GetBackendType())); + if (!gGradientCache->RegisterEntry(cached)) { + delete cached; + } + } + return gs.forget(); +} + +void gfxGradientCache::PurgeAllCaches() { + if (gGradientCache) { + gGradientCache->AgeAllGenerations(); + } +} + +void gfxGradientCache::Shutdown() { + delete gGradientCache; + gGradientCache = nullptr; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/thebes/gfxGradientCache.h b/gfx/thebes/gfxGradientCache.h new file mode 100644 index 0000000000..0f45e8fcaa --- /dev/null +++ b/gfx/thebes/gfxGradientCache.h @@ -0,0 +1,34 @@ + +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_GRADIENT_CACHE_H +#define GFX_GRADIENT_CACHE_H + +#include "nsTArray.h" +#include "gfxPattern.h" +#include "mozilla/gfx/2D.h" + +namespace mozilla { +namespace gfx { + +class gfxGradientCache { + public: + static gfx::GradientStops* GetGradientStops( + const gfx::DrawTarget* aDT, nsTArray<gfx::GradientStop>& aStops, + gfx::ExtendMode aExtend); + + static already_AddRefed<gfx::GradientStops> GetOrCreateGradientStops( + const gfx::DrawTarget* aDT, nsTArray<gfx::GradientStop>& aStops, + gfx::ExtendMode aExtend); + + static void PurgeAllCaches(); + static void Shutdown(); +}; + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/thebes/gfxGraphiteShaper.cpp b/gfx/thebes/gfxGraphiteShaper.cpp new file mode 100644 index 0000000000..45996fff2d --- /dev/null +++ b/gfx/thebes/gfxGraphiteShaper.cpp @@ -0,0 +1,514 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxGraphiteShaper.h" +#include "nsString.h" +#include "gfxContext.h" +#include "gfxFontConstants.h" +#include "gfxTextRun.h" + +#include "graphite2/Font.h" +#include "graphite2/GraphiteExtra.h" +#include "graphite2/Segment.h" + +#include "harfbuzz/hb.h" + +#include "mozilla/ScopeExit.h" + +#include "ThebesRLBox.h" + +#define FloatToFixed(f) (65536 * (f)) +#define FixedToFloat(f) ((f) * (1.0 / 65536.0)) +// Right shifts of negative (signed) integers are undefined, as are overflows +// when converting unsigned to negative signed integers. +// (If speed were an issue we could make some 2's complement assumptions.) +#define FixedToIntRound(f) \ + ((f) > 0 ? ((32768 + (f)) >> 16) : -((32767 - (f)) >> 16)) + +#define CopyAndVerifyOrFail(t, cond, failed) \ + (t).copy_and_verify([&](auto val) { \ + if (!(cond)) { \ + *(failed) = true; \ + } \ + return val; \ + }) + +using namespace mozilla; // for AutoSwap_* types + +/* + * Creation and destruction; on deletion, release any font tables we're holding + */ + +gfxGraphiteShaper::gfxGraphiteShaper(gfxFont* aFont) + : gfxFontShaper(aFont), + mGrFace(mFont->GetFontEntry()->GetGrFace()), + mSandbox(mFont->GetFontEntry()->GetGrSandbox()), + mCallback(mFont->GetFontEntry()->GetGrSandboxAdvanceCallbackHandle()), + mFallbackToSmallCaps(false) { + mCallbackData.mFont = aFont; +} + +gfxGraphiteShaper::~gfxGraphiteShaper() { + auto t_mGrFont = rlbox::from_opaque(mGrFont); + if (t_mGrFont) { + sandbox_invoke(*mSandbox, gr_font_destroy, t_mGrFont); + } + mFont->GetFontEntry()->ReleaseGrFace(mGrFace); +} + +/*static*/ +thread_local gfxGraphiteShaper::CallbackData* + gfxGraphiteShaper::tl_GrGetAdvanceData = nullptr; + +/*static*/ +tainted_opaque_gr<float> gfxGraphiteShaper::GrGetAdvance( + rlbox_sandbox_gr& sandbox, + tainted_opaque_gr<const void*> /* appFontHandle */, + tainted_opaque_gr<uint16_t> t_glyphid) { + CallbackData* cb = tl_GrGetAdvanceData; + if (!cb) { + // GrGetAdvance callback called unexpectedly. Just return safe value. + tainted_gr<float> ret = 0; + return ret.to_opaque(); + } + auto glyphid = rlbox::from_opaque(t_glyphid).unverified_safe_because( + "Here the only use of a glyphid is for lookup to get a width. " + "Implementations of GetGlyphWidth in this code base use a hashtable " + "which is robust to unknown keys. So no validation is required."); + tainted_gr<float> ret = FixedToFloat(cb->mFont->GetGlyphWidth(glyphid)); + return ret.to_opaque(); +} + +static inline uint32_t MakeGraphiteLangTag(uint32_t aTag) { + uint32_t grLangTag = aTag; + // replace trailing space-padding with NULs for graphite + uint32_t mask = 0x000000FF; + while ((grLangTag & mask) == ' ') { + grLangTag &= ~mask; + mask <<= 8; + } + return grLangTag; +} + +struct GrFontFeatures { + tainted_gr<gr_face*> mFace; + tainted_gr<gr_feature_val*> mFeatures; + rlbox_sandbox_gr* mSandbox; +}; + +static void AddFeature(const uint32_t& aTag, uint32_t& aValue, void* aUserArg) { + GrFontFeatures* f = static_cast<GrFontFeatures*>(aUserArg); + + tainted_gr<const gr_feature_ref*> fref = + sandbox_invoke(*(f->mSandbox), gr_face_find_fref, f->mFace, aTag); + if (fref) { + sandbox_invoke(*(f->mSandbox), gr_fref_set_feature_value, fref, aValue, + f->mFeatures); + } +} + +// Count the number of Unicode characters in a UTF-16 string (i.e. surrogate +// pairs are counted as 1, although they are 2 code units). +// (Any isolated surrogates will count 1 each, because in decoding they would +// be replaced by individual U+FFFD REPLACEMENT CHARACTERs.) +static inline size_t CountUnicodes(const char16_t* aText, uint32_t aLength) { + size_t total = 0; + const char16_t* end = aText + aLength; + while (aText < end) { + if (NS_IS_HIGH_SURROGATE(*aText) && aText + 1 < end && + NS_IS_LOW_SURROGATE(*(aText + 1))) { + aText += 2; + } else { + aText++; + } + total++; + } + return total; +} + +bool gfxGraphiteShaper::ShapeText(DrawTarget* aDrawTarget, + const char16_t* aText, uint32_t aOffset, + uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, + RoundingFlags aRounding, + gfxShapedText* aShapedText) { + const gfxFontStyle* style = mFont->GetStyle(); + auto t_mGrFace = rlbox::from_opaque(mGrFace); + auto t_mGrFont = rlbox::from_opaque(mGrFont); + + if (!t_mGrFont) { + if (!t_mGrFace) { + return false; + } + + if (mFont->ProvidesGlyphWidths()) { + auto p_ops = mSandbox->malloc_in_sandbox<gr_font_ops>(); + if (!p_ops) { + return false; + } + auto clean_ops = MakeScopeExit([&] { mSandbox->free_in_sandbox(p_ops); }); + p_ops->size = sizeof(*p_ops); + p_ops->glyph_advance_x = *mCallback; + p_ops->glyph_advance_y = nullptr; // vertical text not yet implemented + t_mGrFont = sandbox_invoke( + *mSandbox, gr_make_font_with_ops, mFont->GetAdjustedSize(), + // For security, we do not pass the callback data to this arg, and use + // a TLS var instead. However, gr_make_font_with_ops expects this to + // be a non null ptr, and changes its behavior if it isn't. Therefore, + // we should pass some dummy non null pointer which will be passed to + // the GrGetAdvance callback, but never used. Let's just pass p_ops + // again, as this is a non-null tainted pointer. + p_ops /* mCallbackData */, p_ops, t_mGrFace); + } else { + t_mGrFont = sandbox_invoke(*mSandbox, gr_make_font, + mFont->GetAdjustedSize(), t_mGrFace); + } + mGrFont = t_mGrFont.to_opaque(); + + if (!t_mGrFont) { + return false; + } + + // determine whether petite-caps falls back to small-caps + if (style->variantCaps != NS_FONT_VARIANT_CAPS_NORMAL) { + switch (style->variantCaps) { + case NS_FONT_VARIANT_CAPS_ALLPETITE: + case NS_FONT_VARIANT_CAPS_PETITECAPS: + bool synLower, synUpper; + mFont->SupportsVariantCaps(aScript, style->variantCaps, + mFallbackToSmallCaps, synLower, synUpper); + break; + default: + break; + } + } + } + + gfxFontEntry* entry = mFont->GetFontEntry(); + uint32_t grLang = 0; + if (style->languageOverride) { + grLang = MakeGraphiteLangTag(style->languageOverride); + } else if (entry->mLanguageOverride) { + grLang = MakeGraphiteLangTag(entry->mLanguageOverride); + } else if (aLanguage) { + nsAutoCString langString; + aLanguage->ToUTF8String(langString); + grLang = GetGraphiteTagForLang(langString); + } + tainted_gr<gr_feature_val*> grFeatures = + sandbox_invoke(*mSandbox, gr_face_featureval_for_lang, t_mGrFace, grLang); + + // insert any merged features into Graphite feature list + GrFontFeatures f = {t_mGrFace, grFeatures, mSandbox}; + MergeFontFeatures(style, mFont->GetFontEntry()->mFeatureSettings, + aShapedText->DisableLigatures(), + mFont->GetFontEntry()->FamilyName(), mFallbackToSmallCaps, + AddFeature, &f); + + // Graphite shaping doesn't map U+00a0 (nbsp) to space if it is missing + // from the font, so check for that possibility. (Most fonts double-map + // the space glyph to both 0x20 and 0xA0, so this won't often be needed; + // so we don't copy the text until we know it's required.) + nsAutoString transformed; + const char16_t NO_BREAK_SPACE = 0x00a0; + if (!entry->HasCharacter(NO_BREAK_SPACE)) { + nsDependentSubstring src(aText, aLength); + if (src.FindChar(NO_BREAK_SPACE) != kNotFound) { + transformed = src; + transformed.ReplaceChar(NO_BREAK_SPACE, ' '); + aText = transformed.BeginReading(); + } + } + + size_t numChars = CountUnicodes(aText, aLength); + gr_bidirtl grBidi = gr_bidirtl( + aShapedText->IsRightToLeft() ? (gr_rtl | gr_nobidi) : gr_nobidi); + + tainted_gr<char16_t*> t_aText = + mSandbox->malloc_in_sandbox<char16_t>(aLength); + if (!t_aText) { + return false; + } + auto clean_txt = MakeScopeExit([&] { mSandbox->free_in_sandbox(t_aText); }); + + rlbox::memcpy(*mSandbox, t_aText, aText, aLength * sizeof(char16_t)); + + tl_GrGetAdvanceData = &mCallbackData; + auto clean_adv_data = MakeScopeExit([&] { tl_GrGetAdvanceData = nullptr; }); + + tainted_gr<gr_segment*> seg = + sandbox_invoke(*mSandbox, gr_make_seg, mGrFont, t_mGrFace, 0, grFeatures, + gr_utf16, t_aText, numChars, grBidi); + + sandbox_invoke(*mSandbox, gr_featureval_destroy, grFeatures); + + if (!seg) { + return false; + } + + nsresult rv = + SetGlyphsFromSegment(aShapedText, aOffset, aLength, aText, + t_aText.to_opaque(), seg.to_opaque(), aRounding); + + sandbox_invoke(*mSandbox, gr_seg_destroy, seg); + + return NS_SUCCEEDED(rv); +} + +nsresult gfxGraphiteShaper::SetGlyphsFromSegment( + gfxShapedText* aShapedText, uint32_t aOffset, uint32_t aLength, + const char16_t* aText, tainted_opaque_gr<char16_t*> t_aText, + tainted_opaque_gr<gr_segment*> aSegment, RoundingFlags aRounding) { + typedef gfxShapedText::CompressedGlyph CompressedGlyph; + + int32_t dev2appUnits = aShapedText->GetAppUnitsPerDevUnit(); + bool rtl = aShapedText->IsRightToLeft(); + + // identify clusters; graphite may have reordered/expanded/ligated glyphs. + tainted_gr<gr_glyph_to_char_association*> data = + sandbox_invoke(*mSandbox, gr_get_glyph_to_char_association, aSegment, + aLength, rlbox::from_opaque(t_aText)); + + if (!data) { + return NS_ERROR_FAILURE; + } + + tainted_gr<gr_glyph_to_char_cluster*> clusters = data->clusters; + tainted_gr<uint16_t*> gids = data->gids; + tainted_gr<float*> xLocs = data->xLocs; + tainted_gr<float*> yLocs = data->yLocs; + + CompressedGlyph* charGlyphs = aShapedText->GetCharacterGlyphs() + aOffset; + + bool roundX = bool(aRounding & RoundingFlags::kRoundX); + bool roundY = bool(aRounding & RoundingFlags::kRoundY); + + bool failedVerify = false; + + // cIndex is primarily used to index into the clusters array which has size + // aLength below. As cIndex is not changing anymore, let's just verify it + // and remove the tainted wrapper. + uint32_t cIndex = + CopyAndVerifyOrFail(data->cIndex, val < aLength, &failedVerify); + if (failedVerify) { + return NS_ERROR_ILLEGAL_VALUE; + } + // now put glyphs into the textrun, one cluster at a time + for (uint32_t i = 0; i <= cIndex; ++i) { + // We makes a local copy of "clusters[i]" which is of type + // tainted_gr<gr_glyph_to_char_cluster> below. We do this intentionally + // rather than taking a reference. Taking a reference with the code + // + // tainted_volatile_gr<gr_glyph_to_char_cluster>& c = clusters[i]; + // + // produces a tainted_volatile which means the value can change at any + // moment allowing for possible time-of-check-time-of-use vuln. We thus + // make a local copy to simplify the verification. + tainted_gr<gr_glyph_to_char_cluster> c = clusters[i]; + + tainted_gr<float> t_adv; // total advance of the cluster + if (rtl) { + if (i == 0) { + t_adv = sandbox_invoke(*mSandbox, gr_seg_advance_X, aSegment) - + xLocs[c.baseGlyph]; + } else { + t_adv = xLocs[clusters[i - 1].baseGlyph] - xLocs[c.baseGlyph]; + } + } else { + if (i == cIndex) { + t_adv = sandbox_invoke(*mSandbox, gr_seg_advance_X, aSegment) - + xLocs[c.baseGlyph]; + } else { + t_adv = xLocs[clusters[i + 1].baseGlyph] - xLocs[c.baseGlyph]; + } + } + + float adv = t_adv.unverified_safe_because( + "Per Bug 1569464 - this is the advance width of a glyph or cluster of " + "glyphs. There are no a-priori limits on what that might be. Incorrect " + "values will tend to result in bad layout or missing text, or bad " + "nscoord values. But, these will not result in safety issues."); + + // check unexpected offset - offs used to index into aText + uint32_t offs = + CopyAndVerifyOrFail(c.baseChar, val < aLength, &failedVerify); + if (failedVerify) { + return NS_ERROR_ILLEGAL_VALUE; + } + + // Check for default-ignorable char that didn't get filtered, combined, + // etc by the shaping process, and skip it. + auto one_glyph = c.nGlyphs == static_cast<uint32_t>(1); + auto one_char = c.nChars == static_cast<uint32_t>(1); + + if ((one_glyph && one_char) + .unverified_safe_because( + "using this boolean check to decide whether to ignore a " + "character or not. The worst that can happen is a bad " + "rendering.")) { + if (aShapedText->FilterIfIgnorable(aOffset + offs, aText[offs])) { + continue; + } + } + + uint32_t appAdvance = roundX ? NSToIntRound(adv) * dev2appUnits + : NSToIntRound(adv * dev2appUnits); + + const char gid_simple_value[] = + "Per Bug 1569464 - these are glyph IDs that can range from 0 to the " + "maximum glyph ID supported by the font. However, out-of-range values " + "here should not lead to safety issues; they would simply result in " + "blank rendering, although this depends on the platform back-end."; + + // gids[c.baseGlyph] is checked and used below. Since this is a + // tainted_volatile, which can change at any moment, we make a local copy + // first to prevent a time-of-check-time-of-use vuln. + uint16_t gid_of_base_glyph = + gids[c.baseGlyph].unverified_safe_because(gid_simple_value); + + const char fast_path[] = + "Even if the number of glyphs set is an incorrect value, the else " + "branch is a more general purpose algorithm which can handle other " + "values of nGlyphs"; + + if (one_glyph.unverified_safe_because(fast_path) && + CompressedGlyph::IsSimpleGlyphID(gid_of_base_glyph) && + CompressedGlyph::IsSimpleAdvance(appAdvance) && + charGlyphs[offs].IsClusterStart() && + (yLocs[c.baseGlyph] == 0).unverified_safe_because(fast_path)) { + charGlyphs[offs].SetSimpleGlyph(appAdvance, gid_of_base_glyph); + + } else { + // not a one-to-one mapping with simple metrics: use DetailedGlyph + AutoTArray<gfxShapedText::DetailedGlyph, 8> details; + float clusterLoc; + + uint32_t glyph_end = + (c.baseGlyph + c.nGlyphs) + .unverified_safe_because( + "This only controls the total number of glyphs set for this " + "particular text. Worst that can happen is a bad rendering"); + + // check overflow - ensure loop start is before the end + uint32_t glyph_start = + CopyAndVerifyOrFail(c.baseGlyph, val <= glyph_end, &failedVerify); + if (failedVerify) { + return NS_ERROR_ILLEGAL_VALUE; + } + + for (uint32_t j = glyph_start; j < glyph_end; ++j) { + gfxShapedText::DetailedGlyph* d = details.AppendElement(); + d->mGlyphID = gids[j].unverified_safe_because(gid_simple_value); + + const char safe_coordinates[] = + "There are no limits on coordinates. Worst case, bad values would " + "force rendering off-screen, but there are no memory safety " + "issues."; + + float yLocs_j = yLocs[j].unverified_safe_because(safe_coordinates); + float xLocs_j = xLocs[j].unverified_safe_because(safe_coordinates); + + d->mOffset.y = roundY ? NSToIntRound(-yLocs_j) * dev2appUnits + : -yLocs_j * dev2appUnits; + if (j == glyph_start) { + d->mAdvance = appAdvance; + clusterLoc = xLocs_j; + } else { + float dx = + rtl ? (xLocs_j - clusterLoc) : (xLocs_j - clusterLoc - adv); + d->mOffset.x = + roundX ? NSToIntRound(dx) * dev2appUnits : dx * dev2appUnits; + d->mAdvance = 0; + } + } + aShapedText->SetDetailedGlyphs(aOffset + offs, details.Length(), + details.Elements()); + } + + // check unexpected offset + uint32_t char_end = CopyAndVerifyOrFail(c.baseChar + c.nChars, + val <= aLength, &failedVerify); + // check overflow - ensure loop start is before the end + uint32_t char_start = + CopyAndVerifyOrFail(c.baseChar + 1, val <= char_end, &failedVerify); + if (failedVerify) { + return NS_ERROR_ILLEGAL_VALUE; + } + + for (uint32_t j = char_start; j < char_end; ++j) { + CompressedGlyph& g = charGlyphs[j]; + NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph"); + g.SetComplex(g.IsClusterStart(), false); + } + } + + sandbox_invoke(*mSandbox, gr_free_char_association, data); + return NS_OK; +} + +// for language tag validation - include list of tags from the IANA registry +#include "gfxLanguageTagList.cpp" + +nsTHashtable<nsUint32HashKey>* gfxGraphiteShaper::sLanguageTags; + +/*static*/ +uint32_t gfxGraphiteShaper::GetGraphiteTagForLang(const nsCString& aLang) { + int len = aLang.Length(); + if (len < 2) { + return 0; + } + + // convert primary language subtag to a left-packed, NUL-padded integer + // for the Graphite API + uint32_t grLang = 0; + for (int i = 0; i < 4; ++i) { + grLang <<= 8; + if (i < len) { + uint8_t ch = aLang[i]; + if (ch == '-') { + // found end of primary language subtag, truncate here + len = i; + continue; + } + if (ch < 'a' || ch > 'z') { + // invalid character in tag, so ignore it completely + return 0; + } + grLang += ch; + } + } + + // valid tags must have length = 2 or 3 + if (len < 2 || len > 3) { + return 0; + } + + if (!sLanguageTags) { + // store the registered IANA tags in a hash for convenient validation + sLanguageTags = + new nsTHashtable<nsUint32HashKey>(ArrayLength(sLanguageTagList)); + for (const uint32_t* tag = sLanguageTagList; *tag != 0; ++tag) { + sLanguageTags->PutEntry(*tag); + } + } + + // only accept tags known in the IANA registry + if (sLanguageTags->GetEntry(grLang)) { + return grLang; + } + + return 0; +} + +/*static*/ +void gfxGraphiteShaper::Shutdown() { +#ifdef NS_FREE_PERMANENT_DATA + if (sLanguageTags) { + sLanguageTags->Clear(); + delete sLanguageTags; + sLanguageTags = nullptr; + } +#endif +} diff --git a/gfx/thebes/gfxGraphiteShaper.h b/gfx/thebes/gfxGraphiteShaper.h new file mode 100644 index 0000000000..fc61ac10df --- /dev/null +++ b/gfx/thebes/gfxGraphiteShaper.h @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_GRAPHITESHAPER_H +#define GFX_GRAPHITESHAPER_H + +#include "gfxFont.h" + +#include "mozilla/gfx/2D.h" + +#include "ThebesRLBoxTypes.h" + +struct gr_face; +struct gr_font; +struct gr_segment; + +class gfxGraphiteShaper : public gfxFontShaper { + public: + explicit gfxGraphiteShaper(gfxFont* aFont); + virtual ~gfxGraphiteShaper(); + + bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, RoundingFlags aRounding, + gfxShapedText* aShapedText) override; + + static void Shutdown(); + + protected: + nsresult SetGlyphsFromSegment(gfxShapedText* aShapedText, uint32_t aOffset, + uint32_t aLength, const char16_t* aText, + tainted_opaque_gr<char16_t*> t_aText, + tainted_opaque_gr<gr_segment*> aSegment, + RoundingFlags aRounding); + + // Graphite is run in a rlbox sandbox. Callback GrGetAdvance must be + // explicitly permitted. Since the sandbox is owned in gfxFontEntry class, + // gfxFontEntry needs access to the protected callback. + friend class gfxFontEntry; + static tainted_opaque_gr<float> GrGetAdvance( + rlbox_sandbox_gr& sandbox, tainted_opaque_gr<const void*> appFontHandle, + tainted_opaque_gr<uint16_t> glyphid); + + tainted_opaque_gr<gr_face*> + mGrFace; // owned by the font entry; shaper must call + // gfxFontEntry::ReleaseGrFace when finished with it + tainted_opaque_gr<gr_font*> mGrFont; // owned by the shaper itself + + // All libGraphite functionality is sandboxed. This is the sandbox instance. + rlbox_sandbox_gr* mSandbox; + + // Holds the handle to the permitted callback into Firefox for the sandboxed + // libGraphite + sandbox_callback_gr<float (*)(const void*, uint16_t)>* mCallback; + + struct CallbackData { + // mFont is a pointer to the font that owns this shaper, so it will + // remain valid throughout our lifetime + gfxFont* MOZ_NON_OWNING_REF mFont; + }; + + CallbackData mCallbackData; + static thread_local CallbackData* tl_GrGetAdvanceData; + + bool mFallbackToSmallCaps; // special fallback for the petite-caps case + + // Convert HTML 'lang' (BCP47) to Graphite language code + static uint32_t GetGraphiteTagForLang(const nsCString& aLang); + static nsTHashtable<nsUint32HashKey>* sLanguageTags; +}; + +#endif /* GFX_GRAPHITESHAPER_H */ diff --git a/gfx/thebes/gfxHarfBuzzShaper.cpp b/gfx/thebes/gfxHarfBuzzShaper.cpp new file mode 100644 index 0000000000..8153613896 --- /dev/null +++ b/gfx/thebes/gfxHarfBuzzShaper.cpp @@ -0,0 +1,1734 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsString.h" +#include "gfxContext.h" +#include "gfxFontConstants.h" +#include "gfxHarfBuzzShaper.h" +#include "gfxFontUtils.h" +#include "gfxTextRun.h" +#include "mozilla/Sprintf.h" +#include "nsUnicodeProperties.h" +#include "nsUnicodeScriptCodes.h" + +#include "harfbuzz/hb.h" +#include "harfbuzz/hb-ot.h" + +#include "unicode/unorm.h" +#include "unicode/utext.h" + +static const UNormalizer2* sNormalizer = nullptr; + +#include <algorithm> + +#define FloatToFixed(f) (65536 * (f)) +#define FixedToFloat(f) ((f) * (1.0 / 65536.0)) +// Right shifts of negative (signed) integers are undefined, as are overflows +// when converting unsigned to negative signed integers. +// (If speed were an issue we could make some 2's complement assumptions.) +#define FixedToIntRound(f) \ + ((f) > 0 ? ((32768 + (f)) >> 16) : -((32767 - (f)) >> 16)) + +using namespace mozilla; // for AutoSwap_* types +using namespace mozilla::unicode; // for Unicode property lookup + +/* + * Creation and destruction; on deletion, release any font tables we're holding + */ + +gfxHarfBuzzShaper::gfxHarfBuzzShaper(gfxFont* aFont) + : gfxFontShaper(aFont), + mHBFont(nullptr), + mBuffer(nullptr), + mCallbackData(), + mKernTable(nullptr), + mHmtxTable(nullptr), + mVmtxTable(nullptr), + mVORGTable(nullptr), + mLocaTable(nullptr), + mGlyfTable(nullptr), + mCmapTable(nullptr), + mCmapFormat(-1), + mSubtableOffset(0), + mUVSTableOffset(0), + mNumLongHMetrics(0), + mNumLongVMetrics(0), + mDefaultVOrg(-1.0), + mUseFontGetGlyph(aFont->ProvidesGetGlyph()), + mUseFontGlyphWidths(aFont->ProvidesGlyphWidths()), + mInitialized(false), + mVerticalInitialized(false), + mUseVerticalPresentationForms(false), + mLoadedLocaGlyf(false), + mLocaLongOffsets(false) {} + +gfxHarfBuzzShaper::~gfxHarfBuzzShaper() { + // hb_*_destroy functions are safe to call on nullptr + hb_blob_destroy(mCmapTable); + hb_blob_destroy(mHmtxTable); + hb_blob_destroy(mKernTable); + hb_blob_destroy(mVmtxTable); + hb_blob_destroy(mVORGTable); + hb_blob_destroy(mLocaTable); + hb_blob_destroy(mGlyfTable); + hb_font_destroy(mHBFont); + hb_buffer_destroy(mBuffer); +} + +#define UNICODE_BMP_LIMIT 0x10000 + +hb_codepoint_t gfxHarfBuzzShaper::GetNominalGlyph( + hb_codepoint_t unicode) const { + hb_codepoint_t gid = 0; + + if (mUseFontGetGlyph) { + gid = mFont->GetGlyph(unicode, 0); + } else { + // we only instantiate a harfbuzz shaper if there's a cmap available + NS_ASSERTION(mFont->GetFontEntry()->HasCmapTable(), + "we cannot be using this font!"); + + NS_ASSERTION(mCmapTable && (mCmapFormat > 0) && (mSubtableOffset > 0), + "cmap data not correctly set up, expect disaster"); + + uint32_t length; + const uint8_t* data = (const uint8_t*)hb_blob_get_data(mCmapTable, &length); + + switch (mCmapFormat) { + case 4: + gid = + unicode < UNICODE_BMP_LIMIT + ? gfxFontUtils::MapCharToGlyphFormat4( + data + mSubtableOffset, length - mSubtableOffset, unicode) + : 0; + break; + case 10: + gid = gfxFontUtils::MapCharToGlyphFormat10(data + mSubtableOffset, + unicode); + break; + case 12: + case 13: + gid = gfxFontUtils::MapCharToGlyphFormat12or13(data + mSubtableOffset, + unicode); + break; + default: + NS_WARNING("unsupported cmap format, glyphs will be missing"); + break; + } + } + + if (!gid) { + switch (unicode) { + case 0xA0: + // if there's no glyph for , just use the space glyph instead. + gid = mFont->GetSpaceGlyph(); + break; + case 0x2010: + case 0x2011: + // For Unicode HYPHEN and NON-BREAKING HYPHEN, fall back to the ASCII + // HYPHEN-MINUS as a substitute. + gid = GetNominalGlyph('-'); + break; + } + } + + return gid; +} + +hb_codepoint_t gfxHarfBuzzShaper::GetVariationGlyph( + hb_codepoint_t unicode, hb_codepoint_t variation_selector) const { + if (mUseFontGetGlyph) { + return mFont->GetGlyph(unicode, variation_selector); + } + + NS_ASSERTION(mFont->GetFontEntry()->HasCmapTable(), + "we cannot be using this font!"); + NS_ASSERTION(mCmapTable && (mCmapFormat > 0) && (mSubtableOffset > 0), + "cmap data not correctly set up, expect disaster"); + + uint32_t length; + const uint8_t* data = (const uint8_t*)hb_blob_get_data(mCmapTable, &length); + + if (mUVSTableOffset) { + hb_codepoint_t gid = gfxFontUtils::MapUVSToGlyphFormat14( + data + mUVSTableOffset, unicode, variation_selector); + if (gid) { + return gid; + } + } + + uint32_t compat = gfxFontUtils::GetUVSFallback(unicode, variation_selector); + if (compat) { + switch (mCmapFormat) { + case 4: + if (compat < UNICODE_BMP_LIMIT) { + return gfxFontUtils::MapCharToGlyphFormat4( + data + mSubtableOffset, length - mSubtableOffset, compat); + } + break; + case 10: + return gfxFontUtils::MapCharToGlyphFormat10(data + mSubtableOffset, + compat); + break; + case 12: + case 13: + return gfxFontUtils::MapCharToGlyphFormat12or13(data + mSubtableOffset, + compat); + break; + } + } + + return 0; +} + +static int VertFormsGlyphCompare(const void* aKey, const void* aElem) { + return int(*((hb_codepoint_t*)(aKey))) - int(*((uint16_t*)(aElem))); +} + +// Return a vertical presentation-form codepoint corresponding to the +// given Unicode value, or 0 if no such form is available. +hb_codepoint_t gfxHarfBuzzShaper::GetVerticalPresentationForm( + hb_codepoint_t aUnicode) { + static const uint16_t sVerticalForms[][2] = { + {0x2013, 0xfe32}, // EN DASH + {0x2014, 0xfe31}, // EM DASH + {0x2025, 0xfe30}, // TWO DOT LEADER + {0x2026, 0xfe19}, // HORIZONTAL ELLIPSIS + {0x3001, 0xfe11}, // IDEOGRAPHIC COMMA + {0x3002, 0xfe12}, // IDEOGRAPHIC FULL STOP + {0x3008, 0xfe3f}, // LEFT ANGLE BRACKET + {0x3009, 0xfe40}, // RIGHT ANGLE BRACKET + {0x300a, 0xfe3d}, // LEFT DOUBLE ANGLE BRACKET + {0x300b, 0xfe3e}, // RIGHT DOUBLE ANGLE BRACKET + {0x300c, 0xfe41}, // LEFT CORNER BRACKET + {0x300d, 0xfe42}, // RIGHT CORNER BRACKET + {0x300e, 0xfe43}, // LEFT WHITE CORNER BRACKET + {0x300f, 0xfe44}, // RIGHT WHITE CORNER BRACKET + {0x3010, 0xfe3b}, // LEFT BLACK LENTICULAR BRACKET + {0x3011, 0xfe3c}, // RIGHT BLACK LENTICULAR BRACKET + {0x3014, 0xfe39}, // LEFT TORTOISE SHELL BRACKET + {0x3015, 0xfe3a}, // RIGHT TORTOISE SHELL BRACKET + {0x3016, 0xfe17}, // LEFT WHITE LENTICULAR BRACKET + {0x3017, 0xfe18}, // RIGHT WHITE LENTICULAR BRACKET + {0xfe4f, 0xfe34}, // WAVY LOW LINE + {0xff01, 0xfe15}, // FULLWIDTH EXCLAMATION MARK + {0xff08, 0xfe35}, // FULLWIDTH LEFT PARENTHESIS + {0xff09, 0xfe36}, // FULLWIDTH RIGHT PARENTHESIS + {0xff0c, 0xfe10}, // FULLWIDTH COMMA + {0xff1a, 0xfe13}, // FULLWIDTH COLON + {0xff1b, 0xfe14}, // FULLWIDTH SEMICOLON + {0xff1f, 0xfe16}, // FULLWIDTH QUESTION MARK + {0xff3b, 0xfe47}, // FULLWIDTH LEFT SQUARE BRACKET + {0xff3d, 0xfe48}, // FULLWIDTH RIGHT SQUARE BRACKET + {0xff3f, 0xfe33}, // FULLWIDTH LOW LINE + {0xff5b, 0xfe37}, // FULLWIDTH LEFT CURLY BRACKET + {0xff5d, 0xfe38} // FULLWIDTH RIGHT CURLY BRACKET + }; + const uint16_t* charPair = static_cast<const uint16_t*>( + bsearch(&aUnicode, sVerticalForms, ArrayLength(sVerticalForms), + sizeof(sVerticalForms[0]), VertFormsGlyphCompare)); + return charPair ? charPair[1] : 0; +} + +static hb_bool_t HBGetNominalGlyph(hb_font_t* font, void* font_data, + hb_codepoint_t unicode, + hb_codepoint_t* glyph, void* user_data) { + const gfxHarfBuzzShaper::FontCallbackData* fcd = + static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data); + + if (fcd->mShaper->UseVerticalPresentationForms()) { + hb_codepoint_t verticalForm = + gfxHarfBuzzShaper::GetVerticalPresentationForm(unicode); + if (verticalForm) { + *glyph = fcd->mShaper->GetNominalGlyph(verticalForm); + if (*glyph != 0) { + return true; + } + } + // fall back to the non-vertical form if we didn't find an alternate + } + + *glyph = fcd->mShaper->GetNominalGlyph(unicode); + return *glyph != 0; +} + +static hb_bool_t HBGetVariationGlyph(hb_font_t* font, void* font_data, + hb_codepoint_t unicode, + hb_codepoint_t variation_selector, + hb_codepoint_t* glyph, void* user_data) { + const gfxHarfBuzzShaper::FontCallbackData* fcd = + static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data); + + if (fcd->mShaper->UseVerticalPresentationForms()) { + hb_codepoint_t verticalForm = + gfxHarfBuzzShaper::GetVerticalPresentationForm(unicode); + if (verticalForm) { + *glyph = + fcd->mShaper->GetVariationGlyph(verticalForm, variation_selector); + if (*glyph != 0) { + return true; + } + } + // fall back to the non-vertical form if we didn't find an alternate + } + + *glyph = fcd->mShaper->GetVariationGlyph(unicode, variation_selector); + return *glyph != 0; +} + +// Glyph metrics structures, shared (with appropriate reinterpretation of +// field names) by horizontal and vertical metrics tables. +struct LongMetric { + AutoSwap_PRUint16 advanceWidth; // or advanceHeight, when vertical + AutoSwap_PRInt16 lsb; // or tsb, when vertical +}; + +struct GlyphMetrics { + LongMetric metrics[1]; // actually numberOfLongMetrics + // the variable-length metrics[] array is immediately followed by: + // AutoSwap_PRUint16 leftSideBearing[]; +}; + +hb_position_t gfxHarfBuzzShaper::GetGlyphHAdvance(hb_codepoint_t glyph) const { + // font did not implement GetGlyphWidth, so get an unhinted value + // directly from the font tables + + NS_ASSERTION((mNumLongHMetrics > 0) && mHmtxTable != nullptr, + "font is lacking metrics, we shouldn't be here"); + + if (glyph >= uint32_t(mNumLongHMetrics)) { + glyph = mNumLongHMetrics - 1; + } + + // glyph must be valid now, because we checked during initialization + // that mNumLongHMetrics is > 0, and that the metrics table is large enough + // to contain mNumLongHMetrics records + const ::GlyphMetrics* metrics = reinterpret_cast<const ::GlyphMetrics*>( + hb_blob_get_data(mHmtxTable, nullptr)); + return FloatToFixed(mFont->FUnitsToDevUnitsFactor() * + uint16_t(metrics->metrics[glyph].advanceWidth)); +} + +hb_position_t gfxHarfBuzzShaper::GetGlyphVAdvance(hb_codepoint_t glyph) const { + if (!mVmtxTable) { + // Must be a "vertical" font that doesn't actually have vertical metrics; + // use a fixed advance. + return FloatToFixed( + mFont->GetMetrics(nsFontMetrics::eVertical).aveCharWidth); + } + + NS_ASSERTION(mNumLongVMetrics > 0, + "font is lacking metrics, we shouldn't be here"); + + if (glyph >= uint32_t(mNumLongVMetrics)) { + glyph = mNumLongVMetrics - 1; + } + + // glyph must be valid now, because we checked during initialization + // that mNumLongVMetrics is > 0, and that the metrics table is large enough + // to contain mNumLongVMetrics records + const ::GlyphMetrics* metrics = reinterpret_cast<const ::GlyphMetrics*>( + hb_blob_get_data(mVmtxTable, nullptr)); + return FloatToFixed(mFont->FUnitsToDevUnitsFactor() * + uint16_t(metrics->metrics[glyph].advanceWidth)); +} + +/* static */ +hb_position_t gfxHarfBuzzShaper::HBGetGlyphHAdvance(hb_font_t* font, + void* font_data, + hb_codepoint_t glyph, + void* user_data) { + const gfxHarfBuzzShaper::FontCallbackData* fcd = + static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data); + const gfxHarfBuzzShaper* shaper = fcd->mShaper; + if (shaper->mUseFontGlyphWidths) { + return shaper->GetFont()->GetGlyphWidth(glyph); + } + return shaper->GetGlyphHAdvance(glyph); +} + +/* static */ +hb_position_t gfxHarfBuzzShaper::HBGetGlyphVAdvance(hb_font_t* font, + void* font_data, + hb_codepoint_t glyph, + void* user_data) { + const gfxHarfBuzzShaper::FontCallbackData* fcd = + static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data); + // Currently, we don't offer gfxFont subclasses a method to override this + // and provide hinted platform-specific vertical advances (analogous to the + // GetGlyphWidth method for horizontal advances). If that proves necessary, + // we'll add a new gfxFont method and call it from here. + // + // We negate the value from GetGlyphVAdvance here because harfbuzz shapes + // with a coordinate system where positive is upwards, whereas the inline + // direction in which glyphs advance is downwards. + return -fcd->mShaper->GetGlyphVAdvance(glyph); +} + +struct VORG { + AutoSwap_PRUint16 majorVersion; + AutoSwap_PRUint16 minorVersion; + AutoSwap_PRInt16 defaultVertOriginY; + AutoSwap_PRUint16 numVertOriginYMetrics; +}; + +struct VORGrec { + AutoSwap_PRUint16 glyphIndex; + AutoSwap_PRInt16 vertOriginY; +}; + +/* static */ +hb_bool_t gfxHarfBuzzShaper::HBGetGlyphVOrigin(hb_font_t* font, void* font_data, + hb_codepoint_t glyph, + hb_position_t* x, + hb_position_t* y, + void* user_data) { + const gfxHarfBuzzShaper::FontCallbackData* fcd = + static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data); + fcd->mShaper->GetGlyphVOrigin(glyph, x, y); + return true; +} + +void gfxHarfBuzzShaper::GetGlyphVOrigin(hb_codepoint_t aGlyph, + hb_position_t* aX, + hb_position_t* aY) const { + *aX = 0.5 * (mUseFontGlyphWidths ? mFont->GetGlyphWidth(aGlyph) + : GetGlyphHAdvance(aGlyph)); + + if (mVORGTable) { + // We checked in Initialize() that the VORG table is safely readable, + // so no length/bounds-check needed here. + const VORG* vorg = + reinterpret_cast<const VORG*>(hb_blob_get_data(mVORGTable, nullptr)); + + const VORGrec* lo = reinterpret_cast<const VORGrec*>(vorg + 1); + const VORGrec* hi = lo + uint16_t(vorg->numVertOriginYMetrics); + const VORGrec* limit = hi; + while (lo < hi) { + const VORGrec* mid = lo + (hi - lo) / 2; + if (uint16_t(mid->glyphIndex) < aGlyph) { + lo = mid + 1; + } else { + hi = mid; + } + } + + if (lo < limit && uint16_t(lo->glyphIndex) == aGlyph) { + *aY = FloatToFixed(GetFont()->FUnitsToDevUnitsFactor() * + int16_t(lo->vertOriginY)); + } else { + *aY = FloatToFixed(GetFont()->FUnitsToDevUnitsFactor() * + int16_t(vorg->defaultVertOriginY)); + } + return; + } + + if (mVmtxTable) { + bool emptyGlyf; + const Glyf* glyf = FindGlyf(aGlyph, &emptyGlyf); + if (glyf) { + if (emptyGlyf) { + *aY = 0; + return; + } + + const ::GlyphMetrics* metrics = reinterpret_cast<const ::GlyphMetrics*>( + hb_blob_get_data(mVmtxTable, nullptr)); + int16_t lsb; + if (aGlyph < hb_codepoint_t(mNumLongVMetrics)) { + // Glyph is covered by the first (advance & sidebearing) array + lsb = int16_t(metrics->metrics[aGlyph].lsb); + } else { + // Glyph is covered by the second (sidebearing-only) array + const AutoSwap_PRInt16* sidebearings = + reinterpret_cast<const AutoSwap_PRInt16*>( + &metrics->metrics[mNumLongVMetrics]); + lsb = int16_t(sidebearings[aGlyph - mNumLongVMetrics]); + } + *aY = FloatToFixed(mFont->FUnitsToDevUnitsFactor() * + (lsb + int16_t(glyf->yMax))); + return; + } else { + // XXX TODO: not a truetype font; need to get glyph extents + // via some other API? + // For now, fall through to default code below. + } + } + + if (mDefaultVOrg < 0.0) { + // XXX should we consider using OS/2 sTypo* metrics if available? + + gfxFontEntry::AutoTable hheaTable(GetFont()->GetFontEntry(), + TRUETYPE_TAG('h', 'h', 'e', 'a')); + if (hheaTable) { + uint32_t len; + const MetricsHeader* hhea = reinterpret_cast<const MetricsHeader*>( + hb_blob_get_data(hheaTable, &len)); + if (len >= sizeof(MetricsHeader)) { + // divide up the default advance we're using (1em) in proportion + // to ascender:descender from the hhea table + int16_t a = int16_t(hhea->ascender); + int16_t d = int16_t(hhea->descender); + mDefaultVOrg = FloatToFixed(GetFont()->GetAdjustedSize() * a / (a - d)); + } + } + + if (mDefaultVOrg < 0.0) { + // Last resort, for non-sfnt fonts: get the horizontal metrics and + // compute a default VOrg from their ascent and descent. + const gfxFont::Metrics& mtx = mFont->GetHorizontalMetrics(); + gfxFloat advance = + mFont->GetMetrics(nsFontMetrics::eVertical).aveCharWidth; + gfxFloat ascent = mtx.emAscent; + gfxFloat height = ascent + mtx.emDescent; + // vOrigin that will place the glyph so that its origin is shifted + // down most of the way within overall (vertical) advance, in + // proportion to the font ascent as a part of the overall font + // height. + mDefaultVOrg = FloatToFixed(advance * ascent / height); + } + } + + *aY = mDefaultVOrg; +} + +static hb_bool_t HBGetGlyphExtents(hb_font_t* font, void* font_data, + hb_codepoint_t glyph, + hb_glyph_extents_t* extents, + void* user_data) { + const gfxHarfBuzzShaper::FontCallbackData* fcd = + static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data); + return fcd->mShaper->GetGlyphExtents(glyph, extents); +} + +// Find the data for glyph ID |aGlyph| in the 'glyf' table, if present. +// Returns null if not found, otherwise pointer to the beginning of the +// glyph's data. Sets aEmptyGlyf true if there is no actual data; +// otherwise, it's guaranteed that we can read at least the bounding box. +const gfxHarfBuzzShaper::Glyf* gfxHarfBuzzShaper::FindGlyf( + hb_codepoint_t aGlyph, bool* aEmptyGlyf) const { + if (!mLoadedLocaGlyf) { + mLoadedLocaGlyf = true; // only try this once; if it fails, this + // isn't a truetype font + gfxFontEntry* entry = mFont->GetFontEntry(); + uint32_t len; + gfxFontEntry::AutoTable headTable(entry, TRUETYPE_TAG('h', 'e', 'a', 'd')); + if (!headTable) { + return nullptr; + } + const HeadTable* head = + reinterpret_cast<const HeadTable*>(hb_blob_get_data(headTable, &len)); + if (len < sizeof(HeadTable)) { + return nullptr; + } + mLocaLongOffsets = int16_t(head->indexToLocFormat) > 0; + mLocaTable = entry->GetFontTable(TRUETYPE_TAG('l', 'o', 'c', 'a')); + mGlyfTable = entry->GetFontTable(TRUETYPE_TAG('g', 'l', 'y', 'f')); + } + + if (!mLocaTable || !mGlyfTable) { + // it's not a truetype font + return nullptr; + } + + uint32_t offset; // offset of glyph record in the 'glyf' table + uint32_t len; + const char* data = hb_blob_get_data(mLocaTable, &len); + if (mLocaLongOffsets) { + if ((aGlyph + 1) * sizeof(AutoSwap_PRUint32) > len) { + return nullptr; + } + const AutoSwap_PRUint32* offsets = + reinterpret_cast<const AutoSwap_PRUint32*>(data); + offset = offsets[aGlyph]; + *aEmptyGlyf = (offset == uint16_t(offsets[aGlyph + 1])); + } else { + if ((aGlyph + 1) * sizeof(AutoSwap_PRUint16) > len) { + return nullptr; + } + const AutoSwap_PRUint16* offsets = + reinterpret_cast<const AutoSwap_PRUint16*>(data); + offset = uint16_t(offsets[aGlyph]); + *aEmptyGlyf = (offset == uint16_t(offsets[aGlyph + 1])); + offset *= 2; + } + + data = hb_blob_get_data(mGlyfTable, &len); + if (offset + sizeof(Glyf) > len) { + return nullptr; + } + + return reinterpret_cast<const Glyf*>(data + offset); +} + +hb_bool_t gfxHarfBuzzShaper::GetGlyphExtents( + hb_codepoint_t aGlyph, hb_glyph_extents_t* aExtents) const { + bool emptyGlyf; + const Glyf* glyf = FindGlyf(aGlyph, &emptyGlyf); + if (!glyf) { + // TODO: for non-truetype fonts, get extents some other way? + return false; + } + + if (emptyGlyf) { + aExtents->x_bearing = 0; + aExtents->y_bearing = 0; + aExtents->width = 0; + aExtents->height = 0; + return true; + } + + double f = mFont->FUnitsToDevUnitsFactor(); + aExtents->x_bearing = FloatToFixed(int16_t(glyf->xMin) * f); + aExtents->width = + FloatToFixed((int16_t(glyf->xMax) - int16_t(glyf->xMin)) * f); + + // Our y-coordinates are positive-downwards, whereas harfbuzz assumes + // positive-upwards; hence the apparently-reversed subtractions here. + aExtents->y_bearing = FloatToFixed(int16_t(glyf->yMax) * f - + mFont->GetHorizontalMetrics().emAscent); + aExtents->height = + FloatToFixed((int16_t(glyf->yMin) - int16_t(glyf->yMax)) * f); + + return true; +} + +static hb_bool_t HBGetContourPoint(hb_font_t* font, void* font_data, + unsigned int point_index, + hb_codepoint_t glyph, hb_position_t* x, + hb_position_t* y, void* user_data) { + /* not yet implemented - no support for used of hinted contour points + to fine-tune anchor positions in GPOS AnchorFormat2 */ + return false; +} + +struct KernHeaderFmt0 { + AutoSwap_PRUint16 nPairs; + AutoSwap_PRUint16 searchRange; + AutoSwap_PRUint16 entrySelector; + AutoSwap_PRUint16 rangeShift; +}; + +struct KernPair { + AutoSwap_PRUint16 left; + AutoSwap_PRUint16 right; + AutoSwap_PRInt16 value; +}; + +// Find a kern pair in a Format 0 subtable. +// The aSubtable parameter points to the subtable itself, NOT its header, +// as the header structure differs between Windows and Mac (v0 and v1.0) +// versions of the 'kern' table. +// aSubtableLen is the length of the subtable EXCLUDING its header. +// If the pair <aFirstGlyph,aSecondGlyph> is found, the kerning value is +// added to aValue, so that multiple subtables can accumulate a total +// kerning value for a given pair. +static void GetKernValueFmt0(const void* aSubtable, uint32_t aSubtableLen, + uint16_t aFirstGlyph, uint16_t aSecondGlyph, + int32_t& aValue, bool aIsOverride = false, + bool aIsMinimum = false) { + const KernHeaderFmt0* hdr = + reinterpret_cast<const KernHeaderFmt0*>(aSubtable); + + const KernPair* lo = reinterpret_cast<const KernPair*>(hdr + 1); + const KernPair* hi = lo + uint16_t(hdr->nPairs); + const KernPair* limit = hi; + + if (reinterpret_cast<const char*>(aSubtable) + aSubtableLen < + reinterpret_cast<const char*>(hi)) { + // subtable is not large enough to contain the claimed number + // of kern pairs, so just ignore it + return; + } + +#define KERN_PAIR_KEY(l, r) (uint32_t((uint16_t(l) << 16) + uint16_t(r))) + + uint32_t key = KERN_PAIR_KEY(aFirstGlyph, aSecondGlyph); + while (lo < hi) { + const KernPair* mid = lo + (hi - lo) / 2; + if (KERN_PAIR_KEY(mid->left, mid->right) < key) { + lo = mid + 1; + } else { + hi = mid; + } + } + + if (lo < limit && KERN_PAIR_KEY(lo->left, lo->right) == key) { + if (aIsOverride) { + aValue = int16_t(lo->value); + } else if (aIsMinimum) { + aValue = std::max(aValue, int32_t(lo->value)); + } else { + aValue += int16_t(lo->value); + } + } +} + +// Get kerning value from Apple (version 1.0) kern table, +// subtable format 2 (simple N x M array of kerning values) + +// See http://developer.apple.com/fonts/TTRefMan/RM06/Chap6kern.html +// for details of version 1.0 format 2 subtable. + +struct KernHeaderVersion1Fmt2 { + KernTableSubtableHeaderVersion1 header; + AutoSwap_PRUint16 rowWidth; + AutoSwap_PRUint16 leftOffsetTable; + AutoSwap_PRUint16 rightOffsetTable; + AutoSwap_PRUint16 array; +}; + +struct KernClassTableHdr { + AutoSwap_PRUint16 firstGlyph; + AutoSwap_PRUint16 nGlyphs; + AutoSwap_PRUint16 offsets[1]; // actually an array of nGlyphs entries +}; + +static int16_t GetKernValueVersion1Fmt2(const void* aSubtable, + uint32_t aSubtableLen, + uint16_t aFirstGlyph, + uint16_t aSecondGlyph) { + if (aSubtableLen < sizeof(KernHeaderVersion1Fmt2)) { + return 0; + } + + const char* base = reinterpret_cast<const char*>(aSubtable); + const char* subtableEnd = base + aSubtableLen; + + const KernHeaderVersion1Fmt2* h = + reinterpret_cast<const KernHeaderVersion1Fmt2*>(aSubtable); + uint32_t offset = h->array; + + const KernClassTableHdr* leftClassTable = + reinterpret_cast<const KernClassTableHdr*>(base + + uint16_t(h->leftOffsetTable)); + if (reinterpret_cast<const char*>(leftClassTable) + + sizeof(KernClassTableHdr) > + subtableEnd) { + return 0; + } + if (aFirstGlyph >= uint16_t(leftClassTable->firstGlyph)) { + aFirstGlyph -= uint16_t(leftClassTable->firstGlyph); + if (aFirstGlyph < uint16_t(leftClassTable->nGlyphs)) { + if (reinterpret_cast<const char*>(leftClassTable) + + sizeof(KernClassTableHdr) + aFirstGlyph * sizeof(uint16_t) >= + subtableEnd) { + return 0; + } + offset = uint16_t(leftClassTable->offsets[aFirstGlyph]); + } + } + + const KernClassTableHdr* rightClassTable = + reinterpret_cast<const KernClassTableHdr*>(base + + uint16_t(h->rightOffsetTable)); + if (reinterpret_cast<const char*>(rightClassTable) + + sizeof(KernClassTableHdr) > + subtableEnd) { + return 0; + } + if (aSecondGlyph >= uint16_t(rightClassTable->firstGlyph)) { + aSecondGlyph -= uint16_t(rightClassTable->firstGlyph); + if (aSecondGlyph < uint16_t(rightClassTable->nGlyphs)) { + if (reinterpret_cast<const char*>(rightClassTable) + + sizeof(KernClassTableHdr) + aSecondGlyph * sizeof(uint16_t) >= + subtableEnd) { + return 0; + } + offset += uint16_t(rightClassTable->offsets[aSecondGlyph]); + } + } + + const AutoSwap_PRInt16* pval = + reinterpret_cast<const AutoSwap_PRInt16*>(base + offset); + if (reinterpret_cast<const char*>(pval + 1) >= subtableEnd) { + return 0; + } + return *pval; +} + +// Get kerning value from Apple (version 1.0) kern table, +// subtable format 3 (simple N x M array of kerning values) + +// See http://developer.apple.com/fonts/TTRefMan/RM06/Chap6kern.html +// for details of version 1.0 format 3 subtable. + +struct KernHeaderVersion1Fmt3 { + KernTableSubtableHeaderVersion1 header; + AutoSwap_PRUint16 glyphCount; + uint8_t kernValueCount; + uint8_t leftClassCount; + uint8_t rightClassCount; + uint8_t flags; +}; + +static int16_t GetKernValueVersion1Fmt3(const void* aSubtable, + uint32_t aSubtableLen, + uint16_t aFirstGlyph, + uint16_t aSecondGlyph) { + // check that we can safely read the header fields + if (aSubtableLen < sizeof(KernHeaderVersion1Fmt3)) { + return 0; + } + + const KernHeaderVersion1Fmt3* hdr = + reinterpret_cast<const KernHeaderVersion1Fmt3*>(aSubtable); + if (hdr->flags != 0) { + return 0; + } + + uint16_t glyphCount = hdr->glyphCount; + + // check that table is large enough for the arrays + if (sizeof(KernHeaderVersion1Fmt3) + hdr->kernValueCount * sizeof(int16_t) + + glyphCount + glyphCount + hdr->leftClassCount * hdr->rightClassCount > + aSubtableLen) { + return 0; + } + + if (aFirstGlyph >= glyphCount || aSecondGlyph >= glyphCount) { + // glyphs are out of range for the class tables + return 0; + } + + // get pointers to the four arrays within the subtable + const AutoSwap_PRInt16* kernValue = + reinterpret_cast<const AutoSwap_PRInt16*>(hdr + 1); + const uint8_t* leftClass = + reinterpret_cast<const uint8_t*>(kernValue + hdr->kernValueCount); + const uint8_t* rightClass = leftClass + glyphCount; + const uint8_t* kernIndex = rightClass + glyphCount; + + uint8_t lc = leftClass[aFirstGlyph]; + uint8_t rc = rightClass[aSecondGlyph]; + if (lc >= hdr->leftClassCount || rc >= hdr->rightClassCount) { + return 0; + } + + uint8_t ki = kernIndex[leftClass[aFirstGlyph] * hdr->rightClassCount + + rightClass[aSecondGlyph]]; + if (ki >= hdr->kernValueCount) { + return 0; + } + + return kernValue[ki]; +} + +#define KERN0_COVERAGE_HORIZONTAL 0x0001 +#define KERN0_COVERAGE_MINIMUM 0x0002 +#define KERN0_COVERAGE_CROSS_STREAM 0x0004 +#define KERN0_COVERAGE_OVERRIDE 0x0008 +#define KERN0_COVERAGE_RESERVED 0x00F0 + +#define KERN1_COVERAGE_VERTICAL 0x8000 +#define KERN1_COVERAGE_CROSS_STREAM 0x4000 +#define KERN1_COVERAGE_VARIATION 0x2000 +#define KERN1_COVERAGE_RESERVED 0x1F00 + +hb_position_t gfxHarfBuzzShaper::GetHKerning(uint16_t aFirstGlyph, + uint16_t aSecondGlyph) const { + // We want to ignore any kern pairs involving <space>, because we are + // handling words in isolation, the only space characters seen here are + // the ones artificially added by the textRun code. + uint32_t spaceGlyph = mFont->GetSpaceGlyph(); + if (aFirstGlyph == spaceGlyph || aSecondGlyph == spaceGlyph) { + return 0; + } + + if (!mKernTable) { + mKernTable = + mFont->GetFontEntry()->GetFontTable(TRUETYPE_TAG('k', 'e', 'r', 'n')); + if (!mKernTable) { + mKernTable = hb_blob_get_empty(); + } + } + + uint32_t len; + const char* base = hb_blob_get_data(mKernTable, &len); + if (len < sizeof(KernTableVersion0)) { + return 0; + } + int32_t value = 0; + + // First try to interpret as "version 0" kern table + // (see http://www.microsoft.com/typography/otspec/kern.htm) + const KernTableVersion0* kern0 = + reinterpret_cast<const KernTableVersion0*>(base); + if (uint16_t(kern0->version) == 0) { + uint16_t nTables = kern0->nTables; + uint32_t offs = sizeof(KernTableVersion0); + for (uint16_t i = 0; i < nTables; ++i) { + if (offs + sizeof(KernTableSubtableHeaderVersion0) > len) { + break; + } + const KernTableSubtableHeaderVersion0* st0 = + reinterpret_cast<const KernTableSubtableHeaderVersion0*>(base + offs); + uint16_t subtableLen = uint16_t(st0->length); + if (offs + subtableLen > len) { + break; + } + offs += subtableLen; + uint16_t coverage = st0->coverage; + if (!(coverage & KERN0_COVERAGE_HORIZONTAL)) { + // we only care about horizontal kerning (for now) + continue; + } + if (coverage & (KERN0_COVERAGE_CROSS_STREAM | KERN0_COVERAGE_RESERVED)) { + // we don't support cross-stream kerning, and + // reserved bits should be zero; + // ignore the subtable if not + continue; + } + uint8_t format = (coverage >> 8); + switch (format) { + case 0: + GetKernValueFmt0(st0 + 1, subtableLen - sizeof(*st0), aFirstGlyph, + aSecondGlyph, value, + (coverage & KERN0_COVERAGE_OVERRIDE) != 0, + (coverage & KERN0_COVERAGE_MINIMUM) != 0); + break; + default: + // TODO: implement support for other formats, + // if they're ever used in practice +#if DEBUG + { + char buf[1024]; + SprintfLiteral(buf, + "unknown kern subtable in %s: " + "ver 0 format %d\n", + mFont->GetName().get(), format); + NS_WARNING(buf); + } +#endif + break; + } + } + } else { + // It wasn't a "version 0" table; check if it is Apple version 1.0 + // (see http://developer.apple.com/fonts/TTRefMan/RM06/Chap6kern.html) + const KernTableVersion1* kern1 = + reinterpret_cast<const KernTableVersion1*>(base); + if (uint32_t(kern1->version) == 0x00010000) { + uint32_t nTables = kern1->nTables; + uint32_t offs = sizeof(KernTableVersion1); + for (uint32_t i = 0; i < nTables; ++i) { + if (offs + sizeof(KernTableSubtableHeaderVersion1) > len) { + break; + } + const KernTableSubtableHeaderVersion1* st1 = + reinterpret_cast<const KernTableSubtableHeaderVersion1*>(base + + offs); + uint32_t subtableLen = uint32_t(st1->length); + offs += subtableLen; + uint16_t coverage = st1->coverage; + if (coverage & (KERN1_COVERAGE_VERTICAL | KERN1_COVERAGE_CROSS_STREAM | + KERN1_COVERAGE_VARIATION | KERN1_COVERAGE_RESERVED)) { + // we only care about horizontal kerning (for now), + // we don't support cross-stream kerning, + // we don't support variations, + // reserved bits should be zero; + // ignore the subtable if not + continue; + } + uint8_t format = (coverage & 0xff); + switch (format) { + case 0: + GetKernValueFmt0(st1 + 1, subtableLen - sizeof(*st1), aFirstGlyph, + aSecondGlyph, value); + break; + case 2: + value = GetKernValueVersion1Fmt2(st1, subtableLen, aFirstGlyph, + aSecondGlyph); + break; + case 3: + value = GetKernValueVersion1Fmt3(st1, subtableLen, aFirstGlyph, + aSecondGlyph); + break; + default: + // TODO: implement support for other formats. + // Note that format 1 cannot be supported here, + // as it requires the full glyph array to run the FSM, + // not just the current glyph pair. +#if DEBUG + { + char buf[1024]; + SprintfLiteral(buf, + "unknown kern subtable in %s: " + "ver 0 format %d\n", + mFont->GetName().get(), format); + NS_WARNING(buf); + } +#endif + break; + } + } + } + } + + if (value != 0) { + return FloatToFixed(mFont->FUnitsToDevUnitsFactor() * value); + } + return 0; +} + +static hb_position_t HBGetHKerning(hb_font_t* font, void* font_data, + hb_codepoint_t first_glyph, + hb_codepoint_t second_glyph, + void* user_data) { + const gfxHarfBuzzShaper::FontCallbackData* fcd = + static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data); + return fcd->mShaper->GetHKerning(first_glyph, second_glyph); +} + +/* + * HarfBuzz unicode property callbacks + */ + +static hb_codepoint_t HBGetMirroring(hb_unicode_funcs_t* ufuncs, + hb_codepoint_t aCh, void* user_data) { + return 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 (sNormalizer) { + UChar32 ch = unorm2_composePair(sNormalizer, a, b); + if (ch >= 0) { + *ab = ch; + return true; + } + } + + return false; +} + +static hb_bool_t HBUnicodeDecompose(hb_unicode_funcs_t* ufuncs, + hb_codepoint_t ab, hb_codepoint_t* a, + hb_codepoint_t* b, void* user_data) { +#ifdef MOZ_WIDGET_ANDROID + // Hack for the SamsungDevanagari font, bug 1012365: + // support U+0972 by decomposing it. + if (ab == 0x0972) { + *a = 0x0905; + *b = 0x0945; + return true; + } +#endif + + 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; +} + +static void AddOpenTypeFeature(const uint32_t& aTag, uint32_t& aValue, + void* aUserArg) { + nsTArray<hb_feature_t>* features = + static_cast<nsTArray<hb_feature_t>*>(aUserArg); + + hb_feature_t feat = {0, 0, 0, UINT_MAX}; + feat.tag = aTag; + feat.value = aValue; + features->AppendElement(feat); +} + +/* + * gfxFontShaper override to initialize the text run using HarfBuzz + */ + +static hb_font_funcs_t* sHBFontFuncs = nullptr; +static hb_font_funcs_t* sNominalGlyphFunc = nullptr; +static hb_unicode_funcs_t* sHBUnicodeFuncs = nullptr; +static const hb_script_t sMathScript = + hb_ot_tag_to_script(HB_TAG('m', 'a', 't', 'h')); + +bool gfxHarfBuzzShaper::Initialize() { + if (mInitialized) { + return mHBFont != nullptr; + } + mInitialized = true; + mCallbackData.mShaper = this; + + if (!sHBFontFuncs) { + // static function callback pointers, initialized by the first + // harfbuzz shaper used + sHBFontFuncs = hb_font_funcs_create(); + hb_font_funcs_set_nominal_glyph_func(sHBFontFuncs, HBGetNominalGlyph, + nullptr, nullptr); + hb_font_funcs_set_variation_glyph_func(sHBFontFuncs, HBGetVariationGlyph, + nullptr, nullptr); + hb_font_funcs_set_glyph_h_advance_func(sHBFontFuncs, HBGetGlyphHAdvance, + nullptr, nullptr); + hb_font_funcs_set_glyph_v_advance_func(sHBFontFuncs, HBGetGlyphVAdvance, + nullptr, nullptr); + hb_font_funcs_set_glyph_v_origin_func(sHBFontFuncs, HBGetGlyphVOrigin, + nullptr, nullptr); + hb_font_funcs_set_glyph_extents_func(sHBFontFuncs, HBGetGlyphExtents, + nullptr, nullptr); + hb_font_funcs_set_glyph_contour_point_func(sHBFontFuncs, HBGetContourPoint, + nullptr, nullptr); + hb_font_funcs_set_glyph_h_kerning_func(sHBFontFuncs, HBGetHKerning, nullptr, + nullptr); + hb_font_funcs_make_immutable(sHBFontFuncs); + + sNominalGlyphFunc = hb_font_funcs_create(); + hb_font_funcs_set_nominal_glyph_func(sNominalGlyphFunc, HBGetNominalGlyph, + nullptr, nullptr); + hb_font_funcs_make_immutable(sNominalGlyphFunc); + + sHBUnicodeFuncs = hb_unicode_funcs_create(hb_unicode_funcs_get_empty()); + hb_unicode_funcs_set_mirroring_func(sHBUnicodeFuncs, HBGetMirroring, + nullptr, nullptr); + hb_unicode_funcs_set_script_func(sHBUnicodeFuncs, HBGetScript, nullptr, + nullptr); + hb_unicode_funcs_set_general_category_func( + sHBUnicodeFuncs, HBGetGeneralCategory, nullptr, nullptr); + hb_unicode_funcs_set_combining_class_func( + sHBUnicodeFuncs, HBGetCombiningClass, nullptr, nullptr); + hb_unicode_funcs_set_compose_func(sHBUnicodeFuncs, HBUnicodeCompose, + nullptr, nullptr); + hb_unicode_funcs_set_decompose_func(sHBUnicodeFuncs, HBUnicodeDecompose, + nullptr, nullptr); + hb_unicode_funcs_make_immutable(sHBUnicodeFuncs); + + UErrorCode error = U_ZERO_ERROR; + sNormalizer = unorm2_getNFCInstance(&error); + MOZ_ASSERT(U_SUCCESS(error), "failed to get ICU normalizer"); + } + + gfxFontEntry* entry = mFont->GetFontEntry(); + if (!mUseFontGetGlyph) { + // get the cmap table and find offset to our subtable + mCmapTable = entry->GetFontTable(TRUETYPE_TAG('c', 'm', 'a', 'p')); + if (!mCmapTable) { + NS_WARNING("failed to load cmap, glyphs will be missing"); + return false; + } + uint32_t len; + const uint8_t* data = (const uint8_t*)hb_blob_get_data(mCmapTable, &len); + mCmapFormat = gfxFontUtils::FindPreferredSubtable( + data, len, &mSubtableOffset, &mUVSTableOffset); + if (mCmapFormat <= 0) { + return false; + } + } + + if (!mUseFontGlyphWidths) { + // If font doesn't implement GetGlyphWidth, we will be reading + // the metrics table directly, so make sure we can load it. + if (!LoadHmtxTable()) { + return false; + } + } + + mBuffer = hb_buffer_create(); + hb_buffer_set_unicode_funcs(mBuffer, sHBUnicodeFuncs); + hb_buffer_set_cluster_level(mBuffer, + HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); + + auto* funcs = + mFont->GetFontEntry()->HasFontTable(TRUETYPE_TAG('C', 'F', 'F', ' ')) + ? sNominalGlyphFunc + : sHBFontFuncs; + mHBFont = CreateHBFont(mFont, funcs, &mCallbackData); + + return true; +} + +hb_font_t* gfxHarfBuzzShaper::CreateHBFont(gfxFont* aFont, + hb_font_funcs_t* aFontFuncs, + FontCallbackData* aCallbackData) { + hb_face_t* hbFace = aFont->GetFontEntry()->GetHBFace(); + hb_font_t* result = hb_font_create(hbFace); + hb_face_destroy(hbFace); + + if (aFontFuncs && aCallbackData) { + if (aFontFuncs == sNominalGlyphFunc) { + hb_font_t* subfont = hb_font_create_sub_font(result); + hb_font_destroy(result); + result = subfont; + } + hb_font_set_funcs(result, aFontFuncs, aCallbackData, nullptr); + } + hb_font_set_ppem(result, aFont->GetAdjustedSize(), aFont->GetAdjustedSize()); + uint32_t scale = FloatToFixed(aFont->GetAdjustedSize()); // 16.16 fixed-point + hb_font_set_scale(result, scale, scale); + + AutoTArray<gfxFontVariation, 8> vars; + aFont->GetFontEntry()->GetVariationsForStyle(vars, *aFont->GetStyle()); + if (vars.Length() > 0) { + // Fortunately, the hb_variation_t struct is compatible with our + // gfxFontVariation, so we can simply cast here. + static_assert( + sizeof(gfxFontVariation) == sizeof(hb_variation_t) && + offsetof(gfxFontVariation, mTag) == offsetof(hb_variation_t, tag) && + offsetof(gfxFontVariation, mValue) == + offsetof(hb_variation_t, value), + "Gecko vs HarfBuzz struct mismatch!"); + auto hbVars = reinterpret_cast<const hb_variation_t*>(vars.Elements()); + hb_font_set_variations(result, hbVars, vars.Length()); + } + + return result; +} + +bool gfxHarfBuzzShaper::LoadHmtxTable() { + // Read mNumLongHMetrics from metrics-head table without caching its + // blob, and preload/cache the metrics table. + gfxFontEntry* entry = mFont->GetFontEntry(); + gfxFontEntry::AutoTable hheaTable(entry, TRUETYPE_TAG('h', 'h', 'e', 'a')); + if (hheaTable) { + uint32_t len; + const MetricsHeader* hhea = reinterpret_cast<const MetricsHeader*>( + hb_blob_get_data(hheaTable, &len)); + if (len >= sizeof(MetricsHeader)) { + mNumLongHMetrics = hhea->numOfLongMetrics; + if (mNumLongHMetrics > 0 && int16_t(hhea->metricDataFormat) == 0) { + // no point reading metrics if number of entries is zero! + // in that case, we won't be able to use this font + // (this method will return FALSE below if mHmtxTable + // is null) + mHmtxTable = entry->GetFontTable(TRUETYPE_TAG('h', 'm', 't', 'x')); + if (mHmtxTable && hb_blob_get_length(mHmtxTable) < + mNumLongHMetrics * sizeof(LongMetric)) { + // metrics table is not large enough for the claimed + // number of entries: invalid, do not use. + hb_blob_destroy(mHmtxTable); + mHmtxTable = nullptr; + } + } + } + } + if (!mHmtxTable) { + return false; + } + return true; +} + +void gfxHarfBuzzShaper::InitializeVertical() { + // We only do this once. If we don't have a mHmtxTable after that, + // we'll be making up fallback metrics. + if (mVerticalInitialized) { + return; + } + mVerticalInitialized = true; + + if (!mHmtxTable) { + if (!LoadHmtxTable()) { + return; + } + } + + // Load vertical metrics if present in the font; if not, we'll synthesize + // vertical glyph advances based on (horizontal) ascent/descent metrics. + gfxFontEntry* entry = mFont->GetFontEntry(); + gfxFontEntry::AutoTable vheaTable(entry, TRUETYPE_TAG('v', 'h', 'e', 'a')); + if (vheaTable) { + uint32_t len; + const MetricsHeader* vhea = reinterpret_cast<const MetricsHeader*>( + hb_blob_get_data(vheaTable, &len)); + if (len >= sizeof(MetricsHeader)) { + mNumLongVMetrics = vhea->numOfLongMetrics; + gfxFontEntry::AutoTable maxpTable(entry, + TRUETYPE_TAG('m', 'a', 'x', 'p')); + int numGlyphs = -1; // invalid if we fail to read 'maxp' + if (maxpTable && + hb_blob_get_length(maxpTable) >= sizeof(MaxpTableHeader)) { + const MaxpTableHeader* maxp = reinterpret_cast<const MaxpTableHeader*>( + hb_blob_get_data(maxpTable, nullptr)); + numGlyphs = uint16_t(maxp->numGlyphs); + } + if (mNumLongVMetrics > 0 && mNumLongVMetrics <= numGlyphs && + int16_t(vhea->metricDataFormat) == 0) { + mVmtxTable = entry->GetFontTable(TRUETYPE_TAG('v', 'm', 't', 'x')); + if (mVmtxTable && + hb_blob_get_length(mVmtxTable) < + mNumLongVMetrics * sizeof(LongMetric) + + (numGlyphs - mNumLongVMetrics) * sizeof(int16_t)) { + // metrics table is not large enough for the claimed + // number of entries: invalid, do not use. + hb_blob_destroy(mVmtxTable); + mVmtxTable = nullptr; + } + } + } + } + + // For CFF fonts only, load a VORG table if present. + if (entry->HasFontTable(TRUETYPE_TAG('C', 'F', 'F', ' '))) { + mVORGTable = entry->GetFontTable(TRUETYPE_TAG('V', 'O', 'R', 'G')); + if (mVORGTable) { + uint32_t len; + const VORG* vorg = + reinterpret_cast<const VORG*>(hb_blob_get_data(mVORGTable, &len)); + if (len < sizeof(VORG) || uint16_t(vorg->majorVersion) != 1 || + uint16_t(vorg->minorVersion) != 0 || + len < sizeof(VORG) + + uint16_t(vorg->numVertOriginYMetrics) * sizeof(VORGrec)) { + // VORG table is an unknown version, or not large enough + // to be valid -- discard it. + NS_WARNING("discarding invalid VORG table"); + hb_blob_destroy(mVORGTable); + mVORGTable = nullptr; + } + } + } +} + +bool gfxHarfBuzzShaper::ShapeText(DrawTarget* aDrawTarget, + const char16_t* aText, uint32_t aOffset, + uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, + RoundingFlags aRounding, + gfxShapedText* aShapedText) { + mUseVerticalPresentationForms = false; + + if (!Initialize()) { + return false; + } + + if (aVertical) { + InitializeVertical(); + if (!mFont->GetFontEntry()->SupportsOpenTypeFeature( + aScript, HB_TAG('v', 'e', 'r', 't'))) { + mUseVerticalPresentationForms = true; + } + } + + const gfxFontStyle* style = mFont->GetStyle(); + + // determine whether petite-caps falls back to small-caps + bool addSmallCaps = false; + if (style->variantCaps != NS_FONT_VARIANT_CAPS_NORMAL) { + switch (style->variantCaps) { + case NS_FONT_VARIANT_CAPS_ALLPETITE: + case NS_FONT_VARIANT_CAPS_PETITECAPS: + bool synLower, synUpper; + mFont->SupportsVariantCaps(aScript, style->variantCaps, addSmallCaps, + synLower, synUpper); + break; + default: + break; + } + } + + gfxFontEntry* entry = mFont->GetFontEntry(); + + // insert any merged features into hb_feature array + AutoTArray<hb_feature_t, 20> features; + MergeFontFeatures(style, entry->mFeatureSettings, + aShapedText->DisableLigatures(), entry->FamilyName(), + addSmallCaps, AddOpenTypeFeature, &features); + + bool isRightToLeft = aShapedText->IsRightToLeft(); + + hb_buffer_set_direction( + mBuffer, aVertical + ? HB_DIRECTION_TTB + : (isRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR)); + hb_script_t scriptTag; + if (aShapedText->GetFlags() & gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT) { + scriptTag = sMathScript; + } else { + scriptTag = GetHBScriptUsedForShaping(aScript); + } + hb_buffer_set_script(mBuffer, scriptTag); + + hb_language_t language; + if (style->languageOverride) { + language = hb_ot_tag_to_language(style->languageOverride); + } else if (entry->mLanguageOverride) { + language = hb_ot_tag_to_language(entry->mLanguageOverride); + } else if (aLanguage) { + nsCString langString; + aLanguage->ToUTF8String(langString); + language = hb_language_from_string(langString.get(), langString.Length()); + } else { + language = hb_ot_tag_to_language(HB_OT_TAG_DEFAULT_LANGUAGE); + } + hb_buffer_set_language(mBuffer, language); + + uint32_t length = aLength; + hb_buffer_add_utf16(mBuffer, reinterpret_cast<const uint16_t*>(aText), length, + 0, length); + + hb_shape(mHBFont, mBuffer, features.Elements(), features.Length()); + + if (isRightToLeft) { + hb_buffer_reverse(mBuffer); + } + + nsresult rv = SetGlyphsFromRun(aShapedText, aOffset, aLength, aText, + aVertical, aRounding); + + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "failed to store glyphs into gfxShapedWord"); + hb_buffer_clear_contents(mBuffer); + + return NS_SUCCEEDED(rv); +} + +#define SMALL_GLYPH_RUN \ + 128 // some testing indicates that 90%+ of text runs + // will fit without requiring separate allocation + // for charToGlyphArray + +nsresult gfxHarfBuzzShaper::SetGlyphsFromRun(gfxShapedText* aShapedText, + uint32_t aOffset, uint32_t aLength, + const char16_t* aText, + bool aVertical, + RoundingFlags aRounding) { + typedef gfxShapedText::CompressedGlyph CompressedGlyph; + + uint32_t numGlyphs; + const hb_glyph_info_t* ginfo = hb_buffer_get_glyph_infos(mBuffer, &numGlyphs); + if (numGlyphs == 0) { + return NS_OK; + } + + AutoTArray<gfxTextRun::DetailedGlyph, 1> detailedGlyphs; + + uint32_t wordLength = aLength; + static const int32_t NO_GLYPH = -1; + AutoTArray<int32_t, SMALL_GLYPH_RUN> charToGlyphArray; + if (!charToGlyphArray.SetLength(wordLength, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + int32_t* charToGlyph = charToGlyphArray.Elements(); + for (uint32_t offset = 0; offset < wordLength; ++offset) { + charToGlyph[offset] = NO_GLYPH; + } + + for (uint32_t i = 0; i < numGlyphs; ++i) { + uint32_t loc = ginfo[i].cluster; + if (loc < wordLength) { + charToGlyph[loc] = i; + } + } + + int32_t glyphStart = 0; // looking for a clump that starts at this glyph + int32_t charStart = 0; // and this char index within the range of the run + + bool roundI, roundB; + if (aVertical) { + roundI = bool(aRounding & RoundingFlags::kRoundY); + roundB = bool(aRounding & RoundingFlags::kRoundX); + } else { + roundI = bool(aRounding & RoundingFlags::kRoundX); + roundB = bool(aRounding & RoundingFlags::kRoundY); + } + + int32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit(); + CompressedGlyph* charGlyphs = aShapedText->GetCharacterGlyphs() + aOffset; + + // factor to convert 16.16 fixed-point pixels to app units + // (only used if not rounding) + double hb2appUnits = FixedToFloat(aShapedText->GetAppUnitsPerDevUnit()); + + // Residual from rounding of previous advance, for use in rounding the + // subsequent offset or advance appropriately. 16.16 fixed-point + // + // When rounding, the goal is to make the distance between glyphs and + // their base glyph equal to the integral number of pixels closest to that + // suggested by that shaper. + // i.e. posInfo[n].x_advance - posInfo[n].x_offset + posInfo[n+1].x_offset + // + // The value of the residual is the part of the desired distance that has + // not been included in integer offsets. + hb_position_t residual = 0; + + // keep track of y-position to set glyph offsets if needed + nscoord bPos = 0; + + const hb_glyph_position_t* posInfo = + hb_buffer_get_glyph_positions(mBuffer, nullptr); + + while (glyphStart < int32_t(numGlyphs)) { + int32_t charEnd = ginfo[glyphStart].cluster; + int32_t glyphEnd = glyphStart; + int32_t charLimit = wordLength; + while (charEnd < charLimit) { + // This is normally executed once for each iteration of the outer loop, + // but in unusual cases where the character/glyph association is complex, + // the initial character range might correspond to a non-contiguous + // glyph range with "holes" in it. If so, we will repeat this loop to + // extend the character range until we have a contiguous glyph sequence. + charEnd += 1; + while (charEnd != charLimit && charToGlyph[charEnd] == NO_GLYPH) { + charEnd += 1; + } + + // find the maximum glyph index covered by the clump so far + for (int32_t i = charStart; i < charEnd; ++i) { + if (charToGlyph[i] != NO_GLYPH) { + glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1); + // update extent of glyph range + } + } + + if (glyphEnd == glyphStart + 1) { + // for the common case of a single-glyph clump, + // we can skip the following checks + break; + } + + if (glyphEnd == glyphStart) { + // no glyphs, try to extend the clump + continue; + } + + // check whether all glyphs in the range are associated with the + // characters in our clump; if not, we have a discontinuous range, and + // should extend it unless we've reached the end of the text + bool allGlyphsAreWithinCluster = true; + for (int32_t i = glyphStart; i < glyphEnd; ++i) { + int32_t glyphCharIndex = ginfo[i].cluster; + if (glyphCharIndex < charStart || glyphCharIndex >= charEnd) { + allGlyphsAreWithinCluster = false; + break; + } + } + if (allGlyphsAreWithinCluster) { + break; + } + } + + NS_ASSERTION(glyphStart < glyphEnd, + "character/glyph clump contains no glyphs!"); + NS_ASSERTION(charStart != charEnd, + "character/glyph clump contains no characters!"); + + // Now charStart..charEnd is a ligature clump, corresponding to + // glyphStart..glyphEnd; Set baseCharIndex to the char we'll actually attach + // the glyphs to (1st of ligature), and endCharIndex to the limit (position + // beyond the last char), adjusting for the offset of the stringRange + // relative to the textRun. + int32_t baseCharIndex, endCharIndex; + while (charEnd < int32_t(wordLength) && charToGlyph[charEnd] == NO_GLYPH) + charEnd++; + baseCharIndex = charStart; + endCharIndex = charEnd; + + // Then we check if the clump falls outside our actual string range; + // if so, just go to the next. + if (baseCharIndex >= int32_t(wordLength)) { + glyphStart = glyphEnd; + charStart = charEnd; + continue; + } + // Ensure we won't try to go beyond the valid length of the textRun's text + endCharIndex = std::min<int32_t>(endCharIndex, wordLength); + + // Now we're ready to set the glyph info in the textRun + int32_t glyphsInClump = glyphEnd - glyphStart; + + // Check for default-ignorable char that didn't get filtered, combined, + // etc by the shaping process, and remove from the run. + // (This may be done within harfbuzz eventually.) + if (glyphsInClump == 1 && baseCharIndex + 1 == endCharIndex && + aShapedText->FilterIfIgnorable(aOffset + baseCharIndex, + aText[baseCharIndex])) { + glyphStart = glyphEnd; + charStart = charEnd; + continue; + } + + // HarfBuzz gives us physical x- and y-coordinates, but we will store + // them as logical inline- and block-direction values in the textrun. + + hb_position_t i_offset, i_advance; // inline-direction offset/advance + hb_position_t b_offset, b_advance; // block-direction offset/advance + if (aVertical) { + // our coordinate directions are the opposite of harfbuzz's + // when doing top-to-bottom shaping + i_offset = -posInfo[glyphStart].y_offset; + i_advance = -posInfo[glyphStart].y_advance; + b_offset = -posInfo[glyphStart].x_offset; + b_advance = -posInfo[glyphStart].x_advance; + } else { + i_offset = posInfo[glyphStart].x_offset; + i_advance = posInfo[glyphStart].x_advance; + b_offset = posInfo[glyphStart].y_offset; + b_advance = posInfo[glyphStart].y_advance; + } + + nscoord iOffset, advance; + if (roundI) { + iOffset = appUnitsPerDevUnit * FixedToIntRound(i_offset + residual); + // Desired distance from the base glyph to the next reference point. + hb_position_t width = i_advance - i_offset; + int intWidth = FixedToIntRound(width); + residual = width - FloatToFixed(intWidth); + advance = appUnitsPerDevUnit * intWidth + iOffset; + } else { + iOffset = floor(hb2appUnits * i_offset + 0.5); + advance = floor(hb2appUnits * i_advance + 0.5); + } + // Check if it's a simple one-to-one mapping + if (glyphsInClump == 1 && + CompressedGlyph::IsSimpleGlyphID(ginfo[glyphStart].codepoint) && + CompressedGlyph::IsSimpleAdvance(advance) && + charGlyphs[baseCharIndex].IsClusterStart() && iOffset == 0 && + b_offset == 0 && b_advance == 0 && bPos == 0) { + charGlyphs[baseCharIndex].SetSimpleGlyph(advance, + ginfo[glyphStart].codepoint); + } else { + // Collect all glyphs in a list to be assigned to the first char; + // there must be at least one in the clump, and we already measured + // its advance, hence the placement of the loop-exit test and the + // measurement of the next glyph. + while (1) { + gfxTextRun::DetailedGlyph* details = detailedGlyphs.AppendElement(); + details->mGlyphID = ginfo[glyphStart].codepoint; + + details->mAdvance = advance; + + if (aVertical) { + details->mOffset.x = + bPos - (roundB ? appUnitsPerDevUnit * FixedToIntRound(b_offset) + : floor(hb2appUnits * b_offset + 0.5)); + details->mOffset.y = iOffset; + } else { + details->mOffset.x = iOffset; + details->mOffset.y = + bPos - (roundB ? appUnitsPerDevUnit * FixedToIntRound(b_offset) + : floor(hb2appUnits * b_offset + 0.5)); + } + + if (b_advance != 0) { + bPos -= roundB ? appUnitsPerDevUnit * FixedToIntRound(b_advance) + : floor(hb2appUnits * b_advance + 0.5); + } + if (++glyphStart >= glyphEnd) { + break; + } + + if (aVertical) { + i_offset = -posInfo[glyphStart].y_offset; + i_advance = -posInfo[glyphStart].y_advance; + b_offset = -posInfo[glyphStart].x_offset; + b_advance = -posInfo[glyphStart].x_advance; + } else { + i_offset = posInfo[glyphStart].x_offset; + i_advance = posInfo[glyphStart].x_advance; + b_offset = posInfo[glyphStart].y_offset; + b_advance = posInfo[glyphStart].y_advance; + } + + if (roundI) { + iOffset = appUnitsPerDevUnit * FixedToIntRound(i_offset + residual); + // Desired distance to the next reference point. The + // residual is considered here, and includes the residual + // from the base glyph offset and subsequent advances, so + // that the distance from the base glyph is optimized + // rather than the distance from combining marks. + i_advance += residual; + int intAdvance = FixedToIntRound(i_advance); + residual = i_advance - FloatToFixed(intAdvance); + advance = appUnitsPerDevUnit * intAdvance; + } else { + iOffset = floor(hb2appUnits * i_offset + 0.5); + advance = floor(hb2appUnits * i_advance + 0.5); + } + } + + aShapedText->SetDetailedGlyphs(aOffset + baseCharIndex, + detailedGlyphs.Length(), + detailedGlyphs.Elements()); + + detailedGlyphs.Clear(); + } + + // the rest of the chars in the group are ligature continuations, + // no associated glyphs + while (++baseCharIndex != endCharIndex && + baseCharIndex < int32_t(wordLength)) { + CompressedGlyph& g = charGlyphs[baseCharIndex]; + NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph"); + g.SetComplex(g.IsClusterStart(), false); + } + + glyphStart = glyphEnd; + charStart = charEnd; + } + + return NS_OK; +} diff --git a/gfx/thebes/gfxHarfBuzzShaper.h b/gfx/thebes/gfxHarfBuzzShaper.h new file mode 100644 index 0000000000..31c879b052 --- /dev/null +++ b/gfx/thebes/gfxHarfBuzzShaper.h @@ -0,0 +1,188 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_HARFBUZZSHAPER_H +#define GFX_HARFBUZZSHAPER_H + +#include "gfxFont.h" + +#include "harfbuzz/hb.h" +#include "nsUnicodeProperties.h" +#include "mozilla/gfx/2D.h" + +class gfxHarfBuzzShaper : public gfxFontShaper { + public: + explicit gfxHarfBuzzShaper(gfxFont* aFont); + virtual ~gfxHarfBuzzShaper(); + + /* + * For HarfBuzz font callback functions, font_data is a ptr to a + * FontCallbackData struct + */ + struct FontCallbackData { + gfxHarfBuzzShaper* mShaper; + }; + + bool Initialize(); + + bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, RoundingFlags aRounding, + gfxShapedText* aShapedText) override; + + // get a given font table in harfbuzz blob form + hb_blob_t* GetFontTable(hb_tag_t aTag) const; + + // map unicode character to glyph ID + hb_codepoint_t GetNominalGlyph(hb_codepoint_t unicode) const; + hb_codepoint_t GetVariationGlyph(hb_codepoint_t unicode, + hb_codepoint_t variation_selector) const; + + // get harfbuzz glyph advance, in font design units + hb_position_t GetGlyphHAdvance(hb_codepoint_t glyph) const; + + 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; + } + + static hb_codepoint_t GetVerticalPresentationForm(hb_codepoint_t aUnicode); + + // Create an hb_font corresponding to the given gfxFont instance, with size + // and variations set appropriately. If aFontFuncs and aCallbackData are + // provided, they may be used as harfbuzz font callbacks for advances, glyph + // bounds, etc; if not, the built-in hb_ot font functions will be used. + static hb_font_t* CreateHBFont(gfxFont* aFont, + hb_font_funcs_t* aFontFuncs = nullptr, + FontCallbackData* aCallbackData = nullptr); + + protected: + nsresult SetGlyphsFromRun(gfxShapedText* aShapedText, uint32_t aOffset, + uint32_t aLength, const char16_t* aText, + bool aVertical, RoundingFlags aRounding); + + // retrieve glyph positions, applying advance adjustments and attachments + // returns results in appUnits + nscoord GetGlyphPositions(gfxContext* aContext, nsTArray<nsPoint>& aPositions, + uint32_t aAppUnitsPerDevUnit); + + void InitializeVertical(); + bool LoadHmtxTable(); + + struct Glyf { // we only need the bounding-box at the beginning + // of the glyph record, not the actual outline data + mozilla::AutoSwap_PRInt16 numberOfContours; + mozilla::AutoSwap_PRInt16 xMin; + mozilla::AutoSwap_PRInt16 yMin; + mozilla::AutoSwap_PRInt16 xMax; + mozilla::AutoSwap_PRInt16 yMax; + }; + + const Glyf* FindGlyf(hb_codepoint_t aGlyph, bool* aEmptyGlyf) const; + + // size-specific font object, owned by the gfxHarfBuzzShaper + hb_font_t* mHBFont; + + // harfbuzz buffer for the shaping process + hb_buffer_t* mBuffer; + + FontCallbackData mCallbackData; + + // Following table references etc are declared "mutable" because the + // harfbuzz callback functions take a const ptr to the shaper, but + // wish to cache tables here to avoid repeatedly looking them up + // in the font. + + // Old-style TrueType kern table, if we're not doing GPOS kerning + mutable hb_blob_t* mKernTable; + + // Cached copy of the hmtx table. + mutable hb_blob_t* mHmtxTable; + + // For vertical fonts, cached vmtx and VORG table, if present. + mutable hb_blob_t* mVmtxTable; + mutable hb_blob_t* mVORGTable; + // And for vertical TrueType (not CFF) fonts that have vmtx, + // we also use loca and glyf to get glyph bounding boxes. + mutable hb_blob_t* mLocaTable; + mutable hb_blob_t* mGlyfTable; + + // Cached pointer to cmap subtable to be used for char-to-glyph mapping. + // This comes from GetFontTablePtr; if it is non-null, our destructor + // must call ReleaseFontTablePtr to avoid permanently caching the table. + mutable hb_blob_t* mCmapTable; + mutable int32_t mCmapFormat; + mutable uint32_t mSubtableOffset; + mutable uint32_t mUVSTableOffset; + + // Cached copy of numLongMetrics field from the hhea table, + // for use when looking up glyph metrics; initialized to 0 by the + // constructor so we can tell it hasn't been set yet. + // This is a signed value so that we can use -1 to indicate + // an error (if the hhea table was not available). + mutable int32_t mNumLongHMetrics; + // Similarly for vhea if it's a vertical font. + mutable int32_t mNumLongVMetrics; + + // Default y-coordinate for glyph vertical origin, used if the font + // does not actually have vertical-layout metrics. + mutable gfxFloat mDefaultVOrg; + + // Whether the font implements GetGlyph, or we should read tables + // directly + bool mUseFontGetGlyph; + // Whether the font implements GetGlyphWidth, or we should read tables + // directly to get ideal widths + bool mUseFontGlyphWidths; + + bool mInitialized; + bool mVerticalInitialized; + + // Whether to use vertical presentation forms for CJK characters + // when available (only set if the 'vert' feature is not available). + bool mUseVerticalPresentationForms; + + // these are set from the FindGlyf callback on first use of the glyf data + mutable bool mLoadedLocaGlyf; + mutable bool mLocaLongOffsets; +}; + +#endif /* GFX_HARFBUZZSHAPER_H */ diff --git a/gfx/thebes/gfxImageSurface.cpp b/gfx/thebes/gfxImageSurface.cpp new file mode 100644 index 0000000000..3cd285dac7 --- /dev/null +++ b/gfx/thebes/gfxImageSurface.cpp @@ -0,0 +1,326 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/MemoryReporting.h" +#if defined(HAVE_POSIX_MEMALIGN) +# include "gfxAlphaRecovery.h" +#endif +#include "gfxImageSurface.h" + +#include "cairo.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/HelpersCairo.h" +#include "gfx2DGlue.h" +#include <algorithm> + +using namespace mozilla; +using namespace mozilla::gfx; + +gfxImageSurface::gfxImageSurface() + : mSize(0, 0), + mOwnsData(false), + mData(nullptr), + mFormat(SurfaceFormat::UNKNOWN), + mStride(0) {} + +void gfxImageSurface::InitFromSurface(cairo_surface_t* csurf) { + if (!csurf || cairo_surface_status(csurf)) { + MakeInvalid(); + return; + } + + mSize.width = cairo_image_surface_get_width(csurf); + mSize.height = cairo_image_surface_get_height(csurf); + mData = cairo_image_surface_get_data(csurf); + mFormat = CairoFormatToGfxFormat(cairo_image_surface_get_format(csurf)); + mOwnsData = false; + mStride = cairo_image_surface_get_stride(csurf); + + Init(csurf, true); +} + +gfxImageSurface::gfxImageSurface(unsigned char* aData, const IntSize& aSize, + long aStride, gfxImageFormat aFormat) { + InitWithData(aData, aSize, aStride, aFormat); +} + +void gfxImageSurface::MakeInvalid() { + mSize = IntSize(-1, -1); + mData = nullptr; + mStride = 0; +} + +void gfxImageSurface::InitWithData(unsigned char* aData, const IntSize& aSize, + long aStride, gfxImageFormat aFormat) { + mSize = aSize; + mOwnsData = false; + mData = aData; + mFormat = aFormat; + mStride = aStride; + + if (!Factory::CheckSurfaceSize(aSize)) MakeInvalid(); + + cairo_format_t cformat = GfxFormatToCairoFormat(mFormat); + cairo_surface_t* surface = cairo_image_surface_create_for_data( + (unsigned char*)mData, cformat, mSize.width, mSize.height, mStride); + + // cairo_image_surface_create_for_data can return a 'null' surface + // in out of memory conditions. The gfxASurface::Init call checks + // the surface it receives to see if there is an error with the + // surface and handles it appropriately. That is why there is + // no check here. + Init(surface); +} + +static void* TryAllocAlignedBytes(size_t aSize) { + // Use fallible allocators here +#if defined(HAVE_POSIX_MEMALIGN) + void* ptr; + // Try to align for fast alpha recovery. This should only help + // cairo too, can't hurt. + return posix_memalign(&ptr, 1 << gfxAlphaRecovery::GoodAlignmentLog2(), aSize) + ? nullptr + : ptr; +#else + // Oh well, hope that luck is with us in the allocator + return malloc(aSize); +#endif +} + +gfxImageSurface::gfxImageSurface(const IntSize& size, gfxImageFormat format, + bool aClear) + : mSize(size), mData(nullptr), mFormat(format) { + AllocateAndInit(0, 0, aClear); +} + +void gfxImageSurface::AllocateAndInit(long aStride, int32_t aMinimalAllocation, + bool aClear) { + // The callers should set mSize and mFormat. + MOZ_ASSERT(!mData); + mData = nullptr; + mOwnsData = false; + + mStride = aStride > 0 ? aStride : ComputeStride(); + if (aMinimalAllocation < mSize.height * mStride) + aMinimalAllocation = mSize.height * mStride; + + if (!Factory::CheckSurfaceSize(mSize)) MakeInvalid(); + + // if we have a zero-sized surface, just leave mData nullptr + if (mSize.height * mStride > 0) { + // This can fail to allocate memory aligned as we requested, + // or it can fail to allocate any memory at all. + mData = (unsigned char*)TryAllocAlignedBytes(aMinimalAllocation); + if (!mData) return; + if (aClear) memset(mData, 0, aMinimalAllocation); + } + + mOwnsData = true; + + cairo_format_t cformat = GfxFormatToCairoFormat(mFormat); + cairo_surface_t* surface = cairo_image_surface_create_for_data( + (unsigned char*)mData, cformat, mSize.width, mSize.height, mStride); + + Init(surface); + + if (mSurfaceValid) { + RecordMemoryUsed(mSize.height * ComputeStride() + sizeof(gfxImageSurface)); + } +} + +gfxImageSurface::gfxImageSurface(const IntSize& size, gfxImageFormat format, + long aStride, int32_t aExtraBytes, bool aClear) + : mSize(size), mData(nullptr), mFormat(format) { + AllocateAndInit(aStride, aExtraBytes, aClear); +} + +gfxImageSurface::gfxImageSurface(cairo_surface_t* csurf) { + mSize.width = cairo_image_surface_get_width(csurf); + mSize.height = cairo_image_surface_get_height(csurf); + mData = cairo_image_surface_get_data(csurf); + mFormat = CairoFormatToGfxFormat(cairo_image_surface_get_format(csurf)); + mOwnsData = false; + mStride = cairo_image_surface_get_stride(csurf); + + Init(csurf, true); +} + +gfxImageSurface::~gfxImageSurface() { + if (mOwnsData) free(mData); +} + +/*static*/ +long gfxImageSurface::ComputeStride(const IntSize& aSize, + gfxImageFormat aFormat) { + long stride; + + if (aFormat == SurfaceFormat::A8R8G8B8_UINT32) + stride = aSize.width * 4; + else if (aFormat == SurfaceFormat::X8R8G8B8_UINT32) + stride = aSize.width * 4; + else if (aFormat == SurfaceFormat::R5G6B5_UINT16) + stride = aSize.width * 2; + else if (aFormat == SurfaceFormat::A8) + stride = aSize.width; + else { + NS_WARNING("Unknown format specified to gfxImageSurface!"); + stride = aSize.width * 4; + } + + stride = ((stride + 3) / 4) * 4; + + return stride; +} + +size_t gfxImageSurface::SizeOfExcludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = gfxASurface::SizeOfExcludingThis(aMallocSizeOf); + if (mOwnsData) { + n += aMallocSizeOf(mData); + } + return n; +} + +size_t gfxImageSurface::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +bool gfxImageSurface::SizeOfIsMeasured() const { return true; } + +// helper function for the CopyFrom methods +static void CopyForStride(unsigned char* aDest, unsigned char* aSrc, + const IntSize& aSize, long aDestStride, + long aSrcStride) { + if (aDestStride == aSrcStride) { + memcpy(aDest, aSrc, aSrcStride * aSize.height); + } else { + int lineSize = std::min(aDestStride, aSrcStride); + for (int i = 0; i < aSize.height; i++) { + unsigned char* src = aSrc + aSrcStride * i; + unsigned char* dst = aDest + aDestStride * i; + + memcpy(dst, src, lineSize); + } + } +} + +// helper function for the CopyFrom methods +static bool FormatsAreCompatible(gfxImageFormat a1, gfxImageFormat a2) { + if (a1 != a2 && + !(a1 == SurfaceFormat::A8R8G8B8_UINT32 && + a2 == SurfaceFormat::X8R8G8B8_UINT32) && + !(a1 == SurfaceFormat::X8R8G8B8_UINT32 && + a2 == SurfaceFormat::A8R8G8B8_UINT32)) { + return false; + } + + return true; +} + +bool gfxImageSurface::CopyFrom(SourceSurface* aSurface) { + RefPtr<DataSourceSurface> data = aSurface->GetDataSurface(); + + if (!data) { + return false; + } + + IntSize size(data->GetSize().width, data->GetSize().height); + if (size != mSize) { + return false; + } + + if (!FormatsAreCompatible(SurfaceFormatToImageFormat(aSurface->GetFormat()), + mFormat)) { + return false; + } + + DataSourceSurface::ScopedMap map(data, DataSourceSurface::READ); + CopyForStride(mData, map.GetData(), size, mStride, map.GetStride()); + + return true; +} + +bool gfxImageSurface::CopyFrom(gfxImageSurface* other) { + if (other->mSize != mSize) { + return false; + } + + if (!FormatsAreCompatible(other->mFormat, mFormat)) { + return false; + } + + CopyForStride(mData, other->mData, mSize, mStride, other->mStride); + + return true; +} + +bool gfxImageSurface::CopyTo(SourceSurface* aSurface) { + RefPtr<DataSourceSurface> data = aSurface->GetDataSurface(); + + if (!data) { + return false; + } + + IntSize size(data->GetSize().width, data->GetSize().height); + if (size != mSize) { + return false; + } + + if (!FormatsAreCompatible(SurfaceFormatToImageFormat(aSurface->GetFormat()), + mFormat)) { + return false; + } + + DataSourceSurface::ScopedMap map(data, DataSourceSurface::READ_WRITE); + CopyForStride(map.GetData(), mData, size, map.GetStride(), mStride); + + return true; +} + +already_AddRefed<DataSourceSurface> +gfxImageSurface::CopyToB8G8R8A8DataSourceSurface() { + RefPtr<DataSourceSurface> dataSurface = Factory::CreateDataSourceSurface( + IntSize(GetSize().width, GetSize().height), SurfaceFormat::B8G8R8A8); + if (dataSurface) { + CopyTo(dataSurface); + } + return dataSurface.forget(); +} + +already_AddRefed<gfxSubimageSurface> gfxImageSurface::GetSubimage( + const gfxRect& aRect) { + gfxRect r(aRect); + r.Round(); + MOZ_ASSERT(gfxRect(0, 0, mSize.width, mSize.height).Contains(r)); + + gfxImageFormat format = Format(); + + unsigned char* subData = + Data() + (Stride() * (int)r.Y()) + + (int)r.X() * gfxASurface::BytePerPixelFromFormat(Format()); + + if (format == SurfaceFormat::A8R8G8B8_UINT32 && + GetOpaqueRect().Contains(aRect)) { + format = SurfaceFormat::X8R8G8B8_UINT32; + } + + RefPtr<gfxSubimageSurface> image = new gfxSubimageSurface( + this, subData, IntSize((int)r.Width(), (int)r.Height()), format); + + return image.forget(); +} + +gfxSubimageSurface::gfxSubimageSurface(gfxImageSurface* aParent, + unsigned char* aData, + const IntSize& aSize, + gfxImageFormat aFormat) + : gfxImageSurface(aData, aSize, aParent->Stride(), aFormat), + mParent(aParent) {} + +already_AddRefed<gfxImageSurface> gfxImageSurface::GetAsImageSurface() { + RefPtr<gfxImageSurface> surface = this; + return surface.forget(); +} diff --git a/gfx/thebes/gfxImageSurface.h b/gfx/thebes/gfxImageSurface.h new file mode 100644 index 0000000000..eefc233738 --- /dev/null +++ b/gfx/thebes/gfxImageSurface.h @@ -0,0 +1,194 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_IMAGESURFACE_H +#define GFX_IMAGESURFACE_H + +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefPtr.h" +#include "gfxASurface.h" +#include "nsSize.h" + +// ARGB -- raw buffer.. wont be changed.. good for storing data. + +class gfxSubimageSurface; + +namespace mozilla { +namespace gfx { +class DataSourceSurface; +class SourceSurface; +} // namespace gfx +} // namespace mozilla + +/** + * A raw image buffer. The format can be set in the constructor. Its main + * purpose is for storing read-only images and using it as a source surface, + * but it can also be drawn to. + */ +class gfxImageSurface : public gfxASurface { + public: + /** + * Construct an image surface around an existing buffer of image data. + * @param aData A buffer containing the image data + * @param aSize The size of the buffer + * @param aStride The stride of the buffer + * @param format Format of the data + * + * @see gfxImageFormat + */ + gfxImageSurface(unsigned char* aData, const mozilla::gfx::IntSize& aSize, + long aStride, gfxImageFormat aFormat); + + /** + * Construct an image surface. + * @param aSize The size of the buffer + * @param format Format of the data + * + * @see gfxImageFormat + */ + gfxImageSurface(const mozilla::gfx::IntSize& size, gfxImageFormat format, + bool aClear = true); + + /** + * Construct an image surface, with a specified stride and allowing the + * allocation of more memory than required for the storage of the surface + * itself. When aStride and aMinimalAllocation are <=0, this constructor + * is the equivalent of the preceeding one. + * + * @param format Format of the data + * @param aSize The size of the buffer + * @param aStride The stride of the buffer - if <=0, use ComputeStride() + * @param aMinimalAllocation Allocate at least this many bytes. If smaller + * than width * stride, or width*stride <=0, this value is ignored. + * @param aClear + * + * @see gfxImageFormat + */ + gfxImageSurface(const mozilla::gfx::IntSize& aSize, gfxImageFormat aFormat, + long aStride, int32_t aMinimalAllocation, bool aClear); + + explicit gfxImageSurface(cairo_surface_t* csurf); + + virtual ~gfxImageSurface(); + + // ImageSurface methods + gfxImageFormat Format() const { return mFormat; } + + virtual const mozilla::gfx::IntSize GetSize() const override { return mSize; } + int32_t Width() const { + if (mSize.width < 0) { + return 0; + } + return mSize.width; + } + int32_t Height() const { + if (mSize.height < 0) { + return 0; + } + return mSize.height; + } + + /** + * Distance in bytes between the start of a line and the start of the + * next line. + */ + int32_t Stride() const { return mStride; } + /** + * Returns a pointer for the image data. Users of this function can + * write to it, but must not attempt to free the buffer. + */ + unsigned char* Data() const { + return mData; + } // delete this data under us and die. + /** + * Returns the total size of the image data. + */ + int32_t GetDataSize() const { + if (mStride < 0 || mSize.height < 0) { + return 0; + } + return mStride * mSize.height; + } + + /* Fast copy from another image surface; returns TRUE if successful, FALSE + * otherwise */ + bool CopyFrom(gfxImageSurface* other); + + /** + * Fast copy from a source surface; returns TRUE if successful, FALSE + * otherwise Assumes that the format of this surface is compatable with + * aSurface + */ + bool CopyFrom(mozilla::gfx::SourceSurface* aSurface); + + /** + * Fast copy to a source surface; returns TRUE if successful, FALSE otherwise + * Assumes that the format of this surface is compatible with aSurface + */ + bool CopyTo(mozilla::gfx::SourceSurface* aSurface); + + /** + * Copy to a Moz2D DataSourceSurface. + * Marked as virtual so that browsercomps can access this method. + */ + virtual already_AddRefed<mozilla::gfx::DataSourceSurface> + CopyToB8G8R8A8DataSourceSurface(); + + /* return new Subimage with pointing to original image starting from aRect.pos + * and size of aRect.size. New subimage keeping current image reference + */ + already_AddRefed<gfxSubimageSurface> GetSubimage(const gfxRect& aRect); + + virtual already_AddRefed<gfxImageSurface> GetAsImageSurface() override; + + /** See gfxASurface.h. */ + static long ComputeStride(const mozilla::gfx::IntSize&, gfxImageFormat); + + virtual size_t SizeOfExcludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const override; + virtual size_t SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const override; + virtual bool SizeOfIsMeasured() const override; + + protected: + gfxImageSurface(); + void InitWithData(unsigned char* aData, const mozilla::gfx::IntSize& aSize, + long aStride, gfxImageFormat aFormat); + /** + * See the parameters to the matching constructor. This should only + * be called once, in the constructor, which has already set mSize + * and mFormat. + */ + void AllocateAndInit(long aStride, int32_t aMinimalAllocation, bool aClear); + void InitFromSurface(cairo_surface_t* csurf); + + long ComputeStride() const { + if (mSize.height < 0 || mSize.width < 0) { + return 0; + } + return ComputeStride(mSize, mFormat); + } + + void MakeInvalid(); + + mozilla::gfx::IntSize mSize; + bool mOwnsData; + unsigned char* mData; + gfxImageFormat mFormat; + long mStride; +}; + +class gfxSubimageSurface : public gfxImageSurface { + protected: + friend class gfxImageSurface; + gfxSubimageSurface(gfxImageSurface* aParent, unsigned char* aData, + const mozilla::gfx::IntSize& aSize, + gfxImageFormat aFormat); + + private: + RefPtr<gfxImageSurface> mParent; +}; + +#endif /* GFX_IMAGESURFACE_H */ diff --git a/gfx/thebes/gfxLanguageTagList.cpp b/gfx/thebes/gfxLanguageTagList.cpp new file mode 100644 index 0000000000..0994fc1a17 --- /dev/null +++ b/gfx/thebes/gfxLanguageTagList.cpp @@ -0,0 +1,7901 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Derived from the IANA language subtag registry by genLanguageTagList.pl. + * + * Created on Mon Nov 7 14:52:44 2011. + * + * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * * + */ + +// Based on IANA registry dated 2011-08-25 + +static const uint32_t sLanguageTagList[] = { + TRUETYPE_TAG('a', 'a', 0, 0), // aa = Afar + TRUETYPE_TAG('a', 'b', 0, 0), // ab = Abkhazian + TRUETYPE_TAG('a', 'e', 0, 0), // ae = Avestan + TRUETYPE_TAG('a', 'f', 0, 0), // af = Afrikaans + TRUETYPE_TAG('a', 'k', 0, 0), // ak = Akan + TRUETYPE_TAG('a', 'm', 0, 0), // am = Amharic + TRUETYPE_TAG('a', 'n', 0, 0), // an = Aragonese + TRUETYPE_TAG('a', 'r', 0, 0), // ar = Arabic + TRUETYPE_TAG('a', 's', 0, 0), // as = Assamese + TRUETYPE_TAG('a', 'v', 0, 0), // av = Avaric + TRUETYPE_TAG('a', 'y', 0, 0), // ay = Aymara + TRUETYPE_TAG('a', 'z', 0, 0), // az = Azerbaijani + TRUETYPE_TAG('b', 'a', 0, 0), // ba = Bashkir + TRUETYPE_TAG('b', 'e', 0, 0), // be = Belarusian + TRUETYPE_TAG('b', 'g', 0, 0), // bg = Bulgarian + TRUETYPE_TAG('b', 'h', 0, 0), // bh = Bihari languages + TRUETYPE_TAG('b', 'i', 0, 0), // bi = Bislama + TRUETYPE_TAG('b', 'm', 0, 0), // bm = Bambara + TRUETYPE_TAG('b', 'n', 0, 0), // bn = Bengali + TRUETYPE_TAG('b', 'o', 0, 0), // bo = Tibetan + TRUETYPE_TAG('b', 'r', 0, 0), // br = Breton + TRUETYPE_TAG('b', 's', 0, 0), // bs = Bosnian + TRUETYPE_TAG('c', 'a', 0, 0), // ca = Catalan + TRUETYPE_TAG('c', 'e', 0, 0), // ce = Chechen + TRUETYPE_TAG('c', 'h', 0, 0), // ch = Chamorro + TRUETYPE_TAG('c', 'o', 0, 0), // co = Corsican + TRUETYPE_TAG('c', 'r', 0, 0), // cr = Cree + TRUETYPE_TAG('c', 's', 0, 0), // cs = Czech + TRUETYPE_TAG('c', 'u', 0, 0), // cu = Church Slavic + TRUETYPE_TAG('c', 'v', 0, 0), // cv = Chuvash + TRUETYPE_TAG('c', 'y', 0, 0), // cy = Welsh + TRUETYPE_TAG('d', 'a', 0, 0), // da = Danish + TRUETYPE_TAG('d', 'e', 0, 0), // de = German + TRUETYPE_TAG('d', 'v', 0, 0), // dv = Dhivehi + TRUETYPE_TAG('d', 'z', 0, 0), // dz = Dzongkha + TRUETYPE_TAG('e', 'e', 0, 0), // ee = Ewe + TRUETYPE_TAG('e', 'l', 0, 0), // el = Modern Greek (1453-) + TRUETYPE_TAG('e', 'n', 0, 0), // en = English + TRUETYPE_TAG('e', 'o', 0, 0), // eo = Esperanto + TRUETYPE_TAG('e', 's', 0, 0), // es = Spanish + TRUETYPE_TAG('e', 't', 0, 0), // et = Estonian + TRUETYPE_TAG('e', 'u', 0, 0), // eu = Basque + TRUETYPE_TAG('f', 'a', 0, 0), // fa = Persian + TRUETYPE_TAG('f', 'f', 0, 0), // ff = Fulah + TRUETYPE_TAG('f', 'i', 0, 0), // fi = Finnish + TRUETYPE_TAG('f', 'j', 0, 0), // fj = Fijian + TRUETYPE_TAG('f', 'o', 0, 0), // fo = Faroese + TRUETYPE_TAG('f', 'r', 0, 0), // fr = French + TRUETYPE_TAG('f', 'y', 0, 0), // fy = Western Frisian + TRUETYPE_TAG('g', 'a', 0, 0), // ga = Irish + TRUETYPE_TAG('g', 'd', 0, 0), // gd = Scottish Gaelic + TRUETYPE_TAG('g', 'l', 0, 0), // gl = Galician + TRUETYPE_TAG('g', 'n', 0, 0), // gn = Guarani + TRUETYPE_TAG('g', 'u', 0, 0), // gu = Gujarati + TRUETYPE_TAG('g', 'v', 0, 0), // gv = Manx + TRUETYPE_TAG('h', 'a', 0, 0), // ha = Hausa + TRUETYPE_TAG('h', 'e', 0, 0), // he = Hebrew + TRUETYPE_TAG('h', 'i', 0, 0), // hi = Hindi + TRUETYPE_TAG('h', 'o', 0, 0), // ho = Hiri Motu + TRUETYPE_TAG('h', 'r', 0, 0), // hr = Croatian + TRUETYPE_TAG('h', 't', 0, 0), // ht = Haitian + TRUETYPE_TAG('h', 'u', 0, 0), // hu = Hungarian + TRUETYPE_TAG('h', 'y', 0, 0), // hy = Armenian + TRUETYPE_TAG('h', 'z', 0, 0), // hz = Herero + TRUETYPE_TAG('i', 'a', 0, + 0), // ia = Interlingua (International Auxiliary Language + TRUETYPE_TAG('i', 'd', 0, 0), // id = Indonesian + TRUETYPE_TAG('i', 'e', 0, 0), // ie = Interlingue + TRUETYPE_TAG('i', 'g', 0, 0), // ig = Igbo + TRUETYPE_TAG('i', 'i', 0, 0), // ii = Sichuan Yi + TRUETYPE_TAG('i', 'k', 0, 0), // ik = Inupiaq + TRUETYPE_TAG('i', 'n', 0, 0), // in = Indonesian + TRUETYPE_TAG('i', 'o', 0, 0), // io = Ido + TRUETYPE_TAG('i', 's', 0, 0), // is = Icelandic + TRUETYPE_TAG('i', 't', 0, 0), // it = Italian + TRUETYPE_TAG('i', 'u', 0, 0), // iu = Inuktitut + TRUETYPE_TAG('i', 'w', 0, 0), // iw = Hebrew + TRUETYPE_TAG('j', 'a', 0, 0), // ja = Japanese + TRUETYPE_TAG('j', 'i', 0, 0), // ji = Yiddish + TRUETYPE_TAG('j', 'v', 0, 0), // jv = Javanese + TRUETYPE_TAG('j', 'w', 0, 0), // jw = Javanese + TRUETYPE_TAG('k', 'a', 0, 0), // ka = Georgian + TRUETYPE_TAG('k', 'g', 0, 0), // kg = Kongo + TRUETYPE_TAG('k', 'i', 0, 0), // ki = Kikuyu + TRUETYPE_TAG('k', 'j', 0, 0), // kj = Kuanyama + TRUETYPE_TAG('k', 'k', 0, 0), // kk = Kazakh + TRUETYPE_TAG('k', 'l', 0, 0), // kl = Kalaallisut + TRUETYPE_TAG('k', 'm', 0, 0), // km = Central Khmer + TRUETYPE_TAG('k', 'n', 0, 0), // kn = Kannada + TRUETYPE_TAG('k', 'o', 0, 0), // ko = Korean + TRUETYPE_TAG('k', 'r', 0, 0), // kr = Kanuri + TRUETYPE_TAG('k', 's', 0, 0), // ks = Kashmiri + TRUETYPE_TAG('k', 'u', 0, 0), // ku = Kurdish + TRUETYPE_TAG('k', 'v', 0, 0), // kv = Komi + TRUETYPE_TAG('k', 'w', 0, 0), // kw = Cornish + TRUETYPE_TAG('k', 'y', 0, 0), // ky = Kirghiz + TRUETYPE_TAG('l', 'a', 0, 0), // la = Latin + TRUETYPE_TAG('l', 'b', 0, 0), // lb = Luxembourgish + TRUETYPE_TAG('l', 'g', 0, 0), // lg = Ganda + TRUETYPE_TAG('l', 'i', 0, 0), // li = Limburgan + TRUETYPE_TAG('l', 'n', 0, 0), // ln = Lingala + TRUETYPE_TAG('l', 'o', 0, 0), // lo = Lao + TRUETYPE_TAG('l', 't', 0, 0), // lt = Lithuanian + TRUETYPE_TAG('l', 'u', 0, 0), // lu = Luba-Katanga + TRUETYPE_TAG('l', 'v', 0, 0), // lv = Latvian + TRUETYPE_TAG('m', 'g', 0, 0), // mg = Malagasy + TRUETYPE_TAG('m', 'h', 0, 0), // mh = Marshallese + TRUETYPE_TAG('m', 'i', 0, 0), // mi = Maori + TRUETYPE_TAG('m', 'k', 0, 0), // mk = Macedonian + TRUETYPE_TAG('m', 'l', 0, 0), // ml = Malayalam + TRUETYPE_TAG('m', 'n', 0, 0), // mn = Mongolian + TRUETYPE_TAG('m', 'o', 0, 0), // mo = Moldavian + TRUETYPE_TAG('m', 'r', 0, 0), // mr = Marathi + TRUETYPE_TAG('m', 's', 0, 0), // ms = Malay (macrolanguage) + TRUETYPE_TAG('m', 't', 0, 0), // mt = Maltese + TRUETYPE_TAG('m', 'y', 0, 0), // my = Burmese + TRUETYPE_TAG('n', 'a', 0, 0), // na = Nauru + TRUETYPE_TAG('n', 'b', 0, 0), // nb = Norwegian Bokmål + TRUETYPE_TAG('n', 'd', 0, 0), // nd = North Ndebele + TRUETYPE_TAG('n', 'e', 0, 0), // ne = Nepali + TRUETYPE_TAG('n', 'g', 0, 0), // ng = Ndonga + TRUETYPE_TAG('n', 'l', 0, 0), // nl = Dutch + TRUETYPE_TAG('n', 'n', 0, 0), // nn = Norwegian Nynorsk + TRUETYPE_TAG('n', 'o', 0, 0), // no = Norwegian + TRUETYPE_TAG('n', 'r', 0, 0), // nr = South Ndebele + TRUETYPE_TAG('n', 'v', 0, 0), // nv = Navajo + TRUETYPE_TAG('n', 'y', 0, 0), // ny = Nyanja + TRUETYPE_TAG('o', 'c', 0, 0), // oc = Occitan (post 1500) + TRUETYPE_TAG('o', 'j', 0, 0), // oj = Ojibwa + TRUETYPE_TAG('o', 'm', 0, 0), // om = Oromo + TRUETYPE_TAG('o', 'r', 0, 0), // or = Oriya + TRUETYPE_TAG('o', 's', 0, 0), // os = Ossetian + TRUETYPE_TAG('p', 'a', 0, 0), // pa = Panjabi + TRUETYPE_TAG('p', 'i', 0, 0), // pi = Pali + TRUETYPE_TAG('p', 'l', 0, 0), // pl = Polish + TRUETYPE_TAG('p', 's', 0, 0), // ps = Pushto + TRUETYPE_TAG('p', 't', 0, 0), // pt = Portuguese + TRUETYPE_TAG('q', 'u', 0, 0), // qu = Quechua + TRUETYPE_TAG('r', 'm', 0, 0), // rm = Romansh + TRUETYPE_TAG('r', 'n', 0, 0), // rn = Rundi + TRUETYPE_TAG('r', 'o', 0, 0), // ro = Romanian + TRUETYPE_TAG('r', 'u', 0, 0), // ru = Russian + TRUETYPE_TAG('r', 'w', 0, 0), // rw = Kinyarwanda + TRUETYPE_TAG('s', 'a', 0, 0), // sa = Sanskrit + TRUETYPE_TAG('s', 'c', 0, 0), // sc = Sardinian + TRUETYPE_TAG('s', 'd', 0, 0), // sd = Sindhi + TRUETYPE_TAG('s', 'e', 0, 0), // se = Northern Sami + TRUETYPE_TAG('s', 'g', 0, 0), // sg = Sango + TRUETYPE_TAG('s', 'h', 0, 0), // sh = Serbo-Croatian + TRUETYPE_TAG('s', 'i', 0, 0), // si = Sinhala + TRUETYPE_TAG('s', 'k', 0, 0), // sk = Slovak + TRUETYPE_TAG('s', 'l', 0, 0), // sl = Slovenian + TRUETYPE_TAG('s', 'm', 0, 0), // sm = Samoan + TRUETYPE_TAG('s', 'n', 0, 0), // sn = Shona + TRUETYPE_TAG('s', 'o', 0, 0), // so = Somali + TRUETYPE_TAG('s', 'q', 0, 0), // sq = Albanian + TRUETYPE_TAG('s', 'r', 0, 0), // sr = Serbian + TRUETYPE_TAG('s', 's', 0, 0), // ss = Swati + TRUETYPE_TAG('s', 't', 0, 0), // st = Southern Sotho + TRUETYPE_TAG('s', 'u', 0, 0), // su = Sundanese + TRUETYPE_TAG('s', 'v', 0, 0), // sv = Swedish + TRUETYPE_TAG('s', 'w', 0, 0), // sw = Swahili (macrolanguage) + TRUETYPE_TAG('t', 'a', 0, 0), // ta = Tamil + TRUETYPE_TAG('t', 'e', 0, 0), // te = Telugu + TRUETYPE_TAG('t', 'g', 0, 0), // tg = Tajik + TRUETYPE_TAG('t', 'h', 0, 0), // th = Thai + TRUETYPE_TAG('t', 'i', 0, 0), // ti = Tigrinya + TRUETYPE_TAG('t', 'k', 0, 0), // tk = Turkmen + TRUETYPE_TAG('t', 'l', 0, 0), // tl = Tagalog + TRUETYPE_TAG('t', 'n', 0, 0), // tn = Tswana + TRUETYPE_TAG('t', 'o', 0, 0), // to = Tonga (Tonga Islands) + TRUETYPE_TAG('t', 'r', 0, 0), // tr = Turkish + TRUETYPE_TAG('t', 's', 0, 0), // ts = Tsonga + TRUETYPE_TAG('t', 't', 0, 0), // tt = Tatar + TRUETYPE_TAG('t', 'w', 0, 0), // tw = Twi + TRUETYPE_TAG('t', 'y', 0, 0), // ty = Tahitian + TRUETYPE_TAG('u', 'g', 0, 0), // ug = Uighur + TRUETYPE_TAG('u', 'k', 0, 0), // uk = Ukrainian + TRUETYPE_TAG('u', 'r', 0, 0), // ur = Urdu + TRUETYPE_TAG('u', 'z', 0, 0), // uz = Uzbek + TRUETYPE_TAG('v', 'e', 0, 0), // ve = Venda + TRUETYPE_TAG('v', 'i', 0, 0), // vi = Vietnamese + TRUETYPE_TAG('v', 'o', 0, 0), // vo = Volapük + TRUETYPE_TAG('w', 'a', 0, 0), // wa = Walloon + TRUETYPE_TAG('w', 'o', 0, 0), // wo = Wolof + TRUETYPE_TAG('x', 'h', 0, 0), // xh = Xhosa + TRUETYPE_TAG('y', 'i', 0, 0), // yi = Yiddish + TRUETYPE_TAG('y', 'o', 0, 0), // yo = Yoruba + TRUETYPE_TAG('z', 'a', 0, 0), // za = Zhuang + TRUETYPE_TAG('z', 'h', 0, 0), // zh = Chinese + TRUETYPE_TAG('z', 'u', 0, 0), // zu = Zulu + TRUETYPE_TAG('a', 'a', 'a', 0), // aaa = Ghotuo + TRUETYPE_TAG('a', 'a', 'b', 0), // aab = Alumu-Tesu + TRUETYPE_TAG('a', 'a', 'c', 0), // aac = Ari + TRUETYPE_TAG('a', 'a', 'd', 0), // aad = Amal + TRUETYPE_TAG('a', 'a', 'e', 0), // aae = Arbëreshë Albanian + TRUETYPE_TAG('a', 'a', 'f', 0), // aaf = Aranadan + TRUETYPE_TAG('a', 'a', 'g', 0), // aag = Ambrak + TRUETYPE_TAG('a', 'a', 'h', 0), // aah = Abu' Arapesh + TRUETYPE_TAG('a', 'a', 'i', 0), // aai = Arifama-Miniafia + TRUETYPE_TAG('a', 'a', 'k', 0), // aak = Ankave + TRUETYPE_TAG('a', 'a', 'l', 0), // aal = Afade + TRUETYPE_TAG('a', 'a', 'm', 0), // aam = Aramanik + TRUETYPE_TAG('a', 'a', 'n', 0), // aan = Anambé + TRUETYPE_TAG('a', 'a', 'o', 0), // aao = Algerian Saharan Arabic + TRUETYPE_TAG('a', 'a', 'p', 0), // aap = Pará Arára + TRUETYPE_TAG('a', 'a', 'q', 0), // aaq = Eastern Abnaki + TRUETYPE_TAG('a', 'a', 's', 0), // aas = Aasáx + TRUETYPE_TAG('a', 'a', 't', 0), // aat = Arvanitika Albanian + TRUETYPE_TAG('a', 'a', 'u', 0), // aau = Abau + TRUETYPE_TAG('a', 'a', 'v', 0), // aav = Austro-Asiatic languages + TRUETYPE_TAG('a', 'a', 'w', 0), // aaw = Solong + TRUETYPE_TAG('a', 'a', 'x', 0), // aax = Mandobo Atas + TRUETYPE_TAG('a', 'a', 'z', 0), // aaz = Amarasi + TRUETYPE_TAG('a', 'b', 'a', 0), // aba = Abé + TRUETYPE_TAG('a', 'b', 'b', 0), // abb = Bankon + TRUETYPE_TAG('a', 'b', 'c', 0), // abc = Ambala Ayta + TRUETYPE_TAG('a', 'b', 'd', 0), // abd = Manide + TRUETYPE_TAG('a', 'b', 'e', 0), // abe = Western Abnaki + TRUETYPE_TAG('a', 'b', 'f', 0), // abf = Abai Sungai + TRUETYPE_TAG('a', 'b', 'g', 0), // abg = Abaga + TRUETYPE_TAG('a', 'b', 'h', 0), // abh = Tajiki Arabic + TRUETYPE_TAG('a', 'b', 'i', 0), // abi = Abidji + TRUETYPE_TAG('a', 'b', 'j', 0), // abj = Aka-Bea + TRUETYPE_TAG('a', 'b', 'l', 0), // abl = Lampung Nyo + TRUETYPE_TAG('a', 'b', 'm', 0), // abm = Abanyom + TRUETYPE_TAG('a', 'b', 'n', 0), // abn = Abua + TRUETYPE_TAG('a', 'b', 'o', 0), // abo = Abon + TRUETYPE_TAG('a', 'b', 'p', 0), // abp = Abellen Ayta + TRUETYPE_TAG('a', 'b', 'q', 0), // abq = Abaza + TRUETYPE_TAG('a', 'b', 'r', 0), // abr = Abron + TRUETYPE_TAG('a', 'b', 's', 0), // abs = Ambonese Malay + TRUETYPE_TAG('a', 'b', 't', 0), // abt = Ambulas + TRUETYPE_TAG('a', 'b', 'u', 0), // abu = Abure + TRUETYPE_TAG('a', 'b', 'v', 0), // abv = Baharna Arabic + TRUETYPE_TAG('a', 'b', 'w', 0), // abw = Pal + TRUETYPE_TAG('a', 'b', 'x', 0), // abx = Inabaknon + TRUETYPE_TAG('a', 'b', 'y', 0), // aby = Aneme Wake + TRUETYPE_TAG('a', 'b', 'z', 0), // abz = Abui + TRUETYPE_TAG('a', 'c', 'a', 0), // aca = Achagua + TRUETYPE_TAG('a', 'c', 'b', 0), // acb = Áncá + TRUETYPE_TAG('a', 'c', 'd', 0), // acd = Gikyode + TRUETYPE_TAG('a', 'c', 'e', 0), // ace = Achinese + TRUETYPE_TAG('a', 'c', 'f', 0), // acf = Saint Lucian Creole French + TRUETYPE_TAG('a', 'c', 'h', 0), // ach = Acoli + TRUETYPE_TAG('a', 'c', 'i', 0), // aci = Aka-Cari + TRUETYPE_TAG('a', 'c', 'k', 0), // ack = Aka-Kora + TRUETYPE_TAG('a', 'c', 'l', 0), // acl = Akar-Bale + TRUETYPE_TAG('a', 'c', 'm', 0), // acm = Mesopotamian Arabic + TRUETYPE_TAG('a', 'c', 'n', 0), // acn = Achang + TRUETYPE_TAG('a', 'c', 'p', 0), // acp = Eastern Acipa + TRUETYPE_TAG('a', 'c', 'q', 0), // acq = Ta'izzi-Adeni Arabic + TRUETYPE_TAG('a', 'c', 'r', 0), // acr = Achi + TRUETYPE_TAG('a', 'c', 's', 0), // acs = Acroá + TRUETYPE_TAG('a', 'c', 't', 0), // act = Achterhoeks + TRUETYPE_TAG('a', 'c', 'u', 0), // acu = Achuar-Shiwiar + TRUETYPE_TAG('a', 'c', 'v', 0), // acv = Achumawi + TRUETYPE_TAG('a', 'c', 'w', 0), // acw = Hijazi Arabic + TRUETYPE_TAG('a', 'c', 'x', 0), // acx = Omani Arabic + TRUETYPE_TAG('a', 'c', 'y', 0), // acy = Cypriot Arabic + TRUETYPE_TAG('a', 'c', 'z', 0), // acz = Acheron + TRUETYPE_TAG('a', 'd', 'a', 0), // ada = Adangme + TRUETYPE_TAG('a', 'd', 'b', 0), // adb = Adabe + TRUETYPE_TAG('a', 'd', 'd', 0), // add = Dzodinka + TRUETYPE_TAG('a', 'd', 'e', 0), // ade = Adele + TRUETYPE_TAG('a', 'd', 'f', 0), // adf = Dhofari Arabic + TRUETYPE_TAG('a', 'd', 'g', 0), // adg = Andegerebinha + TRUETYPE_TAG('a', 'd', 'h', 0), // adh = Adhola + TRUETYPE_TAG('a', 'd', 'i', 0), // adi = Adi + TRUETYPE_TAG('a', 'd', 'j', 0), // adj = Adioukrou + TRUETYPE_TAG('a', 'd', 'l', 0), // adl = Galo + TRUETYPE_TAG('a', 'd', 'n', 0), // adn = Adang + TRUETYPE_TAG('a', 'd', 'o', 0), // ado = Abu + TRUETYPE_TAG('a', 'd', 'p', 0), // adp = Adap + TRUETYPE_TAG('a', 'd', 'q', 0), // adq = Adangbe + TRUETYPE_TAG('a', 'd', 'r', 0), // adr = Adonara + TRUETYPE_TAG('a', 'd', 's', 0), // ads = Adamorobe Sign Language + TRUETYPE_TAG('a', 'd', 't', 0), // adt = Adnyamathanha + TRUETYPE_TAG('a', 'd', 'u', 0), // adu = Aduge + TRUETYPE_TAG('a', 'd', 'w', 0), // adw = Amundava + TRUETYPE_TAG('a', 'd', 'x', 0), // adx = Amdo Tibetan + TRUETYPE_TAG('a', 'd', 'y', 0), // ady = Adyghe + TRUETYPE_TAG('a', 'd', 'z', 0), // adz = Adzera + TRUETYPE_TAG('a', 'e', 'a', 0), // aea = Areba + TRUETYPE_TAG('a', 'e', 'b', 0), // aeb = Tunisian Arabic + TRUETYPE_TAG('a', 'e', 'c', 0), // aec = Saidi Arabic + TRUETYPE_TAG('a', 'e', 'd', 0), // aed = Argentine Sign Language + TRUETYPE_TAG('a', 'e', 'e', 0), // aee = Northeast Pashayi + TRUETYPE_TAG('a', 'e', 'k', 0), // aek = Haeke + TRUETYPE_TAG('a', 'e', 'l', 0), // ael = Ambele + TRUETYPE_TAG('a', 'e', 'm', 0), // aem = Arem + TRUETYPE_TAG('a', 'e', 'n', 0), // aen = Armenian Sign Language + TRUETYPE_TAG('a', 'e', 'q', 0), // aeq = Aer + TRUETYPE_TAG('a', 'e', 'r', 0), // aer = Eastern Arrernte + TRUETYPE_TAG('a', 'e', 's', 0), // aes = Alsea + TRUETYPE_TAG('a', 'e', 'u', 0), // aeu = Akeu + TRUETYPE_TAG('a', 'e', 'w', 0), // aew = Ambakich + TRUETYPE_TAG('a', 'e', 'y', 0), // aey = Amele + TRUETYPE_TAG('a', 'e', 'z', 0), // aez = Aeka + TRUETYPE_TAG('a', 'f', 'a', 0), // afa = Afro-Asiatic languages + TRUETYPE_TAG('a', 'f', 'b', 0), // afb = Gulf Arabic + TRUETYPE_TAG('a', 'f', 'd', 0), // afd = Andai + TRUETYPE_TAG('a', 'f', 'e', 0), // afe = Putukwam + TRUETYPE_TAG('a', 'f', 'g', 0), // afg = Afghan Sign Language + TRUETYPE_TAG('a', 'f', 'h', 0), // afh = Afrihili + TRUETYPE_TAG('a', 'f', 'i', 0), // afi = Akrukay + TRUETYPE_TAG('a', 'f', 'k', 0), // afk = Nanubae + TRUETYPE_TAG('a', 'f', 'n', 0), // afn = Defaka + TRUETYPE_TAG('a', 'f', 'o', 0), // afo = Eloyi + TRUETYPE_TAG('a', 'f', 'p', 0), // afp = Tapei + TRUETYPE_TAG('a', 'f', 's', 0), // afs = Afro-Seminole Creole + TRUETYPE_TAG('a', 'f', 't', 0), // aft = Afitti + TRUETYPE_TAG('a', 'f', 'u', 0), // afu = Awutu + TRUETYPE_TAG('a', 'f', 'z', 0), // afz = Obokuitai + TRUETYPE_TAG('a', 'g', 'a', 0), // aga = Aguano + TRUETYPE_TAG('a', 'g', 'b', 0), // agb = Legbo + TRUETYPE_TAG('a', 'g', 'c', 0), // agc = Agatu + TRUETYPE_TAG('a', 'g', 'd', 0), // agd = Agarabi + TRUETYPE_TAG('a', 'g', 'e', 0), // age = Angal + TRUETYPE_TAG('a', 'g', 'f', 0), // agf = Arguni + TRUETYPE_TAG('a', 'g', 'g', 0), // agg = Angor + TRUETYPE_TAG('a', 'g', 'h', 0), // agh = Ngelima + TRUETYPE_TAG('a', 'g', 'i', 0), // agi = Agariya + TRUETYPE_TAG('a', 'g', 'j', 0), // agj = Argobba + TRUETYPE_TAG('a', 'g', 'k', 0), // agk = Isarog Agta + TRUETYPE_TAG('a', 'g', 'l', 0), // agl = Fembe + TRUETYPE_TAG('a', 'g', 'm', 0), // agm = Angaataha + TRUETYPE_TAG('a', 'g', 'n', 0), // agn = Agutaynen + TRUETYPE_TAG('a', 'g', 'o', 0), // ago = Tainae + TRUETYPE_TAG('a', 'g', 'p', 0), // agp = Paranan + TRUETYPE_TAG('a', 'g', 'q', 0), // agq = Aghem + TRUETYPE_TAG('a', 'g', 'r', 0), // agr = Aguaruna + TRUETYPE_TAG('a', 'g', 's', 0), // ags = Esimbi + TRUETYPE_TAG('a', 'g', 't', 0), // agt = Central Cagayan Agta + TRUETYPE_TAG('a', 'g', 'u', 0), // agu = Aguacateco + TRUETYPE_TAG('a', 'g', 'v', 0), // agv = Remontado Dumagat + TRUETYPE_TAG('a', 'g', 'w', 0), // agw = Kahua + TRUETYPE_TAG('a', 'g', 'x', 0), // agx = Aghul + TRUETYPE_TAG('a', 'g', 'y', 0), // agy = Southern Alta + TRUETYPE_TAG('a', 'g', 'z', 0), // agz = Mt. Iriga Agta + TRUETYPE_TAG('a', 'h', 'a', 0), // aha = Ahanta + TRUETYPE_TAG('a', 'h', 'b', 0), // ahb = Axamb + TRUETYPE_TAG('a', 'h', 'g', 0), // ahg = Qimant + TRUETYPE_TAG('a', 'h', 'h', 0), // ahh = Aghu + TRUETYPE_TAG('a', 'h', 'i', 0), // ahi = Tiagbamrin Aizi + TRUETYPE_TAG('a', 'h', 'k', 0), // ahk = Akha + TRUETYPE_TAG('a', 'h', 'l', 0), // ahl = Igo + TRUETYPE_TAG('a', 'h', 'm', 0), // ahm = Mobumrin Aizi + TRUETYPE_TAG('a', 'h', 'n', 0), // ahn = Àhàn + TRUETYPE_TAG('a', 'h', 'o', 0), // aho = Ahom + TRUETYPE_TAG('a', 'h', 'p', 0), // ahp = Aproumu Aizi + TRUETYPE_TAG('a', 'h', 'r', 0), // ahr = Ahirani + TRUETYPE_TAG('a', 'h', 's', 0), // ahs = Ashe + TRUETYPE_TAG('a', 'h', 't', 0), // aht = Ahtena + TRUETYPE_TAG('a', 'i', 'a', 0), // aia = Arosi + TRUETYPE_TAG('a', 'i', 'b', 0), // aib = Ainu (China) + TRUETYPE_TAG('a', 'i', 'c', 0), // aic = Ainbai + TRUETYPE_TAG('a', 'i', 'd', 0), // aid = Alngith + TRUETYPE_TAG('a', 'i', 'e', 0), // aie = Amara + TRUETYPE_TAG('a', 'i', 'f', 0), // aif = Agi + TRUETYPE_TAG('a', 'i', 'g', 0), // aig = Antigua and Barbuda Creole English + TRUETYPE_TAG('a', 'i', 'h', 0), // aih = Ai-Cham + TRUETYPE_TAG('a', 'i', 'i', 0), // aii = Assyrian Neo-Aramaic + TRUETYPE_TAG('a', 'i', 'j', 0), // aij = Lishanid Noshan + TRUETYPE_TAG('a', 'i', 'k', 0), // aik = Ake + TRUETYPE_TAG('a', 'i', 'l', 0), // ail = Aimele + TRUETYPE_TAG('a', 'i', 'm', 0), // aim = Aimol + TRUETYPE_TAG('a', 'i', 'n', 0), // ain = Ainu (Japan) + TRUETYPE_TAG('a', 'i', 'o', 0), // aio = Aiton + TRUETYPE_TAG('a', 'i', 'p', 0), // aip = Burumakok + TRUETYPE_TAG('a', 'i', 'q', 0), // aiq = Aimaq + TRUETYPE_TAG('a', 'i', 'r', 0), // air = Airoran + TRUETYPE_TAG('a', 'i', 's', 0), // ais = Nataoran Amis + TRUETYPE_TAG('a', 'i', 't', 0), // ait = Arikem + TRUETYPE_TAG('a', 'i', 'w', 0), // aiw = Aari + TRUETYPE_TAG('a', 'i', 'x', 0), // aix = Aighon + TRUETYPE_TAG('a', 'i', 'y', 0), // aiy = Ali + TRUETYPE_TAG('a', 'j', 'a', 0), // aja = Aja (Sudan) + TRUETYPE_TAG('a', 'j', 'g', 0), // ajg = Aja (Benin) + TRUETYPE_TAG('a', 'j', 'i', 0), // aji = Ajië + TRUETYPE_TAG('a', 'j', 'p', 0), // ajp = South Levantine Arabic + TRUETYPE_TAG('a', 'j', 't', 0), // ajt = Judeo-Tunisian Arabic + TRUETYPE_TAG('a', 'j', 'u', 0), // aju = Judeo-Moroccan Arabic + TRUETYPE_TAG('a', 'j', 'w', 0), // ajw = Ajawa + TRUETYPE_TAG('a', 'j', 'z', 0), // ajz = Amri Karbi + TRUETYPE_TAG('a', 'k', 'b', 0), // akb = Batak Angkola + TRUETYPE_TAG('a', 'k', 'c', 0), // akc = Mpur + TRUETYPE_TAG('a', 'k', 'd', 0), // akd = Ukpet-Ehom + TRUETYPE_TAG('a', 'k', 'e', 0), // ake = Akawaio + TRUETYPE_TAG('a', 'k', 'f', 0), // akf = Akpa + TRUETYPE_TAG('a', 'k', 'g', 0), // akg = Anakalangu + TRUETYPE_TAG('a', 'k', 'h', 0), // akh = Angal Heneng + TRUETYPE_TAG('a', 'k', 'i', 0), // aki = Aiome + TRUETYPE_TAG('a', 'k', 'j', 0), // akj = Aka-Jeru + TRUETYPE_TAG('a', 'k', 'k', 0), // akk = Akkadian + TRUETYPE_TAG('a', 'k', 'l', 0), // akl = Aklanon + TRUETYPE_TAG('a', 'k', 'm', 0), // akm = Aka-Bo + TRUETYPE_TAG('a', 'k', 'o', 0), // ako = Akurio + TRUETYPE_TAG('a', 'k', 'p', 0), // akp = Siwu + TRUETYPE_TAG('a', 'k', 'q', 0), // akq = Ak + TRUETYPE_TAG('a', 'k', 'r', 0), // akr = Araki + TRUETYPE_TAG('a', 'k', 's', 0), // aks = Akaselem + TRUETYPE_TAG('a', 'k', 't', 0), // akt = Akolet + TRUETYPE_TAG('a', 'k', 'u', 0), // aku = Akum + TRUETYPE_TAG('a', 'k', 'v', 0), // akv = Akhvakh + TRUETYPE_TAG('a', 'k', 'w', 0), // akw = Akwa + TRUETYPE_TAG('a', 'k', 'x', 0), // akx = Aka-Kede + TRUETYPE_TAG('a', 'k', 'y', 0), // aky = Aka-Kol + TRUETYPE_TAG('a', 'k', 'z', 0), // akz = Alabama + TRUETYPE_TAG('a', 'l', 'a', 0), // ala = Alago + TRUETYPE_TAG('a', 'l', 'c', 0), // alc = Qawasqar + TRUETYPE_TAG('a', 'l', 'd', 0), // ald = Alladian + TRUETYPE_TAG('a', 'l', 'e', 0), // ale = Aleut + TRUETYPE_TAG('a', 'l', 'f', 0), // alf = Alege + TRUETYPE_TAG('a', 'l', 'g', 0), // alg = Algonquian languages + TRUETYPE_TAG('a', 'l', 'h', 0), // alh = Alawa + TRUETYPE_TAG('a', 'l', 'i', 0), // ali = Amaimon + TRUETYPE_TAG('a', 'l', 'j', 0), // alj = Alangan + TRUETYPE_TAG('a', 'l', 'k', 0), // alk = Alak + TRUETYPE_TAG('a', 'l', 'l', 0), // all = Allar + TRUETYPE_TAG('a', 'l', 'm', 0), // alm = Amblong + TRUETYPE_TAG('a', 'l', 'n', 0), // aln = Gheg Albanian + TRUETYPE_TAG('a', 'l', 'o', 0), // alo = Larike-Wakasihu + TRUETYPE_TAG('a', 'l', 'p', 0), // alp = Alune + TRUETYPE_TAG('a', 'l', 'q', 0), // alq = Algonquin + TRUETYPE_TAG('a', 'l', 'r', 0), // alr = Alutor + TRUETYPE_TAG('a', 'l', 's', 0), // als = Tosk Albanian + TRUETYPE_TAG('a', 'l', 't', 0), // alt = Southern Altai + TRUETYPE_TAG('a', 'l', 'u', 0), // alu = 'Are'are + TRUETYPE_TAG('a', 'l', 'v', 0), // alv = Atlantic-Congo languages + TRUETYPE_TAG('a', 'l', 'w', 0), // alw = Alaba-K’abeena + TRUETYPE_TAG('a', 'l', 'x', 0), // alx = Amol + TRUETYPE_TAG('a', 'l', 'y', 0), // aly = Alyawarr + TRUETYPE_TAG('a', 'l', 'z', 0), // alz = Alur + TRUETYPE_TAG('a', 'm', 'a', 0), // ama = Amanayé + TRUETYPE_TAG('a', 'm', 'b', 0), // amb = Ambo + TRUETYPE_TAG('a', 'm', 'c', 0), // amc = Amahuaca + TRUETYPE_TAG('a', 'm', 'e', 0), // ame = Yanesha' + TRUETYPE_TAG('a', 'm', 'f', 0), // amf = Hamer-Banna + TRUETYPE_TAG('a', 'm', 'g', 0), // amg = Amarag + TRUETYPE_TAG('a', 'm', 'i', 0), // ami = Amis + TRUETYPE_TAG('a', 'm', 'j', 0), // amj = Amdang + TRUETYPE_TAG('a', 'm', 'k', 0), // amk = Ambai + TRUETYPE_TAG('a', 'm', 'l', 0), // aml = War-Jaintia + TRUETYPE_TAG('a', 'm', 'm', 0), // amm = Ama (Papua New Guinea) + TRUETYPE_TAG('a', 'm', 'n', 0), // amn = Amanab + TRUETYPE_TAG('a', 'm', 'o', 0), // amo = Amo + TRUETYPE_TAG('a', 'm', 'p', 0), // amp = Alamblak + TRUETYPE_TAG('a', 'm', 'q', 0), // amq = Amahai + TRUETYPE_TAG('a', 'm', 'r', 0), // amr = Amarakaeri + TRUETYPE_TAG('a', 'm', 's', 0), // ams = Southern Amami-Oshima + TRUETYPE_TAG('a', 'm', 't', 0), // amt = Amto + TRUETYPE_TAG('a', 'm', 'u', 0), // amu = Guerrero Amuzgo + TRUETYPE_TAG('a', 'm', 'v', 0), // amv = Ambelau + TRUETYPE_TAG('a', 'm', 'w', 0), // amw = Western Neo-Aramaic + TRUETYPE_TAG('a', 'm', 'x', 0), // amx = Anmatyerre + TRUETYPE_TAG('a', 'm', 'y', 0), // amy = Ami + TRUETYPE_TAG('a', 'm', 'z', 0), // amz = Atampaya + TRUETYPE_TAG('a', 'n', 'a', 0), // ana = Andaqui + TRUETYPE_TAG('a', 'n', 'b', 0), // anb = Andoa + TRUETYPE_TAG('a', 'n', 'c', 0), // anc = Ngas + TRUETYPE_TAG('a', 'n', 'd', 0), // and = Ansus + TRUETYPE_TAG('a', 'n', 'e', 0), // ane = Xârâcùù + TRUETYPE_TAG('a', 'n', 'f', 0), // anf = Animere + TRUETYPE_TAG('a', 'n', 'g', 0), // ang = Old English (ca. 450-1100) + TRUETYPE_TAG('a', 'n', 'h', 0), // anh = Nend + TRUETYPE_TAG('a', 'n', 'i', 0), // ani = Andi + TRUETYPE_TAG('a', 'n', 'j', 0), // anj = Anor + TRUETYPE_TAG('a', 'n', 'k', 0), // ank = Goemai + TRUETYPE_TAG('a', 'n', 'l', 0), // anl = Anu + TRUETYPE_TAG('a', 'n', 'm', 0), // anm = Anal + TRUETYPE_TAG('a', 'n', 'n', 0), // ann = Obolo + TRUETYPE_TAG('a', 'n', 'o', 0), // ano = Andoque + TRUETYPE_TAG('a', 'n', 'p', 0), // anp = Angika + TRUETYPE_TAG('a', 'n', 'q', 0), // anq = Jarawa (India) + TRUETYPE_TAG('a', 'n', 'r', 0), // anr = Andh + TRUETYPE_TAG('a', 'n', 's', 0), // ans = Anserma + TRUETYPE_TAG('a', 'n', 't', 0), // ant = Antakarinya + TRUETYPE_TAG('a', 'n', 'u', 0), // anu = Anuak + TRUETYPE_TAG('a', 'n', 'v', 0), // anv = Denya + TRUETYPE_TAG('a', 'n', 'w', 0), // anw = Anaang + TRUETYPE_TAG('a', 'n', 'x', 0), // anx = Andra-Hus + TRUETYPE_TAG('a', 'n', 'y', 0), // any = Anyin + TRUETYPE_TAG('a', 'n', 'z', 0), // anz = Anem + TRUETYPE_TAG('a', 'o', 'a', 0), // aoa = Angolar + TRUETYPE_TAG('a', 'o', 'b', 0), // aob = Abom + TRUETYPE_TAG('a', 'o', 'c', 0), // aoc = Pemon + TRUETYPE_TAG('a', 'o', 'd', 0), // aod = Andarum + TRUETYPE_TAG('a', 'o', 'e', 0), // aoe = Angal Enen + TRUETYPE_TAG('a', 'o', 'f', 0), // aof = Bragat + TRUETYPE_TAG('a', 'o', 'g', 0), // aog = Angoram + TRUETYPE_TAG('a', 'o', 'h', 0), // aoh = Arma + TRUETYPE_TAG('a', 'o', 'i', 0), // aoi = Anindilyakwa + TRUETYPE_TAG('a', 'o', 'j', 0), // aoj = Mufian + TRUETYPE_TAG('a', 'o', 'k', 0), // aok = Arhö + TRUETYPE_TAG('a', 'o', 'l', 0), // aol = Alor + TRUETYPE_TAG('a', 'o', 'm', 0), // aom = Ömie + TRUETYPE_TAG('a', 'o', 'n', 0), // aon = Bumbita Arapesh + TRUETYPE_TAG('a', 'o', 'r', 0), // aor = Aore + TRUETYPE_TAG('a', 'o', 's', 0), // aos = Taikat + TRUETYPE_TAG('a', 'o', 't', 0), // aot = A'tong + TRUETYPE_TAG('a', 'o', 'x', 0), // aox = Atorada + TRUETYPE_TAG('a', 'o', 'z', 0), // aoz = Uab Meto + TRUETYPE_TAG('a', 'p', 'a', 0), // apa = Apache languages + TRUETYPE_TAG('a', 'p', 'b', 0), // apb = Sa'a + TRUETYPE_TAG('a', 'p', 'c', 0), // apc = North Levantine Arabic + TRUETYPE_TAG('a', 'p', 'd', 0), // apd = Sudanese Arabic + TRUETYPE_TAG('a', 'p', 'e', 0), // ape = Bukiyip + TRUETYPE_TAG('a', 'p', 'f', 0), // apf = Pahanan Agta + TRUETYPE_TAG('a', 'p', 'g', 0), // apg = Ampanang + TRUETYPE_TAG('a', 'p', 'h', 0), // aph = Athpariya + TRUETYPE_TAG('a', 'p', 'i', 0), // api = Apiaká + TRUETYPE_TAG('a', 'p', 'j', 0), // apj = Jicarilla Apache + TRUETYPE_TAG('a', 'p', 'k', 0), // apk = Kiowa Apache + TRUETYPE_TAG('a', 'p', 'l', 0), // apl = Lipan Apache + TRUETYPE_TAG('a', 'p', 'm', 0), // apm = Mescalero-Chiricahua Apache + TRUETYPE_TAG('a', 'p', 'n', 0), // apn = Apinayé + TRUETYPE_TAG('a', 'p', 'o', 0), // apo = Ambul + TRUETYPE_TAG('a', 'p', 'p', 0), // app = Apma + TRUETYPE_TAG('a', 'p', 'q', 0), // apq = A-Pucikwar + TRUETYPE_TAG('a', 'p', 'r', 0), // apr = Arop-Lokep + TRUETYPE_TAG('a', 'p', 's', 0), // aps = Arop-Sissano + TRUETYPE_TAG('a', 'p', 't', 0), // apt = Apatani + TRUETYPE_TAG('a', 'p', 'u', 0), // apu = Apurinã + TRUETYPE_TAG('a', 'p', 'v', 0), // apv = Alapmunte + TRUETYPE_TAG('a', 'p', 'w', 0), // apw = Western Apache + TRUETYPE_TAG('a', 'p', 'x', 0), // apx = Aputai + TRUETYPE_TAG('a', 'p', 'y', 0), // apy = Apalaí + TRUETYPE_TAG('a', 'p', 'z', 0), // apz = Safeyoka + TRUETYPE_TAG('a', 'q', 'a', 0), // aqa = Alacalufan languages + TRUETYPE_TAG('a', 'q', 'c', 0), // aqc = Archi + TRUETYPE_TAG('a', 'q', 'd', 0), // aqd = Ampari Dogon + TRUETYPE_TAG('a', 'q', 'g', 0), // aqg = Arigidi + TRUETYPE_TAG('a', 'q', 'l', 0), // aql = Algic languages + TRUETYPE_TAG('a', 'q', 'm', 0), // aqm = Atohwaim + TRUETYPE_TAG('a', 'q', 'n', 0), // aqn = Northern Alta + TRUETYPE_TAG('a', 'q', 'p', 0), // aqp = Atakapa + TRUETYPE_TAG('a', 'q', 'r', 0), // aqr = Arhâ + TRUETYPE_TAG('a', 'q', 'z', 0), // aqz = Akuntsu + TRUETYPE_TAG('a', 'r', 'b', 0), // arb = Standard Arabic + TRUETYPE_TAG('a', 'r', 'c', 0), // arc = Official Aramaic (700-300 BCE) + TRUETYPE_TAG('a', 'r', 'd', 0), // ard = Arabana + TRUETYPE_TAG('a', 'r', 'e', 0), // are = Western Arrarnta + TRUETYPE_TAG('a', 'r', 'h', 0), // arh = Arhuaco + TRUETYPE_TAG('a', 'r', 'i', 0), // ari = Arikara + TRUETYPE_TAG('a', 'r', 'j', 0), // arj = Arapaso + TRUETYPE_TAG('a', 'r', 'k', 0), // ark = Arikapú + TRUETYPE_TAG('a', 'r', 'l', 0), // arl = Arabela + TRUETYPE_TAG('a', 'r', 'n', 0), // arn = Mapudungun + TRUETYPE_TAG('a', 'r', 'o', 0), // aro = Araona + TRUETYPE_TAG('a', 'r', 'p', 0), // arp = Arapaho + TRUETYPE_TAG('a', 'r', 'q', 0), // arq = Algerian Arabic + TRUETYPE_TAG('a', 'r', 'r', 0), // arr = Karo (Brazil) + TRUETYPE_TAG('a', 'r', 's', 0), // ars = Najdi Arabic + TRUETYPE_TAG('a', 'r', 't', 0), // art = Artificial languages + TRUETYPE_TAG('a', 'r', 'u', 0), // aru = Aruá (Amazonas State) + TRUETYPE_TAG('a', 'r', 'v', 0), // arv = Arbore + TRUETYPE_TAG('a', 'r', 'w', 0), // arw = Arawak + TRUETYPE_TAG('a', 'r', 'x', 0), // arx = Aruá (Rodonia State) + TRUETYPE_TAG('a', 'r', 'y', 0), // ary = Moroccan Arabic + TRUETYPE_TAG('a', 'r', 'z', 0), // arz = Egyptian Arabic + TRUETYPE_TAG('a', 's', 'a', 0), // asa = Asu (Tanzania) + TRUETYPE_TAG('a', 's', 'b', 0), // asb = Assiniboine + TRUETYPE_TAG('a', 's', 'c', 0), // asc = Casuarina Coast Asmat + TRUETYPE_TAG('a', 's', 'd', 0), // asd = Asas + TRUETYPE_TAG('a', 's', 'e', 0), // ase = American Sign Language + TRUETYPE_TAG('a', 's', 'f', 0), // asf = Australian Sign Language + TRUETYPE_TAG('a', 's', 'g', 0), // asg = Cishingini + TRUETYPE_TAG('a', 's', 'h', 0), // ash = Abishira + TRUETYPE_TAG('a', 's', 'i', 0), // asi = Buruwai + TRUETYPE_TAG('a', 's', 'j', 0), // asj = Nsari + TRUETYPE_TAG('a', 's', 'k', 0), // ask = Ashkun + TRUETYPE_TAG('a', 's', 'l', 0), // asl = Asilulu + TRUETYPE_TAG('a', 's', 'n', 0), // asn = Xingú Asuriní + TRUETYPE_TAG('a', 's', 'o', 0), // aso = Dano + TRUETYPE_TAG('a', 's', 'p', 0), // asp = Algerian Sign Language + TRUETYPE_TAG('a', 's', 'q', 0), // asq = Austrian Sign Language + TRUETYPE_TAG('a', 's', 'r', 0), // asr = Asuri + TRUETYPE_TAG('a', 's', 's', 0), // ass = Ipulo + TRUETYPE_TAG('a', 's', 't', 0), // ast = Asturian + TRUETYPE_TAG('a', 's', 'u', 0), // asu = Tocantins Asurini + TRUETYPE_TAG('a', 's', 'v', 0), // asv = Asoa + TRUETYPE_TAG('a', 's', 'w', + 0), // asw = Australian Aborigines Sign Language + TRUETYPE_TAG('a', 's', 'x', 0), // asx = Muratayak + TRUETYPE_TAG('a', 's', 'y', 0), // asy = Yaosakor Asmat + TRUETYPE_TAG('a', 's', 'z', 0), // asz = As + TRUETYPE_TAG('a', 't', 'a', 0), // ata = Pele-Ata + TRUETYPE_TAG('a', 't', 'b', 0), // atb = Zaiwa + TRUETYPE_TAG('a', 't', 'c', 0), // atc = Atsahuaca + TRUETYPE_TAG('a', 't', 'd', 0), // atd = Ata Manobo + TRUETYPE_TAG('a', 't', 'e', 0), // ate = Atemble + TRUETYPE_TAG('a', 't', 'g', 0), // atg = Ivbie North-Okpela-Arhe + TRUETYPE_TAG('a', 't', 'h', 0), // ath = Athapascan languages + TRUETYPE_TAG('a', 't', 'i', 0), // ati = Attié + TRUETYPE_TAG('a', 't', 'j', 0), // atj = Atikamekw + TRUETYPE_TAG('a', 't', 'k', 0), // atk = Ati + TRUETYPE_TAG('a', 't', 'l', 0), // atl = Mt. Iraya Agta + TRUETYPE_TAG('a', 't', 'm', 0), // atm = Ata + TRUETYPE_TAG('a', 't', 'n', 0), // atn = Ashtiani + TRUETYPE_TAG('a', 't', 'o', 0), // ato = Atong + TRUETYPE_TAG('a', 't', 'p', 0), // atp = Pudtol Atta + TRUETYPE_TAG('a', 't', 'q', 0), // atq = Aralle-Tabulahan + TRUETYPE_TAG('a', 't', 'r', 0), // atr = Waimiri-Atroari + TRUETYPE_TAG('a', 't', 's', 0), // ats = Gros Ventre + TRUETYPE_TAG('a', 't', 't', 0), // att = Pamplona Atta + TRUETYPE_TAG('a', 't', 'u', 0), // atu = Reel + TRUETYPE_TAG('a', 't', 'v', 0), // atv = Northern Altai + TRUETYPE_TAG('a', 't', 'w', 0), // atw = Atsugewi + TRUETYPE_TAG('a', 't', 'x', 0), // atx = Arutani + TRUETYPE_TAG('a', 't', 'y', 0), // aty = Aneityum + TRUETYPE_TAG('a', 't', 'z', 0), // atz = Arta + TRUETYPE_TAG('a', 'u', 'a', 0), // aua = Asumboa + TRUETYPE_TAG('a', 'u', 'b', 0), // aub = Alugu + TRUETYPE_TAG('a', 'u', 'c', 0), // auc = Waorani + TRUETYPE_TAG('a', 'u', 'd', 0), // aud = Anuta + TRUETYPE_TAG('a', 'u', 'e', 0), // aue = =/Kx'au//'ein + TRUETYPE_TAG('a', 'u', 'f', 0), // auf = Arauan languages + TRUETYPE_TAG('a', 'u', 'g', 0), // aug = Aguna + TRUETYPE_TAG('a', 'u', 'h', 0), // auh = Aushi + TRUETYPE_TAG('a', 'u', 'i', 0), // aui = Anuki + TRUETYPE_TAG('a', 'u', 'j', 0), // auj = Awjilah + TRUETYPE_TAG('a', 'u', 'k', 0), // auk = Heyo + TRUETYPE_TAG('a', 'u', 'l', 0), // aul = Aulua + TRUETYPE_TAG('a', 'u', 'm', 0), // aum = Asu (Nigeria) + TRUETYPE_TAG('a', 'u', 'n', 0), // aun = Molmo One + TRUETYPE_TAG('a', 'u', 'o', 0), // auo = Auyokawa + TRUETYPE_TAG('a', 'u', 'p', 0), // aup = Makayam + TRUETYPE_TAG('a', 'u', 'q', 0), // auq = Anus + TRUETYPE_TAG('a', 'u', 'r', 0), // aur = Aruek + TRUETYPE_TAG('a', 'u', 's', 0), // aus = Australian languages + TRUETYPE_TAG('a', 'u', 't', 0), // aut = Austral + TRUETYPE_TAG('a', 'u', 'u', 0), // auu = Auye + TRUETYPE_TAG('a', 'u', 'w', 0), // auw = Awyi + TRUETYPE_TAG('a', 'u', 'x', 0), // aux = Aurá + TRUETYPE_TAG('a', 'u', 'y', 0), // auy = Awiyaana + TRUETYPE_TAG('a', 'u', 'z', 0), // auz = Uzbeki Arabic + TRUETYPE_TAG('a', 'v', 'b', 0), // avb = Avau + TRUETYPE_TAG('a', 'v', 'd', 0), // avd = Alviri-Vidari + TRUETYPE_TAG('a', 'v', 'i', 0), // avi = Avikam + TRUETYPE_TAG('a', 'v', 'k', 0), // avk = Kotava + TRUETYPE_TAG('a', 'v', 'l', 0), // avl = Eastern Egyptian Bedawi Arabic + TRUETYPE_TAG('a', 'v', 'n', 0), // avn = Avatime + TRUETYPE_TAG('a', 'v', 'o', 0), // avo = Agavotaguerra + TRUETYPE_TAG('a', 'v', 's', 0), // avs = Aushiri + TRUETYPE_TAG('a', 'v', 't', 0), // avt = Au + TRUETYPE_TAG('a', 'v', 'u', 0), // avu = Avokaya + TRUETYPE_TAG('a', 'v', 'v', 0), // avv = Avá-Canoeiro + TRUETYPE_TAG('a', 'w', 'a', 0), // awa = Awadhi + TRUETYPE_TAG('a', 'w', 'b', 0), // awb = Awa (Papua New Guinea) + TRUETYPE_TAG('a', 'w', 'c', 0), // awc = Cicipu + TRUETYPE_TAG('a', 'w', 'd', 0), // awd = Arawakan languages + TRUETYPE_TAG('a', 'w', 'e', 0), // awe = Awetí + TRUETYPE_TAG('a', 'w', 'h', 0), // awh = Awbono + TRUETYPE_TAG('a', 'w', 'i', 0), // awi = Aekyom + TRUETYPE_TAG('a', 'w', 'k', 0), // awk = Awabakal + TRUETYPE_TAG('a', 'w', 'm', 0), // awm = Arawum + TRUETYPE_TAG('a', 'w', 'n', 0), // awn = Awngi + TRUETYPE_TAG('a', 'w', 'o', 0), // awo = Awak + TRUETYPE_TAG('a', 'w', 'r', 0), // awr = Awera + TRUETYPE_TAG('a', 'w', 's', 0), // aws = South Awyu + TRUETYPE_TAG('a', 'w', 't', 0), // awt = Araweté + TRUETYPE_TAG('a', 'w', 'u', 0), // awu = Central Awyu + TRUETYPE_TAG('a', 'w', 'v', 0), // awv = Jair Awyu + TRUETYPE_TAG('a', 'w', 'w', 0), // aww = Awun + TRUETYPE_TAG('a', 'w', 'x', 0), // awx = Awara + TRUETYPE_TAG('a', 'w', 'y', 0), // awy = Edera Awyu + TRUETYPE_TAG('a', 'x', 'b', 0), // axb = Abipon + TRUETYPE_TAG('a', 'x', 'g', 0), // axg = Mato Grosso Arára + TRUETYPE_TAG('a', 'x', 'k', 0), // axk = Yaka (Central African Republic) + TRUETYPE_TAG('a', 'x', 'm', 0), // axm = Middle Armenian + TRUETYPE_TAG('a', 'x', 'x', 0), // axx = Xaragure + TRUETYPE_TAG('a', 'y', 'a', 0), // aya = Awar + TRUETYPE_TAG('a', 'y', 'b', 0), // ayb = Ayizo Gbe + TRUETYPE_TAG('a', 'y', 'c', 0), // ayc = Southern Aymara + TRUETYPE_TAG('a', 'y', 'd', 0), // ayd = Ayabadhu + TRUETYPE_TAG('a', 'y', 'e', 0), // aye = Ayere + TRUETYPE_TAG('a', 'y', 'g', 0), // ayg = Ginyanga + TRUETYPE_TAG('a', 'y', 'h', 0), // ayh = Hadrami Arabic + TRUETYPE_TAG('a', 'y', 'i', 0), // ayi = Leyigha + TRUETYPE_TAG('a', 'y', 'k', 0), // ayk = Akuku + TRUETYPE_TAG('a', 'y', 'l', 0), // ayl = Libyan Arabic + TRUETYPE_TAG('a', 'y', 'n', 0), // ayn = Sanaani Arabic + TRUETYPE_TAG('a', 'y', 'o', 0), // ayo = Ayoreo + TRUETYPE_TAG('a', 'y', 'p', 0), // ayp = North Mesopotamian Arabic + TRUETYPE_TAG('a', 'y', 'q', 0), // ayq = Ayi (Papua New Guinea) + TRUETYPE_TAG('a', 'y', 'r', 0), // ayr = Central Aymara + TRUETYPE_TAG('a', 'y', 's', 0), // ays = Sorsogon Ayta + TRUETYPE_TAG('a', 'y', 't', 0), // ayt = Magbukun Ayta + TRUETYPE_TAG('a', 'y', 'u', 0), // ayu = Ayu + TRUETYPE_TAG('a', 'y', 'x', 0), // ayx = Ayi (China) + TRUETYPE_TAG('a', 'y', 'y', 0), // ayy = Tayabas Ayta + TRUETYPE_TAG('a', 'y', 'z', 0), // ayz = Mai Brat + TRUETYPE_TAG('a', 'z', 'a', 0), // aza = Azha + TRUETYPE_TAG('a', 'z', 'b', 0), // azb = South Azerbaijani + TRUETYPE_TAG('a', 'z', 'c', 0), // azc = Uto-Aztecan languages + TRUETYPE_TAG('a', 'z', 'g', 0), // azg = San Pedro Amuzgos Amuzgo + TRUETYPE_TAG('a', 'z', 'j', 0), // azj = North Azerbaijani + TRUETYPE_TAG('a', 'z', 'm', 0), // azm = Ipalapa Amuzgo + TRUETYPE_TAG('a', 'z', 'o', 0), // azo = Awing + TRUETYPE_TAG('a', 'z', 't', 0), // azt = Faire Atta + TRUETYPE_TAG('a', 'z', 'z', 0), // azz = Highland Puebla Nahuatl + TRUETYPE_TAG('b', 'a', 'a', 0), // baa = Babatana + TRUETYPE_TAG('b', 'a', 'b', 0), // bab = Bainouk-Gunyuño + TRUETYPE_TAG('b', 'a', 'c', 0), // bac = Badui + TRUETYPE_TAG('b', 'a', 'd', 0), // bad = Banda languages + TRUETYPE_TAG('b', 'a', 'e', 0), // bae = Baré + TRUETYPE_TAG('b', 'a', 'f', 0), // baf = Nubaca + TRUETYPE_TAG('b', 'a', 'g', 0), // bag = Tuki + TRUETYPE_TAG('b', 'a', 'h', 0), // bah = Bahamas Creole English + TRUETYPE_TAG('b', 'a', 'i', 0), // bai = Bamileke languages + TRUETYPE_TAG('b', 'a', 'j', 0), // baj = Barakai + TRUETYPE_TAG('b', 'a', 'l', 0), // bal = Baluchi + TRUETYPE_TAG('b', 'a', 'n', 0), // ban = Balinese + TRUETYPE_TAG('b', 'a', 'o', 0), // bao = Waimaha + TRUETYPE_TAG('b', 'a', 'p', 0), // bap = Bantawa + TRUETYPE_TAG('b', 'a', 'r', 0), // bar = Bavarian + TRUETYPE_TAG('b', 'a', 's', 0), // bas = Basa (Cameroon) + TRUETYPE_TAG('b', 'a', 't', 0), // bat = Baltic languages + TRUETYPE_TAG('b', 'a', 'u', 0), // bau = Bada (Nigeria) + TRUETYPE_TAG('b', 'a', 'v', 0), // bav = Vengo + TRUETYPE_TAG('b', 'a', 'w', 0), // baw = Bambili-Bambui + TRUETYPE_TAG('b', 'a', 'x', 0), // bax = Bamun + TRUETYPE_TAG('b', 'a', 'y', 0), // bay = Batuley + TRUETYPE_TAG('b', 'a', 'z', 0), // baz = Tunen + TRUETYPE_TAG('b', 'b', 'a', 0), // bba = Baatonum + TRUETYPE_TAG('b', 'b', 'b', 0), // bbb = Barai + TRUETYPE_TAG('b', 'b', 'c', 0), // bbc = Batak Toba + TRUETYPE_TAG('b', 'b', 'd', 0), // bbd = Bau + TRUETYPE_TAG('b', 'b', 'e', 0), // bbe = Bangba + TRUETYPE_TAG('b', 'b', 'f', 0), // bbf = Baibai + TRUETYPE_TAG('b', 'b', 'g', 0), // bbg = Barama + TRUETYPE_TAG('b', 'b', 'h', 0), // bbh = Bugan + TRUETYPE_TAG('b', 'b', 'i', 0), // bbi = Barombi + TRUETYPE_TAG('b', 'b', 'j', 0), // bbj = Ghomálá' + TRUETYPE_TAG('b', 'b', 'k', 0), // bbk = Babanki + TRUETYPE_TAG('b', 'b', 'l', 0), // bbl = Bats + TRUETYPE_TAG('b', 'b', 'm', 0), // bbm = Babango + TRUETYPE_TAG('b', 'b', 'n', 0), // bbn = Uneapa + TRUETYPE_TAG('b', 'b', 'o', 0), // bbo = Northern Bobo Madaré + TRUETYPE_TAG('b', 'b', 'p', 0), // bbp = West Central Banda + TRUETYPE_TAG('b', 'b', 'q', 0), // bbq = Bamali + TRUETYPE_TAG('b', 'b', 'r', 0), // bbr = Girawa + TRUETYPE_TAG('b', 'b', 's', 0), // bbs = Bakpinka + TRUETYPE_TAG('b', 'b', 't', 0), // bbt = Mburku + TRUETYPE_TAG('b', 'b', 'u', 0), // bbu = Kulung (Nigeria) + TRUETYPE_TAG('b', 'b', 'v', 0), // bbv = Karnai + TRUETYPE_TAG('b', 'b', 'w', 0), // bbw = Baba + TRUETYPE_TAG('b', 'b', 'x', 0), // bbx = Bubia + TRUETYPE_TAG('b', 'b', 'y', 0), // bby = Befang + TRUETYPE_TAG('b', 'b', 'z', 0), // bbz = Babalia Creole Arabic + TRUETYPE_TAG('b', 'c', 'a', 0), // bca = Central Bai + TRUETYPE_TAG('b', 'c', 'b', 0), // bcb = Bainouk-Samik + TRUETYPE_TAG('b', 'c', 'c', 0), // bcc = Southern Balochi + TRUETYPE_TAG('b', 'c', 'd', 0), // bcd = North Babar + TRUETYPE_TAG('b', 'c', 'e', 0), // bce = Bamenyam + TRUETYPE_TAG('b', 'c', 'f', 0), // bcf = Bamu + TRUETYPE_TAG('b', 'c', 'g', 0), // bcg = Baga Binari + TRUETYPE_TAG('b', 'c', 'h', 0), // bch = Bariai + TRUETYPE_TAG('b', 'c', 'i', 0), // bci = Baoulé + TRUETYPE_TAG('b', 'c', 'j', 0), // bcj = Bardi + TRUETYPE_TAG('b', 'c', 'k', 0), // bck = Bunaba + TRUETYPE_TAG('b', 'c', 'l', 0), // bcl = Central Bicolano + TRUETYPE_TAG('b', 'c', 'm', 0), // bcm = Bannoni + TRUETYPE_TAG('b', 'c', 'n', 0), // bcn = Bali (Nigeria) + TRUETYPE_TAG('b', 'c', 'o', 0), // bco = Kaluli + TRUETYPE_TAG('b', 'c', 'p', + 0), // bcp = Bali (Democratic Republic of Congo) + TRUETYPE_TAG('b', 'c', 'q', 0), // bcq = Bench + TRUETYPE_TAG('b', 'c', 'r', 0), // bcr = Babine + TRUETYPE_TAG('b', 'c', 's', 0), // bcs = Kohumono + TRUETYPE_TAG('b', 'c', 't', 0), // bct = Bendi + TRUETYPE_TAG('b', 'c', 'u', 0), // bcu = Awad Bing + TRUETYPE_TAG('b', 'c', 'v', 0), // bcv = Shoo-Minda-Nye + TRUETYPE_TAG('b', 'c', 'w', 0), // bcw = Bana + TRUETYPE_TAG('b', 'c', 'y', 0), // bcy = Bacama + TRUETYPE_TAG('b', 'c', 'z', 0), // bcz = Bainouk-Gunyaamolo + TRUETYPE_TAG('b', 'd', 'a', 0), // bda = Bayot + TRUETYPE_TAG('b', 'd', 'b', 0), // bdb = Basap + TRUETYPE_TAG('b', 'd', 'c', 0), // bdc = Emberá-Baudó + TRUETYPE_TAG('b', 'd', 'd', 0), // bdd = Bunama + TRUETYPE_TAG('b', 'd', 'e', 0), // bde = Bade + TRUETYPE_TAG('b', 'd', 'f', 0), // bdf = Biage + TRUETYPE_TAG('b', 'd', 'g', 0), // bdg = Bonggi + TRUETYPE_TAG('b', 'd', 'h', 0), // bdh = Baka (Sudan) + TRUETYPE_TAG('b', 'd', 'i', 0), // bdi = Burun + TRUETYPE_TAG('b', 'd', 'j', 0), // bdj = Bai + TRUETYPE_TAG('b', 'd', 'k', 0), // bdk = Budukh + TRUETYPE_TAG('b', 'd', 'l', 0), // bdl = Indonesian Bajau + TRUETYPE_TAG('b', 'd', 'm', 0), // bdm = Buduma + TRUETYPE_TAG('b', 'd', 'n', 0), // bdn = Baldemu + TRUETYPE_TAG('b', 'd', 'o', 0), // bdo = Morom + TRUETYPE_TAG('b', 'd', 'p', 0), // bdp = Bende + TRUETYPE_TAG('b', 'd', 'q', 0), // bdq = Bahnar + TRUETYPE_TAG('b', 'd', 'r', 0), // bdr = West Coast Bajau + TRUETYPE_TAG('b', 'd', 's', 0), // bds = Burunge + TRUETYPE_TAG('b', 'd', 't', 0), // bdt = Bokoto + TRUETYPE_TAG('b', 'd', 'u', 0), // bdu = Oroko + TRUETYPE_TAG('b', 'd', 'v', 0), // bdv = Bodo Parja + TRUETYPE_TAG('b', 'd', 'w', 0), // bdw = Baham + TRUETYPE_TAG('b', 'd', 'x', 0), // bdx = Budong-Budong + TRUETYPE_TAG('b', 'd', 'y', 0), // bdy = Bandjalang + TRUETYPE_TAG('b', 'd', 'z', 0), // bdz = Badeshi + TRUETYPE_TAG('b', 'e', 'a', 0), // bea = Beaver + TRUETYPE_TAG('b', 'e', 'b', 0), // beb = Bebele + TRUETYPE_TAG('b', 'e', 'c', 0), // bec = Iceve-Maci + TRUETYPE_TAG('b', 'e', 'd', 0), // bed = Bedoanas + TRUETYPE_TAG('b', 'e', 'e', 0), // bee = Byangsi + TRUETYPE_TAG('b', 'e', 'f', 0), // bef = Benabena + TRUETYPE_TAG('b', 'e', 'g', 0), // beg = Belait + TRUETYPE_TAG('b', 'e', 'h', 0), // beh = Biali + TRUETYPE_TAG('b', 'e', 'i', 0), // bei = Bekati' + TRUETYPE_TAG('b', 'e', 'j', 0), // bej = Beja + TRUETYPE_TAG('b', 'e', 'k', 0), // bek = Bebeli + TRUETYPE_TAG('b', 'e', 'm', 0), // bem = Bemba (Zambia) + TRUETYPE_TAG('b', 'e', 'o', 0), // beo = Beami + TRUETYPE_TAG('b', 'e', 'p', 0), // bep = Besoa + TRUETYPE_TAG('b', 'e', 'q', 0), // beq = Beembe + TRUETYPE_TAG('b', 'e', 'r', 0), // ber = Berber languages + TRUETYPE_TAG('b', 'e', 's', 0), // bes = Besme + TRUETYPE_TAG('b', 'e', 't', 0), // bet = Guiberoua Béte + TRUETYPE_TAG('b', 'e', 'u', 0), // beu = Blagar + TRUETYPE_TAG('b', 'e', 'v', 0), // bev = Daloa Bété + TRUETYPE_TAG('b', 'e', 'w', 0), // bew = Betawi + TRUETYPE_TAG('b', 'e', 'x', 0), // bex = Jur Modo + TRUETYPE_TAG('b', 'e', 'y', 0), // bey = Beli (Papua New Guinea) + TRUETYPE_TAG('b', 'e', 'z', 0), // bez = Bena (Tanzania) + TRUETYPE_TAG('b', 'f', 'a', 0), // bfa = Bari + TRUETYPE_TAG('b', 'f', 'b', 0), // bfb = Pauri Bareli + TRUETYPE_TAG('b', 'f', 'c', 0), // bfc = Northern Bai + TRUETYPE_TAG('b', 'f', 'd', 0), // bfd = Bafut + TRUETYPE_TAG('b', 'f', 'e', 0), // bfe = Betaf + TRUETYPE_TAG('b', 'f', 'f', 0), // bff = Bofi + TRUETYPE_TAG('b', 'f', 'g', 0), // bfg = Busang Kayan + TRUETYPE_TAG('b', 'f', 'h', 0), // bfh = Blafe + TRUETYPE_TAG('b', 'f', 'i', 0), // bfi = British Sign Language + TRUETYPE_TAG('b', 'f', 'j', 0), // bfj = Bafanji + TRUETYPE_TAG('b', 'f', 'k', 0), // bfk = Ban Khor Sign Language + TRUETYPE_TAG('b', 'f', 'l', 0), // bfl = Banda-Ndélé + TRUETYPE_TAG('b', 'f', 'm', 0), // bfm = Mmen + TRUETYPE_TAG('b', 'f', 'n', 0), // bfn = Bunak + TRUETYPE_TAG('b', 'f', 'o', 0), // bfo = Malba Birifor + TRUETYPE_TAG('b', 'f', 'p', 0), // bfp = Beba + TRUETYPE_TAG('b', 'f', 'q', 0), // bfq = Badaga + TRUETYPE_TAG('b', 'f', 'r', 0), // bfr = Bazigar + TRUETYPE_TAG('b', 'f', 's', 0), // bfs = Southern Bai + TRUETYPE_TAG('b', 'f', 't', 0), // bft = Balti + TRUETYPE_TAG('b', 'f', 'u', 0), // bfu = Gahri + TRUETYPE_TAG('b', 'f', 'w', 0), // bfw = Bondo + TRUETYPE_TAG('b', 'f', 'x', 0), // bfx = Bantayanon + TRUETYPE_TAG('b', 'f', 'y', 0), // bfy = Bagheli + TRUETYPE_TAG('b', 'f', 'z', 0), // bfz = Mahasu Pahari + TRUETYPE_TAG('b', 'g', 'a', 0), // bga = Gwamhi-Wuri + TRUETYPE_TAG('b', 'g', 'b', 0), // bgb = Bobongko + TRUETYPE_TAG('b', 'g', 'c', 0), // bgc = Haryanvi + TRUETYPE_TAG('b', 'g', 'd', 0), // bgd = Rathwi Bareli + TRUETYPE_TAG('b', 'g', 'e', 0), // bge = Bauria + TRUETYPE_TAG('b', 'g', 'f', 0), // bgf = Bangandu + TRUETYPE_TAG('b', 'g', 'g', 0), // bgg = Bugun + TRUETYPE_TAG('b', 'g', 'i', 0), // bgi = Giangan + TRUETYPE_TAG('b', 'g', 'j', 0), // bgj = Bangolan + TRUETYPE_TAG('b', 'g', 'k', 0), // bgk = Bit + TRUETYPE_TAG('b', 'g', 'l', 0), // bgl = Bo (Laos) + TRUETYPE_TAG('b', 'g', 'm', 0), // bgm = Baga Mboteni + TRUETYPE_TAG('b', 'g', 'n', 0), // bgn = Western Balochi + TRUETYPE_TAG('b', 'g', 'o', 0), // bgo = Baga Koga + TRUETYPE_TAG('b', 'g', 'p', 0), // bgp = Eastern Balochi + TRUETYPE_TAG('b', 'g', 'q', 0), // bgq = Bagri + TRUETYPE_TAG('b', 'g', 'r', 0), // bgr = Bawm Chin + TRUETYPE_TAG('b', 'g', 's', 0), // bgs = Tagabawa + TRUETYPE_TAG('b', 'g', 't', 0), // bgt = Bughotu + TRUETYPE_TAG('b', 'g', 'u', 0), // bgu = Mbongno + TRUETYPE_TAG('b', 'g', 'v', 0), // bgv = Warkay-Bipim + TRUETYPE_TAG('b', 'g', 'w', 0), // bgw = Bhatri + TRUETYPE_TAG('b', 'g', 'x', 0), // bgx = Balkan Gagauz Turkish + TRUETYPE_TAG('b', 'g', 'y', 0), // bgy = Benggoi + TRUETYPE_TAG('b', 'g', 'z', 0), // bgz = Banggai + TRUETYPE_TAG('b', 'h', 'a', 0), // bha = Bharia + TRUETYPE_TAG('b', 'h', 'b', 0), // bhb = Bhili + TRUETYPE_TAG('b', 'h', 'c', 0), // bhc = Biga + TRUETYPE_TAG('b', 'h', 'd', 0), // bhd = Bhadrawahi + TRUETYPE_TAG('b', 'h', 'e', 0), // bhe = Bhaya + TRUETYPE_TAG('b', 'h', 'f', 0), // bhf = Odiai + TRUETYPE_TAG('b', 'h', 'g', 0), // bhg = Binandere + TRUETYPE_TAG('b', 'h', 'h', 0), // bhh = Bukharic + TRUETYPE_TAG('b', 'h', 'i', 0), // bhi = Bhilali + TRUETYPE_TAG('b', 'h', 'j', 0), // bhj = Bahing + TRUETYPE_TAG('b', 'h', 'k', 0), // bhk = Albay Bicolano + TRUETYPE_TAG('b', 'h', 'l', 0), // bhl = Bimin + TRUETYPE_TAG('b', 'h', 'm', 0), // bhm = Bathari + TRUETYPE_TAG('b', 'h', 'n', 0), // bhn = Bohtan Neo-Aramaic + TRUETYPE_TAG('b', 'h', 'o', 0), // bho = Bhojpuri + TRUETYPE_TAG('b', 'h', 'p', 0), // bhp = Bima + TRUETYPE_TAG('b', 'h', 'q', 0), // bhq = Tukang Besi South + TRUETYPE_TAG('b', 'h', 'r', 0), // bhr = Bara Malagasy + TRUETYPE_TAG('b', 'h', 's', 0), // bhs = Buwal + TRUETYPE_TAG('b', 'h', 't', 0), // bht = Bhattiyali + TRUETYPE_TAG('b', 'h', 'u', 0), // bhu = Bhunjia + TRUETYPE_TAG('b', 'h', 'v', 0), // bhv = Bahau + TRUETYPE_TAG('b', 'h', 'w', 0), // bhw = Biak + TRUETYPE_TAG('b', 'h', 'x', 0), // bhx = Bhalay + TRUETYPE_TAG('b', 'h', 'y', 0), // bhy = Bhele + TRUETYPE_TAG('b', 'h', 'z', 0), // bhz = Bada (Indonesia) + TRUETYPE_TAG('b', 'i', 'a', 0), // bia = Badimaya + TRUETYPE_TAG('b', 'i', 'b', 0), // bib = Bissa + TRUETYPE_TAG('b', 'i', 'c', 0), // bic = Bikaru + TRUETYPE_TAG('b', 'i', 'd', 0), // bid = Bidiyo + TRUETYPE_TAG('b', 'i', 'e', 0), // bie = Bepour + TRUETYPE_TAG('b', 'i', 'f', 0), // bif = Biafada + TRUETYPE_TAG('b', 'i', 'g', 0), // big = Biangai + TRUETYPE_TAG('b', 'i', 'j', 0), // bij = Vaghat-Ya-Bijim-Legeri + TRUETYPE_TAG('b', 'i', 'k', 0), // bik = Bikol + TRUETYPE_TAG('b', 'i', 'l', 0), // bil = Bile + TRUETYPE_TAG('b', 'i', 'm', 0), // bim = Bimoba + TRUETYPE_TAG('b', 'i', 'n', 0), // bin = Bini + TRUETYPE_TAG('b', 'i', 'o', 0), // bio = Nai + TRUETYPE_TAG('b', 'i', 'p', 0), // bip = Bila + TRUETYPE_TAG('b', 'i', 'q', 0), // biq = Bipi + TRUETYPE_TAG('b', 'i', 'r', 0), // bir = Bisorio + TRUETYPE_TAG('b', 'i', 't', 0), // bit = Berinomo + TRUETYPE_TAG('b', 'i', 'u', 0), // biu = Biete + TRUETYPE_TAG('b', 'i', 'v', 0), // biv = Southern Birifor + TRUETYPE_TAG('b', 'i', 'w', 0), // biw = Kol (Cameroon) + TRUETYPE_TAG('b', 'i', 'x', 0), // bix = Bijori + TRUETYPE_TAG('b', 'i', 'y', 0), // biy = Birhor + TRUETYPE_TAG('b', 'i', 'z', 0), // biz = Baloi + TRUETYPE_TAG('b', 'j', 'a', 0), // bja = Budza + TRUETYPE_TAG('b', 'j', 'b', 0), // bjb = Banggarla + TRUETYPE_TAG('b', 'j', 'c', 0), // bjc = Bariji + TRUETYPE_TAG('b', 'j', 'd', 0), // bjd = Bandjigali + TRUETYPE_TAG('b', 'j', 'e', 0), // bje = Biao-Jiao Mien + TRUETYPE_TAG('b', 'j', 'f', 0), // bjf = Barzani Jewish Neo-Aramaic + TRUETYPE_TAG('b', 'j', 'g', 0), // bjg = Bidyogo + TRUETYPE_TAG('b', 'j', 'h', 0), // bjh = Bahinemo + TRUETYPE_TAG('b', 'j', 'i', 0), // bji = Burji + TRUETYPE_TAG('b', 'j', 'j', 0), // bjj = Kanauji + TRUETYPE_TAG('b', 'j', 'k', 0), // bjk = Barok + TRUETYPE_TAG('b', 'j', 'l', 0), // bjl = Bulu (Papua New Guinea) + TRUETYPE_TAG('b', 'j', 'm', 0), // bjm = Bajelani + TRUETYPE_TAG('b', 'j', 'n', 0), // bjn = Banjar + TRUETYPE_TAG('b', 'j', 'o', 0), // bjo = Mid-Southern Banda + TRUETYPE_TAG('b', 'j', 'q', 0), // bjq = Southern Betsimisaraka Malagasy + TRUETYPE_TAG('b', 'j', 'r', 0), // bjr = Binumarien + TRUETYPE_TAG('b', 'j', 's', 0), // bjs = Bajan + TRUETYPE_TAG('b', 'j', 't', 0), // bjt = Balanta-Ganja + TRUETYPE_TAG('b', 'j', 'u', 0), // bju = Busuu + TRUETYPE_TAG('b', 'j', 'v', 0), // bjv = Bedjond + TRUETYPE_TAG('b', 'j', 'w', 0), // bjw = Bakwé + TRUETYPE_TAG('b', 'j', 'x', 0), // bjx = Banao Itneg + TRUETYPE_TAG('b', 'j', 'y', 0), // bjy = Bayali + TRUETYPE_TAG('b', 'j', 'z', 0), // bjz = Baruga + TRUETYPE_TAG('b', 'k', 'a', 0), // bka = Kyak + TRUETYPE_TAG('b', 'k', 'b', 0), // bkb = Finallig + TRUETYPE_TAG('b', 'k', 'c', 0), // bkc = Baka (Cameroon) + TRUETYPE_TAG('b', 'k', 'd', 0), // bkd = Binukid + TRUETYPE_TAG('b', 'k', 'f', 0), // bkf = Beeke + TRUETYPE_TAG('b', 'k', 'g', 0), // bkg = Buraka + TRUETYPE_TAG('b', 'k', 'h', 0), // bkh = Bakoko + TRUETYPE_TAG('b', 'k', 'i', 0), // bki = Baki + TRUETYPE_TAG('b', 'k', 'j', 0), // bkj = Pande + TRUETYPE_TAG('b', 'k', 'k', 0), // bkk = Brokskat + TRUETYPE_TAG('b', 'k', 'l', 0), // bkl = Berik + TRUETYPE_TAG('b', 'k', 'm', 0), // bkm = Kom (Cameroon) + TRUETYPE_TAG('b', 'k', 'n', 0), // bkn = Bukitan + TRUETYPE_TAG('b', 'k', 'o', 0), // bko = Kwa' + TRUETYPE_TAG('b', 'k', 'p', + 0), // bkp = Boko (Democratic Republic of Congo) + TRUETYPE_TAG('b', 'k', 'q', 0), // bkq = Bakairí + TRUETYPE_TAG('b', 'k', 'r', 0), // bkr = Bakumpai + TRUETYPE_TAG('b', 'k', 's', 0), // bks = Northern Sorsoganon + TRUETYPE_TAG('b', 'k', 't', 0), // bkt = Boloki + TRUETYPE_TAG('b', 'k', 'u', 0), // bku = Buhid + TRUETYPE_TAG('b', 'k', 'v', 0), // bkv = Bekwarra + TRUETYPE_TAG('b', 'k', 'w', 0), // bkw = Bekwel + TRUETYPE_TAG('b', 'k', 'x', 0), // bkx = Baikeno + TRUETYPE_TAG('b', 'k', 'y', 0), // bky = Bokyi + TRUETYPE_TAG('b', 'k', 'z', 0), // bkz = Bungku + TRUETYPE_TAG('b', 'l', 'a', 0), // bla = Siksika + TRUETYPE_TAG('b', 'l', 'b', 0), // blb = Bilua + TRUETYPE_TAG('b', 'l', 'c', 0), // blc = Bella Coola + TRUETYPE_TAG('b', 'l', 'd', 0), // bld = Bolango + TRUETYPE_TAG('b', 'l', 'e', 0), // ble = Balanta-Kentohe + TRUETYPE_TAG('b', 'l', 'f', 0), // blf = Buol + TRUETYPE_TAG('b', 'l', 'g', 0), // blg = Balau + TRUETYPE_TAG('b', 'l', 'h', 0), // blh = Kuwaa + TRUETYPE_TAG('b', 'l', 'i', 0), // bli = Bolia + TRUETYPE_TAG('b', 'l', 'j', 0), // blj = Bolongan + TRUETYPE_TAG('b', 'l', 'k', 0), // blk = Pa'o Karen + TRUETYPE_TAG('b', 'l', 'l', 0), // bll = Biloxi + TRUETYPE_TAG('b', 'l', 'm', 0), // blm = Beli (Sudan) + TRUETYPE_TAG('b', 'l', 'n', 0), // bln = Southern Catanduanes Bicolano + TRUETYPE_TAG('b', 'l', 'o', 0), // blo = Anii + TRUETYPE_TAG('b', 'l', 'p', 0), // blp = Blablanga + TRUETYPE_TAG('b', 'l', 'q', 0), // blq = Baluan-Pam + TRUETYPE_TAG('b', 'l', 'r', 0), // blr = Blang + TRUETYPE_TAG('b', 'l', 's', 0), // bls = Balaesang + TRUETYPE_TAG('b', 'l', 't', 0), // blt = Tai Dam + TRUETYPE_TAG('b', 'l', 'v', 0), // blv = Bolo + TRUETYPE_TAG('b', 'l', 'w', 0), // blw = Balangao + TRUETYPE_TAG('b', 'l', 'x', 0), // blx = Mag-Indi Ayta + TRUETYPE_TAG('b', 'l', 'y', 0), // bly = Notre + TRUETYPE_TAG('b', 'l', 'z', 0), // blz = Balantak + TRUETYPE_TAG('b', 'm', 'a', 0), // bma = Lame + TRUETYPE_TAG('b', 'm', 'b', 0), // bmb = Bembe + TRUETYPE_TAG('b', 'm', 'c', 0), // bmc = Biem + TRUETYPE_TAG('b', 'm', 'd', 0), // bmd = Baga Manduri + TRUETYPE_TAG('b', 'm', 'e', 0), // bme = Limassa + TRUETYPE_TAG('b', 'm', 'f', 0), // bmf = Bom + TRUETYPE_TAG('b', 'm', 'g', 0), // bmg = Bamwe + TRUETYPE_TAG('b', 'm', 'h', 0), // bmh = Kein + TRUETYPE_TAG('b', 'm', 'i', 0), // bmi = Bagirmi + TRUETYPE_TAG('b', 'm', 'j', 0), // bmj = Bote-Majhi + TRUETYPE_TAG('b', 'm', 'k', 0), // bmk = Ghayavi + TRUETYPE_TAG('b', 'm', 'l', 0), // bml = Bomboli + TRUETYPE_TAG('b', 'm', 'm', 0), // bmm = Northern Betsimisaraka Malagasy + TRUETYPE_TAG('b', 'm', 'n', 0), // bmn = Bina (Papua New Guinea) + TRUETYPE_TAG('b', 'm', 'o', 0), // bmo = Bambalang + TRUETYPE_TAG('b', 'm', 'p', 0), // bmp = Bulgebi + TRUETYPE_TAG('b', 'm', 'q', 0), // bmq = Bomu + TRUETYPE_TAG('b', 'm', 'r', 0), // bmr = Muinane + TRUETYPE_TAG('b', 'm', 's', 0), // bms = Bilma Kanuri + TRUETYPE_TAG('b', 'm', 't', 0), // bmt = Biao Mon + TRUETYPE_TAG('b', 'm', 'u', 0), // bmu = Somba-Siawari + TRUETYPE_TAG('b', 'm', 'v', 0), // bmv = Bum + TRUETYPE_TAG('b', 'm', 'w', 0), // bmw = Bomwali + TRUETYPE_TAG('b', 'm', 'x', 0), // bmx = Baimak + TRUETYPE_TAG('b', 'm', 'y', + 0), // bmy = Bemba (Democratic Republic of Congo) + TRUETYPE_TAG('b', 'm', 'z', 0), // bmz = Baramu + TRUETYPE_TAG('b', 'n', 'a', 0), // bna = Bonerate + TRUETYPE_TAG('b', 'n', 'b', 0), // bnb = Bookan + TRUETYPE_TAG('b', 'n', 'c', 0), // bnc = Bontok + TRUETYPE_TAG('b', 'n', 'd', 0), // bnd = Banda (Indonesia) + TRUETYPE_TAG('b', 'n', 'e', 0), // bne = Bintauna + TRUETYPE_TAG('b', 'n', 'f', 0), // bnf = Masiwang + TRUETYPE_TAG('b', 'n', 'g', 0), // bng = Benga + TRUETYPE_TAG('b', 'n', 'i', 0), // bni = Bangi + TRUETYPE_TAG('b', 'n', 'j', 0), // bnj = Eastern Tawbuid + TRUETYPE_TAG('b', 'n', 'k', 0), // bnk = Bierebo + TRUETYPE_TAG('b', 'n', 'l', 0), // bnl = Boon + TRUETYPE_TAG('b', 'n', 'm', 0), // bnm = Batanga + TRUETYPE_TAG('b', 'n', 'n', 0), // bnn = Bunun + TRUETYPE_TAG('b', 'n', 'o', 0), // bno = Bantoanon + TRUETYPE_TAG('b', 'n', 'p', 0), // bnp = Bola + TRUETYPE_TAG('b', 'n', 'q', 0), // bnq = Bantik + TRUETYPE_TAG('b', 'n', 'r', 0), // bnr = Butmas-Tur + TRUETYPE_TAG('b', 'n', 's', 0), // bns = Bundeli + TRUETYPE_TAG('b', 'n', 't', 0), // bnt = Bantu languages + TRUETYPE_TAG('b', 'n', 'u', 0), // bnu = Bentong + TRUETYPE_TAG('b', 'n', 'v', 0), // bnv = Bonerif + TRUETYPE_TAG('b', 'n', 'w', 0), // bnw = Bisis + TRUETYPE_TAG('b', 'n', 'x', 0), // bnx = Bangubangu + TRUETYPE_TAG('b', 'n', 'y', 0), // bny = Bintulu + TRUETYPE_TAG('b', 'n', 'z', 0), // bnz = Beezen + TRUETYPE_TAG('b', 'o', 'a', 0), // boa = Bora + TRUETYPE_TAG('b', 'o', 'b', 0), // bob = Aweer + TRUETYPE_TAG('b', 'o', 'e', 0), // boe = Mundabli + TRUETYPE_TAG('b', 'o', 'f', 0), // bof = Bolon + TRUETYPE_TAG('b', 'o', 'g', 0), // bog = Bamako Sign Language + TRUETYPE_TAG('b', 'o', 'h', 0), // boh = Boma + TRUETYPE_TAG('b', 'o', 'i', 0), // boi = Barbareño + TRUETYPE_TAG('b', 'o', 'j', 0), // boj = Anjam + TRUETYPE_TAG('b', 'o', 'k', 0), // bok = Bonjo + TRUETYPE_TAG('b', 'o', 'l', 0), // bol = Bole + TRUETYPE_TAG('b', 'o', 'm', 0), // bom = Berom + TRUETYPE_TAG('b', 'o', 'n', 0), // bon = Bine + TRUETYPE_TAG('b', 'o', 'o', 0), // boo = Tiemacèwè Bozo + TRUETYPE_TAG('b', 'o', 'p', 0), // bop = Bonkiman + TRUETYPE_TAG('b', 'o', 'q', 0), // boq = Bogaya + TRUETYPE_TAG('b', 'o', 'r', 0), // bor = Borôro + TRUETYPE_TAG('b', 'o', 't', 0), // bot = Bongo + TRUETYPE_TAG('b', 'o', 'u', 0), // bou = Bondei + TRUETYPE_TAG('b', 'o', 'v', 0), // bov = Tuwuli + TRUETYPE_TAG('b', 'o', 'w', 0), // bow = Rema + TRUETYPE_TAG('b', 'o', 'x', 0), // box = Buamu + TRUETYPE_TAG('b', 'o', 'y', 0), // boy = Bodo (Central African Republic) + TRUETYPE_TAG('b', 'o', 'z', 0), // boz = Tiéyaxo Bozo + TRUETYPE_TAG('b', 'p', 'a', 0), // bpa = Dakaka + TRUETYPE_TAG('b', 'p', 'b', 0), // bpb = Barbacoas + TRUETYPE_TAG('b', 'p', 'd', 0), // bpd = Banda-Banda + TRUETYPE_TAG('b', 'p', 'g', 0), // bpg = Bonggo + TRUETYPE_TAG('b', 'p', 'h', 0), // bph = Botlikh + TRUETYPE_TAG('b', 'p', 'i', 0), // bpi = Bagupi + TRUETYPE_TAG('b', 'p', 'j', 0), // bpj = Binji + TRUETYPE_TAG('b', 'p', 'k', 0), // bpk = Orowe + TRUETYPE_TAG('b', 'p', 'l', 0), // bpl = Broome Pearling Lugger Pidgin + TRUETYPE_TAG('b', 'p', 'm', 0), // bpm = Biyom + TRUETYPE_TAG('b', 'p', 'n', 0), // bpn = Dzao Min + TRUETYPE_TAG('b', 'p', 'o', 0), // bpo = Anasi + TRUETYPE_TAG('b', 'p', 'p', 0), // bpp = Kaure + TRUETYPE_TAG('b', 'p', 'q', 0), // bpq = Banda Malay + TRUETYPE_TAG('b', 'p', 'r', 0), // bpr = Koronadal Blaan + TRUETYPE_TAG('b', 'p', 's', 0), // bps = Sarangani Blaan + TRUETYPE_TAG('b', 'p', 't', 0), // bpt = Barrow Point + TRUETYPE_TAG('b', 'p', 'u', 0), // bpu = Bongu + TRUETYPE_TAG('b', 'p', 'v', 0), // bpv = Bian Marind + TRUETYPE_TAG('b', 'p', 'w', 0), // bpw = Bo (Papua New Guinea) + TRUETYPE_TAG('b', 'p', 'x', 0), // bpx = Palya Bareli + TRUETYPE_TAG('b', 'p', 'y', 0), // bpy = Bishnupriya + TRUETYPE_TAG('b', 'p', 'z', 0), // bpz = Bilba + TRUETYPE_TAG('b', 'q', 'a', 0), // bqa = Tchumbuli + TRUETYPE_TAG('b', 'q', 'b', 0), // bqb = Bagusa + TRUETYPE_TAG('b', 'q', 'c', 0), // bqc = Boko (Benin) + TRUETYPE_TAG('b', 'q', 'd', 0), // bqd = Bung + TRUETYPE_TAG('b', 'q', 'f', 0), // bqf = Baga Kaloum + TRUETYPE_TAG('b', 'q', 'g', 0), // bqg = Bago-Kusuntu + TRUETYPE_TAG('b', 'q', 'h', 0), // bqh = Baima + TRUETYPE_TAG('b', 'q', 'i', 0), // bqi = Bakhtiari + TRUETYPE_TAG('b', 'q', 'j', 0), // bqj = Bandial + TRUETYPE_TAG('b', 'q', 'k', 0), // bqk = Banda-Mbrès + TRUETYPE_TAG('b', 'q', 'l', 0), // bql = Bilakura + TRUETYPE_TAG('b', 'q', 'm', 0), // bqm = Wumboko + TRUETYPE_TAG('b', 'q', 'n', 0), // bqn = Bulgarian Sign Language + TRUETYPE_TAG('b', 'q', 'o', 0), // bqo = Balo + TRUETYPE_TAG('b', 'q', 'p', 0), // bqp = Busa + TRUETYPE_TAG('b', 'q', 'q', 0), // bqq = Biritai + TRUETYPE_TAG('b', 'q', 'r', 0), // bqr = Burusu + TRUETYPE_TAG('b', 'q', 's', 0), // bqs = Bosngun + TRUETYPE_TAG('b', 'q', 't', 0), // bqt = Bamukumbit + TRUETYPE_TAG('b', 'q', 'u', 0), // bqu = Boguru + TRUETYPE_TAG('b', 'q', 'v', 0), // bqv = Begbere-Ejar + TRUETYPE_TAG('b', 'q', 'w', 0), // bqw = Buru (Nigeria) + TRUETYPE_TAG('b', 'q', 'x', 0), // bqx = Baangi + TRUETYPE_TAG('b', 'q', 'y', 0), // bqy = Bengkala Sign Language + TRUETYPE_TAG('b', 'q', 'z', 0), // bqz = Bakaka + TRUETYPE_TAG('b', 'r', 'a', 0), // bra = Braj + TRUETYPE_TAG('b', 'r', 'b', 0), // brb = Lave + TRUETYPE_TAG('b', 'r', 'c', 0), // brc = Berbice Creole Dutch + TRUETYPE_TAG('b', 'r', 'd', 0), // brd = Baraamu + TRUETYPE_TAG('b', 'r', 'f', 0), // brf = Bera + TRUETYPE_TAG('b', 'r', 'g', 0), // brg = Baure + TRUETYPE_TAG('b', 'r', 'h', 0), // brh = Brahui + TRUETYPE_TAG('b', 'r', 'i', 0), // bri = Mokpwe + TRUETYPE_TAG('b', 'r', 'j', 0), // brj = Bieria + TRUETYPE_TAG('b', 'r', 'k', 0), // brk = Birked + TRUETYPE_TAG('b', 'r', 'l', 0), // brl = Birwa + TRUETYPE_TAG('b', 'r', 'm', 0), // brm = Barambu + TRUETYPE_TAG('b', 'r', 'n', 0), // brn = Boruca + TRUETYPE_TAG('b', 'r', 'o', 0), // bro = Brokkat + TRUETYPE_TAG('b', 'r', 'p', 0), // brp = Barapasi + TRUETYPE_TAG('b', 'r', 'q', 0), // brq = Breri + TRUETYPE_TAG('b', 'r', 'r', 0), // brr = Birao + TRUETYPE_TAG('b', 'r', 's', 0), // brs = Baras + TRUETYPE_TAG('b', 'r', 't', 0), // brt = Bitare + TRUETYPE_TAG('b', 'r', 'u', 0), // bru = Eastern Bru + TRUETYPE_TAG('b', 'r', 'v', 0), // brv = Western Bru + TRUETYPE_TAG('b', 'r', 'w', 0), // brw = Bellari + TRUETYPE_TAG('b', 'r', 'x', 0), // brx = Bodo (India) + TRUETYPE_TAG('b', 'r', 'y', 0), // bry = Burui + TRUETYPE_TAG('b', 'r', 'z', 0), // brz = Bilbil + TRUETYPE_TAG('b', 's', 'a', 0), // bsa = Abinomn + TRUETYPE_TAG('b', 's', 'b', 0), // bsb = Brunei Bisaya + TRUETYPE_TAG('b', 's', 'c', 0), // bsc = Bassari + TRUETYPE_TAG('b', 's', 'e', 0), // bse = Wushi + TRUETYPE_TAG('b', 's', 'f', 0), // bsf = Bauchi + TRUETYPE_TAG('b', 's', 'g', 0), // bsg = Bashkardi + TRUETYPE_TAG('b', 's', 'h', 0), // bsh = Kati + TRUETYPE_TAG('b', 's', 'i', 0), // bsi = Bassossi + TRUETYPE_TAG('b', 's', 'j', 0), // bsj = Bangwinji + TRUETYPE_TAG('b', 's', 'k', 0), // bsk = Burushaski + TRUETYPE_TAG('b', 's', 'l', 0), // bsl = Basa-Gumna + TRUETYPE_TAG('b', 's', 'm', 0), // bsm = Busami + TRUETYPE_TAG('b', 's', 'n', 0), // bsn = Barasana-Eduria + TRUETYPE_TAG('b', 's', 'o', 0), // bso = Buso + TRUETYPE_TAG('b', 's', 'p', 0), // bsp = Baga Sitemu + TRUETYPE_TAG('b', 's', 'q', 0), // bsq = Bassa + TRUETYPE_TAG('b', 's', 'r', 0), // bsr = Bassa-Kontagora + TRUETYPE_TAG('b', 's', 's', 0), // bss = Akoose + TRUETYPE_TAG('b', 's', 't', 0), // bst = Basketo + TRUETYPE_TAG('b', 's', 'u', 0), // bsu = Bahonsuai + TRUETYPE_TAG('b', 's', 'v', 0), // bsv = Baga Sobané + TRUETYPE_TAG('b', 's', 'w', 0), // bsw = Baiso + TRUETYPE_TAG('b', 's', 'x', 0), // bsx = Yangkam + TRUETYPE_TAG('b', 's', 'y', 0), // bsy = Sabah Bisaya + TRUETYPE_TAG('b', 't', 'a', 0), // bta = Bata + TRUETYPE_TAG('b', 't', 'b', 0), // btb = Beti (Cameroon) + TRUETYPE_TAG('b', 't', 'c', 0), // btc = Bati (Cameroon) + TRUETYPE_TAG('b', 't', 'd', 0), // btd = Batak Dairi + TRUETYPE_TAG('b', 't', 'e', 0), // bte = Gamo-Ningi + TRUETYPE_TAG('b', 't', 'f', 0), // btf = Birgit + TRUETYPE_TAG('b', 't', 'g', 0), // btg = Gagnoa Bété + TRUETYPE_TAG('b', 't', 'h', 0), // bth = Biatah Bidayuh + TRUETYPE_TAG('b', 't', 'i', 0), // bti = Burate + TRUETYPE_TAG('b', 't', 'j', 0), // btj = Bacanese Malay + TRUETYPE_TAG('b', 't', 'k', 0), // btk = Batak languages + TRUETYPE_TAG('b', 't', 'l', 0), // btl = Bhatola + TRUETYPE_TAG('b', 't', 'm', 0), // btm = Batak Mandailing + TRUETYPE_TAG('b', 't', 'n', 0), // btn = Ratagnon + TRUETYPE_TAG('b', 't', 'o', 0), // bto = Rinconada Bikol + TRUETYPE_TAG('b', 't', 'p', 0), // btp = Budibud + TRUETYPE_TAG('b', 't', 'q', 0), // btq = Batek + TRUETYPE_TAG('b', 't', 'r', 0), // btr = Baetora + TRUETYPE_TAG('b', 't', 's', 0), // bts = Batak Simalungun + TRUETYPE_TAG('b', 't', 't', 0), // btt = Bete-Bendi + TRUETYPE_TAG('b', 't', 'u', 0), // btu = Batu + TRUETYPE_TAG('b', 't', 'v', 0), // btv = Bateri + TRUETYPE_TAG('b', 't', 'w', 0), // btw = Butuanon + TRUETYPE_TAG('b', 't', 'x', 0), // btx = Batak Karo + TRUETYPE_TAG('b', 't', 'y', 0), // bty = Bobot + TRUETYPE_TAG('b', 't', 'z', 0), // btz = Batak Alas-Kluet + TRUETYPE_TAG('b', 'u', 'a', 0), // bua = Buriat + TRUETYPE_TAG('b', 'u', 'b', 0), // bub = Bua + TRUETYPE_TAG('b', 'u', 'c', 0), // buc = Bushi + TRUETYPE_TAG('b', 'u', 'd', 0), // bud = Ntcham + TRUETYPE_TAG('b', 'u', 'e', 0), // bue = Beothuk + TRUETYPE_TAG('b', 'u', 'f', 0), // buf = Bushoong + TRUETYPE_TAG('b', 'u', 'g', 0), // bug = Buginese + TRUETYPE_TAG('b', 'u', 'h', 0), // buh = Younuo Bunu + TRUETYPE_TAG('b', 'u', 'i', 0), // bui = Bongili + TRUETYPE_TAG('b', 'u', 'j', 0), // buj = Basa-Gurmana + TRUETYPE_TAG('b', 'u', 'k', 0), // buk = Bugawac + TRUETYPE_TAG('b', 'u', 'm', 0), // bum = Bulu (Cameroon) + TRUETYPE_TAG('b', 'u', 'n', 0), // bun = Sherbro + TRUETYPE_TAG('b', 'u', 'o', 0), // buo = Terei + TRUETYPE_TAG('b', 'u', 'p', 0), // bup = Busoa + TRUETYPE_TAG('b', 'u', 'q', 0), // buq = Brem + TRUETYPE_TAG('b', 'u', 's', 0), // bus = Bokobaru + TRUETYPE_TAG('b', 'u', 't', 0), // but = Bungain + TRUETYPE_TAG('b', 'u', 'u', 0), // buu = Budu + TRUETYPE_TAG('b', 'u', 'v', 0), // buv = Bun + TRUETYPE_TAG('b', 'u', 'w', 0), // buw = Bubi + TRUETYPE_TAG('b', 'u', 'x', 0), // bux = Boghom + TRUETYPE_TAG('b', 'u', 'y', 0), // buy = Bullom So + TRUETYPE_TAG('b', 'u', 'z', 0), // buz = Bukwen + TRUETYPE_TAG('b', 'v', 'a', 0), // bva = Barein + TRUETYPE_TAG('b', 'v', 'b', 0), // bvb = Bube + TRUETYPE_TAG('b', 'v', 'c', 0), // bvc = Baelelea + TRUETYPE_TAG('b', 'v', 'd', 0), // bvd = Baeggu + TRUETYPE_TAG('b', 'v', 'e', 0), // bve = Berau Malay + TRUETYPE_TAG('b', 'v', 'f', 0), // bvf = Boor + TRUETYPE_TAG('b', 'v', 'g', 0), // bvg = Bonkeng + TRUETYPE_TAG('b', 'v', 'h', 0), // bvh = Bure + TRUETYPE_TAG('b', 'v', 'i', 0), // bvi = Belanda Viri + TRUETYPE_TAG('b', 'v', 'j', 0), // bvj = Baan + TRUETYPE_TAG('b', 'v', 'k', 0), // bvk = Bukat + TRUETYPE_TAG('b', 'v', 'l', 0), // bvl = Bolivian Sign Language + TRUETYPE_TAG('b', 'v', 'm', 0), // bvm = Bamunka + TRUETYPE_TAG('b', 'v', 'n', 0), // bvn = Buna + TRUETYPE_TAG('b', 'v', 'o', 0), // bvo = Bolgo + TRUETYPE_TAG('b', 'v', 'q', 0), // bvq = Birri + TRUETYPE_TAG('b', 'v', 'r', 0), // bvr = Burarra + TRUETYPE_TAG('b', 'v', 't', 0), // bvt = Bati (Indonesia) + TRUETYPE_TAG('b', 'v', 'u', 0), // bvu = Bukit Malay + TRUETYPE_TAG('b', 'v', 'v', 0), // bvv = Baniva + TRUETYPE_TAG('b', 'v', 'w', 0), // bvw = Boga + TRUETYPE_TAG('b', 'v', 'x', 0), // bvx = Dibole + TRUETYPE_TAG('b', 'v', 'y', 0), // bvy = Baybayanon + TRUETYPE_TAG('b', 'v', 'z', 0), // bvz = Bauzi + TRUETYPE_TAG('b', 'w', 'a', 0), // bwa = Bwatoo + TRUETYPE_TAG('b', 'w', 'b', 0), // bwb = Namosi-Naitasiri-Serua + TRUETYPE_TAG('b', 'w', 'c', 0), // bwc = Bwile + TRUETYPE_TAG('b', 'w', 'd', 0), // bwd = Bwaidoka + TRUETYPE_TAG('b', 'w', 'e', 0), // bwe = Bwe Karen + TRUETYPE_TAG('b', 'w', 'f', 0), // bwf = Boselewa + TRUETYPE_TAG('b', 'w', 'g', 0), // bwg = Barwe + TRUETYPE_TAG('b', 'w', 'h', 0), // bwh = Bishuo + TRUETYPE_TAG('b', 'w', 'i', 0), // bwi = Baniwa + TRUETYPE_TAG('b', 'w', 'j', 0), // bwj = Láá Láá Bwamu + TRUETYPE_TAG('b', 'w', 'k', 0), // bwk = Bauwaki + TRUETYPE_TAG('b', 'w', 'l', 0), // bwl = Bwela + TRUETYPE_TAG('b', 'w', 'm', 0), // bwm = Biwat + TRUETYPE_TAG('b', 'w', 'n', 0), // bwn = Wunai Bunu + TRUETYPE_TAG('b', 'w', 'o', 0), // bwo = Boro (Ethiopia) + TRUETYPE_TAG('b', 'w', 'p', 0), // bwp = Mandobo Bawah + TRUETYPE_TAG('b', 'w', 'q', 0), // bwq = Southern Bobo Madaré + TRUETYPE_TAG('b', 'w', 'r', 0), // bwr = Bura-Pabir + TRUETYPE_TAG('b', 'w', 's', 0), // bws = Bomboma + TRUETYPE_TAG('b', 'w', 't', 0), // bwt = Bafaw-Balong + TRUETYPE_TAG('b', 'w', 'u', 0), // bwu = Buli (Ghana) + TRUETYPE_TAG('b', 'w', 'w', 0), // bww = Bwa + TRUETYPE_TAG('b', 'w', 'x', 0), // bwx = Bu-Nao Bunu + TRUETYPE_TAG('b', 'w', 'y', 0), // bwy = Cwi Bwamu + TRUETYPE_TAG('b', 'w', 'z', 0), // bwz = Bwisi + TRUETYPE_TAG('b', 'x', 'a', 0), // bxa = Bauro + TRUETYPE_TAG('b', 'x', 'b', 0), // bxb = Belanda Bor + TRUETYPE_TAG('b', 'x', 'c', 0), // bxc = Molengue + TRUETYPE_TAG('b', 'x', 'd', 0), // bxd = Pela + TRUETYPE_TAG('b', 'x', 'e', 0), // bxe = Birale + TRUETYPE_TAG('b', 'x', 'f', 0), // bxf = Bilur + TRUETYPE_TAG('b', 'x', 'g', 0), // bxg = Bangala + TRUETYPE_TAG('b', 'x', 'h', 0), // bxh = Buhutu + TRUETYPE_TAG('b', 'x', 'i', 0), // bxi = Pirlatapa + TRUETYPE_TAG('b', 'x', 'j', 0), // bxj = Bayungu + TRUETYPE_TAG('b', 'x', 'k', 0), // bxk = Bukusu + TRUETYPE_TAG('b', 'x', 'l', 0), // bxl = Jalkunan + TRUETYPE_TAG('b', 'x', 'm', 0), // bxm = Mongolia Buriat + TRUETYPE_TAG('b', 'x', 'n', 0), // bxn = Burduna + TRUETYPE_TAG('b', 'x', 'o', 0), // bxo = Barikanchi + TRUETYPE_TAG('b', 'x', 'p', 0), // bxp = Bebil + TRUETYPE_TAG('b', 'x', 'q', 0), // bxq = Beele + TRUETYPE_TAG('b', 'x', 'r', 0), // bxr = Russia Buriat + TRUETYPE_TAG('b', 'x', 's', 0), // bxs = Busam + TRUETYPE_TAG('b', 'x', 'u', 0), // bxu = China Buriat + TRUETYPE_TAG('b', 'x', 'v', 0), // bxv = Berakou + TRUETYPE_TAG('b', 'x', 'w', 0), // bxw = Bankagooma + TRUETYPE_TAG('b', 'x', 'x', + 0), // bxx = Borna (Democratic Republic of Congo) + TRUETYPE_TAG('b', 'x', 'z', 0), // bxz = Binahari + TRUETYPE_TAG('b', 'y', 'a', 0), // bya = Batak + TRUETYPE_TAG('b', 'y', 'b', 0), // byb = Bikya + TRUETYPE_TAG('b', 'y', 'c', 0), // byc = Ubaghara + TRUETYPE_TAG('b', 'y', 'd', 0), // byd = Benyadu' + TRUETYPE_TAG('b', 'y', 'e', 0), // bye = Pouye + TRUETYPE_TAG('b', 'y', 'f', 0), // byf = Bete + TRUETYPE_TAG('b', 'y', 'g', 0), // byg = Baygo + TRUETYPE_TAG('b', 'y', 'h', 0), // byh = Bhujel + TRUETYPE_TAG('b', 'y', 'i', 0), // byi = Buyu + TRUETYPE_TAG('b', 'y', 'j', 0), // byj = Bina (Nigeria) + TRUETYPE_TAG('b', 'y', 'k', 0), // byk = Biao + TRUETYPE_TAG('b', 'y', 'l', 0), // byl = Bayono + TRUETYPE_TAG('b', 'y', 'm', 0), // bym = Bidyara + TRUETYPE_TAG('b', 'y', 'n', 0), // byn = Bilin + TRUETYPE_TAG('b', 'y', 'o', 0), // byo = Biyo + TRUETYPE_TAG('b', 'y', 'p', 0), // byp = Bumaji + TRUETYPE_TAG('b', 'y', 'q', 0), // byq = Basay + TRUETYPE_TAG('b', 'y', 'r', 0), // byr = Baruya + TRUETYPE_TAG('b', 'y', 's', 0), // bys = Burak + TRUETYPE_TAG('b', 'y', 't', 0), // byt = Berti + TRUETYPE_TAG('b', 'y', 'v', 0), // byv = Medumba + TRUETYPE_TAG('b', 'y', 'w', 0), // byw = Belhariya + TRUETYPE_TAG('b', 'y', 'x', 0), // byx = Qaqet + TRUETYPE_TAG('b', 'y', 'y', 0), // byy = Buya + TRUETYPE_TAG('b', 'y', 'z', 0), // byz = Banaro + TRUETYPE_TAG('b', 'z', 'a', 0), // bza = Bandi + TRUETYPE_TAG('b', 'z', 'b', 0), // bzb = Andio + TRUETYPE_TAG('b', 'z', 'c', 0), // bzc = Southern Betsimisaraka Malagasy + TRUETYPE_TAG('b', 'z', 'd', 0), // bzd = Bribri + TRUETYPE_TAG('b', 'z', 'e', 0), // bze = Jenaama Bozo + TRUETYPE_TAG('b', 'z', 'f', 0), // bzf = Boikin + TRUETYPE_TAG('b', 'z', 'g', 0), // bzg = Babuza + TRUETYPE_TAG('b', 'z', 'h', 0), // bzh = Mapos Buang + TRUETYPE_TAG('b', 'z', 'i', 0), // bzi = Bisu + TRUETYPE_TAG('b', 'z', 'j', 0), // bzj = Belize Kriol English + TRUETYPE_TAG('b', 'z', 'k', 0), // bzk = Nicaragua Creole English + TRUETYPE_TAG('b', 'z', 'l', 0), // bzl = Boano (Sulawesi) + TRUETYPE_TAG('b', 'z', 'm', 0), // bzm = Bolondo + TRUETYPE_TAG('b', 'z', 'n', 0), // bzn = Boano (Maluku) + TRUETYPE_TAG('b', 'z', 'o', 0), // bzo = Bozaba + TRUETYPE_TAG('b', 'z', 'p', 0), // bzp = Kemberano + TRUETYPE_TAG('b', 'z', 'q', 0), // bzq = Buli (Indonesia) + TRUETYPE_TAG('b', 'z', 'r', 0), // bzr = Biri + TRUETYPE_TAG('b', 'z', 's', 0), // bzs = Brazilian Sign Language + TRUETYPE_TAG('b', 'z', 't', 0), // bzt = Brithenig + TRUETYPE_TAG('b', 'z', 'u', 0), // bzu = Burmeso + TRUETYPE_TAG('b', 'z', 'v', 0), // bzv = Bebe + TRUETYPE_TAG('b', 'z', 'w', 0), // bzw = Basa (Nigeria) + TRUETYPE_TAG('b', 'z', 'x', 0), // bzx = Kɛlɛngaxo Bozo + TRUETYPE_TAG('b', 'z', 'y', 0), // bzy = Obanliku + TRUETYPE_TAG('b', 'z', 'z', 0), // bzz = Evant + TRUETYPE_TAG('c', 'a', 'a', 0), // caa = Chortí + TRUETYPE_TAG('c', 'a', 'b', 0), // cab = Garifuna + TRUETYPE_TAG('c', 'a', 'c', 0), // cac = Chuj + TRUETYPE_TAG('c', 'a', 'd', 0), // cad = Caddo + TRUETYPE_TAG('c', 'a', 'e', 0), // cae = Lehar + TRUETYPE_TAG('c', 'a', 'f', 0), // caf = Southern Carrier + TRUETYPE_TAG('c', 'a', 'g', 0), // cag = Nivaclé + TRUETYPE_TAG('c', 'a', 'h', 0), // cah = Cahuarano + TRUETYPE_TAG('c', 'a', 'i', 0), // cai = Central American Indian languages + TRUETYPE_TAG('c', 'a', 'j', 0), // caj = Chané + TRUETYPE_TAG('c', 'a', 'k', 0), // cak = Kaqchikel + TRUETYPE_TAG('c', 'a', 'l', 0), // cal = Carolinian + TRUETYPE_TAG('c', 'a', 'm', 0), // cam = Cemuhî + TRUETYPE_TAG('c', 'a', 'n', 0), // can = Chambri + TRUETYPE_TAG('c', 'a', 'o', 0), // cao = Chácobo + TRUETYPE_TAG('c', 'a', 'p', 0), // cap = Chipaya + TRUETYPE_TAG('c', 'a', 'q', 0), // caq = Car Nicobarese + TRUETYPE_TAG('c', 'a', 'r', 0), // car = Galibi Carib + TRUETYPE_TAG('c', 'a', 's', 0), // cas = Tsimané + TRUETYPE_TAG('c', 'a', 'u', 0), // cau = Caucasian languages + TRUETYPE_TAG('c', 'a', 'v', 0), // cav = Cavineña + TRUETYPE_TAG('c', 'a', 'w', 0), // caw = Callawalla + TRUETYPE_TAG('c', 'a', 'x', 0), // cax = Chiquitano + TRUETYPE_TAG('c', 'a', 'y', 0), // cay = Cayuga + TRUETYPE_TAG('c', 'a', 'z', 0), // caz = Canichana + TRUETYPE_TAG('c', 'b', 'a', 0), // cba = Chibchan languages + TRUETYPE_TAG('c', 'b', 'b', 0), // cbb = Cabiyarí + TRUETYPE_TAG('c', 'b', 'c', 0), // cbc = Carapana + TRUETYPE_TAG('c', 'b', 'd', 0), // cbd = Carijona + TRUETYPE_TAG('c', 'b', 'e', 0), // cbe = Chipiajes + TRUETYPE_TAG('c', 'b', 'g', 0), // cbg = Chimila + TRUETYPE_TAG('c', 'b', 'h', 0), // cbh = Cagua + TRUETYPE_TAG('c', 'b', 'i', 0), // cbi = Chachi + TRUETYPE_TAG('c', 'b', 'j', 0), // cbj = Ede Cabe + TRUETYPE_TAG('c', 'b', 'k', 0), // cbk = Chavacano + TRUETYPE_TAG('c', 'b', 'l', 0), // cbl = Bualkhaw Chin + TRUETYPE_TAG('c', 'b', 'n', 0), // cbn = Nyahkur + TRUETYPE_TAG('c', 'b', 'o', 0), // cbo = Izora + TRUETYPE_TAG('c', 'b', 'r', 0), // cbr = Cashibo-Cacataibo + TRUETYPE_TAG('c', 'b', 's', 0), // cbs = Cashinahua + TRUETYPE_TAG('c', 'b', 't', 0), // cbt = Chayahuita + TRUETYPE_TAG('c', 'b', 'u', 0), // cbu = Candoshi-Shapra + TRUETYPE_TAG('c', 'b', 'v', 0), // cbv = Cacua + TRUETYPE_TAG('c', 'b', 'w', 0), // cbw = Kinabalian + TRUETYPE_TAG('c', 'b', 'y', 0), // cby = Carabayo + TRUETYPE_TAG('c', 'c', 'a', 0), // cca = Cauca + TRUETYPE_TAG('c', 'c', 'c', 0), // ccc = Chamicuro + TRUETYPE_TAG('c', 'c', 'd', 0), // ccd = Cafundo Creole + TRUETYPE_TAG('c', 'c', 'e', 0), // cce = Chopi + TRUETYPE_TAG('c', 'c', 'g', 0), // ccg = Samba Daka + TRUETYPE_TAG('c', 'c', 'h', 0), // cch = Atsam + TRUETYPE_TAG('c', 'c', 'j', 0), // ccj = Kasanga + TRUETYPE_TAG('c', 'c', 'l', 0), // ccl = Cutchi-Swahili + TRUETYPE_TAG('c', 'c', 'm', 0), // ccm = Malaccan Creole Malay + TRUETYPE_TAG('c', 'c', 'n', 0), // ccn = North Caucasian languages + TRUETYPE_TAG('c', 'c', 'o', 0), // cco = Comaltepec Chinantec + TRUETYPE_TAG('c', 'c', 'p', 0), // ccp = Chakma + TRUETYPE_TAG('c', 'c', 'q', 0), // ccq = Chaungtha + TRUETYPE_TAG('c', 'c', 'r', 0), // ccr = Cacaopera + TRUETYPE_TAG('c', 'c', 's', 0), // ccs = South Caucasian languages + TRUETYPE_TAG('c', 'd', 'a', 0), // cda = Choni + TRUETYPE_TAG('c', 'd', 'c', 0), // cdc = Chadic languages + TRUETYPE_TAG('c', 'd', 'd', 0), // cdd = Caddoan languages + TRUETYPE_TAG('c', 'd', 'e', 0), // cde = Chenchu + TRUETYPE_TAG('c', 'd', 'f', 0), // cdf = Chiru + TRUETYPE_TAG('c', 'd', 'g', 0), // cdg = Chamari + TRUETYPE_TAG('c', 'd', 'h', 0), // cdh = Chambeali + TRUETYPE_TAG('c', 'd', 'i', 0), // cdi = Chodri + TRUETYPE_TAG('c', 'd', 'j', 0), // cdj = Churahi + TRUETYPE_TAG('c', 'd', 'm', 0), // cdm = Chepang + TRUETYPE_TAG('c', 'd', 'n', 0), // cdn = Chaudangsi + TRUETYPE_TAG('c', 'd', 'o', 0), // cdo = Min Dong Chinese + TRUETYPE_TAG('c', 'd', 'r', 0), // cdr = Cinda-Regi-Tiyal + TRUETYPE_TAG('c', 'd', 's', 0), // cds = Chadian Sign Language + TRUETYPE_TAG('c', 'd', 'y', 0), // cdy = Chadong + TRUETYPE_TAG('c', 'd', 'z', 0), // cdz = Koda + TRUETYPE_TAG('c', 'e', 'a', 0), // cea = Lower Chehalis + TRUETYPE_TAG('c', 'e', 'b', 0), // ceb = Cebuano + TRUETYPE_TAG('c', 'e', 'g', 0), // ceg = Chamacoco + TRUETYPE_TAG('c', 'e', 'l', 0), // cel = Celtic languages + TRUETYPE_TAG('c', 'e', 'n', 0), // cen = Cen + TRUETYPE_TAG('c', 'e', 't', 0), // cet = Centúúm + TRUETYPE_TAG('c', 'f', 'a', 0), // cfa = Dijim-Bwilim + TRUETYPE_TAG('c', 'f', 'd', 0), // cfd = Cara + TRUETYPE_TAG('c', 'f', 'g', 0), // cfg = Como Karim + TRUETYPE_TAG('c', 'f', 'm', 0), // cfm = Falam Chin + TRUETYPE_TAG('c', 'g', 'a', 0), // cga = Changriwa + TRUETYPE_TAG('c', 'g', 'c', 0), // cgc = Kagayanen + TRUETYPE_TAG('c', 'g', 'g', 0), // cgg = Chiga + TRUETYPE_TAG('c', 'g', 'k', 0), // cgk = Chocangacakha + TRUETYPE_TAG('c', 'h', 'b', 0), // chb = Chibcha + TRUETYPE_TAG('c', 'h', 'c', 0), // chc = Catawba + TRUETYPE_TAG('c', 'h', 'd', 0), // chd = Highland Oaxaca Chontal + TRUETYPE_TAG('c', 'h', 'f', 0), // chf = Tabasco Chontal + TRUETYPE_TAG('c', 'h', 'g', 0), // chg = Chagatai + TRUETYPE_TAG('c', 'h', 'h', 0), // chh = Chinook + TRUETYPE_TAG('c', 'h', 'j', 0), // chj = Ojitlán Chinantec + TRUETYPE_TAG('c', 'h', 'k', 0), // chk = Chuukese + TRUETYPE_TAG('c', 'h', 'l', 0), // chl = Cahuilla + TRUETYPE_TAG('c', 'h', 'm', 0), // chm = Mari (Russia) + TRUETYPE_TAG('c', 'h', 'n', 0), // chn = Chinook jargon + TRUETYPE_TAG('c', 'h', 'o', 0), // cho = Choctaw + TRUETYPE_TAG('c', 'h', 'p', 0), // chp = Chipewyan + TRUETYPE_TAG('c', 'h', 'q', 0), // chq = Quiotepec Chinantec + TRUETYPE_TAG('c', 'h', 'r', 0), // chr = Cherokee + TRUETYPE_TAG('c', 'h', 't', 0), // cht = Cholón + TRUETYPE_TAG('c', 'h', 'w', 0), // chw = Chuwabu + TRUETYPE_TAG('c', 'h', 'x', 0), // chx = Chantyal + TRUETYPE_TAG('c', 'h', 'y', 0), // chy = Cheyenne + TRUETYPE_TAG('c', 'h', 'z', 0), // chz = Ozumacín Chinantec + TRUETYPE_TAG('c', 'i', 'a', 0), // cia = Cia-Cia + TRUETYPE_TAG('c', 'i', 'b', 0), // cib = Ci Gbe + TRUETYPE_TAG('c', 'i', 'c', 0), // cic = Chickasaw + TRUETYPE_TAG('c', 'i', 'd', 0), // cid = Chimariko + TRUETYPE_TAG('c', 'i', 'e', 0), // cie = Cineni + TRUETYPE_TAG('c', 'i', 'h', 0), // cih = Chinali + TRUETYPE_TAG('c', 'i', 'k', 0), // cik = Chitkuli Kinnauri + TRUETYPE_TAG('c', 'i', 'm', 0), // cim = Cimbrian + TRUETYPE_TAG('c', 'i', 'n', 0), // cin = Cinta Larga + TRUETYPE_TAG('c', 'i', 'p', 0), // cip = Chiapanec + TRUETYPE_TAG('c', 'i', 'r', 0), // cir = Tiri + TRUETYPE_TAG('c', 'i', 'w', 0), // ciw = Chippewa + TRUETYPE_TAG('c', 'i', 'y', 0), // ciy = Chaima + TRUETYPE_TAG('c', 'j', 'a', 0), // cja = Western Cham + TRUETYPE_TAG('c', 'j', 'e', 0), // cje = Chru + TRUETYPE_TAG('c', 'j', 'h', 0), // cjh = Upper Chehalis + TRUETYPE_TAG('c', 'j', 'i', 0), // cji = Chamalal + TRUETYPE_TAG('c', 'j', 'k', 0), // cjk = Chokwe + TRUETYPE_TAG('c', 'j', 'm', 0), // cjm = Eastern Cham + TRUETYPE_TAG('c', 'j', 'n', 0), // cjn = Chenapian + TRUETYPE_TAG('c', 'j', 'o', 0), // cjo = Ashéninka Pajonal + TRUETYPE_TAG('c', 'j', 'p', 0), // cjp = Cabécar + TRUETYPE_TAG('c', 'j', 'r', 0), // cjr = Chorotega + TRUETYPE_TAG('c', 'j', 's', 0), // cjs = Shor + TRUETYPE_TAG('c', 'j', 'v', 0), // cjv = Chuave + TRUETYPE_TAG('c', 'j', 'y', 0), // cjy = Jinyu Chinese + TRUETYPE_TAG('c', 'k', 'a', 0), // cka = Khumi Awa Chin + TRUETYPE_TAG('c', 'k', 'b', 0), // ckb = Central Kurdish + TRUETYPE_TAG('c', 'k', 'h', 0), // ckh = Chak + TRUETYPE_TAG('c', 'k', 'l', 0), // ckl = Cibak + TRUETYPE_TAG('c', 'k', 'o', 0), // cko = Anufo + TRUETYPE_TAG('c', 'k', 'q', 0), // ckq = Kajakse + TRUETYPE_TAG('c', 'k', 'r', 0), // ckr = Kairak + TRUETYPE_TAG('c', 'k', 's', 0), // cks = Tayo + TRUETYPE_TAG('c', 'k', 't', 0), // ckt = Chukot + TRUETYPE_TAG('c', 'k', 'u', 0), // cku = Koasati + TRUETYPE_TAG('c', 'k', 'v', 0), // ckv = Kavalan + TRUETYPE_TAG('c', 'k', 'x', 0), // ckx = Caka + TRUETYPE_TAG('c', 'k', 'y', 0), // cky = Cakfem-Mushere + TRUETYPE_TAG('c', 'k', 'z', 0), // ckz = Cakchiquel-Quiché Mixed Language + TRUETYPE_TAG('c', 'l', 'a', 0), // cla = Ron + TRUETYPE_TAG('c', 'l', 'c', 0), // clc = Chilcotin + TRUETYPE_TAG('c', 'l', 'd', 0), // cld = Chaldean Neo-Aramaic + TRUETYPE_TAG('c', 'l', 'e', 0), // cle = Lealao Chinantec + TRUETYPE_TAG('c', 'l', 'h', 0), // clh = Chilisso + TRUETYPE_TAG('c', 'l', 'i', 0), // cli = Chakali + TRUETYPE_TAG('c', 'l', 'k', 0), // clk = Idu-Mishmi + TRUETYPE_TAG('c', 'l', 'l', 0), // cll = Chala + TRUETYPE_TAG('c', 'l', 'm', 0), // clm = Clallam + TRUETYPE_TAG('c', 'l', 'o', 0), // clo = Lowland Oaxaca Chontal + TRUETYPE_TAG('c', 'l', 'u', 0), // clu = Caluyanun + TRUETYPE_TAG('c', 'l', 'w', 0), // clw = Chulym + TRUETYPE_TAG('c', 'l', 'y', 0), // cly = Eastern Highland Chatino + TRUETYPE_TAG('c', 'm', 'a', 0), // cma = Maa + TRUETYPE_TAG('c', 'm', 'c', 0), // cmc = Chamic languages + TRUETYPE_TAG('c', 'm', 'e', 0), // cme = Cerma + TRUETYPE_TAG('c', 'm', 'g', 0), // cmg = Classical Mongolian + TRUETYPE_TAG('c', 'm', 'i', 0), // cmi = Emberá-Chamí + TRUETYPE_TAG('c', 'm', 'k', 0), // cmk = Chimakum + TRUETYPE_TAG('c', 'm', 'l', 0), // cml = Campalagian + TRUETYPE_TAG('c', 'm', 'm', 0), // cmm = Michigamea + TRUETYPE_TAG('c', 'm', 'n', 0), // cmn = Mandarin Chinese + TRUETYPE_TAG('c', 'm', 'o', 0), // cmo = Central Mnong + TRUETYPE_TAG('c', 'm', 'r', 0), // cmr = Mro Chin + TRUETYPE_TAG('c', 'm', 's', 0), // cms = Messapic + TRUETYPE_TAG('c', 'm', 't', 0), // cmt = Camtho + TRUETYPE_TAG('c', 'n', 'a', 0), // cna = Changthang + TRUETYPE_TAG('c', 'n', 'b', 0), // cnb = Chinbon Chin + TRUETYPE_TAG('c', 'n', 'c', 0), // cnc = Côông + TRUETYPE_TAG('c', 'n', 'g', 0), // cng = Northern Qiang + TRUETYPE_TAG('c', 'n', 'h', 0), // cnh = Haka Chin + TRUETYPE_TAG('c', 'n', 'i', 0), // cni = Asháninka + TRUETYPE_TAG('c', 'n', 'k', 0), // cnk = Khumi Chin + TRUETYPE_TAG('c', 'n', 'l', 0), // cnl = Lalana Chinantec + TRUETYPE_TAG('c', 'n', 'o', 0), // cno = Con + TRUETYPE_TAG('c', 'n', 's', 0), // cns = Central Asmat + TRUETYPE_TAG('c', 'n', 't', 0), // cnt = Tepetotutla Chinantec + TRUETYPE_TAG('c', 'n', 'u', 0), // cnu = Chenoua + TRUETYPE_TAG('c', 'n', 'w', 0), // cnw = Ngawn Chin + TRUETYPE_TAG('c', 'n', 'x', 0), // cnx = Middle Cornish + TRUETYPE_TAG('c', 'o', 'a', 0), // coa = Cocos Islands Malay + TRUETYPE_TAG('c', 'o', 'b', 0), // cob = Chicomuceltec + TRUETYPE_TAG('c', 'o', 'c', 0), // coc = Cocopa + TRUETYPE_TAG('c', 'o', 'd', 0), // cod = Cocama-Cocamilla + TRUETYPE_TAG('c', 'o', 'e', 0), // coe = Koreguaje + TRUETYPE_TAG('c', 'o', 'f', 0), // cof = Colorado + TRUETYPE_TAG('c', 'o', 'g', 0), // cog = Chong + TRUETYPE_TAG('c', 'o', 'h', 0), // coh = Chonyi-Dzihana-Kauma + TRUETYPE_TAG('c', 'o', 'j', 0), // coj = Cochimi + TRUETYPE_TAG('c', 'o', 'k', 0), // cok = Santa Teresa Cora + TRUETYPE_TAG('c', 'o', 'l', 0), // col = Columbia-Wenatchi + TRUETYPE_TAG('c', 'o', 'm', 0), // com = Comanche + TRUETYPE_TAG('c', 'o', 'n', 0), // con = Cofán + TRUETYPE_TAG('c', 'o', 'o', 0), // coo = Comox + TRUETYPE_TAG('c', 'o', 'p', 0), // cop = Coptic + TRUETYPE_TAG('c', 'o', 'q', 0), // coq = Coquille + TRUETYPE_TAG('c', 'o', 't', 0), // cot = Caquinte + TRUETYPE_TAG('c', 'o', 'u', 0), // cou = Wamey + TRUETYPE_TAG('c', 'o', 'v', 0), // cov = Cao Miao + TRUETYPE_TAG('c', 'o', 'w', 0), // cow = Cowlitz + TRUETYPE_TAG('c', 'o', 'x', 0), // cox = Nanti + TRUETYPE_TAG('c', 'o', 'y', 0), // coy = Coyaima + TRUETYPE_TAG('c', 'o', 'z', 0), // coz = Chochotec + TRUETYPE_TAG('c', 'p', 'a', 0), // cpa = Palantla Chinantec + TRUETYPE_TAG('c', 'p', 'b', 0), // cpb = Ucayali-Yurúa Ashéninka + TRUETYPE_TAG('c', 'p', 'c', 0), // cpc = Ajyíninka Apurucayali + TRUETYPE_TAG('c', 'p', 'e', 0), // cpe = English-based creoles and pidgins + TRUETYPE_TAG('c', 'p', 'f', 0), // cpf = French-based creoles and pidgins + TRUETYPE_TAG('c', 'p', 'g', 0), // cpg = Cappadocian Greek + TRUETYPE_TAG('c', 'p', 'i', 0), // cpi = Chinese Pidgin English + TRUETYPE_TAG('c', 'p', 'n', 0), // cpn = Cherepon + TRUETYPE_TAG('c', 'p', 'p', + 0), // cpp = Portuguese-based creoles and pidgins + TRUETYPE_TAG('c', 'p', 's', 0), // cps = Capiznon + TRUETYPE_TAG('c', 'p', 'u', 0), // cpu = Pichis Ashéninka + TRUETYPE_TAG('c', 'p', 'x', 0), // cpx = Pu-Xian Chinese + TRUETYPE_TAG('c', 'p', 'y', 0), // cpy = South Ucayali Ashéninka + TRUETYPE_TAG('c', 'q', 'd', 0), // cqd = Chuanqiandian Cluster Miao + TRUETYPE_TAG('c', 'q', 'u', 0), // cqu = Chilean Quechua + TRUETYPE_TAG('c', 'r', 'a', 0), // cra = Chara + TRUETYPE_TAG('c', 'r', 'b', 0), // crb = Island Carib + TRUETYPE_TAG('c', 'r', 'c', 0), // crc = Lonwolwol + TRUETYPE_TAG('c', 'r', 'd', 0), // crd = Coeur d'Alene + TRUETYPE_TAG('c', 'r', 'f', 0), // crf = Caramanta + TRUETYPE_TAG('c', 'r', 'g', 0), // crg = Michif + TRUETYPE_TAG('c', 'r', 'h', 0), // crh = Crimean Tatar + TRUETYPE_TAG('c', 'r', 'i', 0), // cri = Sãotomense + TRUETYPE_TAG('c', 'r', 'j', 0), // crj = Southern East Cree + TRUETYPE_TAG('c', 'r', 'k', 0), // crk = Plains Cree + TRUETYPE_TAG('c', 'r', 'l', 0), // crl = Northern East Cree + TRUETYPE_TAG('c', 'r', 'm', 0), // crm = Moose Cree + TRUETYPE_TAG('c', 'r', 'n', 0), // crn = El Nayar Cora + TRUETYPE_TAG('c', 'r', 'o', 0), // cro = Crow + TRUETYPE_TAG('c', 'r', 'p', 0), // crp = Creoles and pidgins + TRUETYPE_TAG('c', 'r', 'q', 0), // crq = Iyo'wujwa Chorote + TRUETYPE_TAG('c', 'r', 'r', 0), // crr = Carolina Algonquian + TRUETYPE_TAG('c', 'r', 's', 0), // crs = Seselwa Creole French + TRUETYPE_TAG('c', 'r', 't', 0), // crt = Iyojwa'ja Chorote + TRUETYPE_TAG('c', 'r', 'v', 0), // crv = Chaura + TRUETYPE_TAG('c', 'r', 'w', 0), // crw = Chrau + TRUETYPE_TAG('c', 'r', 'x', 0), // crx = Carrier + TRUETYPE_TAG('c', 'r', 'y', 0), // cry = Cori + TRUETYPE_TAG('c', 'r', 'z', 0), // crz = Cruzeño + TRUETYPE_TAG('c', 's', 'a', 0), // csa = Chiltepec Chinantec + TRUETYPE_TAG('c', 's', 'b', 0), // csb = Kashubian + TRUETYPE_TAG('c', 's', 'c', 0), // csc = Catalan Sign Language + TRUETYPE_TAG('c', 's', 'd', 0), // csd = Chiangmai Sign Language + TRUETYPE_TAG('c', 's', 'e', 0), // cse = Czech Sign Language + TRUETYPE_TAG('c', 's', 'f', 0), // csf = Cuba Sign Language + TRUETYPE_TAG('c', 's', 'g', 0), // csg = Chilean Sign Language + TRUETYPE_TAG('c', 's', 'h', 0), // csh = Asho Chin + TRUETYPE_TAG('c', 's', 'i', 0), // csi = Coast Miwok + TRUETYPE_TAG('c', 's', 'k', 0), // csk = Jola-Kasa + TRUETYPE_TAG('c', 's', 'l', 0), // csl = Chinese Sign Language + TRUETYPE_TAG('c', 's', 'm', 0), // csm = Central Sierra Miwok + TRUETYPE_TAG('c', 's', 'n', 0), // csn = Colombian Sign Language + TRUETYPE_TAG('c', 's', 'o', 0), // cso = Sochiapam Chinantec + TRUETYPE_TAG('c', 's', 'q', 0), // csq = Croatia Sign Language + TRUETYPE_TAG('c', 's', 'r', 0), // csr = Costa Rican Sign Language + TRUETYPE_TAG('c', 's', 's', 0), // css = Southern Ohlone + TRUETYPE_TAG('c', 's', 't', 0), // cst = Northern Ohlone + TRUETYPE_TAG('c', 's', 'u', 0), // csu = Central Sudanic languages + TRUETYPE_TAG('c', 's', 'w', 0), // csw = Swampy Cree + TRUETYPE_TAG('c', 's', 'y', 0), // csy = Siyin Chin + TRUETYPE_TAG('c', 's', 'z', 0), // csz = Coos + TRUETYPE_TAG('c', 't', 'a', 0), // cta = Tataltepec Chatino + TRUETYPE_TAG('c', 't', 'c', 0), // ctc = Chetco + TRUETYPE_TAG('c', 't', 'd', 0), // ctd = Tedim Chin + TRUETYPE_TAG('c', 't', 'e', 0), // cte = Tepinapa Chinantec + TRUETYPE_TAG('c', 't', 'g', 0), // ctg = Chittagonian + TRUETYPE_TAG('c', 't', 'l', 0), // ctl = Tlacoatzintepec Chinantec + TRUETYPE_TAG('c', 't', 'm', 0), // ctm = Chitimacha + TRUETYPE_TAG('c', 't', 'n', 0), // ctn = Chhintange + TRUETYPE_TAG('c', 't', 'o', 0), // cto = Emberá-Catío + TRUETYPE_TAG('c', 't', 'p', 0), // ctp = Western Highland Chatino + TRUETYPE_TAG('c', 't', 's', 0), // cts = Northern Catanduanes Bicolano + TRUETYPE_TAG('c', 't', 't', 0), // ctt = Wayanad Chetti + TRUETYPE_TAG('c', 't', 'u', 0), // ctu = Chol + TRUETYPE_TAG('c', 't', 'z', 0), // ctz = Zacatepec Chatino + TRUETYPE_TAG('c', 'u', 'a', 0), // cua = Cua + TRUETYPE_TAG('c', 'u', 'b', 0), // cub = Cubeo + TRUETYPE_TAG('c', 'u', 'c', 0), // cuc = Usila Chinantec + TRUETYPE_TAG('c', 'u', 'g', 0), // cug = Cung + TRUETYPE_TAG('c', 'u', 'h', 0), // cuh = Chuka + TRUETYPE_TAG('c', 'u', 'i', 0), // cui = Cuiba + TRUETYPE_TAG('c', 'u', 'j', 0), // cuj = Mashco Piro + TRUETYPE_TAG('c', 'u', 'k', 0), // cuk = San Blas Kuna + TRUETYPE_TAG('c', 'u', 'l', 0), // cul = Culina + TRUETYPE_TAG('c', 'u', 'm', 0), // cum = Cumeral + TRUETYPE_TAG('c', 'u', 'o', 0), // cuo = Cumanagoto + TRUETYPE_TAG('c', 'u', 'p', 0), // cup = Cupeño + TRUETYPE_TAG('c', 'u', 'q', 0), // cuq = Cun + TRUETYPE_TAG('c', 'u', 'r', 0), // cur = Chhulung + TRUETYPE_TAG('c', 'u', 's', 0), // cus = Cushitic languages + TRUETYPE_TAG('c', 'u', 't', 0), // cut = Teutila Cuicatec + TRUETYPE_TAG('c', 'u', 'u', 0), // cuu = Tai Ya + TRUETYPE_TAG('c', 'u', 'v', 0), // cuv = Cuvok + TRUETYPE_TAG('c', 'u', 'w', 0), // cuw = Chukwa + TRUETYPE_TAG('c', 'u', 'x', 0), // cux = Tepeuxila Cuicatec + TRUETYPE_TAG('c', 'v', 'g', 0), // cvg = Chug + TRUETYPE_TAG('c', 'v', 'n', 0), // cvn = Valle Nacional Chinantec + TRUETYPE_TAG('c', 'w', 'a', 0), // cwa = Kabwa + TRUETYPE_TAG('c', 'w', 'b', 0), // cwb = Maindo + TRUETYPE_TAG('c', 'w', 'd', 0), // cwd = Woods Cree + TRUETYPE_TAG('c', 'w', 'e', 0), // cwe = Kwere + TRUETYPE_TAG('c', 'w', 'g', 0), // cwg = Chewong + TRUETYPE_TAG('c', 'w', 't', 0), // cwt = Kuwaataay + TRUETYPE_TAG('c', 'y', 'a', 0), // cya = Nopala Chatino + TRUETYPE_TAG('c', 'y', 'b', 0), // cyb = Cayubaba + TRUETYPE_TAG('c', 'y', 'o', 0), // cyo = Cuyonon + TRUETYPE_TAG('c', 'z', 'h', 0), // czh = Huizhou Chinese + TRUETYPE_TAG('c', 'z', 'k', 0), // czk = Knaanic + TRUETYPE_TAG('c', 'z', 'n', 0), // czn = Zenzontepec Chatino + TRUETYPE_TAG('c', 'z', 'o', 0), // czo = Min Zhong Chinese + TRUETYPE_TAG('c', 'z', 't', 0), // czt = Zotung Chin + TRUETYPE_TAG('d', 'a', 'a', 0), // daa = Dangaléat + TRUETYPE_TAG('d', 'a', 'c', 0), // dac = Dambi + TRUETYPE_TAG('d', 'a', 'd', 0), // dad = Marik + TRUETYPE_TAG('d', 'a', 'e', 0), // dae = Duupa + TRUETYPE_TAG('d', 'a', 'f', 0), // daf = Dan + TRUETYPE_TAG('d', 'a', 'g', 0), // dag = Dagbani + TRUETYPE_TAG('d', 'a', 'h', 0), // dah = Gwahatike + TRUETYPE_TAG('d', 'a', 'i', 0), // dai = Day + TRUETYPE_TAG('d', 'a', 'j', 0), // daj = Dar Fur Daju + TRUETYPE_TAG('d', 'a', 'k', 0), // dak = Dakota + TRUETYPE_TAG('d', 'a', 'l', 0), // dal = Dahalo + TRUETYPE_TAG('d', 'a', 'm', 0), // dam = Damakawa + TRUETYPE_TAG('d', 'a', 'o', 0), // dao = Daai Chin + TRUETYPE_TAG('d', 'a', 'p', 0), // dap = Nisi (India) + TRUETYPE_TAG('d', 'a', 'q', 0), // daq = Dandami Maria + TRUETYPE_TAG('d', 'a', 'r', 0), // dar = Dargwa + TRUETYPE_TAG('d', 'a', 's', 0), // das = Daho-Doo + TRUETYPE_TAG('d', 'a', 'u', 0), // dau = Dar Sila Daju + TRUETYPE_TAG('d', 'a', 'v', 0), // dav = Taita + TRUETYPE_TAG('d', 'a', 'w', 0), // daw = Davawenyo + TRUETYPE_TAG('d', 'a', 'x', 0), // dax = Dayi + TRUETYPE_TAG('d', 'a', 'y', 0), // day = Land Dayak languages + TRUETYPE_TAG('d', 'a', 'z', 0), // daz = Dao + TRUETYPE_TAG('d', 'b', 'a', 0), // dba = Bangi Me + TRUETYPE_TAG('d', 'b', 'b', 0), // dbb = Deno + TRUETYPE_TAG('d', 'b', 'd', 0), // dbd = Dadiya + TRUETYPE_TAG('d', 'b', 'e', 0), // dbe = Dabe + TRUETYPE_TAG('d', 'b', 'f', 0), // dbf = Edopi + TRUETYPE_TAG('d', 'b', 'g', 0), // dbg = Dogul Dom Dogon + TRUETYPE_TAG('d', 'b', 'i', 0), // dbi = Doka + TRUETYPE_TAG('d', 'b', 'j', 0), // dbj = Ida'an + TRUETYPE_TAG('d', 'b', 'l', 0), // dbl = Dyirbal + TRUETYPE_TAG('d', 'b', 'm', 0), // dbm = Duguri + TRUETYPE_TAG('d', 'b', 'n', 0), // dbn = Duriankere + TRUETYPE_TAG('d', 'b', 'o', 0), // dbo = Dulbu + TRUETYPE_TAG('d', 'b', 'p', 0), // dbp = Duwai + TRUETYPE_TAG('d', 'b', 'q', 0), // dbq = Daba + TRUETYPE_TAG('d', 'b', 'r', 0), // dbr = Dabarre + TRUETYPE_TAG('d', 'b', 'u', 0), // dbu = Bondum Dom Dogon + TRUETYPE_TAG('d', 'b', 'v', 0), // dbv = Dungu + TRUETYPE_TAG('d', 'b', 'y', 0), // dby = Dibiyaso + TRUETYPE_TAG('d', 'c', 'c', 0), // dcc = Deccan + TRUETYPE_TAG('d', 'c', 'r', 0), // dcr = Negerhollands + TRUETYPE_TAG('d', 'd', 'd', 0), // ddd = Dongotono + TRUETYPE_TAG('d', 'd', 'e', 0), // dde = Doondo + TRUETYPE_TAG('d', 'd', 'g', 0), // ddg = Fataluku + TRUETYPE_TAG('d', 'd', 'i', 0), // ddi = West Goodenough + TRUETYPE_TAG('d', 'd', 'j', 0), // ddj = Jaru + TRUETYPE_TAG('d', 'd', 'n', 0), // ddn = Dendi (Benin) + TRUETYPE_TAG('d', 'd', 'o', 0), // ddo = Dido + TRUETYPE_TAG('d', 'd', 's', 0), // dds = Donno So Dogon + TRUETYPE_TAG('d', 'd', 'w', 0), // ddw = Dawera-Daweloor + TRUETYPE_TAG('d', 'e', 'c', 0), // dec = Dagik + TRUETYPE_TAG('d', 'e', 'd', 0), // ded = Dedua + TRUETYPE_TAG('d', 'e', 'e', 0), // dee = Dewoin + TRUETYPE_TAG('d', 'e', 'f', 0), // def = Dezfuli + TRUETYPE_TAG('d', 'e', 'g', 0), // deg = Degema + TRUETYPE_TAG('d', 'e', 'h', 0), // deh = Dehwari + TRUETYPE_TAG('d', 'e', 'i', 0), // dei = Demisa + TRUETYPE_TAG('d', 'e', 'k', 0), // dek = Dek + TRUETYPE_TAG('d', 'e', 'l', 0), // del = Delaware + TRUETYPE_TAG('d', 'e', 'm', 0), // dem = Dem + TRUETYPE_TAG('d', 'e', 'n', 0), // den = Slave (Athapascan) + TRUETYPE_TAG('d', 'e', 'p', 0), // dep = Pidgin Delaware + TRUETYPE_TAG('d', 'e', 'q', 0), // deq = Dendi (Central African Republic) + TRUETYPE_TAG('d', 'e', 'r', 0), // der = Deori + TRUETYPE_TAG('d', 'e', 's', 0), // des = Desano + TRUETYPE_TAG('d', 'e', 'v', 0), // dev = Domung + TRUETYPE_TAG('d', 'e', 'z', 0), // dez = Dengese + TRUETYPE_TAG('d', 'g', 'a', 0), // dga = Southern Dagaare + TRUETYPE_TAG('d', 'g', 'b', 0), // dgb = Bunoge Dogon + TRUETYPE_TAG('d', 'g', 'c', 0), // dgc = Casiguran Dumagat Agta + TRUETYPE_TAG('d', 'g', 'd', 0), // dgd = Dagaari Dioula + TRUETYPE_TAG('d', 'g', 'e', 0), // dge = Degenan + TRUETYPE_TAG('d', 'g', 'g', 0), // dgg = Doga + TRUETYPE_TAG('d', 'g', 'h', 0), // dgh = Dghwede + TRUETYPE_TAG('d', 'g', 'i', 0), // dgi = Northern Dagara + TRUETYPE_TAG('d', 'g', 'k', 0), // dgk = Dagba + TRUETYPE_TAG('d', 'g', 'n', 0), // dgn = Dagoman + TRUETYPE_TAG('d', 'g', 'o', 0), // dgo = Dogri (individual language) + TRUETYPE_TAG('d', 'g', 'r', 0), // dgr = Dogrib + TRUETYPE_TAG('d', 'g', 's', 0), // dgs = Dogoso + TRUETYPE_TAG('d', 'g', 'u', 0), // dgu = Degaru + TRUETYPE_TAG('d', 'g', 'x', 0), // dgx = Doghoro + TRUETYPE_TAG('d', 'g', 'z', 0), // dgz = Daga + TRUETYPE_TAG('d', 'h', 'a', 0), // dha = Dhanwar (India) + TRUETYPE_TAG('d', 'h', 'd', 0), // dhd = Dhundari + TRUETYPE_TAG('d', 'h', 'g', 0), // dhg = Dhangu + TRUETYPE_TAG('d', 'h', 'i', 0), // dhi = Dhimal + TRUETYPE_TAG('d', 'h', 'l', 0), // dhl = Dhalandji + TRUETYPE_TAG('d', 'h', 'm', 0), // dhm = Zemba + TRUETYPE_TAG('d', 'h', 'n', 0), // dhn = Dhanki + TRUETYPE_TAG('d', 'h', 'o', 0), // dho = Dhodia + TRUETYPE_TAG('d', 'h', 'r', 0), // dhr = Dhargari + TRUETYPE_TAG('d', 'h', 's', 0), // dhs = Dhaiso + TRUETYPE_TAG('d', 'h', 'u', 0), // dhu = Dhurga + TRUETYPE_TAG('d', 'h', 'v', 0), // dhv = Dehu + TRUETYPE_TAG('d', 'h', 'w', 0), // dhw = Dhanwar (Nepal) + TRUETYPE_TAG('d', 'i', 'a', 0), // dia = Dia + TRUETYPE_TAG('d', 'i', 'b', 0), // dib = South Central Dinka + TRUETYPE_TAG('d', 'i', 'c', 0), // dic = Lakota Dida + TRUETYPE_TAG('d', 'i', 'd', 0), // did = Didinga + TRUETYPE_TAG('d', 'i', 'f', 0), // dif = Dieri + TRUETYPE_TAG('d', 'i', 'g', 0), // dig = Digo + TRUETYPE_TAG('d', 'i', 'h', 0), // dih = Kumiai + TRUETYPE_TAG('d', 'i', 'i', 0), // dii = Dimbong + TRUETYPE_TAG('d', 'i', 'j', 0), // dij = Dai + TRUETYPE_TAG('d', 'i', 'k', 0), // dik = Southwestern Dinka + TRUETYPE_TAG('d', 'i', 'l', 0), // dil = Dilling + TRUETYPE_TAG('d', 'i', 'm', 0), // dim = Dime + TRUETYPE_TAG('d', 'i', 'n', 0), // din = Dinka + TRUETYPE_TAG('d', 'i', 'o', 0), // dio = Dibo + TRUETYPE_TAG('d', 'i', 'p', 0), // dip = Northeastern Dinka + TRUETYPE_TAG('d', 'i', 'q', 0), // diq = Dimli (individual language) + TRUETYPE_TAG('d', 'i', 'r', 0), // dir = Dirim + TRUETYPE_TAG('d', 'i', 's', 0), // dis = Dimasa + TRUETYPE_TAG('d', 'i', 't', 0), // dit = Dirari + TRUETYPE_TAG('d', 'i', 'u', 0), // diu = Diriku + TRUETYPE_TAG('d', 'i', 'w', 0), // diw = Northwestern Dinka + TRUETYPE_TAG('d', 'i', 'x', 0), // dix = Dixon Reef + TRUETYPE_TAG('d', 'i', 'y', 0), // diy = Diuwe + TRUETYPE_TAG('d', 'i', 'z', 0), // diz = Ding + TRUETYPE_TAG('d', 'j', 'b', 0), // djb = Djinba + TRUETYPE_TAG('d', 'j', 'c', 0), // djc = Dar Daju Daju + TRUETYPE_TAG('d', 'j', 'd', 0), // djd = Djamindjung + TRUETYPE_TAG('d', 'j', 'e', 0), // dje = Zarma + TRUETYPE_TAG('d', 'j', 'f', 0), // djf = Djangun + TRUETYPE_TAG('d', 'j', 'i', 0), // dji = Djinang + TRUETYPE_TAG('d', 'j', 'j', 0), // djj = Djeebbana + TRUETYPE_TAG('d', 'j', 'k', 0), // djk = Eastern Maroon Creole + TRUETYPE_TAG('d', 'j', 'l', 0), // djl = Djiwarli + TRUETYPE_TAG('d', 'j', 'm', 0), // djm = Jamsay Dogon + TRUETYPE_TAG('d', 'j', 'n', 0), // djn = Djauan + TRUETYPE_TAG('d', 'j', 'o', 0), // djo = Jangkang + TRUETYPE_TAG('d', 'j', 'r', 0), // djr = Djambarrpuyngu + TRUETYPE_TAG('d', 'j', 'u', 0), // dju = Kapriman + TRUETYPE_TAG('d', 'j', 'w', 0), // djw = Djawi + TRUETYPE_TAG('d', 'k', 'a', 0), // dka = Dakpakha + TRUETYPE_TAG('d', 'k', 'k', 0), // dkk = Dakka + TRUETYPE_TAG('d', 'k', 'l', 0), // dkl = Kolum So Dogon + TRUETYPE_TAG('d', 'k', 'r', 0), // dkr = Kuijau + TRUETYPE_TAG('d', 'k', 's', 0), // dks = Southeastern Dinka + TRUETYPE_TAG('d', 'k', 'x', 0), // dkx = Mazagway + TRUETYPE_TAG('d', 'l', 'g', 0), // dlg = Dolgan + TRUETYPE_TAG('d', 'l', 'm', 0), // dlm = Dalmatian + TRUETYPE_TAG('d', 'l', 'n', 0), // dln = Darlong + TRUETYPE_TAG('d', 'm', 'a', 0), // dma = Duma + TRUETYPE_TAG('d', 'm', 'b', 0), // dmb = Mombo Dogon + TRUETYPE_TAG('d', 'm', 'c', 0), // dmc = Dimir + TRUETYPE_TAG('d', 'm', 'e', 0), // dme = Dugwor + TRUETYPE_TAG('d', 'm', 'g', 0), // dmg = Upper Kinabatangan + TRUETYPE_TAG('d', 'm', 'k', 0), // dmk = Domaaki + TRUETYPE_TAG('d', 'm', 'l', 0), // dml = Dameli + TRUETYPE_TAG('d', 'm', 'm', 0), // dmm = Dama + TRUETYPE_TAG('d', 'm', 'n', 0), // dmn = Mande languages + TRUETYPE_TAG('d', 'm', 'o', 0), // dmo = Kemezung + TRUETYPE_TAG('d', 'm', 'r', 0), // dmr = East Damar + TRUETYPE_TAG('d', 'm', 's', 0), // dms = Dampelas + TRUETYPE_TAG('d', 'm', 'u', 0), // dmu = Dubu + TRUETYPE_TAG('d', 'm', 'v', 0), // dmv = Dumpas + TRUETYPE_TAG('d', 'm', 'x', 0), // dmx = Dema + TRUETYPE_TAG('d', 'm', 'y', 0), // dmy = Demta + TRUETYPE_TAG('d', 'n', 'a', 0), // dna = Upper Grand Valley Dani + TRUETYPE_TAG('d', 'n', 'd', 0), // dnd = Daonda + TRUETYPE_TAG('d', 'n', 'e', 0), // dne = Ndendeule + TRUETYPE_TAG('d', 'n', 'g', 0), // dng = Dungan + TRUETYPE_TAG('d', 'n', 'i', 0), // dni = Lower Grand Valley Dani + TRUETYPE_TAG('d', 'n', 'k', 0), // dnk = Dengka + TRUETYPE_TAG('d', 'n', 'n', 0), // dnn = Dzùùngoo + TRUETYPE_TAG('d', 'n', 'r', 0), // dnr = Danaru + TRUETYPE_TAG('d', 'n', 't', 0), // dnt = Mid Grand Valley Dani + TRUETYPE_TAG('d', 'n', 'u', 0), // dnu = Danau + TRUETYPE_TAG('d', 'n', 'w', 0), // dnw = Western Dani + TRUETYPE_TAG('d', 'n', 'y', 0), // dny = Dení + TRUETYPE_TAG('d', 'o', 'a', 0), // doa = Dom + TRUETYPE_TAG('d', 'o', 'b', 0), // dob = Dobu + TRUETYPE_TAG('d', 'o', 'c', 0), // doc = Northern Dong + TRUETYPE_TAG('d', 'o', 'e', 0), // doe = Doe + TRUETYPE_TAG('d', 'o', 'f', 0), // dof = Domu + TRUETYPE_TAG('d', 'o', 'h', 0), // doh = Dong + TRUETYPE_TAG('d', 'o', 'i', 0), // doi = Dogri (macrolanguage) + TRUETYPE_TAG('d', 'o', 'k', 0), // dok = Dondo + TRUETYPE_TAG('d', 'o', 'l', 0), // dol = Doso + TRUETYPE_TAG('d', 'o', 'n', 0), // don = Toura (Papua New Guinea) + TRUETYPE_TAG('d', 'o', 'o', 0), // doo = Dongo + TRUETYPE_TAG('d', 'o', 'p', 0), // dop = Lukpa + TRUETYPE_TAG('d', 'o', 'q', 0), // doq = Dominican Sign Language + TRUETYPE_TAG('d', 'o', 'r', 0), // dor = Dori'o + TRUETYPE_TAG('d', 'o', 's', 0), // dos = Dogosé + TRUETYPE_TAG('d', 'o', 't', 0), // dot = Dass + TRUETYPE_TAG('d', 'o', 'v', 0), // dov = Dombe + TRUETYPE_TAG('d', 'o', 'w', 0), // dow = Doyayo + TRUETYPE_TAG('d', 'o', 'x', 0), // dox = Bussa + TRUETYPE_TAG('d', 'o', 'y', 0), // doy = Dompo + TRUETYPE_TAG('d', 'o', 'z', 0), // doz = Dorze + TRUETYPE_TAG('d', 'p', 'p', 0), // dpp = Papar + TRUETYPE_TAG('d', 'r', 'a', 0), // dra = Dravidian languages + TRUETYPE_TAG('d', 'r', 'b', 0), // drb = Dair + TRUETYPE_TAG('d', 'r', 'c', 0), // drc = Minderico + TRUETYPE_TAG('d', 'r', 'd', 0), // drd = Darmiya + TRUETYPE_TAG('d', 'r', 'e', 0), // dre = Dolpo + TRUETYPE_TAG('d', 'r', 'g', 0), // drg = Rungus + TRUETYPE_TAG('d', 'r', 'h', 0), // drh = Darkhat + TRUETYPE_TAG('d', 'r', 'i', 0), // dri = C'lela + TRUETYPE_TAG('d', 'r', 'l', 0), // drl = Darling + TRUETYPE_TAG('d', 'r', 'n', 0), // drn = West Damar + TRUETYPE_TAG('d', 'r', 'o', 0), // dro = Daro-Matu Melanau + TRUETYPE_TAG('d', 'r', 'q', 0), // drq = Dura + TRUETYPE_TAG('d', 'r', 'r', 0), // drr = Dororo + TRUETYPE_TAG('d', 'r', 's', 0), // drs = Gedeo + TRUETYPE_TAG('d', 'r', 't', 0), // drt = Drents + TRUETYPE_TAG('d', 'r', 'u', 0), // dru = Rukai + TRUETYPE_TAG('d', 'r', 'w', 0), // drw = Darwazi + TRUETYPE_TAG('d', 'r', 'y', 0), // dry = Darai + TRUETYPE_TAG('d', 's', 'b', 0), // dsb = Lower Sorbian + TRUETYPE_TAG('d', 's', 'e', 0), // dse = Dutch Sign Language + TRUETYPE_TAG('d', 's', 'h', 0), // dsh = Daasanach + TRUETYPE_TAG('d', 's', 'i', 0), // dsi = Disa + TRUETYPE_TAG('d', 's', 'l', 0), // dsl = Danish Sign Language + TRUETYPE_TAG('d', 's', 'n', 0), // dsn = Dusner + TRUETYPE_TAG('d', 's', 'o', 0), // dso = Desiya + TRUETYPE_TAG('d', 's', 'q', 0), // dsq = Tadaksahak + TRUETYPE_TAG('d', 't', 'a', 0), // dta = Daur + TRUETYPE_TAG('d', 't', 'b', 0), // dtb = Labuk-Kinabatangan Kadazan + TRUETYPE_TAG('d', 't', 'd', 0), // dtd = Ditidaht + TRUETYPE_TAG('d', 't', 'i', 0), // dti = Ana Tinga Dogon + TRUETYPE_TAG('d', 't', 'k', 0), // dtk = Tene Kan Dogon + TRUETYPE_TAG('d', 't', 'm', 0), // dtm = Tomo Kan Dogon + TRUETYPE_TAG('d', 't', 'p', 0), // dtp = Central Dusun + TRUETYPE_TAG('d', 't', 'r', 0), // dtr = Lotud + TRUETYPE_TAG('d', 't', 's', 0), // dts = Toro So Dogon + TRUETYPE_TAG('d', 't', 't', 0), // dtt = Toro Tegu Dogon + TRUETYPE_TAG('d', 't', 'u', 0), // dtu = Tebul Ure Dogon + TRUETYPE_TAG('d', 'u', 'a', 0), // dua = Duala + TRUETYPE_TAG('d', 'u', 'b', 0), // dub = Dubli + TRUETYPE_TAG('d', 'u', 'c', 0), // duc = Duna + TRUETYPE_TAG('d', 'u', 'd', 0), // dud = Hun-Saare + TRUETYPE_TAG('d', 'u', 'e', 0), // due = Umiray Dumaget Agta + TRUETYPE_TAG('d', 'u', 'f', 0), // duf = Dumbea + TRUETYPE_TAG('d', 'u', 'g', 0), // dug = Duruma + TRUETYPE_TAG('d', 'u', 'h', 0), // duh = Dungra Bhil + TRUETYPE_TAG('d', 'u', 'i', 0), // dui = Dumun + TRUETYPE_TAG('d', 'u', 'j', 0), // duj = Dhuwal + TRUETYPE_TAG('d', 'u', 'k', 0), // duk = Uyajitaya + TRUETYPE_TAG('d', 'u', 'l', 0), // dul = Alabat Island Agta + TRUETYPE_TAG('d', 'u', 'm', 0), // dum = Middle Dutch (ca. 1050-1350) + TRUETYPE_TAG('d', 'u', 'n', 0), // dun = Dusun Deyah + TRUETYPE_TAG('d', 'u', 'o', 0), // duo = Dupaninan Agta + TRUETYPE_TAG('d', 'u', 'p', 0), // dup = Duano + TRUETYPE_TAG('d', 'u', 'q', 0), // duq = Dusun Malang + TRUETYPE_TAG('d', 'u', 'r', 0), // dur = Dii + TRUETYPE_TAG('d', 'u', 's', 0), // dus = Dumi + TRUETYPE_TAG('d', 'u', 'u', 0), // duu = Drung + TRUETYPE_TAG('d', 'u', 'v', 0), // duv = Duvle + TRUETYPE_TAG('d', 'u', 'w', 0), // duw = Dusun Witu + TRUETYPE_TAG('d', 'u', 'x', 0), // dux = Duungooma + TRUETYPE_TAG('d', 'u', 'y', 0), // duy = Dicamay Agta + TRUETYPE_TAG('d', 'u', 'z', 0), // duz = Duli + TRUETYPE_TAG('d', 'v', 'a', 0), // dva = Duau + TRUETYPE_TAG('d', 'w', 'a', 0), // dwa = Diri + TRUETYPE_TAG('d', 'w', 'l', 0), // dwl = Walo Kumbe Dogon + TRUETYPE_TAG('d', 'w', 'r', 0), // dwr = Dawro + TRUETYPE_TAG('d', 'w', 's', 0), // dws = Dutton World Speedwords + TRUETYPE_TAG('d', 'w', 'w', 0), // dww = Dawawa + TRUETYPE_TAG('d', 'y', 'a', 0), // dya = Dyan + TRUETYPE_TAG('d', 'y', 'b', 0), // dyb = Dyaberdyaber + TRUETYPE_TAG('d', 'y', 'd', 0), // dyd = Dyugun + TRUETYPE_TAG('d', 'y', 'g', 0), // dyg = Villa Viciosa Agta + TRUETYPE_TAG('d', 'y', 'i', 0), // dyi = Djimini Senoufo + TRUETYPE_TAG('d', 'y', 'm', 0), // dym = Yanda Dom Dogon + TRUETYPE_TAG('d', 'y', 'n', 0), // dyn = Dyangadi + TRUETYPE_TAG('d', 'y', 'o', 0), // dyo = Jola-Fonyi + TRUETYPE_TAG('d', 'y', 'u', 0), // dyu = Dyula + TRUETYPE_TAG('d', 'y', 'y', 0), // dyy = Dyaabugay + TRUETYPE_TAG('d', 'z', 'a', 0), // dza = Tunzu + TRUETYPE_TAG('d', 'z', 'd', 0), // dzd = Daza + TRUETYPE_TAG('d', 'z', 'g', 0), // dzg = Dazaga + TRUETYPE_TAG('d', 'z', 'l', 0), // dzl = Dzalakha + TRUETYPE_TAG('d', 'z', 'n', 0), // dzn = Dzando + TRUETYPE_TAG('e', 'b', 'g', 0), // ebg = Ebughu + TRUETYPE_TAG('e', 'b', 'k', 0), // ebk = Eastern Bontok + TRUETYPE_TAG('e', 'b', 'o', 0), // ebo = Teke-Ebo + TRUETYPE_TAG('e', 'b', 'r', 0), // ebr = Ebrié + TRUETYPE_TAG('e', 'b', 'u', 0), // ebu = Embu + TRUETYPE_TAG('e', 'c', 'r', 0), // ecr = Eteocretan + TRUETYPE_TAG('e', 'c', 's', 0), // ecs = Ecuadorian Sign Language + TRUETYPE_TAG('e', 'c', 'y', 0), // ecy = Eteocypriot + TRUETYPE_TAG('e', 'e', 'e', 0), // eee = E + TRUETYPE_TAG('e', 'f', 'a', 0), // efa = Efai + TRUETYPE_TAG('e', 'f', 'e', 0), // efe = Efe + TRUETYPE_TAG('e', 'f', 'i', 0), // efi = Efik + TRUETYPE_TAG('e', 'g', 'a', 0), // ega = Ega + TRUETYPE_TAG('e', 'g', 'l', 0), // egl = Emilian + TRUETYPE_TAG('e', 'g', 'o', 0), // ego = Eggon + TRUETYPE_TAG('e', 'g', 'x', 0), // egx = Egyptian languages + TRUETYPE_TAG('e', 'g', 'y', 0), // egy = Egyptian (Ancient) + TRUETYPE_TAG('e', 'h', 'u', 0), // ehu = Ehueun + TRUETYPE_TAG('e', 'i', 'p', 0), // eip = Eipomek + TRUETYPE_TAG('e', 'i', 't', 0), // eit = Eitiep + TRUETYPE_TAG('e', 'i', 'v', 0), // eiv = Askopan + TRUETYPE_TAG('e', 'j', 'a', 0), // eja = Ejamat + TRUETYPE_TAG('e', 'k', 'a', 0), // eka = Ekajuk + TRUETYPE_TAG('e', 'k', 'e', 0), // eke = Ekit + TRUETYPE_TAG('e', 'k', 'g', 0), // ekg = Ekari + TRUETYPE_TAG('e', 'k', 'i', 0), // eki = Eki + TRUETYPE_TAG('e', 'k', 'k', 0), // ekk = Standard Estonian + TRUETYPE_TAG('e', 'k', 'l', 0), // ekl = Kol + TRUETYPE_TAG('e', 'k', 'm', 0), // ekm = Elip + TRUETYPE_TAG('e', 'k', 'o', 0), // eko = Koti + TRUETYPE_TAG('e', 'k', 'p', 0), // ekp = Ekpeye + TRUETYPE_TAG('e', 'k', 'r', 0), // ekr = Yace + TRUETYPE_TAG('e', 'k', 'y', 0), // eky = Eastern Kayah + TRUETYPE_TAG('e', 'l', 'e', 0), // ele = Elepi + TRUETYPE_TAG('e', 'l', 'h', 0), // elh = El Hugeirat + TRUETYPE_TAG('e', 'l', 'i', 0), // eli = Nding + TRUETYPE_TAG('e', 'l', 'k', 0), // elk = Elkei + TRUETYPE_TAG('e', 'l', 'm', 0), // elm = Eleme + TRUETYPE_TAG('e', 'l', 'o', 0), // elo = El Molo + TRUETYPE_TAG('e', 'l', 'p', 0), // elp = Elpaputih + TRUETYPE_TAG('e', 'l', 'u', 0), // elu = Elu + TRUETYPE_TAG('e', 'l', 'x', 0), // elx = Elamite + TRUETYPE_TAG('e', 'm', 'a', 0), // ema = Emai-Iuleha-Ora + TRUETYPE_TAG('e', 'm', 'b', 0), // emb = Embaloh + TRUETYPE_TAG('e', 'm', 'e', 0), // eme = Emerillon + TRUETYPE_TAG('e', 'm', 'g', 0), // emg = Eastern Meohang + TRUETYPE_TAG('e', 'm', 'i', 0), // emi = Mussau-Emira + TRUETYPE_TAG('e', 'm', 'k', 0), // emk = Eastern Maninkakan + TRUETYPE_TAG('e', 'm', 'm', 0), // emm = Mamulique + TRUETYPE_TAG('e', 'm', 'n', 0), // emn = Eman + TRUETYPE_TAG('e', 'm', 'o', 0), // emo = Emok + TRUETYPE_TAG('e', 'm', 'p', 0), // emp = Northern Emberá + TRUETYPE_TAG('e', 'm', 's', 0), // ems = Pacific Gulf Yupik + TRUETYPE_TAG('e', 'm', 'u', 0), // emu = Eastern Muria + TRUETYPE_TAG('e', 'm', 'w', 0), // emw = Emplawas + TRUETYPE_TAG('e', 'm', 'x', 0), // emx = Erromintxela + TRUETYPE_TAG('e', 'm', 'y', 0), // emy = Epigraphic Mayan + TRUETYPE_TAG('e', 'n', 'a', 0), // ena = Apali + TRUETYPE_TAG('e', 'n', 'b', 0), // enb = Markweeta + TRUETYPE_TAG('e', 'n', 'c', 0), // enc = En + TRUETYPE_TAG('e', 'n', 'd', 0), // end = Ende + TRUETYPE_TAG('e', 'n', 'f', 0), // enf = Forest Enets + TRUETYPE_TAG('e', 'n', 'h', 0), // enh = Tundra Enets + TRUETYPE_TAG('e', 'n', 'm', 0), // enm = Middle English (1100-1500) + TRUETYPE_TAG('e', 'n', 'n', 0), // enn = Engenni + TRUETYPE_TAG('e', 'n', 'o', 0), // eno = Enggano + TRUETYPE_TAG('e', 'n', 'q', 0), // enq = Enga + TRUETYPE_TAG('e', 'n', 'r', 0), // enr = Emumu + TRUETYPE_TAG('e', 'n', 'u', 0), // enu = Enu + TRUETYPE_TAG('e', 'n', 'v', 0), // env = Enwan (Edu State) + TRUETYPE_TAG('e', 'n', 'w', 0), // enw = Enwan (Akwa Ibom State) + TRUETYPE_TAG('e', 'o', 't', 0), // eot = Beti (Côte d'Ivoire) + TRUETYPE_TAG('e', 'p', 'i', 0), // epi = Epie + TRUETYPE_TAG('e', 'r', 'a', 0), // era = Eravallan + TRUETYPE_TAG('e', 'r', 'g', 0), // erg = Sie + TRUETYPE_TAG('e', 'r', 'h', 0), // erh = Eruwa + TRUETYPE_TAG('e', 'r', 'i', 0), // eri = Ogea + TRUETYPE_TAG('e', 'r', 'k', 0), // erk = South Efate + TRUETYPE_TAG('e', 'r', 'o', 0), // ero = Horpa + TRUETYPE_TAG('e', 'r', 'r', 0), // err = Erre + TRUETYPE_TAG('e', 'r', 's', 0), // ers = Ersu + TRUETYPE_TAG('e', 'r', 't', 0), // ert = Eritai + TRUETYPE_TAG('e', 'r', 'w', 0), // erw = Erokwanas + TRUETYPE_TAG('e', 's', 'e', 0), // ese = Ese Ejja + TRUETYPE_TAG('e', 's', 'h', 0), // esh = Eshtehardi + TRUETYPE_TAG('e', 's', 'i', 0), // esi = North Alaskan Inupiatun + TRUETYPE_TAG('e', 's', 'k', 0), // esk = Northwest Alaska Inupiatun + TRUETYPE_TAG('e', 's', 'l', 0), // esl = Egypt Sign Language + TRUETYPE_TAG('e', 's', 'm', 0), // esm = Esuma + TRUETYPE_TAG('e', 's', 'n', 0), // esn = Salvadoran Sign Language + TRUETYPE_TAG('e', 's', 'o', 0), // eso = Estonian Sign Language + TRUETYPE_TAG('e', 's', 'q', 0), // esq = Esselen + TRUETYPE_TAG('e', 's', 's', 0), // ess = Central Siberian Yupik + TRUETYPE_TAG('e', 's', 'u', 0), // esu = Central Yupik + TRUETYPE_TAG('e', 's', 'x', 0), // esx = Eskimo-Aleut languages + TRUETYPE_TAG('e', 't', 'b', 0), // etb = Etebi + TRUETYPE_TAG('e', 't', 'c', 0), // etc = Etchemin + TRUETYPE_TAG('e', 't', 'h', 0), // eth = Ethiopian Sign Language + TRUETYPE_TAG('e', 't', 'n', 0), // etn = Eton (Vanuatu) + TRUETYPE_TAG('e', 't', 'o', 0), // eto = Eton (Cameroon) + TRUETYPE_TAG('e', 't', 'r', 0), // etr = Edolo + TRUETYPE_TAG('e', 't', 's', 0), // ets = Yekhee + TRUETYPE_TAG('e', 't', 't', 0), // ett = Etruscan + TRUETYPE_TAG('e', 't', 'u', 0), // etu = Ejagham + TRUETYPE_TAG('e', 't', 'x', 0), // etx = Eten + TRUETYPE_TAG('e', 't', 'z', 0), // etz = Semimi + TRUETYPE_TAG('e', 'u', 'q', 0), // euq = Basque (family) + TRUETYPE_TAG('e', 'v', 'e', 0), // eve = Even + TRUETYPE_TAG('e', 'v', 'h', 0), // evh = Uvbie + TRUETYPE_TAG('e', 'v', 'n', 0), // evn = Evenki + TRUETYPE_TAG('e', 'w', 'o', 0), // ewo = Ewondo + TRUETYPE_TAG('e', 'x', 't', 0), // ext = Extremaduran + TRUETYPE_TAG('e', 'y', 'a', 0), // eya = Eyak + TRUETYPE_TAG('e', 'y', 'o', 0), // eyo = Keiyo + TRUETYPE_TAG('e', 'z', 'e', 0), // eze = Uzekwe + TRUETYPE_TAG('f', 'a', 'a', 0), // faa = Fasu + TRUETYPE_TAG('f', 'a', 'b', 0), // fab = Fa D'ambu + TRUETYPE_TAG('f', 'a', 'd', 0), // fad = Wagi + TRUETYPE_TAG('f', 'a', 'f', 0), // faf = Fagani + TRUETYPE_TAG('f', 'a', 'g', 0), // fag = Finongan + TRUETYPE_TAG('f', 'a', 'h', 0), // fah = Baissa Fali + TRUETYPE_TAG('f', 'a', 'i', 0), // fai = Faiwol + TRUETYPE_TAG('f', 'a', 'j', 0), // faj = Faita + TRUETYPE_TAG('f', 'a', 'k', 0), // fak = Fang (Cameroon) + TRUETYPE_TAG('f', 'a', 'l', 0), // fal = South Fali + TRUETYPE_TAG('f', 'a', 'm', 0), // fam = Fam + TRUETYPE_TAG('f', 'a', 'n', 0), // fan = Fang (Equatorial Guinea) + TRUETYPE_TAG('f', 'a', 'p', 0), // fap = Palor + TRUETYPE_TAG('f', 'a', 'r', 0), // far = Fataleka + TRUETYPE_TAG('f', 'a', 't', 0), // fat = Fanti + TRUETYPE_TAG('f', 'a', 'u', 0), // fau = Fayu + TRUETYPE_TAG('f', 'a', 'x', 0), // fax = Fala + TRUETYPE_TAG('f', 'a', 'y', 0), // fay = Southwestern Fars + TRUETYPE_TAG('f', 'a', 'z', 0), // faz = Northwestern Fars + TRUETYPE_TAG('f', 'b', 'l', 0), // fbl = West Albay Bikol + TRUETYPE_TAG('f', 'c', 's', 0), // fcs = Quebec Sign Language + TRUETYPE_TAG('f', 'e', 'r', 0), // fer = Feroge + TRUETYPE_TAG('f', 'f', 'i', 0), // ffi = Foia Foia + TRUETYPE_TAG('f', 'f', 'm', 0), // ffm = Maasina Fulfulde + TRUETYPE_TAG('f', 'g', 'r', 0), // fgr = Fongoro + TRUETYPE_TAG('f', 'i', 'a', 0), // fia = Nobiin + TRUETYPE_TAG('f', 'i', 'e', 0), // fie = Fyer + TRUETYPE_TAG('f', 'i', 'l', 0), // fil = Filipino + TRUETYPE_TAG('f', 'i', 'p', 0), // fip = Fipa + TRUETYPE_TAG('f', 'i', 'r', 0), // fir = Firan + TRUETYPE_TAG('f', 'i', 't', 0), // fit = Tornedalen Finnish + TRUETYPE_TAG('f', 'i', 'u', 0), // fiu = Finno-Ugrian languages + TRUETYPE_TAG('f', 'i', 'w', 0), // fiw = Fiwaga + TRUETYPE_TAG('f', 'k', 'v', 0), // fkv = Kven Finnish + TRUETYPE_TAG('f', 'l', 'a', 0), // fla = Kalispel-Pend d'Oreille + TRUETYPE_TAG('f', 'l', 'h', 0), // flh = Foau + TRUETYPE_TAG('f', 'l', 'i', 0), // fli = Fali + TRUETYPE_TAG('f', 'l', 'l', 0), // fll = North Fali + TRUETYPE_TAG('f', 'l', 'n', 0), // fln = Flinders Island + TRUETYPE_TAG('f', 'l', 'r', 0), // flr = Fuliiru + TRUETYPE_TAG('f', 'l', 'y', 0), // fly = Tsotsitaal + TRUETYPE_TAG('f', 'm', 'p', 0), // fmp = Fe'fe' + TRUETYPE_TAG('f', 'm', 'u', 0), // fmu = Far Western Muria + TRUETYPE_TAG('f', 'n', 'g', 0), // fng = Fanagalo + TRUETYPE_TAG('f', 'n', 'i', 0), // fni = Fania + TRUETYPE_TAG('f', 'o', 'd', 0), // fod = Foodo + TRUETYPE_TAG('f', 'o', 'i', 0), // foi = Foi + TRUETYPE_TAG('f', 'o', 'm', 0), // fom = Foma + TRUETYPE_TAG('f', 'o', 'n', 0), // fon = Fon + TRUETYPE_TAG('f', 'o', 'r', 0), // for = Fore + TRUETYPE_TAG('f', 'o', 's', 0), // fos = Siraya + TRUETYPE_TAG('f', 'o', 'x', 0), // fox = Formosan languages + TRUETYPE_TAG('f', 'p', 'e', 0), // fpe = Fernando Po Creole English + TRUETYPE_TAG('f', 'q', 's', 0), // fqs = Fas + TRUETYPE_TAG('f', 'r', 'c', 0), // frc = Cajun French + TRUETYPE_TAG('f', 'r', 'd', 0), // frd = Fordata + TRUETYPE_TAG('f', 'r', 'k', 0), // frk = Frankish + TRUETYPE_TAG('f', 'r', 'm', 0), // frm = Middle French (ca. 1400-1600) + TRUETYPE_TAG('f', 'r', 'o', 0), // fro = Old French (842-ca. 1400) + TRUETYPE_TAG('f', 'r', 'p', 0), // frp = Arpitan + TRUETYPE_TAG('f', 'r', 'q', 0), // frq = Forak + TRUETYPE_TAG('f', 'r', 'r', 0), // frr = Northern Frisian + TRUETYPE_TAG('f', 'r', 's', 0), // frs = Eastern Frisian + TRUETYPE_TAG('f', 'r', 't', 0), // frt = Fortsenal + TRUETYPE_TAG('f', 's', 'e', 0), // fse = Finnish Sign Language + TRUETYPE_TAG('f', 's', 'l', 0), // fsl = French Sign Language + TRUETYPE_TAG('f', 's', 's', 0), // fss = Finland-Swedish Sign Language + TRUETYPE_TAG('f', 'u', 'b', 0), // fub = Adamawa Fulfulde + TRUETYPE_TAG('f', 'u', 'c', 0), // fuc = Pulaar + TRUETYPE_TAG('f', 'u', 'd', 0), // fud = East Futuna + TRUETYPE_TAG('f', 'u', 'e', 0), // fue = Borgu Fulfulde + TRUETYPE_TAG('f', 'u', 'f', 0), // fuf = Pular + TRUETYPE_TAG('f', 'u', 'h', 0), // fuh = Western Niger Fulfulde + TRUETYPE_TAG('f', 'u', 'i', 0), // fui = Bagirmi Fulfulde + TRUETYPE_TAG('f', 'u', 'j', 0), // fuj = Ko + TRUETYPE_TAG('f', 'u', 'm', 0), // fum = Fum + TRUETYPE_TAG('f', 'u', 'n', 0), // fun = Fulniô + TRUETYPE_TAG('f', 'u', 'q', 0), // fuq = Central-Eastern Niger Fulfulde + TRUETYPE_TAG('f', 'u', 'r', 0), // fur = Friulian + TRUETYPE_TAG('f', 'u', 't', 0), // fut = Futuna-Aniwa + TRUETYPE_TAG('f', 'u', 'u', 0), // fuu = Furu + TRUETYPE_TAG('f', 'u', 'v', 0), // fuv = Nigerian Fulfulde + TRUETYPE_TAG('f', 'u', 'y', 0), // fuy = Fuyug + TRUETYPE_TAG('f', 'v', 'r', 0), // fvr = Fur + TRUETYPE_TAG('f', 'w', 'a', 0), // fwa = Fwâi + TRUETYPE_TAG('f', 'w', 'e', 0), // fwe = Fwe + TRUETYPE_TAG('g', 'a', 'a', 0), // gaa = Ga + TRUETYPE_TAG('g', 'a', 'b', 0), // gab = Gabri + TRUETYPE_TAG('g', 'a', 'c', 0), // gac = Mixed Great Andamanese + TRUETYPE_TAG('g', 'a', 'd', 0), // gad = Gaddang + TRUETYPE_TAG('g', 'a', 'e', 0), // gae = Guarequena + TRUETYPE_TAG('g', 'a', 'f', 0), // gaf = Gende + TRUETYPE_TAG('g', 'a', 'g', 0), // gag = Gagauz + TRUETYPE_TAG('g', 'a', 'h', 0), // gah = Alekano + TRUETYPE_TAG('g', 'a', 'i', 0), // gai = Borei + TRUETYPE_TAG('g', 'a', 'j', 0), // gaj = Gadsup + TRUETYPE_TAG('g', 'a', 'k', 0), // gak = Gamkonora + TRUETYPE_TAG('g', 'a', 'l', 0), // gal = Galoli + TRUETYPE_TAG('g', 'a', 'm', 0), // gam = Kandawo + TRUETYPE_TAG('g', 'a', 'n', 0), // gan = Gan Chinese + TRUETYPE_TAG('g', 'a', 'o', 0), // gao = Gants + TRUETYPE_TAG('g', 'a', 'p', 0), // gap = Gal + TRUETYPE_TAG('g', 'a', 'q', 0), // gaq = Gata' + TRUETYPE_TAG('g', 'a', 'r', 0), // gar = Galeya + TRUETYPE_TAG('g', 'a', 's', 0), // gas = Adiwasi Garasia + TRUETYPE_TAG('g', 'a', 't', 0), // gat = Kenati + TRUETYPE_TAG('g', 'a', 'u', 0), // gau = Mudhili Gadaba + TRUETYPE_TAG('g', 'a', 'v', 0), // gav = Gabutamon + TRUETYPE_TAG('g', 'a', 'w', 0), // gaw = Nobonob + TRUETYPE_TAG('g', 'a', 'x', 0), // gax = Borana-Arsi-Guji Oromo + TRUETYPE_TAG('g', 'a', 'y', 0), // gay = Gayo + TRUETYPE_TAG('g', 'a', 'z', 0), // gaz = West Central Oromo + TRUETYPE_TAG('g', 'b', 'a', 0), // gba = Gbaya (Central African Republic) + TRUETYPE_TAG('g', 'b', 'b', 0), // gbb = Kaytetye + TRUETYPE_TAG('g', 'b', 'c', 0), // gbc = Garawa + TRUETYPE_TAG('g', 'b', 'd', 0), // gbd = Karadjeri + TRUETYPE_TAG('g', 'b', 'e', 0), // gbe = Niksek + TRUETYPE_TAG('g', 'b', 'f', 0), // gbf = Gaikundi + TRUETYPE_TAG('g', 'b', 'g', 0), // gbg = Gbanziri + TRUETYPE_TAG('g', 'b', 'h', 0), // gbh = Defi Gbe + TRUETYPE_TAG('g', 'b', 'i', 0), // gbi = Galela + TRUETYPE_TAG('g', 'b', 'j', 0), // gbj = Bodo Gadaba + TRUETYPE_TAG('g', 'b', 'k', 0), // gbk = Gaddi + TRUETYPE_TAG('g', 'b', 'l', 0), // gbl = Gamit + TRUETYPE_TAG('g', 'b', 'm', 0), // gbm = Garhwali + TRUETYPE_TAG('g', 'b', 'n', 0), // gbn = Mo'da + TRUETYPE_TAG('g', 'b', 'o', 0), // gbo = Northern Grebo + TRUETYPE_TAG('g', 'b', 'p', 0), // gbp = Gbaya-Bossangoa + TRUETYPE_TAG('g', 'b', 'q', 0), // gbq = Gbaya-Bozoum + TRUETYPE_TAG('g', 'b', 'r', 0), // gbr = Gbagyi + TRUETYPE_TAG('g', 'b', 's', 0), // gbs = Gbesi Gbe + TRUETYPE_TAG('g', 'b', 'u', 0), // gbu = Gagadu + TRUETYPE_TAG('g', 'b', 'v', 0), // gbv = Gbanu + TRUETYPE_TAG('g', 'b', 'x', 0), // gbx = Eastern Xwla Gbe + TRUETYPE_TAG('g', 'b', 'y', 0), // gby = Gbari + TRUETYPE_TAG('g', 'b', 'z', 0), // gbz = Zoroastrian Dari + TRUETYPE_TAG('g', 'c', 'c', 0), // gcc = Mali + TRUETYPE_TAG('g', 'c', 'd', 0), // gcd = Ganggalida + TRUETYPE_TAG('g', 'c', 'e', 0), // gce = Galice + TRUETYPE_TAG('g', 'c', 'f', 0), // gcf = Guadeloupean Creole French + TRUETYPE_TAG('g', 'c', 'l', 0), // gcl = Grenadian Creole English + TRUETYPE_TAG('g', 'c', 'n', 0), // gcn = Gaina + TRUETYPE_TAG('g', 'c', 'r', 0), // gcr = Guianese Creole French + TRUETYPE_TAG('g', 'c', 't', 0), // gct = Colonia Tovar German + TRUETYPE_TAG('g', 'd', 'a', 0), // gda = Gade Lohar + TRUETYPE_TAG('g', 'd', 'b', 0), // gdb = Pottangi Ollar Gadaba + TRUETYPE_TAG('g', 'd', 'c', 0), // gdc = Gugu Badhun + TRUETYPE_TAG('g', 'd', 'd', 0), // gdd = Gedaged + TRUETYPE_TAG('g', 'd', 'e', 0), // gde = Gude + TRUETYPE_TAG('g', 'd', 'f', 0), // gdf = Guduf-Gava + TRUETYPE_TAG('g', 'd', 'g', 0), // gdg = Ga'dang + TRUETYPE_TAG('g', 'd', 'h', 0), // gdh = Gadjerawang + TRUETYPE_TAG('g', 'd', 'i', 0), // gdi = Gundi + TRUETYPE_TAG('g', 'd', 'j', 0), // gdj = Gurdjar + TRUETYPE_TAG('g', 'd', 'k', 0), // gdk = Gadang + TRUETYPE_TAG('g', 'd', 'l', 0), // gdl = Dirasha + TRUETYPE_TAG('g', 'd', 'm', 0), // gdm = Laal + TRUETYPE_TAG('g', 'd', 'n', 0), // gdn = Umanakaina + TRUETYPE_TAG('g', 'd', 'o', 0), // gdo = Ghodoberi + TRUETYPE_TAG('g', 'd', 'q', 0), // gdq = Mehri + TRUETYPE_TAG('g', 'd', 'r', 0), // gdr = Wipi + TRUETYPE_TAG('g', 'd', 'u', 0), // gdu = Gudu + TRUETYPE_TAG('g', 'd', 'x', 0), // gdx = Godwari + TRUETYPE_TAG('g', 'e', 'a', 0), // gea = Geruma + TRUETYPE_TAG('g', 'e', 'b', 0), // geb = Kire + TRUETYPE_TAG('g', 'e', 'c', 0), // gec = Gboloo Grebo + TRUETYPE_TAG('g', 'e', 'd', 0), // ged = Gade + TRUETYPE_TAG('g', 'e', 'g', 0), // geg = Gengle + TRUETYPE_TAG('g', 'e', 'h', 0), // geh = Hutterite German + TRUETYPE_TAG('g', 'e', 'i', 0), // gei = Gebe + TRUETYPE_TAG('g', 'e', 'j', 0), // gej = Gen + TRUETYPE_TAG('g', 'e', 'k', 0), // gek = Yiwom + TRUETYPE_TAG('g', 'e', 'l', 0), // gel = ut-Ma'in + TRUETYPE_TAG('g', 'e', 'm', 0), // gem = Germanic languages + TRUETYPE_TAG('g', 'e', 'q', 0), // geq = Geme + TRUETYPE_TAG('g', 'e', 's', 0), // ges = Geser-Gorom + TRUETYPE_TAG('g', 'e', 'w', 0), // gew = Gera + TRUETYPE_TAG('g', 'e', 'x', 0), // gex = Garre + TRUETYPE_TAG('g', 'e', 'y', 0), // gey = Enya + TRUETYPE_TAG('g', 'e', 'z', 0), // gez = Geez + TRUETYPE_TAG('g', 'f', 'k', 0), // gfk = Patpatar + TRUETYPE_TAG('g', 'f', 't', 0), // gft = Gafat + TRUETYPE_TAG('g', 'g', 'a', 0), // gga = Gao + TRUETYPE_TAG('g', 'g', 'b', 0), // ggb = Gbii + TRUETYPE_TAG('g', 'g', 'd', 0), // ggd = Gugadj + TRUETYPE_TAG('g', 'g', 'e', 0), // gge = Guragone + TRUETYPE_TAG('g', 'g', 'g', 0), // ggg = Gurgula + TRUETYPE_TAG('g', 'g', 'k', 0), // ggk = Kungarakany + TRUETYPE_TAG('g', 'g', 'l', 0), // ggl = Ganglau + TRUETYPE_TAG('g', 'g', 'n', 0), // ggn = Eastern Gurung + TRUETYPE_TAG('g', 'g', 'o', 0), // ggo = Southern Gondi + TRUETYPE_TAG('g', 'g', 'r', 0), // ggr = Aghu Tharnggalu + TRUETYPE_TAG('g', 'g', 't', 0), // ggt = Gitua + TRUETYPE_TAG('g', 'g', 'u', 0), // ggu = Gagu + TRUETYPE_TAG('g', 'g', 'w', 0), // ggw = Gogodala + TRUETYPE_TAG('g', 'h', 'a', 0), // gha = Ghadamès + TRUETYPE_TAG('g', 'h', 'c', 0), // ghc = Hiberno-Scottish Gaelic + TRUETYPE_TAG('g', 'h', 'e', 0), // ghe = Southern Ghale + TRUETYPE_TAG('g', 'h', 'h', 0), // ghh = Northern Ghale + TRUETYPE_TAG('g', 'h', 'k', 0), // ghk = Geko Karen + TRUETYPE_TAG('g', 'h', 'l', 0), // ghl = Ghulfan + TRUETYPE_TAG('g', 'h', 'n', 0), // ghn = Ghanongga + TRUETYPE_TAG('g', 'h', 'o', 0), // gho = Ghomara + TRUETYPE_TAG('g', 'h', 'r', 0), // ghr = Ghera + TRUETYPE_TAG('g', 'h', 's', 0), // ghs = Guhu-Samane + TRUETYPE_TAG('g', 'h', 't', 0), // ght = Kutang Ghale + TRUETYPE_TAG('g', 'i', 'a', 0), // gia = Kitja + TRUETYPE_TAG('g', 'i', 'b', 0), // gib = Gibanawa + TRUETYPE_TAG('g', 'i', 'c', 0), // gic = Gail + TRUETYPE_TAG('g', 'i', 'd', 0), // gid = Gidar + TRUETYPE_TAG('g', 'i', 'g', 0), // gig = Goaria + TRUETYPE_TAG('g', 'i', 'l', 0), // gil = Gilbertese + TRUETYPE_TAG('g', 'i', 'm', 0), // gim = Gimi (Eastern Highlands) + TRUETYPE_TAG('g', 'i', 'n', 0), // gin = Hinukh + TRUETYPE_TAG('g', 'i', 'o', 0), // gio = Gelao + TRUETYPE_TAG('g', 'i', 'p', 0), // gip = Gimi (West New Britain) + TRUETYPE_TAG('g', 'i', 'q', 0), // giq = Green Gelao + TRUETYPE_TAG('g', 'i', 'r', 0), // gir = Red Gelao + TRUETYPE_TAG('g', 'i', 's', 0), // gis = North Giziga + TRUETYPE_TAG('g', 'i', 't', 0), // git = Gitxsan + TRUETYPE_TAG('g', 'i', 'w', 0), // giw = White Gelao + TRUETYPE_TAG('g', 'i', 'x', 0), // gix = Gilima + TRUETYPE_TAG('g', 'i', 'y', 0), // giy = Giyug + TRUETYPE_TAG('g', 'i', 'z', 0), // giz = South Giziga + TRUETYPE_TAG('g', 'j', 'i', 0), // gji = Geji + TRUETYPE_TAG('g', 'j', 'k', 0), // gjk = Kachi Koli + TRUETYPE_TAG('g', 'j', 'n', 0), // gjn = Gonja + TRUETYPE_TAG('g', 'j', 'u', 0), // gju = Gujari + TRUETYPE_TAG('g', 'k', 'a', 0), // gka = Guya + TRUETYPE_TAG('g', 'k', 'e', 0), // gke = Ndai + TRUETYPE_TAG('g', 'k', 'n', 0), // gkn = Gokana + TRUETYPE_TAG('g', 'k', 'p', 0), // gkp = Guinea Kpelle + TRUETYPE_TAG('g', 'l', 'c', 0), // glc = Bon Gula + TRUETYPE_TAG('g', 'l', 'd', 0), // gld = Nanai + TRUETYPE_TAG('g', 'l', 'h', 0), // glh = Northwest Pashayi + TRUETYPE_TAG('g', 'l', 'i', 0), // gli = Guliguli + TRUETYPE_TAG('g', 'l', 'j', 0), // glj = Gula Iro + TRUETYPE_TAG('g', 'l', 'k', 0), // glk = Gilaki + TRUETYPE_TAG('g', 'l', 'o', 0), // glo = Galambu + TRUETYPE_TAG('g', 'l', 'r', 0), // glr = Glaro-Twabo + TRUETYPE_TAG('g', 'l', 'u', 0), // glu = Gula (Chad) + TRUETYPE_TAG('g', 'l', 'w', 0), // glw = Glavda + TRUETYPE_TAG('g', 'l', 'y', 0), // gly = Gule + TRUETYPE_TAG('g', 'm', 'a', 0), // gma = Gambera + TRUETYPE_TAG('g', 'm', 'b', 0), // gmb = Gula'alaa + TRUETYPE_TAG('g', 'm', 'd', 0), // gmd = Mághdì + TRUETYPE_TAG('g', 'm', 'e', 0), // gme = East Germanic languages + TRUETYPE_TAG('g', 'm', 'h', 0), // gmh = Middle High German (ca. 1050-1500) + TRUETYPE_TAG('g', 'm', 'l', 0), // gml = Middle Low German + TRUETYPE_TAG('g', 'm', 'm', 0), // gmm = Gbaya-Mbodomo + TRUETYPE_TAG('g', 'm', 'n', 0), // gmn = Gimnime + TRUETYPE_TAG('g', 'm', 'q', 0), // gmq = North Germanic languages + TRUETYPE_TAG('g', 'm', 'u', 0), // gmu = Gumalu + TRUETYPE_TAG('g', 'm', 'v', 0), // gmv = Gamo + TRUETYPE_TAG('g', 'm', 'w', 0), // gmw = West Germanic languages + TRUETYPE_TAG('g', 'm', 'x', 0), // gmx = Magoma + TRUETYPE_TAG('g', 'm', 'y', 0), // gmy = Mycenaean Greek + TRUETYPE_TAG('g', 'n', 'a', 0), // gna = Kaansa + TRUETYPE_TAG('g', 'n', 'b', 0), // gnb = Gangte + TRUETYPE_TAG('g', 'n', 'c', 0), // gnc = Guanche + TRUETYPE_TAG('g', 'n', 'd', 0), // gnd = Zulgo-Gemzek + TRUETYPE_TAG('g', 'n', 'e', 0), // gne = Ganang + TRUETYPE_TAG('g', 'n', 'g', 0), // gng = Ngangam + TRUETYPE_TAG('g', 'n', 'h', 0), // gnh = Lere + TRUETYPE_TAG('g', 'n', 'i', 0), // gni = Gooniyandi + TRUETYPE_TAG('g', 'n', 'k', 0), // gnk = //Gana + TRUETYPE_TAG('g', 'n', 'l', 0), // gnl = Gangulu + TRUETYPE_TAG('g', 'n', 'm', 0), // gnm = Ginuman + TRUETYPE_TAG('g', 'n', 'n', 0), // gnn = Gumatj + TRUETYPE_TAG('g', 'n', 'o', 0), // gno = Northern Gondi + TRUETYPE_TAG('g', 'n', 'q', 0), // gnq = Gana + TRUETYPE_TAG('g', 'n', 'r', 0), // gnr = Gureng Gureng + TRUETYPE_TAG('g', 'n', 't', 0), // gnt = Guntai + TRUETYPE_TAG('g', 'n', 'u', 0), // gnu = Gnau + TRUETYPE_TAG('g', 'n', 'w', 0), // gnw = Western Bolivian Guaraní + TRUETYPE_TAG('g', 'n', 'z', 0), // gnz = Ganzi + TRUETYPE_TAG('g', 'o', 'a', 0), // goa = Guro + TRUETYPE_TAG('g', 'o', 'b', 0), // gob = Playero + TRUETYPE_TAG('g', 'o', 'c', 0), // goc = Gorakor + TRUETYPE_TAG('g', 'o', 'd', 0), // god = Godié + TRUETYPE_TAG('g', 'o', 'e', 0), // goe = Gongduk + TRUETYPE_TAG('g', 'o', 'f', 0), // gof = Gofa + TRUETYPE_TAG('g', 'o', 'g', 0), // gog = Gogo + TRUETYPE_TAG('g', 'o', 'h', 0), // goh = Old High German (ca. 750-1050) + TRUETYPE_TAG('g', 'o', 'i', 0), // goi = Gobasi + TRUETYPE_TAG('g', 'o', 'j', 0), // goj = Gowlan + TRUETYPE_TAG('g', 'o', 'k', 0), // gok = Gowli + TRUETYPE_TAG('g', 'o', 'l', 0), // gol = Gola + TRUETYPE_TAG('g', 'o', 'm', 0), // gom = Goan Konkani + TRUETYPE_TAG('g', 'o', 'n', 0), // gon = Gondi + TRUETYPE_TAG('g', 'o', 'o', 0), // goo = Gone Dau + TRUETYPE_TAG('g', 'o', 'p', 0), // gop = Yeretuar + TRUETYPE_TAG('g', 'o', 'q', 0), // goq = Gorap + TRUETYPE_TAG('g', 'o', 'r', 0), // gor = Gorontalo + TRUETYPE_TAG('g', 'o', 's', 0), // gos = Gronings + TRUETYPE_TAG('g', 'o', 't', 0), // got = Gothic + TRUETYPE_TAG('g', 'o', 'u', 0), // gou = Gavar + TRUETYPE_TAG('g', 'o', 'w', 0), // gow = Gorowa + TRUETYPE_TAG('g', 'o', 'x', 0), // gox = Gobu + TRUETYPE_TAG('g', 'o', 'y', 0), // goy = Goundo + TRUETYPE_TAG('g', 'o', 'z', 0), // goz = Gozarkhani + TRUETYPE_TAG('g', 'p', 'a', 0), // gpa = Gupa-Abawa + TRUETYPE_TAG('g', 'p', 'n', 0), // gpn = Taiap + TRUETYPE_TAG('g', 'q', 'a', 0), // gqa = Ga'anda + TRUETYPE_TAG('g', 'q', 'i', 0), // gqi = Guiqiong + TRUETYPE_TAG('g', 'q', 'n', 0), // gqn = Guana (Brazil) + TRUETYPE_TAG('g', 'q', 'r', 0), // gqr = Gor + TRUETYPE_TAG('g', 'r', 'a', 0), // gra = Rajput Garasia + TRUETYPE_TAG('g', 'r', 'b', 0), // grb = Grebo + TRUETYPE_TAG('g', 'r', 'c', 0), // grc = Ancient Greek (to 1453) + TRUETYPE_TAG('g', 'r', 'd', 0), // grd = Guruntum-Mbaaru + TRUETYPE_TAG('g', 'r', 'g', 0), // grg = Madi + TRUETYPE_TAG('g', 'r', 'h', 0), // grh = Gbiri-Niragu + TRUETYPE_TAG('g', 'r', 'i', 0), // gri = Ghari + TRUETYPE_TAG('g', 'r', 'j', 0), // grj = Southern Grebo + TRUETYPE_TAG('g', 'r', 'k', 0), // grk = Greek languages + TRUETYPE_TAG('g', 'r', 'm', 0), // grm = Kota Marudu Talantang + TRUETYPE_TAG('g', 'r', 'o', 0), // gro = Groma + TRUETYPE_TAG('g', 'r', 'q', 0), // grq = Gorovu + TRUETYPE_TAG('g', 'r', 'r', 0), // grr = Taznatit + TRUETYPE_TAG('g', 'r', 's', 0), // grs = Gresi + TRUETYPE_TAG('g', 'r', 't', 0), // grt = Garo + TRUETYPE_TAG('g', 'r', 'u', 0), // gru = Kistane + TRUETYPE_TAG('g', 'r', 'v', 0), // grv = Central Grebo + TRUETYPE_TAG('g', 'r', 'w', 0), // grw = Gweda + TRUETYPE_TAG('g', 'r', 'x', 0), // grx = Guriaso + TRUETYPE_TAG('g', 'r', 'y', 0), // gry = Barclayville Grebo + TRUETYPE_TAG('g', 'r', 'z', 0), // grz = Guramalum + TRUETYPE_TAG('g', 's', 'e', 0), // gse = Ghanaian Sign Language + TRUETYPE_TAG('g', 's', 'g', 0), // gsg = German Sign Language + TRUETYPE_TAG('g', 's', 'l', 0), // gsl = Gusilay + TRUETYPE_TAG('g', 's', 'm', 0), // gsm = Guatemalan Sign Language + TRUETYPE_TAG('g', 's', 'n', 0), // gsn = Gusan + TRUETYPE_TAG('g', 's', 'o', 0), // gso = Southwest Gbaya + TRUETYPE_TAG('g', 's', 'p', 0), // gsp = Wasembo + TRUETYPE_TAG('g', 's', 's', 0), // gss = Greek Sign Language + TRUETYPE_TAG('g', 's', 'w', 0), // gsw = Swiss German + TRUETYPE_TAG('g', 't', 'a', 0), // gta = Guató + TRUETYPE_TAG('g', 't', 'i', 0), // gti = Gbati-ri + TRUETYPE_TAG('g', 'u', 'a', 0), // gua = Shiki + TRUETYPE_TAG('g', 'u', 'b', 0), // gub = Guajajára + TRUETYPE_TAG('g', 'u', 'c', 0), // guc = Wayuu + TRUETYPE_TAG('g', 'u', 'd', 0), // gud = Yocoboué Dida + TRUETYPE_TAG('g', 'u', 'e', 0), // gue = Gurinji + TRUETYPE_TAG('g', 'u', 'f', 0), // guf = Gupapuyngu + TRUETYPE_TAG('g', 'u', 'g', 0), // gug = Paraguayan Guaraní + TRUETYPE_TAG('g', 'u', 'h', 0), // guh = Guahibo + TRUETYPE_TAG('g', 'u', 'i', 0), // gui = Eastern Bolivian Guaraní + TRUETYPE_TAG('g', 'u', 'k', 0), // guk = Gumuz + TRUETYPE_TAG('g', 'u', 'l', 0), // gul = Sea Island Creole English + TRUETYPE_TAG('g', 'u', 'm', 0), // gum = Guambiano + TRUETYPE_TAG('g', 'u', 'n', 0), // gun = Mbyá Guaraní + TRUETYPE_TAG('g', 'u', 'o', 0), // guo = Guayabero + TRUETYPE_TAG('g', 'u', 'p', 0), // gup = Gunwinggu + TRUETYPE_TAG('g', 'u', 'q', 0), // guq = Aché + TRUETYPE_TAG('g', 'u', 'r', 0), // gur = Farefare + TRUETYPE_TAG('g', 'u', 's', 0), // gus = Guinean Sign Language + TRUETYPE_TAG('g', 'u', 't', 0), // gut = Maléku Jaíka + TRUETYPE_TAG('g', 'u', 'u', 0), // guu = Yanomamö + TRUETYPE_TAG('g', 'u', 'v', 0), // guv = Gey + TRUETYPE_TAG('g', 'u', 'w', 0), // guw = Gun + TRUETYPE_TAG('g', 'u', 'x', 0), // gux = Gourmanchéma + TRUETYPE_TAG('g', 'u', 'z', 0), // guz = Gusii + TRUETYPE_TAG('g', 'v', 'a', 0), // gva = Guana (Paraguay) + TRUETYPE_TAG('g', 'v', 'c', 0), // gvc = Guanano + TRUETYPE_TAG('g', 'v', 'e', 0), // gve = Duwet + TRUETYPE_TAG('g', 'v', 'f', 0), // gvf = Golin + TRUETYPE_TAG('g', 'v', 'j', 0), // gvj = Guajá + TRUETYPE_TAG('g', 'v', 'l', 0), // gvl = Gulay + TRUETYPE_TAG('g', 'v', 'm', 0), // gvm = Gurmana + TRUETYPE_TAG('g', 'v', 'n', 0), // gvn = Kuku-Yalanji + TRUETYPE_TAG('g', 'v', 'o', 0), // gvo = Gavião Do Jiparaná + TRUETYPE_TAG('g', 'v', 'p', 0), // gvp = Pará Gavião + TRUETYPE_TAG('g', 'v', 'r', 0), // gvr = Western Gurung + TRUETYPE_TAG('g', 'v', 's', 0), // gvs = Gumawana + TRUETYPE_TAG('g', 'v', 'y', 0), // gvy = Guyani + TRUETYPE_TAG('g', 'w', 'a', 0), // gwa = Mbato + TRUETYPE_TAG('g', 'w', 'b', 0), // gwb = Gwa + TRUETYPE_TAG('g', 'w', 'c', 0), // gwc = Kalami + TRUETYPE_TAG('g', 'w', 'd', 0), // gwd = Gawwada + TRUETYPE_TAG('g', 'w', 'e', 0), // gwe = Gweno + TRUETYPE_TAG('g', 'w', 'f', 0), // gwf = Gowro + TRUETYPE_TAG('g', 'w', 'g', 0), // gwg = Moo + TRUETYPE_TAG('g', 'w', 'i', 0), // gwi = Gwichʼin + TRUETYPE_TAG('g', 'w', 'j', 0), // gwj = /Gwi + TRUETYPE_TAG('g', 'w', 'n', 0), // gwn = Gwandara + TRUETYPE_TAG('g', 'w', 'r', 0), // gwr = Gwere + TRUETYPE_TAG('g', 'w', 't', 0), // gwt = Gawar-Bati + TRUETYPE_TAG('g', 'w', 'u', 0), // gwu = Guwamu + TRUETYPE_TAG('g', 'w', 'w', 0), // gww = Kwini + TRUETYPE_TAG('g', 'w', 'x', 0), // gwx = Gua + TRUETYPE_TAG('g', 'x', 'x', 0), // gxx = Wè Southern + TRUETYPE_TAG('g', 'y', 'a', 0), // gya = Northwest Gbaya + TRUETYPE_TAG('g', 'y', 'b', 0), // gyb = Garus + TRUETYPE_TAG('g', 'y', 'd', 0), // gyd = Kayardild + TRUETYPE_TAG('g', 'y', 'e', 0), // gye = Gyem + TRUETYPE_TAG('g', 'y', 'f', 0), // gyf = Gungabula + TRUETYPE_TAG('g', 'y', 'g', 0), // gyg = Gbayi + TRUETYPE_TAG('g', 'y', 'i', 0), // gyi = Gyele + TRUETYPE_TAG('g', 'y', 'l', 0), // gyl = Gayil + TRUETYPE_TAG('g', 'y', 'm', 0), // gym = Ngäbere + TRUETYPE_TAG('g', 'y', 'n', 0), // gyn = Guyanese Creole English + TRUETYPE_TAG('g', 'y', 'r', 0), // gyr = Guarayu + TRUETYPE_TAG('g', 'y', 'y', 0), // gyy = Gunya + TRUETYPE_TAG('g', 'z', 'a', 0), // gza = Ganza + TRUETYPE_TAG('g', 'z', 'i', 0), // gzi = Gazi + TRUETYPE_TAG('g', 'z', 'n', 0), // gzn = Gane + TRUETYPE_TAG('h', 'a', 'a', 0), // haa = Han + TRUETYPE_TAG('h', 'a', 'b', 0), // hab = Hanoi Sign Language + TRUETYPE_TAG('h', 'a', 'c', 0), // hac = Gurani + TRUETYPE_TAG('h', 'a', 'd', 0), // had = Hatam + TRUETYPE_TAG('h', 'a', 'e', 0), // hae = Eastern Oromo + TRUETYPE_TAG('h', 'a', 'f', 0), // haf = Haiphong Sign Language + TRUETYPE_TAG('h', 'a', 'g', 0), // hag = Hanga + TRUETYPE_TAG('h', 'a', 'h', 0), // hah = Hahon + TRUETYPE_TAG('h', 'a', 'i', 0), // hai = Haida + TRUETYPE_TAG('h', 'a', 'j', 0), // haj = Hajong + TRUETYPE_TAG('h', 'a', 'k', 0), // hak = Hakka Chinese + TRUETYPE_TAG('h', 'a', 'l', 0), // hal = Halang + TRUETYPE_TAG('h', 'a', 'm', 0), // ham = Hewa + TRUETYPE_TAG('h', 'a', 'n', 0), // han = Hangaza + TRUETYPE_TAG('h', 'a', 'o', 0), // hao = Hakö + TRUETYPE_TAG('h', 'a', 'p', 0), // hap = Hupla + TRUETYPE_TAG('h', 'a', 'q', 0), // haq = Ha + TRUETYPE_TAG('h', 'a', 'r', 0), // har = Harari + TRUETYPE_TAG('h', 'a', 's', 0), // has = Haisla + TRUETYPE_TAG('h', 'a', 'v', 0), // hav = Havu + TRUETYPE_TAG('h', 'a', 'w', 0), // haw = Hawaiian + TRUETYPE_TAG('h', 'a', 'x', 0), // hax = Southern Haida + TRUETYPE_TAG('h', 'a', 'y', 0), // hay = Haya + TRUETYPE_TAG('h', 'a', 'z', 0), // haz = Hazaragi + TRUETYPE_TAG('h', 'b', 'a', 0), // hba = Hamba + TRUETYPE_TAG('h', 'b', 'b', 0), // hbb = Huba + TRUETYPE_TAG('h', 'b', 'n', 0), // hbn = Heiban + TRUETYPE_TAG('h', 'b', 'o', 0), // hbo = Ancient Hebrew + TRUETYPE_TAG('h', 'b', 'u', 0), // hbu = Habu + TRUETYPE_TAG('h', 'c', 'a', 0), // hca = Andaman Creole Hindi + TRUETYPE_TAG('h', 'c', 'h', 0), // hch = Huichol + TRUETYPE_TAG('h', 'd', 'n', 0), // hdn = Northern Haida + TRUETYPE_TAG('h', 'd', 's', 0), // hds = Honduras Sign Language + TRUETYPE_TAG('h', 'd', 'y', 0), // hdy = Hadiyya + TRUETYPE_TAG('h', 'e', 'a', 0), // hea = Northern Qiandong Miao + TRUETYPE_TAG('h', 'e', 'd', 0), // hed = Herdé + TRUETYPE_TAG('h', 'e', 'g', 0), // heg = Helong + TRUETYPE_TAG('h', 'e', 'h', 0), // heh = Hehe + TRUETYPE_TAG('h', 'e', 'i', 0), // hei = Heiltsuk + TRUETYPE_TAG('h', 'e', 'm', 0), // hem = Hemba + TRUETYPE_TAG('h', 'g', 'm', 0), // hgm = Hai//om + TRUETYPE_TAG('h', 'g', 'w', 0), // hgw = Haigwai + TRUETYPE_TAG('h', 'h', 'i', 0), // hhi = Hoia Hoia + TRUETYPE_TAG('h', 'h', 'r', 0), // hhr = Kerak + TRUETYPE_TAG('h', 'h', 'y', 0), // hhy = Hoyahoya + TRUETYPE_TAG('h', 'i', 'a', 0), // hia = Lamang + TRUETYPE_TAG('h', 'i', 'b', 0), // hib = Hibito + TRUETYPE_TAG('h', 'i', 'd', 0), // hid = Hidatsa + TRUETYPE_TAG('h', 'i', 'f', 0), // hif = Fiji Hindi + TRUETYPE_TAG('h', 'i', 'g', 0), // hig = Kamwe + TRUETYPE_TAG('h', 'i', 'h', 0), // hih = Pamosu + TRUETYPE_TAG('h', 'i', 'i', 0), // hii = Hinduri + TRUETYPE_TAG('h', 'i', 'j', 0), // hij = Hijuk + TRUETYPE_TAG('h', 'i', 'k', 0), // hik = Seit-Kaitetu + TRUETYPE_TAG('h', 'i', 'l', 0), // hil = Hiligaynon + TRUETYPE_TAG('h', 'i', 'm', 0), // him = Himachali languages + TRUETYPE_TAG('h', 'i', 'o', 0), // hio = Tsoa + TRUETYPE_TAG('h', 'i', 'r', 0), // hir = Himarimã + TRUETYPE_TAG('h', 'i', 't', 0), // hit = Hittite + TRUETYPE_TAG('h', 'i', 'w', 0), // hiw = Hiw + TRUETYPE_TAG('h', 'i', 'x', 0), // hix = Hixkaryána + TRUETYPE_TAG('h', 'j', 'i', 0), // hji = Haji + TRUETYPE_TAG('h', 'k', 'a', 0), // hka = Kahe + TRUETYPE_TAG('h', 'k', 'e', 0), // hke = Hunde + TRUETYPE_TAG('h', 'k', 'k', 0), // hkk = Hunjara-Kaina Ke + TRUETYPE_TAG('h', 'k', 's', 0), // hks = Hong Kong Sign Language + TRUETYPE_TAG('h', 'l', 'a', 0), // hla = Halia + TRUETYPE_TAG('h', 'l', 'b', 0), // hlb = Halbi + TRUETYPE_TAG('h', 'l', 'd', 0), // hld = Halang Doan + TRUETYPE_TAG('h', 'l', 'e', 0), // hle = Hlersu + TRUETYPE_TAG('h', 'l', 't', 0), // hlt = Nga La + TRUETYPE_TAG('h', 'l', 'u', 0), // hlu = Hieroglyphic Luwian + TRUETYPE_TAG('h', 'm', 'a', 0), // hma = Southern Mashan Hmong + TRUETYPE_TAG('h', 'm', 'b', 0), // hmb = Humburi Senni Songhay + TRUETYPE_TAG('h', 'm', 'c', 0), // hmc = Central Huishui Hmong + TRUETYPE_TAG('h', 'm', 'd', 0), // hmd = Large Flowery Miao + TRUETYPE_TAG('h', 'm', 'e', 0), // hme = Eastern Huishui Hmong + TRUETYPE_TAG('h', 'm', 'f', 0), // hmf = Hmong Don + TRUETYPE_TAG('h', 'm', 'g', 0), // hmg = Southwestern Guiyang Hmong + TRUETYPE_TAG('h', 'm', 'h', 0), // hmh = Southwestern Huishui Hmong + TRUETYPE_TAG('h', 'm', 'i', 0), // hmi = Northern Huishui Hmong + TRUETYPE_TAG('h', 'm', 'j', 0), // hmj = Ge + TRUETYPE_TAG('h', 'm', 'k', 0), // hmk = Maek + TRUETYPE_TAG('h', 'm', 'l', 0), // hml = Luopohe Hmong + TRUETYPE_TAG('h', 'm', 'm', 0), // hmm = Central Mashan Hmong + TRUETYPE_TAG('h', 'm', 'n', 0), // hmn = Hmong + TRUETYPE_TAG('h', 'm', 'p', 0), // hmp = Northern Mashan Hmong + TRUETYPE_TAG('h', 'm', 'q', 0), // hmq = Eastern Qiandong Miao + TRUETYPE_TAG('h', 'm', 'r', 0), // hmr = Hmar + TRUETYPE_TAG('h', 'm', 's', 0), // hms = Southern Qiandong Miao + TRUETYPE_TAG('h', 'm', 't', 0), // hmt = Hamtai + TRUETYPE_TAG('h', 'm', 'u', 0), // hmu = Hamap + TRUETYPE_TAG('h', 'm', 'v', 0), // hmv = Hmong Dô + TRUETYPE_TAG('h', 'm', 'w', 0), // hmw = Western Mashan Hmong + TRUETYPE_TAG('h', 'm', 'x', 0), // hmx = Hmong-Mien languages + TRUETYPE_TAG('h', 'm', 'y', 0), // hmy = Southern Guiyang Hmong + TRUETYPE_TAG('h', 'm', 'z', 0), // hmz = Hmong Shua + TRUETYPE_TAG('h', 'n', 'a', 0), // hna = Mina (Cameroon) + TRUETYPE_TAG('h', 'n', 'd', 0), // hnd = Southern Hindko + TRUETYPE_TAG('h', 'n', 'e', 0), // hne = Chhattisgarhi + TRUETYPE_TAG('h', 'n', 'h', 0), // hnh = //Ani + TRUETYPE_TAG('h', 'n', 'i', 0), // hni = Hani + TRUETYPE_TAG('h', 'n', 'j', 0), // hnj = Hmong Njua + TRUETYPE_TAG('h', 'n', 'n', 0), // hnn = Hanunoo + TRUETYPE_TAG('h', 'n', 'o', 0), // hno = Northern Hindko + TRUETYPE_TAG('h', 'n', 's', 0), // hns = Caribbean Hindustani + TRUETYPE_TAG('h', 'n', 'u', 0), // hnu = Hung + TRUETYPE_TAG('h', 'o', 'a', 0), // hoa = Hoava + TRUETYPE_TAG('h', 'o', 'b', 0), // hob = Mari (Madang Province) + TRUETYPE_TAG('h', 'o', 'c', 0), // hoc = Ho + TRUETYPE_TAG('h', 'o', 'd', 0), // hod = Holma + TRUETYPE_TAG('h', 'o', 'e', 0), // hoe = Horom + TRUETYPE_TAG('h', 'o', 'h', 0), // hoh = Hobyót + TRUETYPE_TAG('h', 'o', 'i', 0), // hoi = Holikachuk + TRUETYPE_TAG('h', 'o', 'j', 0), // hoj = Hadothi + TRUETYPE_TAG('h', 'o', 'k', 0), // hok = Hokan languages + TRUETYPE_TAG('h', 'o', 'l', 0), // hol = Holu + TRUETYPE_TAG('h', 'o', 'm', 0), // hom = Homa + TRUETYPE_TAG('h', 'o', 'o', 0), // hoo = Holoholo + TRUETYPE_TAG('h', 'o', 'p', 0), // hop = Hopi + TRUETYPE_TAG('h', 'o', 'r', 0), // hor = Horo + TRUETYPE_TAG('h', 'o', 's', 0), // hos = Ho Chi Minh City Sign Language + TRUETYPE_TAG('h', 'o', 't', 0), // hot = Hote + TRUETYPE_TAG('h', 'o', 'v', 0), // hov = Hovongan + TRUETYPE_TAG('h', 'o', 'w', 0), // how = Honi + TRUETYPE_TAG('h', 'o', 'y', 0), // hoy = Holiya + TRUETYPE_TAG('h', 'o', 'z', 0), // hoz = Hozo + TRUETYPE_TAG('h', 'p', 'o', 0), // hpo = Hpon + TRUETYPE_TAG('h', 'p', 's', 0), // hps = Hawai'i Pidgin Sign Language + TRUETYPE_TAG('h', 'r', 'a', 0), // hra = Hrangkhol + TRUETYPE_TAG('h', 'r', 'e', 0), // hre = Hre + TRUETYPE_TAG('h', 'r', 'k', 0), // hrk = Haruku + TRUETYPE_TAG('h', 'r', 'm', 0), // hrm = Horned Miao + TRUETYPE_TAG('h', 'r', 'o', 0), // hro = Haroi + TRUETYPE_TAG('h', 'r', 'r', 0), // hrr = Horuru + TRUETYPE_TAG('h', 'r', 't', 0), // hrt = Hértevin + TRUETYPE_TAG('h', 'r', 'u', 0), // hru = Hruso + TRUETYPE_TAG('h', 'r', 'x', 0), // hrx = Hunsrik + TRUETYPE_TAG('h', 'r', 'z', 0), // hrz = Harzani + TRUETYPE_TAG('h', 's', 'b', 0), // hsb = Upper Sorbian + TRUETYPE_TAG('h', 's', 'h', 0), // hsh = Hungarian Sign Language + TRUETYPE_TAG('h', 's', 'l', 0), // hsl = Hausa Sign Language + TRUETYPE_TAG('h', 's', 'n', 0), // hsn = Xiang Chinese + TRUETYPE_TAG('h', 's', 's', 0), // hss = Harsusi + TRUETYPE_TAG('h', 't', 'i', 0), // hti = Hoti + TRUETYPE_TAG('h', 't', 'o', 0), // hto = Minica Huitoto + TRUETYPE_TAG('h', 't', 's', 0), // hts = Hadza + TRUETYPE_TAG('h', 't', 'u', 0), // htu = Hitu + TRUETYPE_TAG('h', 't', 'x', 0), // htx = Middle Hittite + TRUETYPE_TAG('h', 'u', 'b', 0), // hub = Huambisa + TRUETYPE_TAG('h', 'u', 'c', 0), // huc = =/Hua + TRUETYPE_TAG('h', 'u', 'd', 0), // hud = Huaulu + TRUETYPE_TAG('h', 'u', 'e', 0), // hue = San Francisco Del Mar Huave + TRUETYPE_TAG('h', 'u', 'f', 0), // huf = Humene + TRUETYPE_TAG('h', 'u', 'g', 0), // hug = Huachipaeri + TRUETYPE_TAG('h', 'u', 'h', 0), // huh = Huilliche + TRUETYPE_TAG('h', 'u', 'i', 0), // hui = Huli + TRUETYPE_TAG('h', 'u', 'j', 0), // huj = Northern Guiyang Hmong + TRUETYPE_TAG('h', 'u', 'k', 0), // huk = Hulung + TRUETYPE_TAG('h', 'u', 'l', 0), // hul = Hula + TRUETYPE_TAG('h', 'u', 'm', 0), // hum = Hungana + TRUETYPE_TAG('h', 'u', 'o', 0), // huo = Hu + TRUETYPE_TAG('h', 'u', 'p', 0), // hup = Hupa + TRUETYPE_TAG('h', 'u', 'q', 0), // huq = Tsat + TRUETYPE_TAG('h', 'u', 'r', 0), // hur = Halkomelem + TRUETYPE_TAG('h', 'u', 's', 0), // hus = Huastec + TRUETYPE_TAG('h', 'u', 't', 0), // hut = Humla + TRUETYPE_TAG('h', 'u', 'u', 0), // huu = Murui Huitoto + TRUETYPE_TAG('h', 'u', 'v', 0), // huv = San Mateo Del Mar Huave + TRUETYPE_TAG('h', 'u', 'w', 0), // huw = Hukumina + TRUETYPE_TAG('h', 'u', 'x', 0), // hux = Nüpode Huitoto + TRUETYPE_TAG('h', 'u', 'y', 0), // huy = Hulaulá + TRUETYPE_TAG('h', 'u', 'z', 0), // huz = Hunzib + TRUETYPE_TAG('h', 'v', 'c', 0), // hvc = Haitian Vodoun Culture Language + TRUETYPE_TAG('h', 'v', 'e', 0), // hve = San Dionisio Del Mar Huave + TRUETYPE_TAG('h', 'v', 'k', 0), // hvk = Haveke + TRUETYPE_TAG('h', 'v', 'n', 0), // hvn = Sabu + TRUETYPE_TAG('h', 'v', 'v', 0), // hvv = Santa María Del Mar Huave + TRUETYPE_TAG('h', 'w', 'a', 0), // hwa = Wané + TRUETYPE_TAG('h', 'w', 'c', 0), // hwc = Hawai'i Creole English + TRUETYPE_TAG('h', 'w', 'o', 0), // hwo = Hwana + TRUETYPE_TAG('h', 'y', 'a', 0), // hya = Hya + TRUETYPE_TAG('h', 'y', 'x', 0), // hyx = Armenian (family) + TRUETYPE_TAG('i', 'a', 'i', 0), // iai = Iaai + TRUETYPE_TAG('i', 'a', 'n', 0), // ian = Iatmul + TRUETYPE_TAG('i', 'a', 'p', 0), // iap = Iapama + TRUETYPE_TAG('i', 'a', 'r', 0), // iar = Purari + TRUETYPE_TAG('i', 'b', 'a', 0), // iba = Iban + TRUETYPE_TAG('i', 'b', 'b', 0), // ibb = Ibibio + TRUETYPE_TAG('i', 'b', 'd', 0), // ibd = Iwaidja + TRUETYPE_TAG('i', 'b', 'e', 0), // ibe = Akpes + TRUETYPE_TAG('i', 'b', 'g', 0), // ibg = Ibanag + TRUETYPE_TAG('i', 'b', 'i', 0), // ibi = Ibilo + TRUETYPE_TAG('i', 'b', 'l', 0), // ibl = Ibaloi + TRUETYPE_TAG('i', 'b', 'm', 0), // ibm = Agoi + TRUETYPE_TAG('i', 'b', 'n', 0), // ibn = Ibino + TRUETYPE_TAG('i', 'b', 'r', 0), // ibr = Ibuoro + TRUETYPE_TAG('i', 'b', 'u', 0), // ibu = Ibu + TRUETYPE_TAG('i', 'b', 'y', 0), // iby = Ibani + TRUETYPE_TAG('i', 'c', 'a', 0), // ica = Ede Ica + TRUETYPE_TAG('i', 'c', 'h', 0), // ich = Etkywan + TRUETYPE_TAG('i', 'c', 'l', 0), // icl = Icelandic Sign Language + TRUETYPE_TAG('i', 'c', 'r', 0), // icr = Islander Creole English + TRUETYPE_TAG('i', 'd', 'a', 0), // ida = Idakho-Isukha-Tiriki + TRUETYPE_TAG('i', 'd', 'b', 0), // idb = Indo-Portuguese + TRUETYPE_TAG('i', 'd', 'c', 0), // idc = Idon + TRUETYPE_TAG('i', 'd', 'd', 0), // idd = Ede Idaca + TRUETYPE_TAG('i', 'd', 'e', 0), // ide = Idere + TRUETYPE_TAG('i', 'd', 'i', 0), // idi = Idi + TRUETYPE_TAG('i', 'd', 'r', 0), // idr = Indri + TRUETYPE_TAG('i', 'd', 's', 0), // ids = Idesa + TRUETYPE_TAG('i', 'd', 't', 0), // idt = Idaté + TRUETYPE_TAG('i', 'd', 'u', 0), // idu = Idoma + TRUETYPE_TAG('i', 'f', 'a', 0), // ifa = Amganad Ifugao + TRUETYPE_TAG('i', 'f', 'b', 0), // ifb = Batad Ifugao + TRUETYPE_TAG('i', 'f', 'e', 0), // ife = Ifè + TRUETYPE_TAG('i', 'f', 'f', 0), // iff = Ifo + TRUETYPE_TAG('i', 'f', 'k', 0), // ifk = Tuwali Ifugao + TRUETYPE_TAG('i', 'f', 'm', 0), // ifm = Teke-Fuumu + TRUETYPE_TAG('i', 'f', 'u', 0), // ifu = Mayoyao Ifugao + TRUETYPE_TAG('i', 'f', 'y', 0), // ify = Keley-I Kallahan + TRUETYPE_TAG('i', 'g', 'b', 0), // igb = Ebira + TRUETYPE_TAG('i', 'g', 'e', 0), // ige = Igede + TRUETYPE_TAG('i', 'g', 'g', 0), // igg = Igana + TRUETYPE_TAG('i', 'g', 'l', 0), // igl = Igala + TRUETYPE_TAG('i', 'g', 'm', 0), // igm = Kanggape + TRUETYPE_TAG('i', 'g', 'n', 0), // ign = Ignaciano + TRUETYPE_TAG('i', 'g', 'o', 0), // igo = Isebe + TRUETYPE_TAG('i', 'g', 's', 0), // igs = Interglossa + TRUETYPE_TAG('i', 'g', 'w', 0), // igw = Igwe + TRUETYPE_TAG('i', 'h', 'b', 0), // ihb = Iha Based Pidgin + TRUETYPE_TAG('i', 'h', 'i', 0), // ihi = Ihievbe + TRUETYPE_TAG('i', 'h', 'p', 0), // ihp = Iha + TRUETYPE_TAG('i', 'i', 'r', 0), // iir = Indo-Iranian languages + TRUETYPE_TAG('i', 'j', 'c', 0), // ijc = Izon + TRUETYPE_TAG('i', 'j', 'e', 0), // ije = Biseni + TRUETYPE_TAG('i', 'j', 'j', 0), // ijj = Ede Ije + TRUETYPE_TAG('i', 'j', 'n', 0), // ijn = Kalabari + TRUETYPE_TAG('i', 'j', 'o', 0), // ijo = Ijo languages + TRUETYPE_TAG('i', 'j', 's', 0), // ijs = Southeast Ijo + TRUETYPE_TAG('i', 'k', 'e', 0), // ike = Eastern Canadian Inuktitut + TRUETYPE_TAG('i', 'k', 'i', 0), // iki = Iko + TRUETYPE_TAG('i', 'k', 'k', 0), // ikk = Ika + TRUETYPE_TAG('i', 'k', 'l', 0), // ikl = Ikulu + TRUETYPE_TAG('i', 'k', 'o', 0), // iko = Olulumo-Ikom + TRUETYPE_TAG('i', 'k', 'p', 0), // ikp = Ikpeshi + TRUETYPE_TAG('i', 'k', 't', 0), // ikt = Western Canadian Inuktitut + TRUETYPE_TAG('i', 'k', 'v', 0), // ikv = Iku-Gora-Ankwa + TRUETYPE_TAG('i', 'k', 'w', 0), // ikw = Ikwere + TRUETYPE_TAG('i', 'k', 'x', 0), // ikx = Ik + TRUETYPE_TAG('i', 'k', 'z', 0), // ikz = Ikizu + TRUETYPE_TAG('i', 'l', 'a', 0), // ila = Ile Ape + TRUETYPE_TAG('i', 'l', 'b', 0), // ilb = Ila + TRUETYPE_TAG('i', 'l', 'g', 0), // ilg = Garig-Ilgar + TRUETYPE_TAG('i', 'l', 'i', 0), // ili = Ili Turki + TRUETYPE_TAG('i', 'l', 'k', 0), // ilk = Ilongot + TRUETYPE_TAG('i', 'l', 'l', 0), // ill = Iranun + TRUETYPE_TAG('i', 'l', 'o', 0), // ilo = Iloko + TRUETYPE_TAG('i', 'l', 's', 0), // ils = International Sign + TRUETYPE_TAG('i', 'l', 'u', 0), // ilu = Ili'uun + TRUETYPE_TAG('i', 'l', 'v', 0), // ilv = Ilue + TRUETYPE_TAG('i', 'l', 'w', 0), // ilw = Talur + TRUETYPE_TAG('i', 'm', 'a', 0), // ima = Mala Malasar + TRUETYPE_TAG('i', 'm', 'e', 0), // ime = Imeraguen + TRUETYPE_TAG('i', 'm', 'i', 0), // imi = Anamgura + TRUETYPE_TAG('i', 'm', 'l', 0), // iml = Miluk + TRUETYPE_TAG('i', 'm', 'n', 0), // imn = Imonda + TRUETYPE_TAG('i', 'm', 'o', 0), // imo = Imbongu + TRUETYPE_TAG('i', 'm', 'r', 0), // imr = Imroing + TRUETYPE_TAG('i', 'm', 's', 0), // ims = Marsian + TRUETYPE_TAG('i', 'm', 'y', 0), // imy = Milyan + TRUETYPE_TAG('i', 'n', 'b', 0), // inb = Inga + TRUETYPE_TAG('i', 'n', 'c', 0), // inc = Indic languages + TRUETYPE_TAG('i', 'n', 'e', 0), // ine = Indo-European languages + TRUETYPE_TAG('i', 'n', 'g', 0), // ing = Degexit'an + TRUETYPE_TAG('i', 'n', 'h', 0), // inh = Ingush + TRUETYPE_TAG('i', 'n', 'j', 0), // inj = Jungle Inga + TRUETYPE_TAG('i', 'n', 'l', 0), // inl = Indonesian Sign Language + TRUETYPE_TAG('i', 'n', 'm', 0), // inm = Minaean + TRUETYPE_TAG('i', 'n', 'n', 0), // inn = Isinai + TRUETYPE_TAG('i', 'n', 'o', 0), // ino = Inoke-Yate + TRUETYPE_TAG('i', 'n', 'p', 0), // inp = Iñapari + TRUETYPE_TAG('i', 'n', 's', 0), // ins = Indian Sign Language + TRUETYPE_TAG('i', 'n', 't', 0), // int = Intha + TRUETYPE_TAG('i', 'n', 'z', 0), // inz = Ineseño + TRUETYPE_TAG('i', 'o', 'r', 0), // ior = Inor + TRUETYPE_TAG('i', 'o', 'u', 0), // iou = Tuma-Irumu + TRUETYPE_TAG('i', 'o', 'w', 0), // iow = Iowa-Oto + TRUETYPE_TAG('i', 'p', 'i', 0), // ipi = Ipili + TRUETYPE_TAG('i', 'p', 'o', 0), // ipo = Ipiko + TRUETYPE_TAG('i', 'q', 'u', 0), // iqu = Iquito + TRUETYPE_TAG('i', 'r', 'a', 0), // ira = Iranian languages + TRUETYPE_TAG('i', 'r', 'e', 0), // ire = Iresim + TRUETYPE_TAG('i', 'r', 'h', 0), // irh = Irarutu + TRUETYPE_TAG('i', 'r', 'i', 0), // iri = Irigwe + TRUETYPE_TAG('i', 'r', 'k', 0), // irk = Iraqw + TRUETYPE_TAG('i', 'r', 'n', 0), // irn = Irántxe + TRUETYPE_TAG('i', 'r', 'o', 0), // iro = Iroquoian languages + TRUETYPE_TAG('i', 'r', 'r', 0), // irr = Ir + TRUETYPE_TAG('i', 'r', 'u', 0), // iru = Irula + TRUETYPE_TAG('i', 'r', 'x', 0), // irx = Kamberau + TRUETYPE_TAG('i', 'r', 'y', 0), // iry = Iraya + TRUETYPE_TAG('i', 's', 'a', 0), // isa = Isabi + TRUETYPE_TAG('i', 's', 'c', 0), // isc = Isconahua + TRUETYPE_TAG('i', 's', 'd', 0), // isd = Isnag + TRUETYPE_TAG('i', 's', 'e', 0), // ise = Italian Sign Language + TRUETYPE_TAG('i', 's', 'g', 0), // isg = Irish Sign Language + TRUETYPE_TAG('i', 's', 'h', 0), // ish = Esan + TRUETYPE_TAG('i', 's', 'i', 0), // isi = Nkem-Nkum + TRUETYPE_TAG('i', 's', 'k', 0), // isk = Ishkashimi + TRUETYPE_TAG('i', 's', 'm', 0), // ism = Masimasi + TRUETYPE_TAG('i', 's', 'n', 0), // isn = Isanzu + TRUETYPE_TAG('i', 's', 'o', 0), // iso = Isoko + TRUETYPE_TAG('i', 's', 'r', 0), // isr = Israeli Sign Language + TRUETYPE_TAG('i', 's', 't', 0), // ist = Istriot + TRUETYPE_TAG('i', 's', 'u', 0), // isu = Isu (Menchum Division) + TRUETYPE_TAG('i', 't', 'b', 0), // itb = Binongan Itneg + TRUETYPE_TAG('i', 't', 'c', 0), // itc = Italic languages + TRUETYPE_TAG('i', 't', 'e', 0), // ite = Itene + TRUETYPE_TAG('i', 't', 'i', 0), // iti = Inlaod Itneg + TRUETYPE_TAG('i', 't', 'k', 0), // itk = Judeo-Italian + TRUETYPE_TAG('i', 't', 'l', 0), // itl = Itelmen + TRUETYPE_TAG('i', 't', 'm', 0), // itm = Itu Mbon Uzo + TRUETYPE_TAG('i', 't', 'o', 0), // ito = Itonama + TRUETYPE_TAG('i', 't', 'r', 0), // itr = Iteri + TRUETYPE_TAG('i', 't', 's', 0), // its = Isekiri + TRUETYPE_TAG('i', 't', 't', 0), // itt = Maeng Itneg + TRUETYPE_TAG('i', 't', 'v', 0), // itv = Itawit + TRUETYPE_TAG('i', 't', 'w', 0), // itw = Ito + TRUETYPE_TAG('i', 't', 'x', 0), // itx = Itik + TRUETYPE_TAG('i', 't', 'y', 0), // ity = Moyadan Itneg + TRUETYPE_TAG('i', 't', 'z', 0), // itz = Itzá + TRUETYPE_TAG('i', 'u', 'm', 0), // ium = Iu Mien + TRUETYPE_TAG('i', 'v', 'b', 0), // ivb = Ibatan + TRUETYPE_TAG('i', 'v', 'v', 0), // ivv = Ivatan + TRUETYPE_TAG('i', 'w', 'k', 0), // iwk = I-Wak + TRUETYPE_TAG('i', 'w', 'm', 0), // iwm = Iwam + TRUETYPE_TAG('i', 'w', 'o', 0), // iwo = Iwur + TRUETYPE_TAG('i', 'w', 's', 0), // iws = Sepik Iwam + TRUETYPE_TAG('i', 'x', 'c', 0), // ixc = Ixcatec + TRUETYPE_TAG('i', 'x', 'l', 0), // ixl = Ixil + TRUETYPE_TAG('i', 'y', 'a', 0), // iya = Iyayu + TRUETYPE_TAG('i', 'y', 'o', 0), // iyo = Mesaka + TRUETYPE_TAG('i', 'y', 'x', 0), // iyx = Yaka (Congo) + TRUETYPE_TAG('i', 'z', 'h', 0), // izh = Ingrian + TRUETYPE_TAG('i', 'z', 'i', 0), // izi = Izi-Ezaa-Ikwo-Mgbo + TRUETYPE_TAG('i', 'z', 'r', 0), // izr = Izere + TRUETYPE_TAG('j', 'a', 'a', 0), // jaa = Jamamadí + TRUETYPE_TAG('j', 'a', 'b', 0), // jab = Hyam + TRUETYPE_TAG('j', 'a', 'c', 0), // jac = Popti' + TRUETYPE_TAG('j', 'a', 'd', 0), // jad = Jahanka + TRUETYPE_TAG('j', 'a', 'e', 0), // jae = Yabem + TRUETYPE_TAG('j', 'a', 'f', 0), // jaf = Jara + TRUETYPE_TAG('j', 'a', 'h', 0), // jah = Jah Hut + TRUETYPE_TAG('j', 'a', 'j', 0), // jaj = Zazao + TRUETYPE_TAG('j', 'a', 'k', 0), // jak = Jakun + TRUETYPE_TAG('j', 'a', 'l', 0), // jal = Yalahatan + TRUETYPE_TAG('j', 'a', 'm', 0), // jam = Jamaican Creole English + TRUETYPE_TAG('j', 'a', 'o', 0), // jao = Yanyuwa + TRUETYPE_TAG('j', 'a', 'q', 0), // jaq = Yaqay + TRUETYPE_TAG('j', 'a', 'r', 0), // jar = Jarawa (Nigeria) + TRUETYPE_TAG('j', 'a', 's', 0), // jas = New Caledonian Javanese + TRUETYPE_TAG('j', 'a', 't', 0), // jat = Jakati + TRUETYPE_TAG('j', 'a', 'u', 0), // jau = Yaur + TRUETYPE_TAG('j', 'a', 'x', 0), // jax = Jambi Malay + TRUETYPE_TAG('j', 'a', 'y', 0), // jay = Yan-nhangu + TRUETYPE_TAG('j', 'a', 'z', 0), // jaz = Jawe + TRUETYPE_TAG('j', 'b', 'e', 0), // jbe = Judeo-Berber + TRUETYPE_TAG('j', 'b', 'j', 0), // jbj = Arandai + TRUETYPE_TAG('j', 'b', 'n', 0), // jbn = Nafusi + TRUETYPE_TAG('j', 'b', 'o', 0), // jbo = Lojban + TRUETYPE_TAG('j', 'b', 'r', 0), // jbr = Jofotek-Bromnya + TRUETYPE_TAG('j', 'b', 't', 0), // jbt = Jabutí + TRUETYPE_TAG('j', 'b', 'u', 0), // jbu = Jukun Takum + TRUETYPE_TAG('j', 'c', 's', 0), // jcs = Jamaican Country Sign Language + TRUETYPE_TAG('j', 'c', 't', 0), // jct = Krymchak + TRUETYPE_TAG('j', 'd', 'a', 0), // jda = Jad + TRUETYPE_TAG('j', 'd', 'g', 0), // jdg = Jadgali + TRUETYPE_TAG('j', 'd', 't', 0), // jdt = Judeo-Tat + TRUETYPE_TAG('j', 'e', 'b', 0), // jeb = Jebero + TRUETYPE_TAG('j', 'e', 'e', 0), // jee = Jerung + TRUETYPE_TAG('j', 'e', 'g', 0), // jeg = Jeng + TRUETYPE_TAG('j', 'e', 'h', 0), // jeh = Jeh + TRUETYPE_TAG('j', 'e', 'i', 0), // jei = Yei + TRUETYPE_TAG('j', 'e', 'k', 0), // jek = Jeri Kuo + TRUETYPE_TAG('j', 'e', 'l', 0), // jel = Yelmek + TRUETYPE_TAG('j', 'e', 'n', 0), // jen = Dza + TRUETYPE_TAG('j', 'e', 'r', 0), // jer = Jere + TRUETYPE_TAG('j', 'e', 't', 0), // jet = Manem + TRUETYPE_TAG('j', 'e', 'u', 0), // jeu = Jonkor Bourmataguil + TRUETYPE_TAG('j', 'g', 'b', 0), // jgb = Ngbee + TRUETYPE_TAG('j', 'g', 'e', 0), // jge = Judeo-Georgian + TRUETYPE_TAG('j', 'g', 'o', 0), // jgo = Ngomba + TRUETYPE_TAG('j', 'h', 'i', 0), // jhi = Jehai + TRUETYPE_TAG('j', 'h', 's', 0), // jhs = Jhankot Sign Language + TRUETYPE_TAG('j', 'i', 'a', 0), // jia = Jina + TRUETYPE_TAG('j', 'i', 'b', 0), // jib = Jibu + TRUETYPE_TAG('j', 'i', 'c', 0), // jic = Tol + TRUETYPE_TAG('j', 'i', 'd', 0), // jid = Bu + TRUETYPE_TAG('j', 'i', 'e', 0), // jie = Jilbe + TRUETYPE_TAG('j', 'i', 'g', 0), // jig = Djingili + TRUETYPE_TAG('j', 'i', 'h', 0), // jih = Shangzhai + TRUETYPE_TAG('j', 'i', 'i', 0), // jii = Jiiddu + TRUETYPE_TAG('j', 'i', 'l', 0), // jil = Jilim + TRUETYPE_TAG('j', 'i', 'm', 0), // jim = Jimi (Cameroon) + TRUETYPE_TAG('j', 'i', 'o', 0), // jio = Jiamao + TRUETYPE_TAG('j', 'i', 'q', 0), // jiq = Guanyinqiao + TRUETYPE_TAG('j', 'i', 't', 0), // jit = Jita + TRUETYPE_TAG('j', 'i', 'u', 0), // jiu = Youle Jinuo + TRUETYPE_TAG('j', 'i', 'v', 0), // jiv = Shuar + TRUETYPE_TAG('j', 'i', 'y', 0), // jiy = Buyuan Jinuo + TRUETYPE_TAG('j', 'k', 'o', 0), // jko = Kubo + TRUETYPE_TAG('j', 'k', 'u', 0), // jku = Labir + TRUETYPE_TAG('j', 'l', 'e', 0), // jle = Ngile + TRUETYPE_TAG('j', 'l', 's', 0), // jls = Jamaican Sign Language + TRUETYPE_TAG('j', 'm', 'a', 0), // jma = Dima + TRUETYPE_TAG('j', 'm', 'b', 0), // jmb = Zumbun + TRUETYPE_TAG('j', 'm', 'c', 0), // jmc = Machame + TRUETYPE_TAG('j', 'm', 'd', 0), // jmd = Yamdena + TRUETYPE_TAG('j', 'm', 'i', 0), // jmi = Jimi (Nigeria) + TRUETYPE_TAG('j', 'm', 'l', 0), // jml = Jumli + TRUETYPE_TAG('j', 'm', 'n', 0), // jmn = Makuri Naga + TRUETYPE_TAG('j', 'm', 'r', 0), // jmr = Kamara + TRUETYPE_TAG('j', 'm', 's', 0), // jms = Mashi (Nigeria) + TRUETYPE_TAG('j', 'm', 'x', 0), // jmx = Western Juxtlahuaca Mixtec + TRUETYPE_TAG('j', 'n', 'a', 0), // jna = Jangshung + TRUETYPE_TAG('j', 'n', 'd', 0), // jnd = Jandavra + TRUETYPE_TAG('j', 'n', 'g', 0), // jng = Yangman + TRUETYPE_TAG('j', 'n', 'i', 0), // jni = Janji + TRUETYPE_TAG('j', 'n', 'j', 0), // jnj = Yemsa + TRUETYPE_TAG('j', 'n', 'l', 0), // jnl = Rawat + TRUETYPE_TAG('j', 'n', 's', 0), // jns = Jaunsari + TRUETYPE_TAG('j', 'o', 'b', 0), // job = Joba + TRUETYPE_TAG('j', 'o', 'd', 0), // jod = Wojenaka + TRUETYPE_TAG('j', 'o', 'r', 0), // jor = Jorá + TRUETYPE_TAG('j', 'o', 's', 0), // jos = Jordanian Sign Language + TRUETYPE_TAG('j', 'o', 'w', 0), // jow = Jowulu + TRUETYPE_TAG('j', 'p', 'a', 0), // jpa = Jewish Palestinian Aramaic + TRUETYPE_TAG('j', 'p', 'r', 0), // jpr = Judeo-Persian + TRUETYPE_TAG('j', 'p', 'x', 0), // jpx = Japanese (family) + TRUETYPE_TAG('j', 'q', 'r', 0), // jqr = Jaqaru + TRUETYPE_TAG('j', 'r', 'a', 0), // jra = Jarai + TRUETYPE_TAG('j', 'r', 'b', 0), // jrb = Judeo-Arabic + TRUETYPE_TAG('j', 'r', 'r', 0), // jrr = Jiru + TRUETYPE_TAG('j', 'r', 't', 0), // jrt = Jorto + TRUETYPE_TAG('j', 'r', 'u', 0), // jru = Japrería + TRUETYPE_TAG('j', 's', 'l', 0), // jsl = Japanese Sign Language + TRUETYPE_TAG('j', 'u', 'a', 0), // jua = Júma + TRUETYPE_TAG('j', 'u', 'b', 0), // jub = Wannu + TRUETYPE_TAG('j', 'u', 'c', 0), // juc = Jurchen + TRUETYPE_TAG('j', 'u', 'd', 0), // jud = Worodougou + TRUETYPE_TAG('j', 'u', 'h', 0), // juh = Hõne + TRUETYPE_TAG('j', 'u', 'k', 0), // juk = Wapan + TRUETYPE_TAG('j', 'u', 'l', 0), // jul = Jirel + TRUETYPE_TAG('j', 'u', 'm', 0), // jum = Jumjum + TRUETYPE_TAG('j', 'u', 'n', 0), // jun = Juang + TRUETYPE_TAG('j', 'u', 'o', 0), // juo = Jiba + TRUETYPE_TAG('j', 'u', 'p', 0), // jup = Hupdë + TRUETYPE_TAG('j', 'u', 'r', 0), // jur = Jurúna + TRUETYPE_TAG('j', 'u', 's', 0), // jus = Jumla Sign Language + TRUETYPE_TAG('j', 'u', 't', 0), // jut = Jutish + TRUETYPE_TAG('j', 'u', 'u', 0), // juu = Ju + TRUETYPE_TAG('j', 'u', 'w', 0), // juw = Wãpha + TRUETYPE_TAG('j', 'u', 'y', 0), // juy = Juray + TRUETYPE_TAG('j', 'v', 'd', 0), // jvd = Javindo + TRUETYPE_TAG('j', 'v', 'n', 0), // jvn = Caribbean Javanese + TRUETYPE_TAG('j', 'w', 'i', 0), // jwi = Jwira-Pepesa + TRUETYPE_TAG('j', 'y', 'a', 0), // jya = Jiarong + TRUETYPE_TAG('j', 'y', 'e', 0), // jye = Judeo-Yemeni Arabic + TRUETYPE_TAG('j', 'y', 'y', 0), // jyy = Jaya + TRUETYPE_TAG('k', 'a', 'a', 0), // kaa = Kara-Kalpak + TRUETYPE_TAG('k', 'a', 'b', 0), // kab = Kabyle + TRUETYPE_TAG('k', 'a', 'c', 0), // kac = Kachin + TRUETYPE_TAG('k', 'a', 'd', 0), // kad = Kadara + TRUETYPE_TAG('k', 'a', 'e', 0), // kae = Ketangalan + TRUETYPE_TAG('k', 'a', 'f', 0), // kaf = Katso + TRUETYPE_TAG('k', 'a', 'g', 0), // kag = Kajaman + TRUETYPE_TAG('k', 'a', 'h', 0), // kah = Kara (Central African Republic) + TRUETYPE_TAG('k', 'a', 'i', 0), // kai = Karekare + TRUETYPE_TAG('k', 'a', 'j', 0), // kaj = Jju + TRUETYPE_TAG('k', 'a', 'k', 0), // kak = Kayapa Kallahan + TRUETYPE_TAG('k', 'a', 'm', 0), // kam = Kamba (Kenya) + TRUETYPE_TAG('k', 'a', 'o', 0), // kao = Xaasongaxango + TRUETYPE_TAG('k', 'a', 'p', 0), // kap = Bezhta + TRUETYPE_TAG('k', 'a', 'q', 0), // kaq = Capanahua + TRUETYPE_TAG('k', 'a', 'r', 0), // kar = Karen languages + TRUETYPE_TAG('k', 'a', 'v', 0), // kav = Katukína + TRUETYPE_TAG('k', 'a', 'w', 0), // kaw = Kawi + TRUETYPE_TAG('k', 'a', 'x', 0), // kax = Kao + TRUETYPE_TAG('k', 'a', 'y', 0), // kay = Kamayurá + TRUETYPE_TAG('k', 'b', 'a', 0), // kba = Kalarko + TRUETYPE_TAG('k', 'b', 'b', 0), // kbb = Kaxuiâna + TRUETYPE_TAG('k', 'b', 'c', 0), // kbc = Kadiwéu + TRUETYPE_TAG('k', 'b', 'd', 0), // kbd = Kabardian + TRUETYPE_TAG('k', 'b', 'e', 0), // kbe = Kanju + TRUETYPE_TAG('k', 'b', 'f', 0), // kbf = Kakauhua + TRUETYPE_TAG('k', 'b', 'g', 0), // kbg = Khamba + TRUETYPE_TAG('k', 'b', 'h', 0), // kbh = Camsá + TRUETYPE_TAG('k', 'b', 'i', 0), // kbi = Kaptiau + TRUETYPE_TAG('k', 'b', 'j', 0), // kbj = Kari + TRUETYPE_TAG('k', 'b', 'k', 0), // kbk = Grass Koiari + TRUETYPE_TAG('k', 'b', 'l', 0), // kbl = Kanembu + TRUETYPE_TAG('k', 'b', 'm', 0), // kbm = Iwal + TRUETYPE_TAG('k', 'b', 'n', 0), // kbn = Kare (Central African Republic) + TRUETYPE_TAG('k', 'b', 'o', 0), // kbo = Keliko + TRUETYPE_TAG('k', 'b', 'p', 0), // kbp = Kabiyè + TRUETYPE_TAG('k', 'b', 'q', 0), // kbq = Kamano + TRUETYPE_TAG('k', 'b', 'r', 0), // kbr = Kafa + TRUETYPE_TAG('k', 'b', 's', 0), // kbs = Kande + TRUETYPE_TAG('k', 'b', 't', 0), // kbt = Abadi + TRUETYPE_TAG('k', 'b', 'u', 0), // kbu = Kabutra + TRUETYPE_TAG('k', 'b', 'v', 0), // kbv = Dera (Indonesia) + TRUETYPE_TAG('k', 'b', 'w', 0), // kbw = Kaiep + TRUETYPE_TAG('k', 'b', 'x', 0), // kbx = Ap Ma + TRUETYPE_TAG('k', 'b', 'y', 0), // kby = Manga Kanuri + TRUETYPE_TAG('k', 'b', 'z', 0), // kbz = Duhwa + TRUETYPE_TAG('k', 'c', 'a', 0), // kca = Khanty + TRUETYPE_TAG('k', 'c', 'b', 0), // kcb = Kawacha + TRUETYPE_TAG('k', 'c', 'c', 0), // kcc = Lubila + TRUETYPE_TAG('k', 'c', 'd', 0), // kcd = Ngkâlmpw Kanum + TRUETYPE_TAG('k', 'c', 'e', 0), // kce = Kaivi + TRUETYPE_TAG('k', 'c', 'f', 0), // kcf = Ukaan + TRUETYPE_TAG('k', 'c', 'g', 0), // kcg = Tyap + TRUETYPE_TAG('k', 'c', 'h', 0), // kch = Vono + TRUETYPE_TAG('k', 'c', 'i', 0), // kci = Kamantan + TRUETYPE_TAG('k', 'c', 'j', 0), // kcj = Kobiana + TRUETYPE_TAG('k', 'c', 'k', 0), // kck = Kalanga + TRUETYPE_TAG('k', 'c', 'l', 0), // kcl = Kela (Papua New Guinea) + TRUETYPE_TAG('k', 'c', 'm', 0), // kcm = Gula (Central African Republic) + TRUETYPE_TAG('k', 'c', 'n', 0), // kcn = Nubi + TRUETYPE_TAG('k', 'c', 'o', 0), // kco = Kinalakna + TRUETYPE_TAG('k', 'c', 'p', 0), // kcp = Kanga + TRUETYPE_TAG('k', 'c', 'q', 0), // kcq = Kamo + TRUETYPE_TAG('k', 'c', 'r', 0), // kcr = Katla + TRUETYPE_TAG('k', 'c', 's', 0), // kcs = Koenoem + TRUETYPE_TAG('k', 'c', 't', 0), // kct = Kaian + TRUETYPE_TAG('k', 'c', 'u', 0), // kcu = Kami (Tanzania) + TRUETYPE_TAG('k', 'c', 'v', 0), // kcv = Kete + TRUETYPE_TAG('k', 'c', 'w', 0), // kcw = Kabwari + TRUETYPE_TAG('k', 'c', 'x', 0), // kcx = Kachama-Ganjule + TRUETYPE_TAG('k', 'c', 'y', 0), // kcy = Korandje + TRUETYPE_TAG('k', 'c', 'z', 0), // kcz = Konongo + TRUETYPE_TAG('k', 'd', 'a', 0), // kda = Worimi + TRUETYPE_TAG('k', 'd', 'c', 0), // kdc = Kutu + TRUETYPE_TAG('k', 'd', 'd', 0), // kdd = Yankunytjatjara + TRUETYPE_TAG('k', 'd', 'e', 0), // kde = Makonde + TRUETYPE_TAG('k', 'd', 'f', 0), // kdf = Mamusi + TRUETYPE_TAG('k', 'd', 'g', 0), // kdg = Seba + TRUETYPE_TAG('k', 'd', 'h', 0), // kdh = Tem + TRUETYPE_TAG('k', 'd', 'i', 0), // kdi = Kumam + TRUETYPE_TAG('k', 'd', 'j', 0), // kdj = Karamojong + TRUETYPE_TAG('k', 'd', 'k', 0), // kdk = Numee + TRUETYPE_TAG('k', 'd', 'l', 0), // kdl = Tsikimba + TRUETYPE_TAG('k', 'd', 'm', 0), // kdm = Kagoma + TRUETYPE_TAG('k', 'd', 'n', 0), // kdn = Kunda + TRUETYPE_TAG('k', 'd', 'o', 0), // kdo = Kordofanian languages + TRUETYPE_TAG('k', 'd', 'p', 0), // kdp = Kaningdon-Nindem + TRUETYPE_TAG('k', 'd', 'q', 0), // kdq = Koch + TRUETYPE_TAG('k', 'd', 'r', 0), // kdr = Karaim + TRUETYPE_TAG('k', 'd', 't', 0), // kdt = Kuy + TRUETYPE_TAG('k', 'd', 'u', 0), // kdu = Kadaru + TRUETYPE_TAG('k', 'd', 'v', 0), // kdv = Kado + TRUETYPE_TAG('k', 'd', 'w', 0), // kdw = Koneraw + TRUETYPE_TAG('k', 'd', 'x', 0), // kdx = Kam + TRUETYPE_TAG('k', 'd', 'y', 0), // kdy = Keder + TRUETYPE_TAG('k', 'd', 'z', 0), // kdz = Kwaja + TRUETYPE_TAG('k', 'e', 'a', 0), // kea = Kabuverdianu + TRUETYPE_TAG('k', 'e', 'b', 0), // keb = Kélé + TRUETYPE_TAG('k', 'e', 'c', 0), // kec = Keiga + TRUETYPE_TAG('k', 'e', 'd', 0), // ked = Kerewe + TRUETYPE_TAG('k', 'e', 'e', 0), // kee = Eastern Keres + TRUETYPE_TAG('k', 'e', 'f', 0), // kef = Kpessi + TRUETYPE_TAG('k', 'e', 'g', 0), // keg = Tese + TRUETYPE_TAG('k', 'e', 'h', 0), // keh = Keak + TRUETYPE_TAG('k', 'e', 'i', 0), // kei = Kei + TRUETYPE_TAG('k', 'e', 'j', 0), // kej = Kadar + TRUETYPE_TAG('k', 'e', 'k', 0), // kek = Kekchí + TRUETYPE_TAG('k', 'e', 'l', + 0), // kel = Kela (Democratic Republic of Congo) + TRUETYPE_TAG('k', 'e', 'm', 0), // kem = Kemak + TRUETYPE_TAG('k', 'e', 'n', 0), // ken = Kenyang + TRUETYPE_TAG('k', 'e', 'o', 0), // keo = Kakwa + TRUETYPE_TAG('k', 'e', 'p', 0), // kep = Kaikadi + TRUETYPE_TAG('k', 'e', 'q', 0), // keq = Kamar + TRUETYPE_TAG('k', 'e', 'r', 0), // ker = Kera + TRUETYPE_TAG('k', 'e', 's', 0), // kes = Kugbo + TRUETYPE_TAG('k', 'e', 't', 0), // ket = Ket + TRUETYPE_TAG('k', 'e', 'u', 0), // keu = Akebu + TRUETYPE_TAG('k', 'e', 'v', 0), // kev = Kanikkaran + TRUETYPE_TAG('k', 'e', 'w', 0), // kew = West Kewa + TRUETYPE_TAG('k', 'e', 'x', 0), // kex = Kukna + TRUETYPE_TAG('k', 'e', 'y', 0), // key = Kupia + TRUETYPE_TAG('k', 'e', 'z', 0), // kez = Kukele + TRUETYPE_TAG('k', 'f', 'a', 0), // kfa = Kodava + TRUETYPE_TAG('k', 'f', 'b', 0), // kfb = Northwestern Kolami + TRUETYPE_TAG('k', 'f', 'c', 0), // kfc = Konda-Dora + TRUETYPE_TAG('k', 'f', 'd', 0), // kfd = Korra Koraga + TRUETYPE_TAG('k', 'f', 'e', 0), // kfe = Kota (India) + TRUETYPE_TAG('k', 'f', 'f', 0), // kff = Koya + TRUETYPE_TAG('k', 'f', 'g', 0), // kfg = Kudiya + TRUETYPE_TAG('k', 'f', 'h', 0), // kfh = Kurichiya + TRUETYPE_TAG('k', 'f', 'i', 0), // kfi = Kannada Kurumba + TRUETYPE_TAG('k', 'f', 'j', 0), // kfj = Kemiehua + TRUETYPE_TAG('k', 'f', 'k', 0), // kfk = Kinnauri + TRUETYPE_TAG('k', 'f', 'l', 0), // kfl = Kung + TRUETYPE_TAG('k', 'f', 'm', 0), // kfm = Khunsari + TRUETYPE_TAG('k', 'f', 'n', 0), // kfn = Kuk + TRUETYPE_TAG('k', 'f', 'o', 0), // kfo = Koro (Côte d'Ivoire) + TRUETYPE_TAG('k', 'f', 'p', 0), // kfp = Korwa + TRUETYPE_TAG('k', 'f', 'q', 0), // kfq = Korku + TRUETYPE_TAG('k', 'f', 'r', 0), // kfr = Kachchi + TRUETYPE_TAG('k', 'f', 's', 0), // kfs = Bilaspuri + TRUETYPE_TAG('k', 'f', 't', 0), // kft = Kanjari + TRUETYPE_TAG('k', 'f', 'u', 0), // kfu = Katkari + TRUETYPE_TAG('k', 'f', 'v', 0), // kfv = Kurmukar + TRUETYPE_TAG('k', 'f', 'w', 0), // kfw = Kharam Naga + TRUETYPE_TAG('k', 'f', 'x', 0), // kfx = Kullu Pahari + TRUETYPE_TAG('k', 'f', 'y', 0), // kfy = Kumaoni + TRUETYPE_TAG('k', 'f', 'z', 0), // kfz = Koromfé + TRUETYPE_TAG('k', 'g', 'a', 0), // kga = Koyaga + TRUETYPE_TAG('k', 'g', 'b', 0), // kgb = Kawe + TRUETYPE_TAG('k', 'g', 'c', 0), // kgc = Kasseng + TRUETYPE_TAG('k', 'g', 'd', 0), // kgd = Kataang + TRUETYPE_TAG('k', 'g', 'e', 0), // kge = Komering + TRUETYPE_TAG('k', 'g', 'f', 0), // kgf = Kube + TRUETYPE_TAG('k', 'g', 'g', 0), // kgg = Kusunda + TRUETYPE_TAG('k', 'g', 'h', 0), // kgh = Upper Tanudan Kalinga + TRUETYPE_TAG('k', 'g', 'i', 0), // kgi = Selangor Sign Language + TRUETYPE_TAG('k', 'g', 'j', 0), // kgj = Gamale Kham + TRUETYPE_TAG('k', 'g', 'k', 0), // kgk = Kaiwá + TRUETYPE_TAG('k', 'g', 'l', 0), // kgl = Kunggari + TRUETYPE_TAG('k', 'g', 'm', 0), // kgm = Karipúna + TRUETYPE_TAG('k', 'g', 'n', 0), // kgn = Karingani + TRUETYPE_TAG('k', 'g', 'o', 0), // kgo = Krongo + TRUETYPE_TAG('k', 'g', 'p', 0), // kgp = Kaingang + TRUETYPE_TAG('k', 'g', 'q', 0), // kgq = Kamoro + TRUETYPE_TAG('k', 'g', 'r', 0), // kgr = Abun + TRUETYPE_TAG('k', 'g', 's', 0), // kgs = Kumbainggar + TRUETYPE_TAG('k', 'g', 't', 0), // kgt = Somyev + TRUETYPE_TAG('k', 'g', 'u', 0), // kgu = Kobol + TRUETYPE_TAG('k', 'g', 'v', 0), // kgv = Karas + TRUETYPE_TAG('k', 'g', 'w', 0), // kgw = Karon Dori + TRUETYPE_TAG('k', 'g', 'x', 0), // kgx = Kamaru + TRUETYPE_TAG('k', 'g', 'y', 0), // kgy = Kyerung + TRUETYPE_TAG('k', 'h', 'a', 0), // kha = Khasi + TRUETYPE_TAG('k', 'h', 'b', 0), // khb = Lü + TRUETYPE_TAG('k', 'h', 'c', 0), // khc = Tukang Besi North + TRUETYPE_TAG('k', 'h', 'd', 0), // khd = Bädi Kanum + TRUETYPE_TAG('k', 'h', 'e', 0), // khe = Korowai + TRUETYPE_TAG('k', 'h', 'f', 0), // khf = Khuen + TRUETYPE_TAG('k', 'h', 'g', 0), // khg = Khams Tibetan + TRUETYPE_TAG('k', 'h', 'h', 0), // khh = Kehu + TRUETYPE_TAG('k', 'h', 'i', 0), // khi = Khoisan languages + TRUETYPE_TAG('k', 'h', 'j', 0), // khj = Kuturmi + TRUETYPE_TAG('k', 'h', 'k', 0), // khk = Halh Mongolian + TRUETYPE_TAG('k', 'h', 'l', 0), // khl = Lusi + TRUETYPE_TAG('k', 'h', 'n', 0), // khn = Khandesi + TRUETYPE_TAG('k', 'h', 'o', 0), // kho = Khotanese + TRUETYPE_TAG('k', 'h', 'p', 0), // khp = Kapori + TRUETYPE_TAG('k', 'h', 'q', 0), // khq = Koyra Chiini Songhay + TRUETYPE_TAG('k', 'h', 'r', 0), // khr = Kharia + TRUETYPE_TAG('k', 'h', 's', 0), // khs = Kasua + TRUETYPE_TAG('k', 'h', 't', 0), // kht = Khamti + TRUETYPE_TAG('k', 'h', 'u', 0), // khu = Nkhumbi + TRUETYPE_TAG('k', 'h', 'v', 0), // khv = Khvarshi + TRUETYPE_TAG('k', 'h', 'w', 0), // khw = Khowar + TRUETYPE_TAG('k', 'h', 'x', 0), // khx = Kanu + TRUETYPE_TAG('k', 'h', 'y', + 0), // khy = Kele (Democratic Republic of Congo) + TRUETYPE_TAG('k', 'h', 'z', 0), // khz = Keapara + TRUETYPE_TAG('k', 'i', 'a', 0), // kia = Kim + TRUETYPE_TAG('k', 'i', 'b', 0), // kib = Koalib + TRUETYPE_TAG('k', 'i', 'c', 0), // kic = Kickapoo + TRUETYPE_TAG('k', 'i', 'd', 0), // kid = Koshin + TRUETYPE_TAG('k', 'i', 'e', 0), // kie = Kibet + TRUETYPE_TAG('k', 'i', 'f', 0), // kif = Eastern Parbate Kham + TRUETYPE_TAG('k', 'i', 'g', 0), // kig = Kimaama + TRUETYPE_TAG('k', 'i', 'h', 0), // kih = Kilmeri + TRUETYPE_TAG('k', 'i', 'i', 0), // kii = Kitsai + TRUETYPE_TAG('k', 'i', 'j', 0), // kij = Kilivila + TRUETYPE_TAG('k', 'i', 'l', 0), // kil = Kariya + TRUETYPE_TAG('k', 'i', 'm', 0), // kim = Karagas + TRUETYPE_TAG('k', 'i', 'o', 0), // kio = Kiowa + TRUETYPE_TAG('k', 'i', 'p', 0), // kip = Sheshi Kham + TRUETYPE_TAG('k', 'i', 'q', 0), // kiq = Kosadle + TRUETYPE_TAG('k', 'i', 's', 0), // kis = Kis + TRUETYPE_TAG('k', 'i', 't', 0), // kit = Agob + TRUETYPE_TAG('k', 'i', 'u', 0), // kiu = Kirmanjki (individual language) + TRUETYPE_TAG('k', 'i', 'v', 0), // kiv = Kimbu + TRUETYPE_TAG('k', 'i', 'w', 0), // kiw = Northeast Kiwai + TRUETYPE_TAG('k', 'i', 'x', 0), // kix = Khiamniungan Naga + TRUETYPE_TAG('k', 'i', 'y', 0), // kiy = Kirikiri + TRUETYPE_TAG('k', 'i', 'z', 0), // kiz = Kisi + TRUETYPE_TAG('k', 'j', 'a', 0), // kja = Mlap + TRUETYPE_TAG('k', 'j', 'b', 0), // kjb = Q'anjob'al + TRUETYPE_TAG('k', 'j', 'c', 0), // kjc = Coastal Konjo + TRUETYPE_TAG('k', 'j', 'd', 0), // kjd = Southern Kiwai + TRUETYPE_TAG('k', 'j', 'e', 0), // kje = Kisar + TRUETYPE_TAG('k', 'j', 'f', 0), // kjf = Khalaj + TRUETYPE_TAG('k', 'j', 'g', 0), // kjg = Khmu + TRUETYPE_TAG('k', 'j', 'h', 0), // kjh = Khakas + TRUETYPE_TAG('k', 'j', 'i', 0), // kji = Zabana + TRUETYPE_TAG('k', 'j', 'j', 0), // kjj = Khinalugh + TRUETYPE_TAG('k', 'j', 'k', 0), // kjk = Highland Konjo + TRUETYPE_TAG('k', 'j', 'l', 0), // kjl = Western Parbate Kham + TRUETYPE_TAG('k', 'j', 'm', 0), // kjm = Kháng + TRUETYPE_TAG('k', 'j', 'n', 0), // kjn = Kunjen + TRUETYPE_TAG('k', 'j', 'o', 0), // kjo = Harijan Kinnauri + TRUETYPE_TAG('k', 'j', 'p', 0), // kjp = Pwo Eastern Karen + TRUETYPE_TAG('k', 'j', 'q', 0), // kjq = Western Keres + TRUETYPE_TAG('k', 'j', 'r', 0), // kjr = Kurudu + TRUETYPE_TAG('k', 'j', 's', 0), // kjs = East Kewa + TRUETYPE_TAG('k', 'j', 't', 0), // kjt = Phrae Pwo Karen + TRUETYPE_TAG('k', 'j', 'u', 0), // kju = Kashaya + TRUETYPE_TAG('k', 'j', 'x', 0), // kjx = Ramopa + TRUETYPE_TAG('k', 'j', 'y', 0), // kjy = Erave + TRUETYPE_TAG('k', 'j', 'z', 0), // kjz = Bumthangkha + TRUETYPE_TAG('k', 'k', 'a', 0), // kka = Kakanda + TRUETYPE_TAG('k', 'k', 'b', 0), // kkb = Kwerisa + TRUETYPE_TAG('k', 'k', 'c', 0), // kkc = Odoodee + TRUETYPE_TAG('k', 'k', 'd', 0), // kkd = Kinuku + TRUETYPE_TAG('k', 'k', 'e', 0), // kke = Kakabe + TRUETYPE_TAG('k', 'k', 'f', 0), // kkf = Kalaktang Monpa + TRUETYPE_TAG('k', 'k', 'g', 0), // kkg = Mabaka Valley Kalinga + TRUETYPE_TAG('k', 'k', 'h', 0), // kkh = Khün + TRUETYPE_TAG('k', 'k', 'i', 0), // kki = Kagulu + TRUETYPE_TAG('k', 'k', 'j', 0), // kkj = Kako + TRUETYPE_TAG('k', 'k', 'k', 0), // kkk = Kokota + TRUETYPE_TAG('k', 'k', 'l', 0), // kkl = Kosarek Yale + TRUETYPE_TAG('k', 'k', 'm', 0), // kkm = Kiong + TRUETYPE_TAG('k', 'k', 'n', 0), // kkn = Kon Keu + TRUETYPE_TAG('k', 'k', 'o', 0), // kko = Karko + TRUETYPE_TAG('k', 'k', 'p', 0), // kkp = Gugubera + TRUETYPE_TAG('k', 'k', 'q', 0), // kkq = Kaiku + TRUETYPE_TAG('k', 'k', 'r', 0), // kkr = Kir-Balar + TRUETYPE_TAG('k', 'k', 's', 0), // kks = Giiwo + TRUETYPE_TAG('k', 'k', 't', 0), // kkt = Koi + TRUETYPE_TAG('k', 'k', 'u', 0), // kku = Tumi + TRUETYPE_TAG('k', 'k', 'v', 0), // kkv = Kangean + TRUETYPE_TAG('k', 'k', 'w', 0), // kkw = Teke-Kukuya + TRUETYPE_TAG('k', 'k', 'x', 0), // kkx = Kohin + TRUETYPE_TAG('k', 'k', 'y', 0), // kky = Guguyimidjir + TRUETYPE_TAG('k', 'k', 'z', 0), // kkz = Kaska + TRUETYPE_TAG('k', 'l', 'a', 0), // kla = Klamath-Modoc + TRUETYPE_TAG('k', 'l', 'b', 0), // klb = Kiliwa + TRUETYPE_TAG('k', 'l', 'c', 0), // klc = Kolbila + TRUETYPE_TAG('k', 'l', 'd', 0), // kld = Gamilaraay + TRUETYPE_TAG('k', 'l', 'e', 0), // kle = Kulung (Nepal) + TRUETYPE_TAG('k', 'l', 'f', 0), // klf = Kendeje + TRUETYPE_TAG('k', 'l', 'g', 0), // klg = Tagakaulo + TRUETYPE_TAG('k', 'l', 'h', 0), // klh = Weliki + TRUETYPE_TAG('k', 'l', 'i', 0), // kli = Kalumpang + TRUETYPE_TAG('k', 'l', 'j', 0), // klj = Turkic Khalaj + TRUETYPE_TAG('k', 'l', 'k', 0), // klk = Kono (Nigeria) + TRUETYPE_TAG('k', 'l', 'l', 0), // kll = Kagan Kalagan + TRUETYPE_TAG('k', 'l', 'm', 0), // klm = Migum + TRUETYPE_TAG('k', 'l', 'n', 0), // kln = Kalenjin + TRUETYPE_TAG('k', 'l', 'o', 0), // klo = Kapya + TRUETYPE_TAG('k', 'l', 'p', 0), // klp = Kamasa + TRUETYPE_TAG('k', 'l', 'q', 0), // klq = Rumu + TRUETYPE_TAG('k', 'l', 'r', 0), // klr = Khaling + TRUETYPE_TAG('k', 'l', 's', 0), // kls = Kalasha + TRUETYPE_TAG('k', 'l', 't', 0), // klt = Nukna + TRUETYPE_TAG('k', 'l', 'u', 0), // klu = Klao + TRUETYPE_TAG('k', 'l', 'v', 0), // klv = Maskelynes + TRUETYPE_TAG('k', 'l', 'w', 0), // klw = Lindu + TRUETYPE_TAG('k', 'l', 'x', 0), // klx = Koluwawa + TRUETYPE_TAG('k', 'l', 'y', 0), // kly = Kalao + TRUETYPE_TAG('k', 'l', 'z', 0), // klz = Kabola + TRUETYPE_TAG('k', 'm', 'a', 0), // kma = Konni + TRUETYPE_TAG('k', 'm', 'b', 0), // kmb = Kimbundu + TRUETYPE_TAG('k', 'm', 'c', 0), // kmc = Southern Dong + TRUETYPE_TAG('k', 'm', 'd', 0), // kmd = Majukayang Kalinga + TRUETYPE_TAG('k', 'm', 'e', 0), // kme = Bakole + TRUETYPE_TAG('k', 'm', 'f', 0), // kmf = Kare (Papua New Guinea) + TRUETYPE_TAG('k', 'm', 'g', 0), // kmg = Kâte + TRUETYPE_TAG('k', 'm', 'h', 0), // kmh = Kalam + TRUETYPE_TAG('k', 'm', 'i', 0), // kmi = Kami (Nigeria) + TRUETYPE_TAG('k', 'm', 'j', 0), // kmj = Kumarbhag Paharia + TRUETYPE_TAG('k', 'm', 'k', 0), // kmk = Limos Kalinga + TRUETYPE_TAG('k', 'm', 'l', 0), // kml = Lower Tanudan Kalinga + TRUETYPE_TAG('k', 'm', 'm', 0), // kmm = Kom (India) + TRUETYPE_TAG('k', 'm', 'n', 0), // kmn = Awtuw + TRUETYPE_TAG('k', 'm', 'o', 0), // kmo = Kwoma + TRUETYPE_TAG('k', 'm', 'p', 0), // kmp = Gimme + TRUETYPE_TAG('k', 'm', 'q', 0), // kmq = Kwama + TRUETYPE_TAG('k', 'm', 'r', 0), // kmr = Northern Kurdish + TRUETYPE_TAG('k', 'm', 's', 0), // kms = Kamasau + TRUETYPE_TAG('k', 'm', 't', 0), // kmt = Kemtuik + TRUETYPE_TAG('k', 'm', 'u', 0), // kmu = Kanite + TRUETYPE_TAG('k', 'm', 'v', 0), // kmv = Karipúna Creole French + TRUETYPE_TAG('k', 'm', 'w', + 0), // kmw = Komo (Democratic Republic of Congo) + TRUETYPE_TAG('k', 'm', 'x', 0), // kmx = Waboda + TRUETYPE_TAG('k', 'm', 'y', 0), // kmy = Koma + TRUETYPE_TAG('k', 'm', 'z', 0), // kmz = Khorasani Turkish + TRUETYPE_TAG('k', 'n', 'a', 0), // kna = Dera (Nigeria) + TRUETYPE_TAG('k', 'n', 'b', 0), // knb = Lubuagan Kalinga + TRUETYPE_TAG('k', 'n', 'c', 0), // knc = Central Kanuri + TRUETYPE_TAG('k', 'n', 'd', 0), // knd = Konda + TRUETYPE_TAG('k', 'n', 'e', 0), // kne = Kankanaey + TRUETYPE_TAG('k', 'n', 'f', 0), // knf = Mankanya + TRUETYPE_TAG('k', 'n', 'g', 0), // kng = Koongo + TRUETYPE_TAG('k', 'n', 'i', 0), // kni = Kanufi + TRUETYPE_TAG('k', 'n', 'j', 0), // knj = Western Kanjobal + TRUETYPE_TAG('k', 'n', 'k', 0), // knk = Kuranko + TRUETYPE_TAG('k', 'n', 'l', 0), // knl = Keninjal + TRUETYPE_TAG('k', 'n', 'm', 0), // knm = Kanamarí + TRUETYPE_TAG('k', 'n', 'n', 0), // knn = Konkani (individual language) + TRUETYPE_TAG('k', 'n', 'o', 0), // kno = Kono (Sierra Leone) + TRUETYPE_TAG('k', 'n', 'p', 0), // knp = Kwanja + TRUETYPE_TAG('k', 'n', 'q', 0), // knq = Kintaq + TRUETYPE_TAG('k', 'n', 'r', 0), // knr = Kaningra + TRUETYPE_TAG('k', 'n', 's', 0), // kns = Kensiu + TRUETYPE_TAG('k', 'n', 't', 0), // knt = Panoan Katukína + TRUETYPE_TAG('k', 'n', 'u', 0), // knu = Kono (Guinea) + TRUETYPE_TAG('k', 'n', 'v', 0), // knv = Tabo + TRUETYPE_TAG('k', 'n', 'w', 0), // knw = Kung-Ekoka + TRUETYPE_TAG('k', 'n', 'x', 0), // knx = Kendayan + TRUETYPE_TAG('k', 'n', 'y', 0), // kny = Kanyok + TRUETYPE_TAG('k', 'n', 'z', 0), // knz = Kalamsé + TRUETYPE_TAG('k', 'o', 'a', 0), // koa = Konomala + TRUETYPE_TAG('k', 'o', 'c', 0), // koc = Kpati + TRUETYPE_TAG('k', 'o', 'd', 0), // kod = Kodi + TRUETYPE_TAG('k', 'o', 'e', 0), // koe = Kacipo-Balesi + TRUETYPE_TAG('k', 'o', 'f', 0), // kof = Kubi + TRUETYPE_TAG('k', 'o', 'g', 0), // kog = Cogui + TRUETYPE_TAG('k', 'o', 'h', 0), // koh = Koyo + TRUETYPE_TAG('k', 'o', 'i', 0), // koi = Komi-Permyak + TRUETYPE_TAG('k', 'o', 'j', 0), // koj = Sara Dunjo + TRUETYPE_TAG('k', 'o', 'k', 0), // kok = Konkani (macrolanguage) + TRUETYPE_TAG('k', 'o', 'l', 0), // kol = Kol (Papua New Guinea) + TRUETYPE_TAG('k', 'o', 'o', 0), // koo = Konzo + TRUETYPE_TAG('k', 'o', 'p', 0), // kop = Waube + TRUETYPE_TAG('k', 'o', 'q', 0), // koq = Kota (Gabon) + TRUETYPE_TAG('k', 'o', 's', 0), // kos = Kosraean + TRUETYPE_TAG('k', 'o', 't', 0), // kot = Lagwan + TRUETYPE_TAG('k', 'o', 'u', 0), // kou = Koke + TRUETYPE_TAG('k', 'o', 'v', 0), // kov = Kudu-Camo + TRUETYPE_TAG('k', 'o', 'w', 0), // kow = Kugama + TRUETYPE_TAG('k', 'o', 'x', 0), // kox = Coxima + TRUETYPE_TAG('k', 'o', 'y', 0), // koy = Koyukon + TRUETYPE_TAG('k', 'o', 'z', 0), // koz = Korak + TRUETYPE_TAG('k', 'p', 'a', 0), // kpa = Kutto + TRUETYPE_TAG('k', 'p', 'b', 0), // kpb = Mullu Kurumba + TRUETYPE_TAG('k', 'p', 'c', 0), // kpc = Curripaco + TRUETYPE_TAG('k', 'p', 'd', 0), // kpd = Koba + TRUETYPE_TAG('k', 'p', 'e', 0), // kpe = Kpelle + TRUETYPE_TAG('k', 'p', 'f', 0), // kpf = Komba + TRUETYPE_TAG('k', 'p', 'g', 0), // kpg = Kapingamarangi + TRUETYPE_TAG('k', 'p', 'h', 0), // kph = Kplang + TRUETYPE_TAG('k', 'p', 'i', 0), // kpi = Kofei + TRUETYPE_TAG('k', 'p', 'j', 0), // kpj = Karajá + TRUETYPE_TAG('k', 'p', 'k', 0), // kpk = Kpan + TRUETYPE_TAG('k', 'p', 'l', 0), // kpl = Kpala + TRUETYPE_TAG('k', 'p', 'm', 0), // kpm = Koho + TRUETYPE_TAG('k', 'p', 'n', 0), // kpn = Kepkiriwát + TRUETYPE_TAG('k', 'p', 'o', 0), // kpo = Ikposo + TRUETYPE_TAG('k', 'p', 'p', 0), // kpp = Paku Karen + TRUETYPE_TAG('k', 'p', 'q', 0), // kpq = Korupun-Sela + TRUETYPE_TAG('k', 'p', 'r', 0), // kpr = Korafe-Yegha + TRUETYPE_TAG('k', 'p', 's', 0), // kps = Tehit + TRUETYPE_TAG('k', 'p', 't', 0), // kpt = Karata + TRUETYPE_TAG('k', 'p', 'u', 0), // kpu = Kafoa + TRUETYPE_TAG('k', 'p', 'v', 0), // kpv = Komi-Zyrian + TRUETYPE_TAG('k', 'p', 'w', 0), // kpw = Kobon + TRUETYPE_TAG('k', 'p', 'x', 0), // kpx = Mountain Koiali + TRUETYPE_TAG('k', 'p', 'y', 0), // kpy = Koryak + TRUETYPE_TAG('k', 'p', 'z', 0), // kpz = Kupsabiny + TRUETYPE_TAG('k', 'q', 'a', 0), // kqa = Mum + TRUETYPE_TAG('k', 'q', 'b', 0), // kqb = Kovai + TRUETYPE_TAG('k', 'q', 'c', 0), // kqc = Doromu-Koki + TRUETYPE_TAG('k', 'q', 'd', 0), // kqd = Koy Sanjaq Surat + TRUETYPE_TAG('k', 'q', 'e', 0), // kqe = Kalagan + TRUETYPE_TAG('k', 'q', 'f', 0), // kqf = Kakabai + TRUETYPE_TAG('k', 'q', 'g', 0), // kqg = Khe + TRUETYPE_TAG('k', 'q', 'h', 0), // kqh = Kisankasa + TRUETYPE_TAG('k', 'q', 'i', 0), // kqi = Koitabu + TRUETYPE_TAG('k', 'q', 'j', 0), // kqj = Koromira + TRUETYPE_TAG('k', 'q', 'k', 0), // kqk = Kotafon Gbe + TRUETYPE_TAG('k', 'q', 'l', 0), // kql = Kyenele + TRUETYPE_TAG('k', 'q', 'm', 0), // kqm = Khisa + TRUETYPE_TAG('k', 'q', 'n', 0), // kqn = Kaonde + TRUETYPE_TAG('k', 'q', 'o', 0), // kqo = Eastern Krahn + TRUETYPE_TAG('k', 'q', 'p', 0), // kqp = Kimré + TRUETYPE_TAG('k', 'q', 'q', 0), // kqq = Krenak + TRUETYPE_TAG('k', 'q', 'r', 0), // kqr = Kimaragang + TRUETYPE_TAG('k', 'q', 's', 0), // kqs = Northern Kissi + TRUETYPE_TAG('k', 'q', 't', 0), // kqt = Klias River Kadazan + TRUETYPE_TAG('k', 'q', 'u', 0), // kqu = Seroa + TRUETYPE_TAG('k', 'q', 'v', 0), // kqv = Okolod + TRUETYPE_TAG('k', 'q', 'w', 0), // kqw = Kandas + TRUETYPE_TAG('k', 'q', 'x', 0), // kqx = Mser + TRUETYPE_TAG('k', 'q', 'y', 0), // kqy = Koorete + TRUETYPE_TAG('k', 'q', 'z', 0), // kqz = Korana + TRUETYPE_TAG('k', 'r', 'a', 0), // kra = Kumhali + TRUETYPE_TAG('k', 'r', 'b', 0), // krb = Karkin + TRUETYPE_TAG('k', 'r', 'c', 0), // krc = Karachay-Balkar + TRUETYPE_TAG('k', 'r', 'd', 0), // krd = Kairui-Midiki + TRUETYPE_TAG('k', 'r', 'e', 0), // kre = Panará + TRUETYPE_TAG('k', 'r', 'f', 0), // krf = Koro (Vanuatu) + TRUETYPE_TAG('k', 'r', 'h', 0), // krh = Kurama + TRUETYPE_TAG('k', 'r', 'i', 0), // kri = Krio + TRUETYPE_TAG('k', 'r', 'j', 0), // krj = Kinaray-A + TRUETYPE_TAG('k', 'r', 'k', 0), // krk = Kerek + TRUETYPE_TAG('k', 'r', 'l', 0), // krl = Karelian + TRUETYPE_TAG('k', 'r', 'm', 0), // krm = Krim + TRUETYPE_TAG('k', 'r', 'n', 0), // krn = Sapo + TRUETYPE_TAG('k', 'r', 'o', 0), // kro = Kru languages + TRUETYPE_TAG('k', 'r', 'p', 0), // krp = Korop + TRUETYPE_TAG('k', 'r', 'r', 0), // krr = Kru'ng 2 + TRUETYPE_TAG('k', 'r', 's', 0), // krs = Gbaya (Sudan) + TRUETYPE_TAG('k', 'r', 't', 0), // krt = Tumari Kanuri + TRUETYPE_TAG('k', 'r', 'u', 0), // kru = Kurukh + TRUETYPE_TAG('k', 'r', 'v', 0), // krv = Kavet + TRUETYPE_TAG('k', 'r', 'w', 0), // krw = Western Krahn + TRUETYPE_TAG('k', 'r', 'x', 0), // krx = Karon + TRUETYPE_TAG('k', 'r', 'y', 0), // kry = Kryts + TRUETYPE_TAG('k', 'r', 'z', 0), // krz = Sota Kanum + TRUETYPE_TAG('k', 's', 'a', 0), // ksa = Shuwa-Zamani + TRUETYPE_TAG('k', 's', 'b', 0), // ksb = Shambala + TRUETYPE_TAG('k', 's', 'c', 0), // ksc = Southern Kalinga + TRUETYPE_TAG('k', 's', 'd', 0), // ksd = Kuanua + TRUETYPE_TAG('k', 's', 'e', 0), // kse = Kuni + TRUETYPE_TAG('k', 's', 'f', 0), // ksf = Bafia + TRUETYPE_TAG('k', 's', 'g', 0), // ksg = Kusaghe + TRUETYPE_TAG('k', 's', 'h', 0), // ksh = Kölsch + TRUETYPE_TAG('k', 's', 'i', 0), // ksi = Krisa + TRUETYPE_TAG('k', 's', 'j', 0), // ksj = Uare + TRUETYPE_TAG('k', 's', 'k', 0), // ksk = Kansa + TRUETYPE_TAG('k', 's', 'l', 0), // ksl = Kumalu + TRUETYPE_TAG('k', 's', 'm', 0), // ksm = Kumba + TRUETYPE_TAG('k', 's', 'n', 0), // ksn = Kasiguranin + TRUETYPE_TAG('k', 's', 'o', 0), // kso = Kofa + TRUETYPE_TAG('k', 's', 'p', 0), // ksp = Kaba + TRUETYPE_TAG('k', 's', 'q', 0), // ksq = Kwaami + TRUETYPE_TAG('k', 's', 'r', 0), // ksr = Borong + TRUETYPE_TAG('k', 's', 's', 0), // kss = Southern Kisi + TRUETYPE_TAG('k', 's', 't', 0), // kst = Winyé + TRUETYPE_TAG('k', 's', 'u', 0), // ksu = Khamyang + TRUETYPE_TAG('k', 's', 'v', 0), // ksv = Kusu + TRUETYPE_TAG('k', 's', 'w', 0), // ksw = S'gaw Karen + TRUETYPE_TAG('k', 's', 'x', 0), // ksx = Kedang + TRUETYPE_TAG('k', 's', 'y', 0), // ksy = Kharia Thar + TRUETYPE_TAG('k', 's', 'z', 0), // ksz = Kodaku + TRUETYPE_TAG('k', 't', 'a', 0), // kta = Katua + TRUETYPE_TAG('k', 't', 'b', 0), // ktb = Kambaata + TRUETYPE_TAG('k', 't', 'c', 0), // ktc = Kholok + TRUETYPE_TAG('k', 't', 'd', 0), // ktd = Kokata + TRUETYPE_TAG('k', 't', 'e', 0), // kte = Nubri + TRUETYPE_TAG('k', 't', 'f', 0), // ktf = Kwami + TRUETYPE_TAG('k', 't', 'g', 0), // ktg = Kalkutung + TRUETYPE_TAG('k', 't', 'h', 0), // kth = Karanga + TRUETYPE_TAG('k', 't', 'i', 0), // kti = North Muyu + TRUETYPE_TAG('k', 't', 'j', 0), // ktj = Plapo Krumen + TRUETYPE_TAG('k', 't', 'k', 0), // ktk = Kaniet + TRUETYPE_TAG('k', 't', 'l', 0), // ktl = Koroshi + TRUETYPE_TAG('k', 't', 'm', 0), // ktm = Kurti + TRUETYPE_TAG('k', 't', 'n', 0), // ktn = Karitiâna + TRUETYPE_TAG('k', 't', 'o', 0), // kto = Kuot + TRUETYPE_TAG('k', 't', 'p', 0), // ktp = Kaduo + TRUETYPE_TAG('k', 't', 'q', 0), // ktq = Katabaga + TRUETYPE_TAG('k', 't', 'r', 0), // ktr = Kota Marudu Tinagas + TRUETYPE_TAG('k', 't', 's', 0), // kts = South Muyu + TRUETYPE_TAG('k', 't', 't', 0), // ktt = Ketum + TRUETYPE_TAG('k', 't', 'u', + 0), // ktu = Kituba (Democratic Republic of Congo) + TRUETYPE_TAG('k', 't', 'v', 0), // ktv = Eastern Katu + TRUETYPE_TAG('k', 't', 'w', 0), // ktw = Kato + TRUETYPE_TAG('k', 't', 'x', 0), // ktx = Kaxararí + TRUETYPE_TAG('k', 't', 'y', 0), // kty = Kango (Bas-Uélé District) + TRUETYPE_TAG('k', 't', 'z', 0), // ktz = Ju/'hoan + TRUETYPE_TAG('k', 'u', 'b', 0), // kub = Kutep + TRUETYPE_TAG('k', 'u', 'c', 0), // kuc = Kwinsu + TRUETYPE_TAG('k', 'u', 'd', 0), // kud = 'Auhelawa + TRUETYPE_TAG('k', 'u', 'e', 0), // kue = Kuman + TRUETYPE_TAG('k', 'u', 'f', 0), // kuf = Western Katu + TRUETYPE_TAG('k', 'u', 'g', 0), // kug = Kupa + TRUETYPE_TAG('k', 'u', 'h', 0), // kuh = Kushi + TRUETYPE_TAG('k', 'u', 'i', 0), // kui = Kuikúro-Kalapálo + TRUETYPE_TAG('k', 'u', 'j', 0), // kuj = Kuria + TRUETYPE_TAG('k', 'u', 'k', 0), // kuk = Kepo' + TRUETYPE_TAG('k', 'u', 'l', 0), // kul = Kulere + TRUETYPE_TAG('k', 'u', 'm', 0), // kum = Kumyk + TRUETYPE_TAG('k', 'u', 'n', 0), // kun = Kunama + TRUETYPE_TAG('k', 'u', 'o', 0), // kuo = Kumukio + TRUETYPE_TAG('k', 'u', 'p', 0), // kup = Kunimaipa + TRUETYPE_TAG('k', 'u', 'q', 0), // kuq = Karipuna + TRUETYPE_TAG('k', 'u', 's', 0), // kus = Kusaal + TRUETYPE_TAG('k', 'u', 't', 0), // kut = Kutenai + TRUETYPE_TAG('k', 'u', 'u', 0), // kuu = Upper Kuskokwim + TRUETYPE_TAG('k', 'u', 'v', 0), // kuv = Kur + TRUETYPE_TAG('k', 'u', 'w', 0), // kuw = Kpagua + TRUETYPE_TAG('k', 'u', 'x', 0), // kux = Kukatja + TRUETYPE_TAG('k', 'u', 'y', 0), // kuy = Kuuku-Ya'u + TRUETYPE_TAG('k', 'u', 'z', 0), // kuz = Kunza + TRUETYPE_TAG('k', 'v', 'a', 0), // kva = Bagvalal + TRUETYPE_TAG('k', 'v', 'b', 0), // kvb = Kubu + TRUETYPE_TAG('k', 'v', 'c', 0), // kvc = Kove + TRUETYPE_TAG('k', 'v', 'd', 0), // kvd = Kui (Indonesia) + TRUETYPE_TAG('k', 'v', 'e', 0), // kve = Kalabakan + TRUETYPE_TAG('k', 'v', 'f', 0), // kvf = Kabalai + TRUETYPE_TAG('k', 'v', 'g', 0), // kvg = Kuni-Boazi + TRUETYPE_TAG('k', 'v', 'h', 0), // kvh = Komodo + TRUETYPE_TAG('k', 'v', 'i', 0), // kvi = Kwang + TRUETYPE_TAG('k', 'v', 'j', 0), // kvj = Psikye + TRUETYPE_TAG('k', 'v', 'k', 0), // kvk = Korean Sign Language + TRUETYPE_TAG('k', 'v', 'l', 0), // kvl = Brek Karen + TRUETYPE_TAG('k', 'v', 'm', 0), // kvm = Kendem + TRUETYPE_TAG('k', 'v', 'n', 0), // kvn = Border Kuna + TRUETYPE_TAG('k', 'v', 'o', 0), // kvo = Dobel + TRUETYPE_TAG('k', 'v', 'p', 0), // kvp = Kompane + TRUETYPE_TAG('k', 'v', 'q', 0), // kvq = Geba Karen + TRUETYPE_TAG('k', 'v', 'r', 0), // kvr = Kerinci + TRUETYPE_TAG('k', 'v', 's', 0), // kvs = Kunggara + TRUETYPE_TAG('k', 'v', 't', 0), // kvt = Lahta Karen + TRUETYPE_TAG('k', 'v', 'u', 0), // kvu = Yinbaw Karen + TRUETYPE_TAG('k', 'v', 'v', 0), // kvv = Kola + TRUETYPE_TAG('k', 'v', 'w', 0), // kvw = Wersing + TRUETYPE_TAG('k', 'v', 'x', 0), // kvx = Parkari Koli + TRUETYPE_TAG('k', 'v', 'y', 0), // kvy = Yintale Karen + TRUETYPE_TAG('k', 'v', 'z', 0), // kvz = Tsakwambo + TRUETYPE_TAG('k', 'w', 'a', 0), // kwa = Dâw + TRUETYPE_TAG('k', 'w', 'b', 0), // kwb = Kwa + TRUETYPE_TAG('k', 'w', 'c', 0), // kwc = Likwala + TRUETYPE_TAG('k', 'w', 'd', 0), // kwd = Kwaio + TRUETYPE_TAG('k', 'w', 'e', 0), // kwe = Kwerba + TRUETYPE_TAG('k', 'w', 'f', 0), // kwf = Kwara'ae + TRUETYPE_TAG('k', 'w', 'g', 0), // kwg = Sara Kaba Deme + TRUETYPE_TAG('k', 'w', 'h', 0), // kwh = Kowiai + TRUETYPE_TAG('k', 'w', 'i', 0), // kwi = Awa-Cuaiquer + TRUETYPE_TAG('k', 'w', 'j', 0), // kwj = Kwanga + TRUETYPE_TAG('k', 'w', 'k', 0), // kwk = Kwakiutl + TRUETYPE_TAG('k', 'w', 'l', 0), // kwl = Kofyar + TRUETYPE_TAG('k', 'w', 'm', 0), // kwm = Kwambi + TRUETYPE_TAG('k', 'w', 'n', 0), // kwn = Kwangali + TRUETYPE_TAG('k', 'w', 'o', 0), // kwo = Kwomtari + TRUETYPE_TAG('k', 'w', 'p', 0), // kwp = Kodia + TRUETYPE_TAG('k', 'w', 'q', 0), // kwq = Kwak + TRUETYPE_TAG('k', 'w', 'r', 0), // kwr = Kwer + TRUETYPE_TAG('k', 'w', 's', 0), // kws = Kwese + TRUETYPE_TAG('k', 'w', 't', 0), // kwt = Kwesten + TRUETYPE_TAG('k', 'w', 'u', 0), // kwu = Kwakum + TRUETYPE_TAG('k', 'w', 'v', 0), // kwv = Sara Kaba Náà + TRUETYPE_TAG('k', 'w', 'w', 0), // kww = Kwinti + TRUETYPE_TAG('k', 'w', 'x', 0), // kwx = Khirwar + TRUETYPE_TAG('k', 'w', 'y', 0), // kwy = San Salvador Kongo + TRUETYPE_TAG('k', 'w', 'z', 0), // kwz = Kwadi + TRUETYPE_TAG('k', 'x', 'a', 0), // kxa = Kairiru + TRUETYPE_TAG('k', 'x', 'b', 0), // kxb = Krobu + TRUETYPE_TAG('k', 'x', 'c', 0), // kxc = Konso + TRUETYPE_TAG('k', 'x', 'd', 0), // kxd = Brunei + TRUETYPE_TAG('k', 'x', 'e', 0), // kxe = Kakihum + TRUETYPE_TAG('k', 'x', 'f', 0), // kxf = Manumanaw Karen + TRUETYPE_TAG('k', 'x', 'h', 0), // kxh = Karo (Ethiopia) + TRUETYPE_TAG('k', 'x', 'i', 0), // kxi = Keningau Murut + TRUETYPE_TAG('k', 'x', 'j', 0), // kxj = Kulfa + TRUETYPE_TAG('k', 'x', 'k', 0), // kxk = Zayein Karen + TRUETYPE_TAG('k', 'x', 'l', 0), // kxl = Nepali Kurux + TRUETYPE_TAG('k', 'x', 'm', 0), // kxm = Northern Khmer + TRUETYPE_TAG('k', 'x', 'n', 0), // kxn = Kanowit-Tanjong Melanau + TRUETYPE_TAG('k', 'x', 'o', 0), // kxo = Kanoé + TRUETYPE_TAG('k', 'x', 'p', 0), // kxp = Wadiyara Koli + TRUETYPE_TAG('k', 'x', 'q', 0), // kxq = Smärky Kanum + TRUETYPE_TAG('k', 'x', 'r', 0), // kxr = Koro (Papua New Guinea) + TRUETYPE_TAG('k', 'x', 's', 0), // kxs = Kangjia + TRUETYPE_TAG('k', 'x', 't', 0), // kxt = Koiwat + TRUETYPE_TAG('k', 'x', 'u', 0), // kxu = Kui (India) + TRUETYPE_TAG('k', 'x', 'v', 0), // kxv = Kuvi + TRUETYPE_TAG('k', 'x', 'w', 0), // kxw = Konai + TRUETYPE_TAG('k', 'x', 'x', 0), // kxx = Likuba + TRUETYPE_TAG('k', 'x', 'y', 0), // kxy = Kayong + TRUETYPE_TAG('k', 'x', 'z', 0), // kxz = Kerewo + TRUETYPE_TAG('k', 'y', 'a', 0), // kya = Kwaya + TRUETYPE_TAG('k', 'y', 'b', 0), // kyb = Butbut Kalinga + TRUETYPE_TAG('k', 'y', 'c', 0), // kyc = Kyaka + TRUETYPE_TAG('k', 'y', 'd', 0), // kyd = Karey + TRUETYPE_TAG('k', 'y', 'e', 0), // kye = Krache + TRUETYPE_TAG('k', 'y', 'f', 0), // kyf = Kouya + TRUETYPE_TAG('k', 'y', 'g', 0), // kyg = Keyagana + TRUETYPE_TAG('k', 'y', 'h', 0), // kyh = Karok + TRUETYPE_TAG('k', 'y', 'i', 0), // kyi = Kiput + TRUETYPE_TAG('k', 'y', 'j', 0), // kyj = Karao + TRUETYPE_TAG('k', 'y', 'k', 0), // kyk = Kamayo + TRUETYPE_TAG('k', 'y', 'l', 0), // kyl = Kalapuya + TRUETYPE_TAG('k', 'y', 'm', 0), // kym = Kpatili + TRUETYPE_TAG('k', 'y', 'n', 0), // kyn = Northern Binukidnon + TRUETYPE_TAG('k', 'y', 'o', 0), // kyo = Kelon + TRUETYPE_TAG('k', 'y', 'p', 0), // kyp = Kang + TRUETYPE_TAG('k', 'y', 'q', 0), // kyq = Kenga + TRUETYPE_TAG('k', 'y', 'r', 0), // kyr = Kuruáya + TRUETYPE_TAG('k', 'y', 's', 0), // kys = Baram Kayan + TRUETYPE_TAG('k', 'y', 't', 0), // kyt = Kayagar + TRUETYPE_TAG('k', 'y', 'u', 0), // kyu = Western Kayah + TRUETYPE_TAG('k', 'y', 'v', 0), // kyv = Kayort + TRUETYPE_TAG('k', 'y', 'w', 0), // kyw = Kudmali + TRUETYPE_TAG('k', 'y', 'x', 0), // kyx = Rapoisi + TRUETYPE_TAG('k', 'y', 'y', 0), // kyy = Kambaira + TRUETYPE_TAG('k', 'y', 'z', 0), // kyz = Kayabí + TRUETYPE_TAG('k', 'z', 'a', 0), // kza = Western Karaboro + TRUETYPE_TAG('k', 'z', 'b', 0), // kzb = Kaibobo + TRUETYPE_TAG('k', 'z', 'c', 0), // kzc = Bondoukou Kulango + TRUETYPE_TAG('k', 'z', 'd', 0), // kzd = Kadai + TRUETYPE_TAG('k', 'z', 'e', 0), // kze = Kosena + TRUETYPE_TAG('k', 'z', 'f', 0), // kzf = Da'a Kaili + TRUETYPE_TAG('k', 'z', 'g', 0), // kzg = Kikai + TRUETYPE_TAG('k', 'z', 'h', 0), // kzh = Kenuzi-Dongola + TRUETYPE_TAG('k', 'z', 'i', 0), // kzi = Kelabit + TRUETYPE_TAG('k', 'z', 'j', 0), // kzj = Coastal Kadazan + TRUETYPE_TAG('k', 'z', 'k', 0), // kzk = Kazukuru + TRUETYPE_TAG('k', 'z', 'l', 0), // kzl = Kayeli + TRUETYPE_TAG('k', 'z', 'm', 0), // kzm = Kais + TRUETYPE_TAG('k', 'z', 'n', 0), // kzn = Kokola + TRUETYPE_TAG('k', 'z', 'o', 0), // kzo = Kaningi + TRUETYPE_TAG('k', 'z', 'p', 0), // kzp = Kaidipang + TRUETYPE_TAG('k', 'z', 'q', 0), // kzq = Kaike + TRUETYPE_TAG('k', 'z', 'r', 0), // kzr = Karang + TRUETYPE_TAG('k', 'z', 's', 0), // kzs = Sugut Dusun + TRUETYPE_TAG('k', 'z', 't', 0), // kzt = Tambunan Dusun + TRUETYPE_TAG('k', 'z', 'u', 0), // kzu = Kayupulau + TRUETYPE_TAG('k', 'z', 'v', 0), // kzv = Komyandaret + TRUETYPE_TAG('k', 'z', 'w', 0), // kzw = Karirí-Xocó + TRUETYPE_TAG('k', 'z', 'x', 0), // kzx = Kamarian + TRUETYPE_TAG('k', 'z', 'y', 0), // kzy = Kango (Tshopo District) + TRUETYPE_TAG('k', 'z', 'z', 0), // kzz = Kalabra + TRUETYPE_TAG('l', 'a', 'a', 0), // laa = Southern Subanen + TRUETYPE_TAG('l', 'a', 'b', 0), // lab = Linear A + TRUETYPE_TAG('l', 'a', 'c', 0), // lac = Lacandon + TRUETYPE_TAG('l', 'a', 'd', 0), // lad = Ladino + TRUETYPE_TAG('l', 'a', 'e', 0), // lae = Pattani + TRUETYPE_TAG('l', 'a', 'f', 0), // laf = Lafofa + TRUETYPE_TAG('l', 'a', 'g', 0), // lag = Langi + TRUETYPE_TAG('l', 'a', 'h', 0), // lah = Lahnda + TRUETYPE_TAG('l', 'a', 'i', 0), // lai = Lambya + TRUETYPE_TAG('l', 'a', 'j', 0), // laj = Lango (Uganda) + TRUETYPE_TAG('l', 'a', 'k', 0), // lak = Laka (Nigeria) + TRUETYPE_TAG('l', 'a', 'l', 0), // lal = Lalia + TRUETYPE_TAG('l', 'a', 'm', 0), // lam = Lamba + TRUETYPE_TAG('l', 'a', 'n', 0), // lan = Laru + TRUETYPE_TAG('l', 'a', 'p', 0), // lap = Laka (Chad) + TRUETYPE_TAG('l', 'a', 'q', 0), // laq = Qabiao + TRUETYPE_TAG('l', 'a', 'r', 0), // lar = Larteh + TRUETYPE_TAG('l', 'a', 's', 0), // las = Lama (Togo) + TRUETYPE_TAG('l', 'a', 'u', 0), // lau = Laba + TRUETYPE_TAG('l', 'a', 'w', 0), // law = Lauje + TRUETYPE_TAG('l', 'a', 'x', 0), // lax = Tiwa + TRUETYPE_TAG('l', 'a', 'y', 0), // lay = Lama (Myanmar) + TRUETYPE_TAG('l', 'a', 'z', 0), // laz = Aribwatsa + TRUETYPE_TAG('l', 'b', 'a', 0), // lba = Lui + TRUETYPE_TAG('l', 'b', 'b', 0), // lbb = Label + TRUETYPE_TAG('l', 'b', 'c', 0), // lbc = Lakkia + TRUETYPE_TAG('l', 'b', 'e', 0), // lbe = Lak + TRUETYPE_TAG('l', 'b', 'f', 0), // lbf = Tinani + TRUETYPE_TAG('l', 'b', 'g', 0), // lbg = Laopang + TRUETYPE_TAG('l', 'b', 'i', 0), // lbi = La'bi + TRUETYPE_TAG('l', 'b', 'j', 0), // lbj = Ladakhi + TRUETYPE_TAG('l', 'b', 'k', 0), // lbk = Central Bontok + TRUETYPE_TAG('l', 'b', 'l', 0), // lbl = Libon Bikol + TRUETYPE_TAG('l', 'b', 'm', 0), // lbm = Lodhi + TRUETYPE_TAG('l', 'b', 'n', 0), // lbn = Lamet + TRUETYPE_TAG('l', 'b', 'o', 0), // lbo = Laven + TRUETYPE_TAG('l', 'b', 'q', 0), // lbq = Wampar + TRUETYPE_TAG('l', 'b', 'r', 0), // lbr = Northern Lorung + TRUETYPE_TAG('l', 'b', 's', 0), // lbs = Libyan Sign Language + TRUETYPE_TAG('l', 'b', 't', 0), // lbt = Lachi + TRUETYPE_TAG('l', 'b', 'u', 0), // lbu = Labu + TRUETYPE_TAG('l', 'b', 'v', 0), // lbv = Lavatbura-Lamusong + TRUETYPE_TAG('l', 'b', 'w', 0), // lbw = Tolaki + TRUETYPE_TAG('l', 'b', 'x', 0), // lbx = Lawangan + TRUETYPE_TAG('l', 'b', 'y', 0), // lby = Lamu-Lamu + TRUETYPE_TAG('l', 'b', 'z', 0), // lbz = Lardil + TRUETYPE_TAG('l', 'c', 'c', 0), // lcc = Legenyem + TRUETYPE_TAG('l', 'c', 'd', 0), // lcd = Lola + TRUETYPE_TAG('l', 'c', 'e', 0), // lce = Loncong + TRUETYPE_TAG('l', 'c', 'f', 0), // lcf = Lubu + TRUETYPE_TAG('l', 'c', 'h', 0), // lch = Luchazi + TRUETYPE_TAG('l', 'c', 'l', 0), // lcl = Lisela + TRUETYPE_TAG('l', 'c', 'm', 0), // lcm = Tungag + TRUETYPE_TAG('l', 'c', 'p', 0), // lcp = Western Lawa + TRUETYPE_TAG('l', 'c', 'q', 0), // lcq = Luhu + TRUETYPE_TAG('l', 'c', 's', 0), // lcs = Lisabata-Nuniali + TRUETYPE_TAG('l', 'd', 'b', 0), // ldb = Idun + TRUETYPE_TAG('l', 'd', 'd', 0), // ldd = Luri + TRUETYPE_TAG('l', 'd', 'g', 0), // ldg = Lenyima + TRUETYPE_TAG('l', 'd', 'h', 0), // ldh = Lamja-Dengsa-Tola + TRUETYPE_TAG('l', 'd', 'i', 0), // ldi = Laari + TRUETYPE_TAG('l', 'd', 'j', 0), // ldj = Lemoro + TRUETYPE_TAG('l', 'd', 'k', 0), // ldk = Leelau + TRUETYPE_TAG('l', 'd', 'l', 0), // ldl = Kaan + TRUETYPE_TAG('l', 'd', 'm', 0), // ldm = Landoma + TRUETYPE_TAG('l', 'd', 'n', 0), // ldn = Láadan + TRUETYPE_TAG('l', 'd', 'o', 0), // ldo = Loo + TRUETYPE_TAG('l', 'd', 'p', 0), // ldp = Tso + TRUETYPE_TAG('l', 'd', 'q', 0), // ldq = Lufu + TRUETYPE_TAG('l', 'e', 'a', 0), // lea = Lega-Shabunda + TRUETYPE_TAG('l', 'e', 'b', 0), // leb = Lala-Bisa + TRUETYPE_TAG('l', 'e', 'c', 0), // lec = Leco + TRUETYPE_TAG('l', 'e', 'd', 0), // led = Lendu + TRUETYPE_TAG('l', 'e', 'e', 0), // lee = Lyélé + TRUETYPE_TAG('l', 'e', 'f', 0), // lef = Lelemi + TRUETYPE_TAG('l', 'e', 'g', 0), // leg = Lengua + TRUETYPE_TAG('l', 'e', 'h', 0), // leh = Lenje + TRUETYPE_TAG('l', 'e', 'i', 0), // lei = Lemio + TRUETYPE_TAG('l', 'e', 'j', 0), // lej = Lengola + TRUETYPE_TAG('l', 'e', 'k', 0), // lek = Leipon + TRUETYPE_TAG('l', 'e', 'l', + 0), // lel = Lele (Democratic Republic of Congo) + TRUETYPE_TAG('l', 'e', 'm', 0), // lem = Nomaande + TRUETYPE_TAG('l', 'e', 'n', 0), // len = Lenca + TRUETYPE_TAG('l', 'e', 'o', 0), // leo = Leti (Cameroon) + TRUETYPE_TAG('l', 'e', 'p', 0), // lep = Lepcha + TRUETYPE_TAG('l', 'e', 'q', 0), // leq = Lembena + TRUETYPE_TAG('l', 'e', 'r', 0), // ler = Lenkau + TRUETYPE_TAG('l', 'e', 's', 0), // les = Lese + TRUETYPE_TAG('l', 'e', 't', 0), // let = Lesing-Gelimi + TRUETYPE_TAG('l', 'e', 'u', 0), // leu = Kara (Papua New Guinea) + TRUETYPE_TAG('l', 'e', 'v', 0), // lev = Lamma + TRUETYPE_TAG('l', 'e', 'w', 0), // lew = Ledo Kaili + TRUETYPE_TAG('l', 'e', 'x', 0), // lex = Luang + TRUETYPE_TAG('l', 'e', 'y', 0), // ley = Lemolang + TRUETYPE_TAG('l', 'e', 'z', 0), // lez = Lezghian + TRUETYPE_TAG('l', 'f', 'a', 0), // lfa = Lefa + TRUETYPE_TAG('l', 'f', 'n', 0), // lfn = Lingua Franca Nova + TRUETYPE_TAG('l', 'g', 'a', 0), // lga = Lungga + TRUETYPE_TAG('l', 'g', 'b', 0), // lgb = Laghu + TRUETYPE_TAG('l', 'g', 'g', 0), // lgg = Lugbara + TRUETYPE_TAG('l', 'g', 'h', 0), // lgh = Laghuu + TRUETYPE_TAG('l', 'g', 'i', 0), // lgi = Lengilu + TRUETYPE_TAG('l', 'g', 'k', 0), // lgk = Lingarak + TRUETYPE_TAG('l', 'g', 'l', 0), // lgl = Wala + TRUETYPE_TAG('l', 'g', 'm', 0), // lgm = Lega-Mwenga + TRUETYPE_TAG('l', 'g', 'n', 0), // lgn = Opuuo + TRUETYPE_TAG('l', 'g', 'q', 0), // lgq = Logba + TRUETYPE_TAG('l', 'g', 'r', 0), // lgr = Lengo + TRUETYPE_TAG('l', 'g', 't', 0), // lgt = Pahi + TRUETYPE_TAG('l', 'g', 'u', 0), // lgu = Longgu + TRUETYPE_TAG('l', 'g', 'z', 0), // lgz = Ligenza + TRUETYPE_TAG('l', 'h', 'a', 0), // lha = Laha (Viet Nam) + TRUETYPE_TAG('l', 'h', 'h', 0), // lhh = Laha (Indonesia) + TRUETYPE_TAG('l', 'h', 'i', 0), // lhi = Lahu Shi + TRUETYPE_TAG('l', 'h', 'l', 0), // lhl = Lahul Lohar + TRUETYPE_TAG('l', 'h', 'm', 0), // lhm = Lhomi + TRUETYPE_TAG('l', 'h', 'n', 0), // lhn = Lahanan + TRUETYPE_TAG('l', 'h', 'p', 0), // lhp = Lhokpu + TRUETYPE_TAG('l', 'h', 's', 0), // lhs = Mlahsö + TRUETYPE_TAG('l', 'h', 't', 0), // lht = Lo-Toga + TRUETYPE_TAG('l', 'h', 'u', 0), // lhu = Lahu + TRUETYPE_TAG('l', 'i', 'a', 0), // lia = West-Central Limba + TRUETYPE_TAG('l', 'i', 'b', 0), // lib = Likum + TRUETYPE_TAG('l', 'i', 'c', 0), // lic = Hlai + TRUETYPE_TAG('l', 'i', 'd', 0), // lid = Nyindrou + TRUETYPE_TAG('l', 'i', 'e', 0), // lie = Likila + TRUETYPE_TAG('l', 'i', 'f', 0), // lif = Limbu + TRUETYPE_TAG('l', 'i', 'g', 0), // lig = Ligbi + TRUETYPE_TAG('l', 'i', 'h', 0), // lih = Lihir + TRUETYPE_TAG('l', 'i', 'i', 0), // lii = Lingkhim + TRUETYPE_TAG('l', 'i', 'j', 0), // lij = Ligurian + TRUETYPE_TAG('l', 'i', 'k', 0), // lik = Lika + TRUETYPE_TAG('l', 'i', 'l', 0), // lil = Lillooet + TRUETYPE_TAG('l', 'i', 'o', 0), // lio = Liki + TRUETYPE_TAG('l', 'i', 'p', 0), // lip = Sekpele + TRUETYPE_TAG('l', 'i', 'q', 0), // liq = Libido + TRUETYPE_TAG('l', 'i', 'r', 0), // lir = Liberian English + TRUETYPE_TAG('l', 'i', 's', 0), // lis = Lisu + TRUETYPE_TAG('l', 'i', 'u', 0), // liu = Logorik + TRUETYPE_TAG('l', 'i', 'v', 0), // liv = Liv + TRUETYPE_TAG('l', 'i', 'w', 0), // liw = Col + TRUETYPE_TAG('l', 'i', 'x', 0), // lix = Liabuku + TRUETYPE_TAG('l', 'i', 'y', 0), // liy = Banda-Bambari + TRUETYPE_TAG('l', 'i', 'z', 0), // liz = Libinza + TRUETYPE_TAG('l', 'j', 'e', 0), // lje = Rampi + TRUETYPE_TAG('l', 'j', 'i', 0), // lji = Laiyolo + TRUETYPE_TAG('l', 'j', 'l', 0), // ljl = Li'o + TRUETYPE_TAG('l', 'j', 'p', 0), // ljp = Lampung Api + TRUETYPE_TAG('l', 'k', 'a', 0), // lka = Lakalei + TRUETYPE_TAG('l', 'k', 'b', 0), // lkb = Kabras + TRUETYPE_TAG('l', 'k', 'c', 0), // lkc = Kucong + TRUETYPE_TAG('l', 'k', 'd', 0), // lkd = Lakondê + TRUETYPE_TAG('l', 'k', 'e', 0), // lke = Kenyi + TRUETYPE_TAG('l', 'k', 'h', 0), // lkh = Lakha + TRUETYPE_TAG('l', 'k', 'i', 0), // lki = Laki + TRUETYPE_TAG('l', 'k', 'j', 0), // lkj = Remun + TRUETYPE_TAG('l', 'k', 'l', 0), // lkl = Laeko-Libuat + TRUETYPE_TAG('l', 'k', 'n', 0), // lkn = Lakon + TRUETYPE_TAG('l', 'k', 'o', 0), // lko = Khayo + TRUETYPE_TAG('l', 'k', 'r', 0), // lkr = Päri + TRUETYPE_TAG('l', 'k', 's', 0), // lks = Kisa + TRUETYPE_TAG('l', 'k', 't', 0), // lkt = Lakota + TRUETYPE_TAG('l', 'k', 'y', 0), // lky = Lokoya + TRUETYPE_TAG('l', 'l', 'a', 0), // lla = Lala-Roba + TRUETYPE_TAG('l', 'l', 'b', 0), // llb = Lolo + TRUETYPE_TAG('l', 'l', 'c', 0), // llc = Lele (Guinea) + TRUETYPE_TAG('l', 'l', 'd', 0), // lld = Ladin + TRUETYPE_TAG('l', 'l', 'e', 0), // lle = Lele (Papua New Guinea) + TRUETYPE_TAG('l', 'l', 'f', 0), // llf = Hermit + TRUETYPE_TAG('l', 'l', 'g', 0), // llg = Lole + TRUETYPE_TAG('l', 'l', 'h', 0), // llh = Lamu + TRUETYPE_TAG('l', 'l', 'i', 0), // lli = Teke-Laali + TRUETYPE_TAG('l', 'l', 'k', 0), // llk = Lelak + TRUETYPE_TAG('l', 'l', 'l', 0), // lll = Lilau + TRUETYPE_TAG('l', 'l', 'm', 0), // llm = Lasalimu + TRUETYPE_TAG('l', 'l', 'n', 0), // lln = Lele (Chad) + TRUETYPE_TAG('l', 'l', 'o', 0), // llo = Khlor + TRUETYPE_TAG('l', 'l', 'p', 0), // llp = North Efate + TRUETYPE_TAG('l', 'l', 'q', 0), // llq = Lolak + TRUETYPE_TAG('l', 'l', 's', 0), // lls = Lithuanian Sign Language + TRUETYPE_TAG('l', 'l', 'u', 0), // llu = Lau + TRUETYPE_TAG('l', 'l', 'x', 0), // llx = Lauan + TRUETYPE_TAG('l', 'm', 'a', 0), // lma = East Limba + TRUETYPE_TAG('l', 'm', 'b', 0), // lmb = Merei + TRUETYPE_TAG('l', 'm', 'c', 0), // lmc = Limilngan + TRUETYPE_TAG('l', 'm', 'd', 0), // lmd = Lumun + TRUETYPE_TAG('l', 'm', 'e', 0), // lme = Pévé + TRUETYPE_TAG('l', 'm', 'f', 0), // lmf = South Lembata + TRUETYPE_TAG('l', 'm', 'g', 0), // lmg = Lamogai + TRUETYPE_TAG('l', 'm', 'h', 0), // lmh = Lambichhong + TRUETYPE_TAG('l', 'm', 'i', 0), // lmi = Lombi + TRUETYPE_TAG('l', 'm', 'j', 0), // lmj = West Lembata + TRUETYPE_TAG('l', 'm', 'k', 0), // lmk = Lamkang + TRUETYPE_TAG('l', 'm', 'l', 0), // lml = Hano + TRUETYPE_TAG('l', 'm', 'm', 0), // lmm = Lamam + TRUETYPE_TAG('l', 'm', 'n', 0), // lmn = Lambadi + TRUETYPE_TAG('l', 'm', 'o', 0), // lmo = Lombard + TRUETYPE_TAG('l', 'm', 'p', 0), // lmp = Limbum + TRUETYPE_TAG('l', 'm', 'q', 0), // lmq = Lamatuka + TRUETYPE_TAG('l', 'm', 'r', 0), // lmr = Lamalera + TRUETYPE_TAG('l', 'm', 'u', 0), // lmu = Lamenu + TRUETYPE_TAG('l', 'm', 'v', 0), // lmv = Lomaiviti + TRUETYPE_TAG('l', 'm', 'w', 0), // lmw = Lake Miwok + TRUETYPE_TAG('l', 'm', 'x', 0), // lmx = Laimbue + TRUETYPE_TAG('l', 'm', 'y', 0), // lmy = Lamboya + TRUETYPE_TAG('l', 'm', 'z', 0), // lmz = Lumbee + TRUETYPE_TAG('l', 'n', 'a', 0), // lna = Langbashe + TRUETYPE_TAG('l', 'n', 'b', 0), // lnb = Mbalanhu + TRUETYPE_TAG('l', 'n', 'd', 0), // lnd = Lundayeh + TRUETYPE_TAG('l', 'n', 'g', 0), // lng = Langobardic + TRUETYPE_TAG('l', 'n', 'h', 0), // lnh = Lanoh + TRUETYPE_TAG('l', 'n', 'i', 0), // lni = Daantanai' + TRUETYPE_TAG('l', 'n', 'j', 0), // lnj = Leningitij + TRUETYPE_TAG('l', 'n', 'l', 0), // lnl = South Central Banda + TRUETYPE_TAG('l', 'n', 'm', 0), // lnm = Langam + TRUETYPE_TAG('l', 'n', 'n', 0), // lnn = Lorediakarkar + TRUETYPE_TAG('l', 'n', 'o', 0), // lno = Lango (Sudan) + TRUETYPE_TAG('l', 'n', 's', 0), // lns = Lamnso' + TRUETYPE_TAG('l', 'n', 'u', 0), // lnu = Longuda + TRUETYPE_TAG('l', 'n', 'z', 0), // lnz = Lonzo + TRUETYPE_TAG('l', 'o', 'a', 0), // loa = Loloda + TRUETYPE_TAG('l', 'o', 'b', 0), // lob = Lobi + TRUETYPE_TAG('l', 'o', 'c', 0), // loc = Inonhan + TRUETYPE_TAG('l', 'o', 'e', 0), // loe = Saluan + TRUETYPE_TAG('l', 'o', 'f', 0), // lof = Logol + TRUETYPE_TAG('l', 'o', 'g', 0), // log = Logo + TRUETYPE_TAG('l', 'o', 'h', 0), // loh = Narim + TRUETYPE_TAG('l', 'o', 'i', 0), // loi = Loma (Côte d'Ivoire) + TRUETYPE_TAG('l', 'o', 'j', 0), // loj = Lou + TRUETYPE_TAG('l', 'o', 'k', 0), // lok = Loko + TRUETYPE_TAG('l', 'o', 'l', 0), // lol = Mongo + TRUETYPE_TAG('l', 'o', 'm', 0), // lom = Loma (Liberia) + TRUETYPE_TAG('l', 'o', 'n', 0), // lon = Malawi Lomwe + TRUETYPE_TAG('l', 'o', 'o', 0), // loo = Lombo + TRUETYPE_TAG('l', 'o', 'p', 0), // lop = Lopa + TRUETYPE_TAG('l', 'o', 'q', 0), // loq = Lobala + TRUETYPE_TAG('l', 'o', 'r', 0), // lor = Téén + TRUETYPE_TAG('l', 'o', 's', 0), // los = Loniu + TRUETYPE_TAG('l', 'o', 't', 0), // lot = Otuho + TRUETYPE_TAG('l', 'o', 'u', 0), // lou = Louisiana Creole French + TRUETYPE_TAG('l', 'o', 'v', 0), // lov = Lopi + TRUETYPE_TAG('l', 'o', 'w', 0), // low = Tampias Lobu + TRUETYPE_TAG('l', 'o', 'x', 0), // lox = Loun + TRUETYPE_TAG('l', 'o', 'y', 0), // loy = Lowa + TRUETYPE_TAG('l', 'o', 'z', 0), // loz = Lozi + TRUETYPE_TAG('l', 'p', 'a', 0), // lpa = Lelepa + TRUETYPE_TAG('l', 'p', 'e', 0), // lpe = Lepki + TRUETYPE_TAG('l', 'p', 'n', 0), // lpn = Long Phuri Naga + TRUETYPE_TAG('l', 'p', 'o', 0), // lpo = Lipo + TRUETYPE_TAG('l', 'p', 'x', 0), // lpx = Lopit + TRUETYPE_TAG('l', 'r', 'a', 0), // lra = Rara Bakati' + TRUETYPE_TAG('l', 'r', 'c', 0), // lrc = Northern Luri + TRUETYPE_TAG('l', 'r', 'e', 0), // lre = Laurentian + TRUETYPE_TAG('l', 'r', 'g', 0), // lrg = Laragia + TRUETYPE_TAG('l', 'r', 'i', 0), // lri = Marachi + TRUETYPE_TAG('l', 'r', 'k', 0), // lrk = Loarki + TRUETYPE_TAG('l', 'r', 'l', 0), // lrl = Lari + TRUETYPE_TAG('l', 'r', 'm', 0), // lrm = Marama + TRUETYPE_TAG('l', 'r', 'n', 0), // lrn = Lorang + TRUETYPE_TAG('l', 'r', 'o', 0), // lro = Laro + TRUETYPE_TAG('l', 'r', 'r', 0), // lrr = Southern Lorung + TRUETYPE_TAG('l', 'r', 't', 0), // lrt = Larantuka Malay + TRUETYPE_TAG('l', 'r', 'v', 0), // lrv = Larevat + TRUETYPE_TAG('l', 'r', 'z', 0), // lrz = Lemerig + TRUETYPE_TAG('l', 's', 'a', 0), // lsa = Lasgerdi + TRUETYPE_TAG('l', 's', 'd', 0), // lsd = Lishana Deni + TRUETYPE_TAG('l', 's', 'e', 0), // lse = Lusengo + TRUETYPE_TAG('l', 's', 'g', 0), // lsg = Lyons Sign Language + TRUETYPE_TAG('l', 's', 'h', 0), // lsh = Lish + TRUETYPE_TAG('l', 's', 'i', 0), // lsi = Lashi + TRUETYPE_TAG('l', 's', 'l', 0), // lsl = Latvian Sign Language + TRUETYPE_TAG('l', 's', 'm', 0), // lsm = Saamia + TRUETYPE_TAG('l', 's', 'o', 0), // lso = Laos Sign Language + TRUETYPE_TAG('l', 's', 'p', 0), // lsp = Panamanian Sign Language + TRUETYPE_TAG('l', 's', 'r', 0), // lsr = Aruop + TRUETYPE_TAG('l', 's', 's', 0), // lss = Lasi + TRUETYPE_TAG('l', 's', 't', 0), // lst = Trinidad and Tobago Sign Language + TRUETYPE_TAG('l', 's', 'y', 0), // lsy = Mauritian Sign Language + TRUETYPE_TAG('l', 't', 'c', 0), // ltc = Late Middle Chinese + TRUETYPE_TAG('l', 't', 'g', 0), // ltg = Latgalian + TRUETYPE_TAG('l', 't', 'i', 0), // lti = Leti (Indonesia) + TRUETYPE_TAG('l', 't', 'n', 0), // ltn = Latundê + TRUETYPE_TAG('l', 't', 'o', 0), // lto = Tsotso + TRUETYPE_TAG('l', 't', 's', 0), // lts = Tachoni + TRUETYPE_TAG('l', 't', 'u', 0), // ltu = Latu + TRUETYPE_TAG('l', 'u', 'a', 0), // lua = Luba-Lulua + TRUETYPE_TAG('l', 'u', 'c', 0), // luc = Aringa + TRUETYPE_TAG('l', 'u', 'd', 0), // lud = Ludian + TRUETYPE_TAG('l', 'u', 'e', 0), // lue = Luvale + TRUETYPE_TAG('l', 'u', 'f', 0), // luf = Laua + TRUETYPE_TAG('l', 'u', 'i', 0), // lui = Luiseno + TRUETYPE_TAG('l', 'u', 'j', 0), // luj = Luna + TRUETYPE_TAG('l', 'u', 'k', 0), // luk = Lunanakha + TRUETYPE_TAG('l', 'u', 'l', 0), // lul = Olu'bo + TRUETYPE_TAG('l', 'u', 'm', 0), // lum = Luimbi + TRUETYPE_TAG('l', 'u', 'n', 0), // lun = Lunda + TRUETYPE_TAG('l', 'u', 'o', 0), // luo = Luo (Kenya and Tanzania) + TRUETYPE_TAG('l', 'u', 'p', 0), // lup = Lumbu + TRUETYPE_TAG('l', 'u', 'q', 0), // luq = Lucumi + TRUETYPE_TAG('l', 'u', 'r', 0), // lur = Laura + TRUETYPE_TAG('l', 'u', 's', 0), // lus = Lushai + TRUETYPE_TAG('l', 'u', 't', 0), // lut = Lushootseed + TRUETYPE_TAG('l', 'u', 'u', 0), // luu = Lumba-Yakkha + TRUETYPE_TAG('l', 'u', 'v', 0), // luv = Luwati + TRUETYPE_TAG('l', 'u', 'w', 0), // luw = Luo (Cameroon) + TRUETYPE_TAG('l', 'u', 'y', 0), // luy = Luyia + TRUETYPE_TAG('l', 'u', 'z', 0), // luz = Southern Luri + TRUETYPE_TAG('l', 'v', 'a', 0), // lva = Maku'a + TRUETYPE_TAG('l', 'v', 'k', 0), // lvk = Lavukaleve + TRUETYPE_TAG('l', 'v', 's', 0), // lvs = Standard Latvian + TRUETYPE_TAG('l', 'v', 'u', 0), // lvu = Levuka + TRUETYPE_TAG('l', 'w', 'a', 0), // lwa = Lwalu + TRUETYPE_TAG('l', 'w', 'e', 0), // lwe = Lewo Eleng + TRUETYPE_TAG('l', 'w', 'g', 0), // lwg = Wanga + TRUETYPE_TAG('l', 'w', 'h', 0), // lwh = White Lachi + TRUETYPE_TAG('l', 'w', 'l', 0), // lwl = Eastern Lawa + TRUETYPE_TAG('l', 'w', 'm', 0), // lwm = Laomian + TRUETYPE_TAG('l', 'w', 'o', 0), // lwo = Luwo + TRUETYPE_TAG('l', 'w', 't', 0), // lwt = Lewotobi + TRUETYPE_TAG('l', 'w', 'w', 0), // lww = Lewo + TRUETYPE_TAG('l', 'y', 'a', 0), // lya = Layakha + TRUETYPE_TAG('l', 'y', 'g', 0), // lyg = Lyngngam + TRUETYPE_TAG('l', 'y', 'n', 0), // lyn = Luyana + TRUETYPE_TAG('l', 'z', 'h', 0), // lzh = Literary Chinese + TRUETYPE_TAG('l', 'z', 'l', 0), // lzl = Litzlitz + TRUETYPE_TAG('l', 'z', 'n', 0), // lzn = Leinong Naga + TRUETYPE_TAG('l', 'z', 'z', 0), // lzz = Laz + TRUETYPE_TAG('m', 'a', 'a', 0), // maa = San Jerónimo Tecóatl Mazatec + TRUETYPE_TAG('m', 'a', 'b', 0), // mab = Yutanduchi Mixtec + TRUETYPE_TAG('m', 'a', 'd', 0), // mad = Madurese + TRUETYPE_TAG('m', 'a', 'e', 0), // mae = Bo-Rukul + TRUETYPE_TAG('m', 'a', 'f', 0), // maf = Mafa + TRUETYPE_TAG('m', 'a', 'g', 0), // mag = Magahi + TRUETYPE_TAG('m', 'a', 'i', 0), // mai = Maithili + TRUETYPE_TAG('m', 'a', 'j', 0), // maj = Jalapa De Díaz Mazatec + TRUETYPE_TAG('m', 'a', 'k', 0), // mak = Makasar + TRUETYPE_TAG('m', 'a', 'm', 0), // mam = Mam + TRUETYPE_TAG('m', 'a', 'n', 0), // man = Mandingo + TRUETYPE_TAG('m', 'a', 'p', 0), // map = Austronesian languages + TRUETYPE_TAG('m', 'a', 'q', 0), // maq = Chiquihuitlán Mazatec + TRUETYPE_TAG('m', 'a', 's', 0), // mas = Masai + TRUETYPE_TAG('m', 'a', 't', 0), // mat = San Francisco Matlatzinca + TRUETYPE_TAG('m', 'a', 'u', 0), // mau = Huautla Mazatec + TRUETYPE_TAG('m', 'a', 'v', 0), // mav = Sateré-Mawé + TRUETYPE_TAG('m', 'a', 'w', 0), // maw = Mampruli + TRUETYPE_TAG('m', 'a', 'x', 0), // max = North Moluccan Malay + TRUETYPE_TAG('m', 'a', 'z', 0), // maz = Central Mazahua + TRUETYPE_TAG('m', 'b', 'a', 0), // mba = Higaonon + TRUETYPE_TAG('m', 'b', 'b', 0), // mbb = Western Bukidnon Manobo + TRUETYPE_TAG('m', 'b', 'c', 0), // mbc = Macushi + TRUETYPE_TAG('m', 'b', 'd', 0), // mbd = Dibabawon Manobo + TRUETYPE_TAG('m', 'b', 'e', 0), // mbe = Molale + TRUETYPE_TAG('m', 'b', 'f', 0), // mbf = Baba Malay + TRUETYPE_TAG('m', 'b', 'h', 0), // mbh = Mangseng + TRUETYPE_TAG('m', 'b', 'i', 0), // mbi = Ilianen Manobo + TRUETYPE_TAG('m', 'b', 'j', 0), // mbj = Nadëb + TRUETYPE_TAG('m', 'b', 'k', 0), // mbk = Malol + TRUETYPE_TAG('m', 'b', 'l', 0), // mbl = Maxakalí + TRUETYPE_TAG('m', 'b', 'm', 0), // mbm = Ombamba + TRUETYPE_TAG('m', 'b', 'n', 0), // mbn = Macaguán + TRUETYPE_TAG('m', 'b', 'o', 0), // mbo = Mbo (Cameroon) + TRUETYPE_TAG('m', 'b', 'p', 0), // mbp = Malayo + TRUETYPE_TAG('m', 'b', 'q', 0), // mbq = Maisin + TRUETYPE_TAG('m', 'b', 'r', 0), // mbr = Nukak Makú + TRUETYPE_TAG('m', 'b', 's', 0), // mbs = Sarangani Manobo + TRUETYPE_TAG('m', 'b', 't', 0), // mbt = Matigsalug Manobo + TRUETYPE_TAG('m', 'b', 'u', 0), // mbu = Mbula-Bwazza + TRUETYPE_TAG('m', 'b', 'v', 0), // mbv = Mbulungish + TRUETYPE_TAG('m', 'b', 'w', 0), // mbw = Maring + TRUETYPE_TAG('m', 'b', 'x', 0), // mbx = Mari (East Sepik Province) + TRUETYPE_TAG('m', 'b', 'y', 0), // mby = Memoni + TRUETYPE_TAG('m', 'b', 'z', 0), // mbz = Amoltepec Mixtec + TRUETYPE_TAG('m', 'c', 'a', 0), // mca = Maca + TRUETYPE_TAG('m', 'c', 'b', 0), // mcb = Machiguenga + TRUETYPE_TAG('m', 'c', 'c', 0), // mcc = Bitur + TRUETYPE_TAG('m', 'c', 'd', 0), // mcd = Sharanahua + TRUETYPE_TAG('m', 'c', 'e', 0), // mce = Itundujia Mixtec + TRUETYPE_TAG('m', 'c', 'f', 0), // mcf = Matsés + TRUETYPE_TAG('m', 'c', 'g', 0), // mcg = Mapoyo + TRUETYPE_TAG('m', 'c', 'h', 0), // mch = Maquiritari + TRUETYPE_TAG('m', 'c', 'i', 0), // mci = Mese + TRUETYPE_TAG('m', 'c', 'j', 0), // mcj = Mvanip + TRUETYPE_TAG('m', 'c', 'k', 0), // mck = Mbunda + TRUETYPE_TAG('m', 'c', 'l', 0), // mcl = Macaguaje + TRUETYPE_TAG('m', 'c', 'm', 0), // mcm = Malaccan Creole Portuguese + TRUETYPE_TAG('m', 'c', 'n', 0), // mcn = Masana + TRUETYPE_TAG('m', 'c', 'o', 0), // mco = Coatlán Mixe + TRUETYPE_TAG('m', 'c', 'p', 0), // mcp = Makaa + TRUETYPE_TAG('m', 'c', 'q', 0), // mcq = Ese + TRUETYPE_TAG('m', 'c', 'r', 0), // mcr = Menya + TRUETYPE_TAG('m', 'c', 's', 0), // mcs = Mambai + TRUETYPE_TAG('m', 'c', 't', 0), // mct = Mengisa + TRUETYPE_TAG('m', 'c', 'u', 0), // mcu = Cameroon Mambila + TRUETYPE_TAG('m', 'c', 'v', 0), // mcv = Minanibai + TRUETYPE_TAG('m', 'c', 'w', 0), // mcw = Mawa (Chad) + TRUETYPE_TAG('m', 'c', 'x', 0), // mcx = Mpiemo + TRUETYPE_TAG('m', 'c', 'y', 0), // mcy = South Watut + TRUETYPE_TAG('m', 'c', 'z', 0), // mcz = Mawan + TRUETYPE_TAG('m', 'd', 'a', 0), // mda = Mada (Nigeria) + TRUETYPE_TAG('m', 'd', 'b', 0), // mdb = Morigi + TRUETYPE_TAG('m', 'd', 'c', 0), // mdc = Male (Papua New Guinea) + TRUETYPE_TAG('m', 'd', 'd', 0), // mdd = Mbum + TRUETYPE_TAG('m', 'd', 'e', 0), // mde = Maba (Chad) + TRUETYPE_TAG('m', 'd', 'f', 0), // mdf = Moksha + TRUETYPE_TAG('m', 'd', 'g', 0), // mdg = Massalat + TRUETYPE_TAG('m', 'd', 'h', 0), // mdh = Maguindanaon + TRUETYPE_TAG('m', 'd', 'i', 0), // mdi = Mamvu + TRUETYPE_TAG('m', 'd', 'j', 0), // mdj = Mangbetu + TRUETYPE_TAG('m', 'd', 'k', 0), // mdk = Mangbutu + TRUETYPE_TAG('m', 'd', 'l', 0), // mdl = Maltese Sign Language + TRUETYPE_TAG('m', 'd', 'm', 0), // mdm = Mayogo + TRUETYPE_TAG('m', 'd', 'n', 0), // mdn = Mbati + TRUETYPE_TAG('m', 'd', 'p', 0), // mdp = Mbala + TRUETYPE_TAG('m', 'd', 'q', 0), // mdq = Mbole + TRUETYPE_TAG('m', 'd', 'r', 0), // mdr = Mandar + TRUETYPE_TAG('m', 'd', 's', 0), // mds = Maria (Papua New Guinea) + TRUETYPE_TAG('m', 'd', 't', 0), // mdt = Mbere + TRUETYPE_TAG('m', 'd', 'u', 0), // mdu = Mboko + TRUETYPE_TAG('m', 'd', 'v', 0), // mdv = Santa Lucía Monteverde Mixtec + TRUETYPE_TAG('m', 'd', 'w', 0), // mdw = Mbosi + TRUETYPE_TAG('m', 'd', 'x', 0), // mdx = Dizin + TRUETYPE_TAG('m', 'd', 'y', 0), // mdy = Male (Ethiopia) + TRUETYPE_TAG('m', 'd', 'z', 0), // mdz = Suruí Do Pará + TRUETYPE_TAG('m', 'e', 'a', 0), // mea = Menka + TRUETYPE_TAG('m', 'e', 'b', 0), // meb = Ikobi-Mena + TRUETYPE_TAG('m', 'e', 'c', 0), // mec = Mara + TRUETYPE_TAG('m', 'e', 'd', 0), // med = Melpa + TRUETYPE_TAG('m', 'e', 'e', 0), // mee = Mengen + TRUETYPE_TAG('m', 'e', 'f', 0), // mef = Megam + TRUETYPE_TAG('m', 'e', 'g', 0), // meg = Mea + TRUETYPE_TAG('m', 'e', 'h', 0), // meh = Southwestern Tlaxiaco Mixtec + TRUETYPE_TAG('m', 'e', 'i', 0), // mei = Midob + TRUETYPE_TAG('m', 'e', 'j', 0), // mej = Meyah + TRUETYPE_TAG('m', 'e', 'k', 0), // mek = Mekeo + TRUETYPE_TAG('m', 'e', 'l', 0), // mel = Central Melanau + TRUETYPE_TAG('m', 'e', 'm', 0), // mem = Mangala + TRUETYPE_TAG('m', 'e', 'n', 0), // men = Mende (Sierra Leone) + TRUETYPE_TAG('m', 'e', 'o', 0), // meo = Kedah Malay + TRUETYPE_TAG('m', 'e', 'p', 0), // mep = Miriwung + TRUETYPE_TAG('m', 'e', 'q', 0), // meq = Merey + TRUETYPE_TAG('m', 'e', 'r', 0), // mer = Meru + TRUETYPE_TAG('m', 'e', 's', 0), // mes = Masmaje + TRUETYPE_TAG('m', 'e', 't', 0), // met = Mato + TRUETYPE_TAG('m', 'e', 'u', 0), // meu = Motu + TRUETYPE_TAG('m', 'e', 'v', 0), // mev = Mann + TRUETYPE_TAG('m', 'e', 'w', 0), // mew = Maaka + TRUETYPE_TAG('m', 'e', 'y', 0), // mey = Hassaniyya + TRUETYPE_TAG('m', 'e', 'z', 0), // mez = Menominee + TRUETYPE_TAG('m', 'f', 'a', 0), // mfa = Pattani Malay + TRUETYPE_TAG('m', 'f', 'b', 0), // mfb = Bangka + TRUETYPE_TAG('m', 'f', 'c', 0), // mfc = Mba + TRUETYPE_TAG('m', 'f', 'd', 0), // mfd = Mendankwe-Nkwen + TRUETYPE_TAG('m', 'f', 'e', 0), // mfe = Morisyen + TRUETYPE_TAG('m', 'f', 'f', 0), // mff = Naki + TRUETYPE_TAG('m', 'f', 'g', 0), // mfg = Mixifore + TRUETYPE_TAG('m', 'f', 'h', 0), // mfh = Matal + TRUETYPE_TAG('m', 'f', 'i', 0), // mfi = Wandala + TRUETYPE_TAG('m', 'f', 'j', 0), // mfj = Mefele + TRUETYPE_TAG('m', 'f', 'k', 0), // mfk = North Mofu + TRUETYPE_TAG('m', 'f', 'l', 0), // mfl = Putai + TRUETYPE_TAG('m', 'f', 'm', 0), // mfm = Marghi South + TRUETYPE_TAG('m', 'f', 'n', 0), // mfn = Cross River Mbembe + TRUETYPE_TAG('m', 'f', 'o', 0), // mfo = Mbe + TRUETYPE_TAG('m', 'f', 'p', 0), // mfp = Makassar Malay + TRUETYPE_TAG('m', 'f', 'q', 0), // mfq = Moba + TRUETYPE_TAG('m', 'f', 'r', 0), // mfr = Marithiel + TRUETYPE_TAG('m', 'f', 's', 0), // mfs = Mexican Sign Language + TRUETYPE_TAG('m', 'f', 't', 0), // mft = Mokerang + TRUETYPE_TAG('m', 'f', 'u', 0), // mfu = Mbwela + TRUETYPE_TAG('m', 'f', 'v', 0), // mfv = Mandjak + TRUETYPE_TAG('m', 'f', 'w', 0), // mfw = Mulaha + TRUETYPE_TAG('m', 'f', 'x', 0), // mfx = Melo + TRUETYPE_TAG('m', 'f', 'y', 0), // mfy = Mayo + TRUETYPE_TAG('m', 'f', 'z', 0), // mfz = Mabaan + TRUETYPE_TAG('m', 'g', 'a', 0), // mga = Middle Irish (900-1200) + TRUETYPE_TAG('m', 'g', 'b', 0), // mgb = Mararit + TRUETYPE_TAG('m', 'g', 'c', 0), // mgc = Morokodo + TRUETYPE_TAG('m', 'g', 'd', 0), // mgd = Moru + TRUETYPE_TAG('m', 'g', 'e', 0), // mge = Mango + TRUETYPE_TAG('m', 'g', 'f', 0), // mgf = Maklew + TRUETYPE_TAG('m', 'g', 'g', 0), // mgg = Mpongmpong + TRUETYPE_TAG('m', 'g', 'h', 0), // mgh = Makhuwa-Meetto + TRUETYPE_TAG('m', 'g', 'i', 0), // mgi = Lijili + TRUETYPE_TAG('m', 'g', 'j', 0), // mgj = Abureni + TRUETYPE_TAG('m', 'g', 'k', 0), // mgk = Mawes + TRUETYPE_TAG('m', 'g', 'l', 0), // mgl = Maleu-Kilenge + TRUETYPE_TAG('m', 'g', 'm', 0), // mgm = Mambae + TRUETYPE_TAG('m', 'g', 'n', 0), // mgn = Mbangi + TRUETYPE_TAG('m', 'g', 'o', 0), // mgo = Meta' + TRUETYPE_TAG('m', 'g', 'p', 0), // mgp = Eastern Magar + TRUETYPE_TAG('m', 'g', 'q', 0), // mgq = Malila + TRUETYPE_TAG('m', 'g', 'r', 0), // mgr = Mambwe-Lungu + TRUETYPE_TAG('m', 'g', 's', 0), // mgs = Manda (Tanzania) + TRUETYPE_TAG('m', 'g', 't', 0), // mgt = Mongol + TRUETYPE_TAG('m', 'g', 'u', 0), // mgu = Mailu + TRUETYPE_TAG('m', 'g', 'v', 0), // mgv = Matengo + TRUETYPE_TAG('m', 'g', 'w', 0), // mgw = Matumbi + TRUETYPE_TAG('m', 'g', 'x', 0), // mgx = Omati + TRUETYPE_TAG('m', 'g', 'y', 0), // mgy = Mbunga + TRUETYPE_TAG('m', 'g', 'z', 0), // mgz = Mbugwe + TRUETYPE_TAG('m', 'h', 'a', 0), // mha = Manda (India) + TRUETYPE_TAG('m', 'h', 'b', 0), // mhb = Mahongwe + TRUETYPE_TAG('m', 'h', 'c', 0), // mhc = Mocho + TRUETYPE_TAG('m', 'h', 'd', 0), // mhd = Mbugu + TRUETYPE_TAG('m', 'h', 'e', 0), // mhe = Besisi + TRUETYPE_TAG('m', 'h', 'f', 0), // mhf = Mamaa + TRUETYPE_TAG('m', 'h', 'g', 0), // mhg = Margu + TRUETYPE_TAG('m', 'h', 'h', 0), // mhh = Maskoy Pidgin + TRUETYPE_TAG('m', 'h', 'i', 0), // mhi = Ma'di + TRUETYPE_TAG('m', 'h', 'j', 0), // mhj = Mogholi + TRUETYPE_TAG('m', 'h', 'k', 0), // mhk = Mungaka + TRUETYPE_TAG('m', 'h', 'l', 0), // mhl = Mauwake + TRUETYPE_TAG('m', 'h', 'm', 0), // mhm = Makhuwa-Moniga + TRUETYPE_TAG('m', 'h', 'n', 0), // mhn = Mócheno + TRUETYPE_TAG('m', 'h', 'o', 0), // mho = Mashi (Zambia) + TRUETYPE_TAG('m', 'h', 'p', 0), // mhp = Balinese Malay + TRUETYPE_TAG('m', 'h', 'q', 0), // mhq = Mandan + TRUETYPE_TAG('m', 'h', 'r', 0), // mhr = Eastern Mari + TRUETYPE_TAG('m', 'h', 's', 0), // mhs = Buru (Indonesia) + TRUETYPE_TAG('m', 'h', 't', 0), // mht = Mandahuaca + TRUETYPE_TAG('m', 'h', 'u', 0), // mhu = Digaro-Mishmi + TRUETYPE_TAG('m', 'h', 'w', 0), // mhw = Mbukushu + TRUETYPE_TAG('m', 'h', 'x', 0), // mhx = Maru + TRUETYPE_TAG('m', 'h', 'y', 0), // mhy = Ma'anyan + TRUETYPE_TAG('m', 'h', 'z', 0), // mhz = Mor (Mor Islands) + TRUETYPE_TAG('m', 'i', 'a', 0), // mia = Miami + TRUETYPE_TAG('m', 'i', 'b', 0), // mib = Atatláhuca Mixtec + TRUETYPE_TAG('m', 'i', 'c', 0), // mic = Mi'kmaq + TRUETYPE_TAG('m', 'i', 'd', 0), // mid = Mandaic + TRUETYPE_TAG('m', 'i', 'e', 0), // mie = Ocotepec Mixtec + TRUETYPE_TAG('m', 'i', 'f', 0), // mif = Mofu-Gudur + TRUETYPE_TAG('m', 'i', 'g', 0), // mig = San Miguel El Grande Mixtec + TRUETYPE_TAG('m', 'i', 'h', 0), // mih = Chayuco Mixtec + TRUETYPE_TAG('m', 'i', 'i', 0), // mii = Chigmecatitlán Mixtec + TRUETYPE_TAG('m', 'i', 'j', 0), // mij = Abar + TRUETYPE_TAG('m', 'i', 'k', 0), // mik = Mikasuki + TRUETYPE_TAG('m', 'i', 'l', 0), // mil = Peñoles Mixtec + TRUETYPE_TAG('m', 'i', 'm', 0), // mim = Alacatlatzala Mixtec + TRUETYPE_TAG('m', 'i', 'n', 0), // min = Minangkabau + TRUETYPE_TAG('m', 'i', 'o', 0), // mio = Pinotepa Nacional Mixtec + TRUETYPE_TAG('m', 'i', 'p', 0), // mip = Apasco-Apoala Mixtec + TRUETYPE_TAG('m', 'i', 'q', 0), // miq = Mískito + TRUETYPE_TAG('m', 'i', 'r', 0), // mir = Isthmus Mixe + TRUETYPE_TAG('m', 'i', 's', 0), // mis = Uncoded languages + TRUETYPE_TAG('m', 'i', 't', 0), // mit = Southern Puebla Mixtec + TRUETYPE_TAG('m', 'i', 'u', 0), // miu = Cacaloxtepec Mixtec + TRUETYPE_TAG('m', 'i', 'w', 0), // miw = Akoye + TRUETYPE_TAG('m', 'i', 'x', 0), // mix = Mixtepec Mixtec + TRUETYPE_TAG('m', 'i', 'y', 0), // miy = Ayutla Mixtec + TRUETYPE_TAG('m', 'i', 'z', 0), // miz = Coatzospan Mixtec + TRUETYPE_TAG('m', 'j', 'a', 0), // mja = Mahei + TRUETYPE_TAG('m', 'j', 'c', 0), // mjc = San Juan Colorado Mixtec + TRUETYPE_TAG('m', 'j', 'd', 0), // mjd = Northwest Maidu + TRUETYPE_TAG('m', 'j', 'e', 0), // mje = Muskum + TRUETYPE_TAG('m', 'j', 'g', 0), // mjg = Tu + TRUETYPE_TAG('m', 'j', 'h', 0), // mjh = Mwera (Nyasa) + TRUETYPE_TAG('m', 'j', 'i', 0), // mji = Kim Mun + TRUETYPE_TAG('m', 'j', 'j', 0), // mjj = Mawak + TRUETYPE_TAG('m', 'j', 'k', 0), // mjk = Matukar + TRUETYPE_TAG('m', 'j', 'l', 0), // mjl = Mandeali + TRUETYPE_TAG('m', 'j', 'm', 0), // mjm = Medebur + TRUETYPE_TAG('m', 'j', 'n', 0), // mjn = Ma (Papua New Guinea) + TRUETYPE_TAG('m', 'j', 'o', 0), // mjo = Malankuravan + TRUETYPE_TAG('m', 'j', 'p', 0), // mjp = Malapandaram + TRUETYPE_TAG('m', 'j', 'q', 0), // mjq = Malaryan + TRUETYPE_TAG('m', 'j', 'r', 0), // mjr = Malavedan + TRUETYPE_TAG('m', 'j', 's', 0), // mjs = Miship + TRUETYPE_TAG('m', 'j', 't', 0), // mjt = Sauria Paharia + TRUETYPE_TAG('m', 'j', 'u', 0), // mju = Manna-Dora + TRUETYPE_TAG('m', 'j', 'v', 0), // mjv = Mannan + TRUETYPE_TAG('m', 'j', 'w', 0), // mjw = Karbi + TRUETYPE_TAG('m', 'j', 'x', 0), // mjx = Mahali + TRUETYPE_TAG('m', 'j', 'y', 0), // mjy = Mahican + TRUETYPE_TAG('m', 'j', 'z', 0), // mjz = Majhi + TRUETYPE_TAG('m', 'k', 'a', 0), // mka = Mbre + TRUETYPE_TAG('m', 'k', 'b', 0), // mkb = Mal Paharia + TRUETYPE_TAG('m', 'k', 'c', 0), // mkc = Siliput + TRUETYPE_TAG('m', 'k', 'e', 0), // mke = Mawchi + TRUETYPE_TAG('m', 'k', 'f', 0), // mkf = Miya + TRUETYPE_TAG('m', 'k', 'g', 0), // mkg = Mak (China) + TRUETYPE_TAG('m', 'k', 'h', 0), // mkh = Mon-Khmer languages + TRUETYPE_TAG('m', 'k', 'i', 0), // mki = Dhatki + TRUETYPE_TAG('m', 'k', 'j', 0), // mkj = Mokilese + TRUETYPE_TAG('m', 'k', 'k', 0), // mkk = Byep + TRUETYPE_TAG('m', 'k', 'l', 0), // mkl = Mokole + TRUETYPE_TAG('m', 'k', 'm', 0), // mkm = Moklen + TRUETYPE_TAG('m', 'k', 'n', 0), // mkn = Kupang Malay + TRUETYPE_TAG('m', 'k', 'o', 0), // mko = Mingang Doso + TRUETYPE_TAG('m', 'k', 'p', 0), // mkp = Moikodi + TRUETYPE_TAG('m', 'k', 'q', 0), // mkq = Bay Miwok + TRUETYPE_TAG('m', 'k', 'r', 0), // mkr = Malas + TRUETYPE_TAG('m', 'k', 's', 0), // mks = Silacayoapan Mixtec + TRUETYPE_TAG('m', 'k', 't', 0), // mkt = Vamale + TRUETYPE_TAG('m', 'k', 'u', 0), // mku = Konyanka Maninka + TRUETYPE_TAG('m', 'k', 'v', 0), // mkv = Mafea + TRUETYPE_TAG('m', 'k', 'w', 0), // mkw = Kituba (Congo) + TRUETYPE_TAG('m', 'k', 'x', 0), // mkx = Kinamiging Manobo + TRUETYPE_TAG('m', 'k', 'y', 0), // mky = East Makian + TRUETYPE_TAG('m', 'k', 'z', 0), // mkz = Makasae + TRUETYPE_TAG('m', 'l', 'a', 0), // mla = Malo + TRUETYPE_TAG('m', 'l', 'b', 0), // mlb = Mbule + TRUETYPE_TAG('m', 'l', 'c', 0), // mlc = Cao Lan + TRUETYPE_TAG('m', 'l', 'd', 0), // mld = Malakhel + TRUETYPE_TAG('m', 'l', 'e', 0), // mle = Manambu + TRUETYPE_TAG('m', 'l', 'f', 0), // mlf = Mal + TRUETYPE_TAG('m', 'l', 'h', 0), // mlh = Mape + TRUETYPE_TAG('m', 'l', 'i', 0), // mli = Malimpung + TRUETYPE_TAG('m', 'l', 'j', 0), // mlj = Miltu + TRUETYPE_TAG('m', 'l', 'k', 0), // mlk = Ilwana + TRUETYPE_TAG('m', 'l', 'l', 0), // mll = Malua Bay + TRUETYPE_TAG('m', 'l', 'm', 0), // mlm = Mulam + TRUETYPE_TAG('m', 'l', 'n', 0), // mln = Malango + TRUETYPE_TAG('m', 'l', 'o', 0), // mlo = Mlomp + TRUETYPE_TAG('m', 'l', 'p', 0), // mlp = Bargam + TRUETYPE_TAG('m', 'l', 'q', 0), // mlq = Western Maninkakan + TRUETYPE_TAG('m', 'l', 'r', 0), // mlr = Vame + TRUETYPE_TAG('m', 'l', 's', 0), // mls = Masalit + TRUETYPE_TAG('m', 'l', 'u', 0), // mlu = To'abaita + TRUETYPE_TAG('m', 'l', 'v', 0), // mlv = Motlav + TRUETYPE_TAG('m', 'l', 'w', 0), // mlw = Moloko + TRUETYPE_TAG('m', 'l', 'x', 0), // mlx = Malfaxal + TRUETYPE_TAG('m', 'l', 'z', 0), // mlz = Malaynon + TRUETYPE_TAG('m', 'm', 'a', 0), // mma = Mama + TRUETYPE_TAG('m', 'm', 'b', 0), // mmb = Momina + TRUETYPE_TAG('m', 'm', 'c', 0), // mmc = Michoacán Mazahua + TRUETYPE_TAG('m', 'm', 'd', 0), // mmd = Maonan + TRUETYPE_TAG('m', 'm', 'e', 0), // mme = Mae + TRUETYPE_TAG('m', 'm', 'f', 0), // mmf = Mundat + TRUETYPE_TAG('m', 'm', 'g', 0), // mmg = North Ambrym + TRUETYPE_TAG('m', 'm', 'h', 0), // mmh = Mehináku + TRUETYPE_TAG('m', 'm', 'i', 0), // mmi = Musar + TRUETYPE_TAG('m', 'm', 'j', 0), // mmj = Majhwar + TRUETYPE_TAG('m', 'm', 'k', 0), // mmk = Mukha-Dora + TRUETYPE_TAG('m', 'm', 'l', 0), // mml = Man Met + TRUETYPE_TAG('m', 'm', 'm', 0), // mmm = Maii + TRUETYPE_TAG('m', 'm', 'n', 0), // mmn = Mamanwa + TRUETYPE_TAG('m', 'm', 'o', 0), // mmo = Mangga Buang + TRUETYPE_TAG('m', 'm', 'p', 0), // mmp = Siawi + TRUETYPE_TAG('m', 'm', 'q', 0), // mmq = Musak + TRUETYPE_TAG('m', 'm', 'r', 0), // mmr = Western Xiangxi Miao + TRUETYPE_TAG('m', 'm', 't', 0), // mmt = Malalamai + TRUETYPE_TAG('m', 'm', 'u', 0), // mmu = Mmaala + TRUETYPE_TAG('m', 'm', 'v', 0), // mmv = Miriti + TRUETYPE_TAG('m', 'm', 'w', 0), // mmw = Emae + TRUETYPE_TAG('m', 'm', 'x', 0), // mmx = Madak + TRUETYPE_TAG('m', 'm', 'y', 0), // mmy = Migaama + TRUETYPE_TAG('m', 'm', 'z', 0), // mmz = Mabaale + TRUETYPE_TAG('m', 'n', 'a', 0), // mna = Mbula + TRUETYPE_TAG('m', 'n', 'b', 0), // mnb = Muna + TRUETYPE_TAG('m', 'n', 'c', 0), // mnc = Manchu + TRUETYPE_TAG('m', 'n', 'd', 0), // mnd = Mondé + TRUETYPE_TAG('m', 'n', 'e', 0), // mne = Naba + TRUETYPE_TAG('m', 'n', 'f', 0), // mnf = Mundani + TRUETYPE_TAG('m', 'n', 'g', 0), // mng = Eastern Mnong + TRUETYPE_TAG('m', 'n', 'h', + 0), // mnh = Mono (Democratic Republic of Congo) + TRUETYPE_TAG('m', 'n', 'i', 0), // mni = Manipuri + TRUETYPE_TAG('m', 'n', 'j', 0), // mnj = Munji + TRUETYPE_TAG('m', 'n', 'k', 0), // mnk = Mandinka + TRUETYPE_TAG('m', 'n', 'l', 0), // mnl = Tiale + TRUETYPE_TAG('m', 'n', 'm', 0), // mnm = Mapena + TRUETYPE_TAG('m', 'n', 'n', 0), // mnn = Southern Mnong + TRUETYPE_TAG('m', 'n', 'o', 0), // mno = Manobo languages + TRUETYPE_TAG('m', 'n', 'p', 0), // mnp = Min Bei Chinese + TRUETYPE_TAG('m', 'n', 'q', 0), // mnq = Minriq + TRUETYPE_TAG('m', 'n', 'r', 0), // mnr = Mono (USA) + TRUETYPE_TAG('m', 'n', 's', 0), // mns = Mansi + TRUETYPE_TAG('m', 'n', 't', 0), // mnt = Maykulan + TRUETYPE_TAG('m', 'n', 'u', 0), // mnu = Mer + TRUETYPE_TAG('m', 'n', 'v', 0), // mnv = Rennell-Bellona + TRUETYPE_TAG('m', 'n', 'w', 0), // mnw = Mon + TRUETYPE_TAG('m', 'n', 'x', 0), // mnx = Manikion + TRUETYPE_TAG('m', 'n', 'y', 0), // mny = Manyawa + TRUETYPE_TAG('m', 'n', 'z', 0), // mnz = Moni + TRUETYPE_TAG('m', 'o', 'a', 0), // moa = Mwan + TRUETYPE_TAG('m', 'o', 'c', 0), // moc = Mocoví + TRUETYPE_TAG('m', 'o', 'd', 0), // mod = Mobilian + TRUETYPE_TAG('m', 'o', 'e', 0), // moe = Montagnais + TRUETYPE_TAG('m', 'o', 'f', 0), // mof = Mohegan-Montauk-Narragansett + TRUETYPE_TAG('m', 'o', 'g', 0), // mog = Mongondow + TRUETYPE_TAG('m', 'o', 'h', 0), // moh = Mohawk + TRUETYPE_TAG('m', 'o', 'i', 0), // moi = Mboi + TRUETYPE_TAG('m', 'o', 'j', 0), // moj = Monzombo + TRUETYPE_TAG('m', 'o', 'k', 0), // mok = Morori + TRUETYPE_TAG('m', 'o', 'm', 0), // mom = Mangue + TRUETYPE_TAG('m', 'o', 'o', 0), // moo = Monom + TRUETYPE_TAG('m', 'o', 'p', 0), // mop = Mopán Maya + TRUETYPE_TAG('m', 'o', 'q', 0), // moq = Mor (Bomberai Peninsula) + TRUETYPE_TAG('m', 'o', 'r', 0), // mor = Moro + TRUETYPE_TAG('m', 'o', 's', 0), // mos = Mossi + TRUETYPE_TAG('m', 'o', 't', 0), // mot = Barí + TRUETYPE_TAG('m', 'o', 'u', 0), // mou = Mogum + TRUETYPE_TAG('m', 'o', 'v', 0), // mov = Mohave + TRUETYPE_TAG('m', 'o', 'w', 0), // mow = Moi (Congo) + TRUETYPE_TAG('m', 'o', 'x', 0), // mox = Molima + TRUETYPE_TAG('m', 'o', 'y', 0), // moy = Shekkacho + TRUETYPE_TAG('m', 'o', 'z', 0), // moz = Mukulu + TRUETYPE_TAG('m', 'p', 'a', 0), // mpa = Mpoto + TRUETYPE_TAG('m', 'p', 'b', 0), // mpb = Mullukmulluk + TRUETYPE_TAG('m', 'p', 'c', 0), // mpc = Mangarayi + TRUETYPE_TAG('m', 'p', 'd', 0), // mpd = Machinere + TRUETYPE_TAG('m', 'p', 'e', 0), // mpe = Majang + TRUETYPE_TAG('m', 'p', 'g', 0), // mpg = Marba + TRUETYPE_TAG('m', 'p', 'h', 0), // mph = Maung + TRUETYPE_TAG('m', 'p', 'i', 0), // mpi = Mpade + TRUETYPE_TAG('m', 'p', 'j', 0), // mpj = Martu Wangka + TRUETYPE_TAG('m', 'p', 'k', 0), // mpk = Mbara (Chad) + TRUETYPE_TAG('m', 'p', 'l', 0), // mpl = Middle Watut + TRUETYPE_TAG('m', 'p', 'm', 0), // mpm = Yosondúa Mixtec + TRUETYPE_TAG('m', 'p', 'n', 0), // mpn = Mindiri + TRUETYPE_TAG('m', 'p', 'o', 0), // mpo = Miu + TRUETYPE_TAG('m', 'p', 'p', 0), // mpp = Migabac + TRUETYPE_TAG('m', 'p', 'q', 0), // mpq = Matís + TRUETYPE_TAG('m', 'p', 'r', 0), // mpr = Vangunu + TRUETYPE_TAG('m', 'p', 's', 0), // mps = Dadibi + TRUETYPE_TAG('m', 'p', 't', 0), // mpt = Mian + TRUETYPE_TAG('m', 'p', 'u', 0), // mpu = Makuráp + TRUETYPE_TAG('m', 'p', 'v', 0), // mpv = Mungkip + TRUETYPE_TAG('m', 'p', 'w', 0), // mpw = Mapidian + TRUETYPE_TAG('m', 'p', 'x', 0), // mpx = Misima-Paneati + TRUETYPE_TAG('m', 'p', 'y', 0), // mpy = Mapia + TRUETYPE_TAG('m', 'p', 'z', 0), // mpz = Mpi + TRUETYPE_TAG('m', 'q', 'a', 0), // mqa = Maba (Indonesia) + TRUETYPE_TAG('m', 'q', 'b', 0), // mqb = Mbuko + TRUETYPE_TAG('m', 'q', 'c', 0), // mqc = Mangole + TRUETYPE_TAG('m', 'q', 'e', 0), // mqe = Matepi + TRUETYPE_TAG('m', 'q', 'f', 0), // mqf = Momuna + TRUETYPE_TAG('m', 'q', 'g', 0), // mqg = Kota Bangun Kutai Malay + TRUETYPE_TAG('m', 'q', 'h', 0), // mqh = Tlazoyaltepec Mixtec + TRUETYPE_TAG('m', 'q', 'i', 0), // mqi = Mariri + TRUETYPE_TAG('m', 'q', 'j', 0), // mqj = Mamasa + TRUETYPE_TAG('m', 'q', 'k', 0), // mqk = Rajah Kabunsuwan Manobo + TRUETYPE_TAG('m', 'q', 'l', 0), // mql = Mbelime + TRUETYPE_TAG('m', 'q', 'm', 0), // mqm = South Marquesan + TRUETYPE_TAG('m', 'q', 'n', 0), // mqn = Moronene + TRUETYPE_TAG('m', 'q', 'o', 0), // mqo = Modole + TRUETYPE_TAG('m', 'q', 'p', 0), // mqp = Manipa + TRUETYPE_TAG('m', 'q', 'q', 0), // mqq = Minokok + TRUETYPE_TAG('m', 'q', 'r', 0), // mqr = Mander + TRUETYPE_TAG('m', 'q', 's', 0), // mqs = West Makian + TRUETYPE_TAG('m', 'q', 't', 0), // mqt = Mok + TRUETYPE_TAG('m', 'q', 'u', 0), // mqu = Mandari + TRUETYPE_TAG('m', 'q', 'v', 0), // mqv = Mosimo + TRUETYPE_TAG('m', 'q', 'w', 0), // mqw = Murupi + TRUETYPE_TAG('m', 'q', 'x', 0), // mqx = Mamuju + TRUETYPE_TAG('m', 'q', 'y', 0), // mqy = Manggarai + TRUETYPE_TAG('m', 'q', 'z', 0), // mqz = Malasanga + TRUETYPE_TAG('m', 'r', 'a', 0), // mra = Mlabri + TRUETYPE_TAG('m', 'r', 'b', 0), // mrb = Marino + TRUETYPE_TAG('m', 'r', 'c', 0), // mrc = Maricopa + TRUETYPE_TAG('m', 'r', 'd', 0), // mrd = Western Magar + TRUETYPE_TAG('m', 'r', 'e', 0), // mre = Martha's Vineyard Sign Language + TRUETYPE_TAG('m', 'r', 'f', 0), // mrf = Elseng + TRUETYPE_TAG('m', 'r', 'g', 0), // mrg = Mising + TRUETYPE_TAG('m', 'r', 'h', 0), // mrh = Mara Chin + TRUETYPE_TAG('m', 'r', 'j', 0), // mrj = Western Mari + TRUETYPE_TAG('m', 'r', 'k', 0), // mrk = Hmwaveke + TRUETYPE_TAG('m', 'r', 'l', 0), // mrl = Mortlockese + TRUETYPE_TAG('m', 'r', 'm', 0), // mrm = Merlav + TRUETYPE_TAG('m', 'r', 'n', 0), // mrn = Cheke Holo + TRUETYPE_TAG('m', 'r', 'o', 0), // mro = Mru + TRUETYPE_TAG('m', 'r', 'p', 0), // mrp = Morouas + TRUETYPE_TAG('m', 'r', 'q', 0), // mrq = North Marquesan + TRUETYPE_TAG('m', 'r', 'r', 0), // mrr = Maria (India) + TRUETYPE_TAG('m', 'r', 's', 0), // mrs = Maragus + TRUETYPE_TAG('m', 'r', 't', 0), // mrt = Marghi Central + TRUETYPE_TAG('m', 'r', 'u', 0), // mru = Mono (Cameroon) + TRUETYPE_TAG('m', 'r', 'v', 0), // mrv = Mangareva + TRUETYPE_TAG('m', 'r', 'w', 0), // mrw = Maranao + TRUETYPE_TAG('m', 'r', 'x', 0), // mrx = Maremgi + TRUETYPE_TAG('m', 'r', 'y', 0), // mry = Mandaya + TRUETYPE_TAG('m', 'r', 'z', 0), // mrz = Marind + TRUETYPE_TAG('m', 's', 'b', 0), // msb = Masbatenyo + TRUETYPE_TAG('m', 's', 'c', 0), // msc = Sankaran Maninka + TRUETYPE_TAG('m', 's', 'd', 0), // msd = Yucatec Maya Sign Language + TRUETYPE_TAG('m', 's', 'e', 0), // mse = Musey + TRUETYPE_TAG('m', 's', 'f', 0), // msf = Mekwei + TRUETYPE_TAG('m', 's', 'g', 0), // msg = Moraid + TRUETYPE_TAG('m', 's', 'h', 0), // msh = Masikoro Malagasy + TRUETYPE_TAG('m', 's', 'i', 0), // msi = Sabah Malay + TRUETYPE_TAG('m', 's', 'j', 0), // msj = Ma (Democratic Republic of Congo) + TRUETYPE_TAG('m', 's', 'k', 0), // msk = Mansaka + TRUETYPE_TAG('m', 's', 'l', 0), // msl = Molof + TRUETYPE_TAG('m', 's', 'm', 0), // msm = Agusan Manobo + TRUETYPE_TAG('m', 's', 'n', 0), // msn = Vurës + TRUETYPE_TAG('m', 's', 'o', 0), // mso = Mombum + TRUETYPE_TAG('m', 's', 'p', 0), // msp = Maritsauá + TRUETYPE_TAG('m', 's', 'q', 0), // msq = Caac + TRUETYPE_TAG('m', 's', 'r', 0), // msr = Mongolian Sign Language + TRUETYPE_TAG('m', 's', 's', 0), // mss = West Masela + TRUETYPE_TAG('m', 's', 't', 0), // mst = Cataelano Mandaya + TRUETYPE_TAG('m', 's', 'u', 0), // msu = Musom + TRUETYPE_TAG('m', 's', 'v', 0), // msv = Maslam + TRUETYPE_TAG('m', 's', 'w', 0), // msw = Mansoanka + TRUETYPE_TAG('m', 's', 'x', 0), // msx = Moresada + TRUETYPE_TAG('m', 's', 'y', 0), // msy = Aruamu + TRUETYPE_TAG('m', 's', 'z', 0), // msz = Momare + TRUETYPE_TAG('m', 't', 'a', 0), // mta = Cotabato Manobo + TRUETYPE_TAG('m', 't', 'b', 0), // mtb = Anyin Morofo + TRUETYPE_TAG('m', 't', 'c', 0), // mtc = Munit + TRUETYPE_TAG('m', 't', 'd', 0), // mtd = Mualang + TRUETYPE_TAG('m', 't', 'e', 0), // mte = Mono (Solomon Islands) + TRUETYPE_TAG('m', 't', 'f', 0), // mtf = Murik (Papua New Guinea) + TRUETYPE_TAG('m', 't', 'g', 0), // mtg = Una + TRUETYPE_TAG('m', 't', 'h', 0), // mth = Munggui + TRUETYPE_TAG('m', 't', 'i', 0), // mti = Maiwa (Papua New Guinea) + TRUETYPE_TAG('m', 't', 'j', 0), // mtj = Moskona + TRUETYPE_TAG('m', 't', 'k', 0), // mtk = Mbe' + TRUETYPE_TAG('m', 't', 'l', 0), // mtl = Montol + TRUETYPE_TAG('m', 't', 'm', 0), // mtm = Mator + TRUETYPE_TAG('m', 't', 'n', 0), // mtn = Matagalpa + TRUETYPE_TAG('m', 't', 'o', 0), // mto = Totontepec Mixe + TRUETYPE_TAG('m', 't', 'p', 0), // mtp = Wichí Lhamtés Nocten + TRUETYPE_TAG('m', 't', 'q', 0), // mtq = Muong + TRUETYPE_TAG('m', 't', 'r', 0), // mtr = Mewari + TRUETYPE_TAG('m', 't', 's', 0), // mts = Yora + TRUETYPE_TAG('m', 't', 't', 0), // mtt = Mota + TRUETYPE_TAG('m', 't', 'u', 0), // mtu = Tututepec Mixtec + TRUETYPE_TAG('m', 't', 'v', 0), // mtv = Asaro'o + TRUETYPE_TAG('m', 't', 'w', 0), // mtw = Southern Binukidnon + TRUETYPE_TAG('m', 't', 'x', 0), // mtx = Tidaá Mixtec + TRUETYPE_TAG('m', 't', 'y', 0), // mty = Nabi + TRUETYPE_TAG('m', 'u', 'a', 0), // mua = Mundang + TRUETYPE_TAG('m', 'u', 'b', 0), // mub = Mubi + TRUETYPE_TAG('m', 'u', 'c', 0), // muc = Mbu' + TRUETYPE_TAG('m', 'u', 'd', 0), // mud = Mednyj Aleut + TRUETYPE_TAG('m', 'u', 'e', 0), // mue = Media Lengua + TRUETYPE_TAG('m', 'u', 'g', 0), // mug = Musgu + TRUETYPE_TAG('m', 'u', 'h', 0), // muh = Mündü + TRUETYPE_TAG('m', 'u', 'i', 0), // mui = Musi + TRUETYPE_TAG('m', 'u', 'j', 0), // muj = Mabire + TRUETYPE_TAG('m', 'u', 'k', 0), // muk = Mugom + TRUETYPE_TAG('m', 'u', 'l', 0), // mul = Multiple languages + TRUETYPE_TAG('m', 'u', 'm', 0), // mum = Maiwala + TRUETYPE_TAG('m', 'u', 'n', 0), // mun = Munda languages + TRUETYPE_TAG('m', 'u', 'o', 0), // muo = Nyong + TRUETYPE_TAG('m', 'u', 'p', 0), // mup = Malvi + TRUETYPE_TAG('m', 'u', 'q', 0), // muq = Eastern Xiangxi Miao + TRUETYPE_TAG('m', 'u', 'r', 0), // mur = Murle + TRUETYPE_TAG('m', 'u', 's', 0), // mus = Creek + TRUETYPE_TAG('m', 'u', 't', 0), // mut = Western Muria + TRUETYPE_TAG('m', 'u', 'u', 0), // muu = Yaaku + TRUETYPE_TAG('m', 'u', 'v', 0), // muv = Muthuvan + TRUETYPE_TAG('m', 'u', 'x', 0), // mux = Bo-Ung + TRUETYPE_TAG('m', 'u', 'y', 0), // muy = Muyang + TRUETYPE_TAG('m', 'u', 'z', 0), // muz = Mursi + TRUETYPE_TAG('m', 'v', 'a', 0), // mva = Manam + TRUETYPE_TAG('m', 'v', 'b', 0), // mvb = Mattole + TRUETYPE_TAG('m', 'v', 'd', 0), // mvd = Mamboru + TRUETYPE_TAG('m', 'v', 'e', 0), // mve = Marwari (Pakistan) + TRUETYPE_TAG('m', 'v', 'f', 0), // mvf = Peripheral Mongolian + TRUETYPE_TAG('m', 'v', 'g', 0), // mvg = Yucuañe Mixtec + TRUETYPE_TAG('m', 'v', 'h', 0), // mvh = Mire + TRUETYPE_TAG('m', 'v', 'i', 0), // mvi = Miyako + TRUETYPE_TAG('m', 'v', 'k', 0), // mvk = Mekmek + TRUETYPE_TAG('m', 'v', 'l', 0), // mvl = Mbara (Australia) + TRUETYPE_TAG('m', 'v', 'm', 0), // mvm = Muya + TRUETYPE_TAG('m', 'v', 'n', 0), // mvn = Minaveha + TRUETYPE_TAG('m', 'v', 'o', 0), // mvo = Marovo + TRUETYPE_TAG('m', 'v', 'p', 0), // mvp = Duri + TRUETYPE_TAG('m', 'v', 'q', 0), // mvq = Moere + TRUETYPE_TAG('m', 'v', 'r', 0), // mvr = Marau + TRUETYPE_TAG('m', 'v', 's', 0), // mvs = Massep + TRUETYPE_TAG('m', 'v', 't', 0), // mvt = Mpotovoro + TRUETYPE_TAG('m', 'v', 'u', 0), // mvu = Marfa + TRUETYPE_TAG('m', 'v', 'v', 0), // mvv = Tagal Murut + TRUETYPE_TAG('m', 'v', 'w', 0), // mvw = Machinga + TRUETYPE_TAG('m', 'v', 'x', 0), // mvx = Meoswar + TRUETYPE_TAG('m', 'v', 'y', 0), // mvy = Indus Kohistani + TRUETYPE_TAG('m', 'v', 'z', 0), // mvz = Mesqan + TRUETYPE_TAG('m', 'w', 'a', 0), // mwa = Mwatebu + TRUETYPE_TAG('m', 'w', 'b', 0), // mwb = Juwal + TRUETYPE_TAG('m', 'w', 'c', 0), // mwc = Are + TRUETYPE_TAG('m', 'w', 'd', 0), // mwd = Mudbura + TRUETYPE_TAG('m', 'w', 'e', 0), // mwe = Mwera (Chimwera) + TRUETYPE_TAG('m', 'w', 'f', 0), // mwf = Murrinh-Patha + TRUETYPE_TAG('m', 'w', 'g', 0), // mwg = Aiklep + TRUETYPE_TAG('m', 'w', 'h', 0), // mwh = Mouk-Aria + TRUETYPE_TAG('m', 'w', 'i', 0), // mwi = Labo + TRUETYPE_TAG('m', 'w', 'j', 0), // mwj = Maligo + TRUETYPE_TAG('m', 'w', 'k', 0), // mwk = Kita Maninkakan + TRUETYPE_TAG('m', 'w', 'l', 0), // mwl = Mirandese + TRUETYPE_TAG('m', 'w', 'm', 0), // mwm = Sar + TRUETYPE_TAG('m', 'w', 'n', 0), // mwn = Nyamwanga + TRUETYPE_TAG('m', 'w', 'o', 0), // mwo = Central Maewo + TRUETYPE_TAG('m', 'w', 'p', 0), // mwp = Kala Lagaw Ya + TRUETYPE_TAG('m', 'w', 'q', 0), // mwq = Mün Chin + TRUETYPE_TAG('m', 'w', 'r', 0), // mwr = Marwari + TRUETYPE_TAG('m', 'w', 's', 0), // mws = Mwimbi-Muthambi + TRUETYPE_TAG('m', 'w', 't', 0), // mwt = Moken + TRUETYPE_TAG('m', 'w', 'u', 0), // mwu = Mittu + TRUETYPE_TAG('m', 'w', 'v', 0), // mwv = Mentawai + TRUETYPE_TAG('m', 'w', 'w', 0), // mww = Hmong Daw + TRUETYPE_TAG('m', 'w', 'x', 0), // mwx = Mediak + TRUETYPE_TAG('m', 'w', 'y', 0), // mwy = Mosiro + TRUETYPE_TAG('m', 'w', 'z', 0), // mwz = Moingi + TRUETYPE_TAG('m', 'x', 'a', 0), // mxa = Northwest Oaxaca Mixtec + TRUETYPE_TAG('m', 'x', 'b', 0), // mxb = Tezoatlán Mixtec + TRUETYPE_TAG('m', 'x', 'c', 0), // mxc = Manyika + TRUETYPE_TAG('m', 'x', 'd', 0), // mxd = Modang + TRUETYPE_TAG('m', 'x', 'e', 0), // mxe = Mele-Fila + TRUETYPE_TAG('m', 'x', 'f', 0), // mxf = Malgbe + TRUETYPE_TAG('m', 'x', 'g', 0), // mxg = Mbangala + TRUETYPE_TAG('m', 'x', 'h', 0), // mxh = Mvuba + TRUETYPE_TAG('m', 'x', 'i', 0), // mxi = Mozarabic + TRUETYPE_TAG('m', 'x', 'j', 0), // mxj = Miju-Mishmi + TRUETYPE_TAG('m', 'x', 'k', 0), // mxk = Monumbo + TRUETYPE_TAG('m', 'x', 'l', 0), // mxl = Maxi Gbe + TRUETYPE_TAG('m', 'x', 'm', 0), // mxm = Meramera + TRUETYPE_TAG('m', 'x', 'n', 0), // mxn = Moi (Indonesia) + TRUETYPE_TAG('m', 'x', 'o', 0), // mxo = Mbowe + TRUETYPE_TAG('m', 'x', 'p', 0), // mxp = Tlahuitoltepec Mixe + TRUETYPE_TAG('m', 'x', 'q', 0), // mxq = Juquila Mixe + TRUETYPE_TAG('m', 'x', 'r', 0), // mxr = Murik (Malaysia) + TRUETYPE_TAG('m', 'x', 's', 0), // mxs = Huitepec Mixtec + TRUETYPE_TAG('m', 'x', 't', 0), // mxt = Jamiltepec Mixtec + TRUETYPE_TAG('m', 'x', 'u', 0), // mxu = Mada (Cameroon) + TRUETYPE_TAG('m', 'x', 'v', 0), // mxv = Metlatónoc Mixtec + TRUETYPE_TAG('m', 'x', 'w', 0), // mxw = Namo + TRUETYPE_TAG('m', 'x', 'x', 0), // mxx = Mahou + TRUETYPE_TAG('m', 'x', 'y', 0), // mxy = Southeastern Nochixtlán Mixtec + TRUETYPE_TAG('m', 'x', 'z', 0), // mxz = Central Masela + TRUETYPE_TAG('m', 'y', 'b', 0), // myb = Mbay + TRUETYPE_TAG('m', 'y', 'c', 0), // myc = Mayeka + TRUETYPE_TAG('m', 'y', 'd', 0), // myd = Maramba + TRUETYPE_TAG('m', 'y', 'e', 0), // mye = Myene + TRUETYPE_TAG('m', 'y', 'f', 0), // myf = Bambassi + TRUETYPE_TAG('m', 'y', 'g', 0), // myg = Manta + TRUETYPE_TAG('m', 'y', 'h', 0), // myh = Makah + TRUETYPE_TAG('m', 'y', 'i', 0), // myi = Mina (India) + TRUETYPE_TAG('m', 'y', 'j', 0), // myj = Mangayat + TRUETYPE_TAG('m', 'y', 'k', 0), // myk = Mamara Senoufo + TRUETYPE_TAG('m', 'y', 'l', 0), // myl = Moma + TRUETYPE_TAG('m', 'y', 'm', 0), // mym = Me'en + TRUETYPE_TAG('m', 'y', 'n', 0), // myn = Mayan languages + TRUETYPE_TAG('m', 'y', 'o', 0), // myo = Anfillo + TRUETYPE_TAG('m', 'y', 'p', 0), // myp = Pirahã + TRUETYPE_TAG('m', 'y', 'q', 0), // myq = Forest Maninka + TRUETYPE_TAG('m', 'y', 'r', 0), // myr = Muniche + TRUETYPE_TAG('m', 'y', 's', 0), // mys = Mesmes + TRUETYPE_TAG('m', 'y', 't', 0), // myt = Sangab Mandaya + TRUETYPE_TAG('m', 'y', 'u', 0), // myu = Mundurukú + TRUETYPE_TAG('m', 'y', 'v', 0), // myv = Erzya + TRUETYPE_TAG('m', 'y', 'w', 0), // myw = Muyuw + TRUETYPE_TAG('m', 'y', 'x', 0), // myx = Masaaba + TRUETYPE_TAG('m', 'y', 'y', 0), // myy = Macuna + TRUETYPE_TAG('m', 'y', 'z', 0), // myz = Classical Mandaic + TRUETYPE_TAG('m', 'z', 'a', 0), // mza = Santa María Zacatepec Mixtec + TRUETYPE_TAG('m', 'z', 'b', 0), // mzb = Tumzabt + TRUETYPE_TAG('m', 'z', 'c', 0), // mzc = Madagascar Sign Language + TRUETYPE_TAG('m', 'z', 'd', 0), // mzd = Malimba + TRUETYPE_TAG('m', 'z', 'e', 0), // mze = Morawa + TRUETYPE_TAG('m', 'z', 'g', 0), // mzg = Monastic Sign Language + TRUETYPE_TAG('m', 'z', 'h', 0), // mzh = Wichí Lhamtés Güisnay + TRUETYPE_TAG('m', 'z', 'i', 0), // mzi = Ixcatlán Mazatec + TRUETYPE_TAG('m', 'z', 'j', 0), // mzj = Manya + TRUETYPE_TAG('m', 'z', 'k', 0), // mzk = Nigeria Mambila + TRUETYPE_TAG('m', 'z', 'l', 0), // mzl = Mazatlán Mixe + TRUETYPE_TAG('m', 'z', 'm', 0), // mzm = Mumuye + TRUETYPE_TAG('m', 'z', 'n', 0), // mzn = Mazanderani + TRUETYPE_TAG('m', 'z', 'o', 0), // mzo = Matipuhy + TRUETYPE_TAG('m', 'z', 'p', 0), // mzp = Movima + TRUETYPE_TAG('m', 'z', 'q', 0), // mzq = Mori Atas + TRUETYPE_TAG('m', 'z', 'r', 0), // mzr = Marúbo + TRUETYPE_TAG('m', 'z', 's', 0), // mzs = Macanese + TRUETYPE_TAG('m', 'z', 't', 0), // mzt = Mintil + TRUETYPE_TAG('m', 'z', 'u', 0), // mzu = Inapang + TRUETYPE_TAG('m', 'z', 'v', 0), // mzv = Manza + TRUETYPE_TAG('m', 'z', 'w', 0), // mzw = Deg + TRUETYPE_TAG('m', 'z', 'x', 0), // mzx = Mawayana + TRUETYPE_TAG('m', 'z', 'y', 0), // mzy = Mozambican Sign Language + TRUETYPE_TAG('m', 'z', 'z', 0), // mzz = Maiadomu + TRUETYPE_TAG('n', 'a', 'a', 0), // naa = Namla + TRUETYPE_TAG('n', 'a', 'b', 0), // nab = Southern Nambikuára + TRUETYPE_TAG('n', 'a', 'c', 0), // nac = Narak + TRUETYPE_TAG('n', 'a', 'd', 0), // nad = Nijadali + TRUETYPE_TAG('n', 'a', 'e', 0), // nae = Naka'ela + TRUETYPE_TAG('n', 'a', 'f', 0), // naf = Nabak + TRUETYPE_TAG('n', 'a', 'g', 0), // nag = Naga Pidgin + TRUETYPE_TAG('n', 'a', 'h', 0), // nah = Nahuatl languages + TRUETYPE_TAG('n', 'a', 'i', 0), // nai = North American Indian languages + TRUETYPE_TAG('n', 'a', 'j', 0), // naj = Nalu + TRUETYPE_TAG('n', 'a', 'k', 0), // nak = Nakanai + TRUETYPE_TAG('n', 'a', 'l', 0), // nal = Nalik + TRUETYPE_TAG('n', 'a', 'm', 0), // nam = Nangikurrunggurr + TRUETYPE_TAG('n', 'a', 'n', 0), // nan = Min Nan Chinese + TRUETYPE_TAG('n', 'a', 'o', 0), // nao = Naaba + TRUETYPE_TAG('n', 'a', 'p', 0), // nap = Neapolitan + TRUETYPE_TAG('n', 'a', 'q', 0), // naq = Nama (Namibia) + TRUETYPE_TAG('n', 'a', 'r', 0), // nar = Iguta + TRUETYPE_TAG('n', 'a', 's', 0), // nas = Naasioi + TRUETYPE_TAG('n', 'a', 't', 0), // nat = Hungworo + TRUETYPE_TAG('n', 'a', 'w', 0), // naw = Nawuri + TRUETYPE_TAG('n', 'a', 'x', 0), // nax = Nakwi + TRUETYPE_TAG('n', 'a', 'y', 0), // nay = Narrinyeri + TRUETYPE_TAG('n', 'a', 'z', 0), // naz = Coatepec Nahuatl + TRUETYPE_TAG('n', 'b', 'a', 0), // nba = Nyemba + TRUETYPE_TAG('n', 'b', 'b', 0), // nbb = Ndoe + TRUETYPE_TAG('n', 'b', 'c', 0), // nbc = Chang Naga + TRUETYPE_TAG('n', 'b', 'd', 0), // nbd = Ngbinda + TRUETYPE_TAG('n', 'b', 'e', 0), // nbe = Konyak Naga + TRUETYPE_TAG('n', 'b', 'f', 0), // nbf = Naxi + TRUETYPE_TAG('n', 'b', 'g', 0), // nbg = Nagarchal + TRUETYPE_TAG('n', 'b', 'h', 0), // nbh = Ngamo + TRUETYPE_TAG('n', 'b', 'i', 0), // nbi = Mao Naga + TRUETYPE_TAG('n', 'b', 'j', 0), // nbj = Ngarinman + TRUETYPE_TAG('n', 'b', 'k', 0), // nbk = Nake + TRUETYPE_TAG('n', 'b', 'm', 0), // nbm = Ngbaka Ma'bo + TRUETYPE_TAG('n', 'b', 'n', 0), // nbn = Kuri + TRUETYPE_TAG('n', 'b', 'o', 0), // nbo = Nkukoli + TRUETYPE_TAG('n', 'b', 'p', 0), // nbp = Nnam + TRUETYPE_TAG('n', 'b', 'q', 0), // nbq = Nggem + TRUETYPE_TAG('n', 'b', 'r', 0), // nbr = Numana-Nunku-Gbantu-Numbu + TRUETYPE_TAG('n', 'b', 's', 0), // nbs = Namibian Sign Language + TRUETYPE_TAG('n', 'b', 't', 0), // nbt = Na + TRUETYPE_TAG('n', 'b', 'u', 0), // nbu = Rongmei Naga + TRUETYPE_TAG('n', 'b', 'v', 0), // nbv = Ngamambo + TRUETYPE_TAG('n', 'b', 'w', 0), // nbw = Southern Ngbandi + TRUETYPE_TAG('n', 'b', 'x', 0), // nbx = Ngura + TRUETYPE_TAG('n', 'b', 'y', 0), // nby = Ningera + TRUETYPE_TAG('n', 'c', 'a', 0), // nca = Iyo + TRUETYPE_TAG('n', 'c', 'b', 0), // ncb = Central Nicobarese + TRUETYPE_TAG('n', 'c', 'c', 0), // ncc = Ponam + TRUETYPE_TAG('n', 'c', 'd', 0), // ncd = Nachering + TRUETYPE_TAG('n', 'c', 'e', 0), // nce = Yale + TRUETYPE_TAG('n', 'c', 'f', 0), // ncf = Notsi + TRUETYPE_TAG('n', 'c', 'g', 0), // ncg = Nisga'a + TRUETYPE_TAG('n', 'c', 'h', 0), // nch = Central Huasteca Nahuatl + TRUETYPE_TAG('n', 'c', 'i', 0), // nci = Classical Nahuatl + TRUETYPE_TAG('n', 'c', 'j', 0), // ncj = Northern Puebla Nahuatl + TRUETYPE_TAG('n', 'c', 'k', 0), // nck = Nakara + TRUETYPE_TAG('n', 'c', 'l', 0), // ncl = Michoacán Nahuatl + TRUETYPE_TAG('n', 'c', 'm', 0), // ncm = Nambo + TRUETYPE_TAG('n', 'c', 'n', 0), // ncn = Nauna + TRUETYPE_TAG('n', 'c', 'o', 0), // nco = Sibe + TRUETYPE_TAG('n', 'c', 'p', 0), // ncp = Ndaktup + TRUETYPE_TAG('n', 'c', 'r', 0), // ncr = Ncane + TRUETYPE_TAG('n', 'c', 's', 0), // ncs = Nicaraguan Sign Language + TRUETYPE_TAG('n', 'c', 't', 0), // nct = Chothe Naga + TRUETYPE_TAG('n', 'c', 'u', 0), // ncu = Chumburung + TRUETYPE_TAG('n', 'c', 'x', 0), // ncx = Central Puebla Nahuatl + TRUETYPE_TAG('n', 'c', 'z', 0), // ncz = Natchez + TRUETYPE_TAG('n', 'd', 'a', 0), // nda = Ndasa + TRUETYPE_TAG('n', 'd', 'b', 0), // ndb = Kenswei Nsei + TRUETYPE_TAG('n', 'd', 'c', 0), // ndc = Ndau + TRUETYPE_TAG('n', 'd', 'd', 0), // ndd = Nde-Nsele-Nta + TRUETYPE_TAG('n', 'd', 'f', 0), // ndf = Nadruvian + TRUETYPE_TAG('n', 'd', 'g', 0), // ndg = Ndengereko + TRUETYPE_TAG('n', 'd', 'h', 0), // ndh = Ndali + TRUETYPE_TAG('n', 'd', 'i', 0), // ndi = Samba Leko + TRUETYPE_TAG('n', 'd', 'j', 0), // ndj = Ndamba + TRUETYPE_TAG('n', 'd', 'k', 0), // ndk = Ndaka + TRUETYPE_TAG('n', 'd', 'l', 0), // ndl = Ndolo + TRUETYPE_TAG('n', 'd', 'm', 0), // ndm = Ndam + TRUETYPE_TAG('n', 'd', 'n', 0), // ndn = Ngundi + TRUETYPE_TAG('n', 'd', 'p', 0), // ndp = Ndo + TRUETYPE_TAG('n', 'd', 'q', 0), // ndq = Ndombe + TRUETYPE_TAG('n', 'd', 'r', 0), // ndr = Ndoola + TRUETYPE_TAG('n', 'd', 's', 0), // nds = Low German + TRUETYPE_TAG('n', 'd', 't', 0), // ndt = Ndunga + TRUETYPE_TAG('n', 'd', 'u', 0), // ndu = Dugun + TRUETYPE_TAG('n', 'd', 'v', 0), // ndv = Ndut + TRUETYPE_TAG('n', 'd', 'w', 0), // ndw = Ndobo + TRUETYPE_TAG('n', 'd', 'x', 0), // ndx = Nduga + TRUETYPE_TAG('n', 'd', 'y', 0), // ndy = Lutos + TRUETYPE_TAG('n', 'd', 'z', 0), // ndz = Ndogo + TRUETYPE_TAG('n', 'e', 'a', 0), // nea = Eastern Ngad'a + TRUETYPE_TAG('n', 'e', 'b', 0), // neb = Toura (Côte d'Ivoire) + TRUETYPE_TAG('n', 'e', 'c', 0), // nec = Nedebang + TRUETYPE_TAG('n', 'e', 'd', 0), // ned = Nde-Gbite + TRUETYPE_TAG('n', 'e', 'e', 0), // nee = Nêlêmwa-Nixumwak + TRUETYPE_TAG('n', 'e', 'f', 0), // nef = Nefamese + TRUETYPE_TAG('n', 'e', 'g', 0), // neg = Negidal + TRUETYPE_TAG('n', 'e', 'h', 0), // neh = Nyenkha + TRUETYPE_TAG('n', 'e', 'i', 0), // nei = Neo-Hittite + TRUETYPE_TAG('n', 'e', 'j', 0), // nej = Neko + TRUETYPE_TAG('n', 'e', 'k', 0), // nek = Neku + TRUETYPE_TAG('n', 'e', 'm', 0), // nem = Nemi + TRUETYPE_TAG('n', 'e', 'n', 0), // nen = Nengone + TRUETYPE_TAG('n', 'e', 'o', 0), // neo = Ná-Meo + TRUETYPE_TAG('n', 'e', 'q', 0), // neq = North Central Mixe + TRUETYPE_TAG('n', 'e', 'r', 0), // ner = Yahadian + TRUETYPE_TAG('n', 'e', 's', 0), // nes = Bhoti Kinnauri + TRUETYPE_TAG('n', 'e', 't', 0), // net = Nete + TRUETYPE_TAG('n', 'e', 'v', 0), // nev = Nyaheun + TRUETYPE_TAG('n', 'e', 'w', 0), // new = Newari + TRUETYPE_TAG('n', 'e', 'x', 0), // nex = Neme + TRUETYPE_TAG('n', 'e', 'y', 0), // ney = Neyo + TRUETYPE_TAG('n', 'e', 'z', 0), // nez = Nez Perce + TRUETYPE_TAG('n', 'f', 'a', 0), // nfa = Dhao + TRUETYPE_TAG('n', 'f', 'd', 0), // nfd = Ahwai + TRUETYPE_TAG('n', 'f', 'l', 0), // nfl = Ayiwo + TRUETYPE_TAG('n', 'f', 'r', 0), // nfr = Nafaanra + TRUETYPE_TAG('n', 'f', 'u', 0), // nfu = Mfumte + TRUETYPE_TAG('n', 'g', 'a', 0), // nga = Ngbaka + TRUETYPE_TAG('n', 'g', 'b', 0), // ngb = Northern Ngbandi + TRUETYPE_TAG('n', 'g', 'c', + 0), // ngc = Ngombe (Democratic Republic of Congo) + TRUETYPE_TAG('n', 'g', 'd', 0), // ngd = Ngando (Central African Republic) + TRUETYPE_TAG('n', 'g', 'e', 0), // nge = Ngemba + TRUETYPE_TAG('n', 'g', 'f', 0), // ngf = Trans-New Guinea languages + TRUETYPE_TAG('n', 'g', 'g', 0), // ngg = Ngbaka Manza + TRUETYPE_TAG('n', 'g', 'h', 0), // ngh = N/u + TRUETYPE_TAG('n', 'g', 'i', 0), // ngi = Ngizim + TRUETYPE_TAG('n', 'g', 'j', 0), // ngj = Ngie + TRUETYPE_TAG('n', 'g', 'k', 0), // ngk = Ngalkbun + TRUETYPE_TAG('n', 'g', 'l', 0), // ngl = Lomwe + TRUETYPE_TAG('n', 'g', 'm', 0), // ngm = Ngatik Men's Creole + TRUETYPE_TAG('n', 'g', 'n', 0), // ngn = Ngwo + TRUETYPE_TAG('n', 'g', 'o', 0), // ngo = Ngoni + TRUETYPE_TAG('n', 'g', 'p', 0), // ngp = Ngulu + TRUETYPE_TAG('n', 'g', 'q', 0), // ngq = Ngurimi + TRUETYPE_TAG('n', 'g', 'r', 0), // ngr = Nanggu + TRUETYPE_TAG('n', 'g', 's', 0), // ngs = Gvoko + TRUETYPE_TAG('n', 'g', 't', 0), // ngt = Ngeq + TRUETYPE_TAG('n', 'g', 'u', 0), // ngu = Guerrero Nahuatl + TRUETYPE_TAG('n', 'g', 'v', 0), // ngv = Nagumi + TRUETYPE_TAG('n', 'g', 'w', 0), // ngw = Ngwaba + TRUETYPE_TAG('n', 'g', 'x', 0), // ngx = Nggwahyi + TRUETYPE_TAG('n', 'g', 'y', 0), // ngy = Tibea + TRUETYPE_TAG('n', 'g', 'z', 0), // ngz = Ngungwel + TRUETYPE_TAG('n', 'h', 'a', 0), // nha = Nhanda + TRUETYPE_TAG('n', 'h', 'b', 0), // nhb = Beng + TRUETYPE_TAG('n', 'h', 'c', 0), // nhc = Tabasco Nahuatl + TRUETYPE_TAG('n', 'h', 'd', 0), // nhd = Chiripá + TRUETYPE_TAG('n', 'h', 'e', 0), // nhe = Eastern Huasteca Nahuatl + TRUETYPE_TAG('n', 'h', 'f', 0), // nhf = Nhuwala + TRUETYPE_TAG('n', 'h', 'g', 0), // nhg = Tetelcingo Nahuatl + TRUETYPE_TAG('n', 'h', 'h', 0), // nhh = Nahari + TRUETYPE_TAG('n', 'h', 'i', + 0), // nhi = Zacatlán-Ahuacatlán-Tepetzintla Nahuatl + TRUETYPE_TAG('n', 'h', 'k', 0), // nhk = Isthmus-Cosoleacaque Nahuatl + TRUETYPE_TAG('n', 'h', 'm', 0), // nhm = Morelos Nahuatl + TRUETYPE_TAG('n', 'h', 'n', 0), // nhn = Central Nahuatl + TRUETYPE_TAG('n', 'h', 'o', 0), // nho = Takuu + TRUETYPE_TAG('n', 'h', 'p', 0), // nhp = Isthmus-Pajapan Nahuatl + TRUETYPE_TAG('n', 'h', 'q', 0), // nhq = Huaxcaleca Nahuatl + TRUETYPE_TAG('n', 'h', 'r', 0), // nhr = Naro + TRUETYPE_TAG('n', 'h', 't', 0), // nht = Ometepec Nahuatl + TRUETYPE_TAG('n', 'h', 'u', 0), // nhu = Noone + TRUETYPE_TAG('n', 'h', 'v', 0), // nhv = Temascaltepec Nahuatl + TRUETYPE_TAG('n', 'h', 'w', 0), // nhw = Western Huasteca Nahuatl + TRUETYPE_TAG('n', 'h', 'x', 0), // nhx = Isthmus-Mecayapan Nahuatl + TRUETYPE_TAG('n', 'h', 'y', 0), // nhy = Northern Oaxaca Nahuatl + TRUETYPE_TAG('n', 'h', 'z', 0), // nhz = Santa María La Alta Nahuatl + TRUETYPE_TAG('n', 'i', 'a', 0), // nia = Nias + TRUETYPE_TAG('n', 'i', 'b', 0), // nib = Nakame + TRUETYPE_TAG('n', 'i', 'c', 0), // nic = Niger-Kordofanian languages + TRUETYPE_TAG('n', 'i', 'd', 0), // nid = Ngandi + TRUETYPE_TAG('n', 'i', 'e', 0), // nie = Niellim + TRUETYPE_TAG('n', 'i', 'f', 0), // nif = Nek + TRUETYPE_TAG('n', 'i', 'g', 0), // nig = Ngalakan + TRUETYPE_TAG('n', 'i', 'h', 0), // nih = Nyiha (Tanzania) + TRUETYPE_TAG('n', 'i', 'i', 0), // nii = Nii + TRUETYPE_TAG('n', 'i', 'j', 0), // nij = Ngaju + TRUETYPE_TAG('n', 'i', 'k', 0), // nik = Southern Nicobarese + TRUETYPE_TAG('n', 'i', 'l', 0), // nil = Nila + TRUETYPE_TAG('n', 'i', 'm', 0), // nim = Nilamba + TRUETYPE_TAG('n', 'i', 'n', 0), // nin = Ninzo + TRUETYPE_TAG('n', 'i', 'o', 0), // nio = Nganasan + TRUETYPE_TAG('n', 'i', 'q', 0), // niq = Nandi + TRUETYPE_TAG('n', 'i', 'r', 0), // nir = Nimboran + TRUETYPE_TAG('n', 'i', 's', 0), // nis = Nimi + TRUETYPE_TAG('n', 'i', 't', 0), // nit = Southeastern Kolami + TRUETYPE_TAG('n', 'i', 'u', 0), // niu = Niuean + TRUETYPE_TAG('n', 'i', 'v', 0), // niv = Gilyak + TRUETYPE_TAG('n', 'i', 'w', 0), // niw = Nimo + TRUETYPE_TAG('n', 'i', 'x', 0), // nix = Hema + TRUETYPE_TAG('n', 'i', 'y', 0), // niy = Ngiti + TRUETYPE_TAG('n', 'i', 'z', 0), // niz = Ningil + TRUETYPE_TAG('n', 'j', 'a', 0), // nja = Nzanyi + TRUETYPE_TAG('n', 'j', 'b', 0), // njb = Nocte Naga + TRUETYPE_TAG('n', 'j', 'd', 0), // njd = Ndonde Hamba + TRUETYPE_TAG('n', 'j', 'h', 0), // njh = Lotha Naga + TRUETYPE_TAG('n', 'j', 'i', 0), // nji = Gudanji + TRUETYPE_TAG('n', 'j', 'j', 0), // njj = Njen + TRUETYPE_TAG('n', 'j', 'l', 0), // njl = Njalgulgule + TRUETYPE_TAG('n', 'j', 'm', 0), // njm = Angami Naga + TRUETYPE_TAG('n', 'j', 'n', 0), // njn = Liangmai Naga + TRUETYPE_TAG('n', 'j', 'o', 0), // njo = Ao Naga + TRUETYPE_TAG('n', 'j', 'r', 0), // njr = Njerep + TRUETYPE_TAG('n', 'j', 's', 0), // njs = Nisa + TRUETYPE_TAG('n', 'j', 't', 0), // njt = Ndyuka-Trio Pidgin + TRUETYPE_TAG('n', 'j', 'u', 0), // nju = Ngadjunmaya + TRUETYPE_TAG('n', 'j', 'x', 0), // njx = Kunyi + TRUETYPE_TAG('n', 'j', 'y', 0), // njy = Njyem + TRUETYPE_TAG('n', 'k', 'a', 0), // nka = Nkoya + TRUETYPE_TAG('n', 'k', 'b', 0), // nkb = Khoibu Naga + TRUETYPE_TAG('n', 'k', 'c', 0), // nkc = Nkongho + TRUETYPE_TAG('n', 'k', 'd', 0), // nkd = Koireng + TRUETYPE_TAG('n', 'k', 'e', 0), // nke = Duke + TRUETYPE_TAG('n', 'k', 'f', 0), // nkf = Inpui Naga + TRUETYPE_TAG('n', 'k', 'g', 0), // nkg = Nekgini + TRUETYPE_TAG('n', 'k', 'h', 0), // nkh = Khezha Naga + TRUETYPE_TAG('n', 'k', 'i', 0), // nki = Thangal Naga + TRUETYPE_TAG('n', 'k', 'j', 0), // nkj = Nakai + TRUETYPE_TAG('n', 'k', 'k', 0), // nkk = Nokuku + TRUETYPE_TAG('n', 'k', 'm', 0), // nkm = Namat + TRUETYPE_TAG('n', 'k', 'n', 0), // nkn = Nkangala + TRUETYPE_TAG('n', 'k', 'o', 0), // nko = Nkonya + TRUETYPE_TAG('n', 'k', 'p', 0), // nkp = Niuatoputapu + TRUETYPE_TAG('n', 'k', 'q', 0), // nkq = Nkami + TRUETYPE_TAG('n', 'k', 'r', 0), // nkr = Nukuoro + TRUETYPE_TAG('n', 'k', 's', 0), // nks = North Asmat + TRUETYPE_TAG('n', 'k', 't', 0), // nkt = Nyika (Tanzania) + TRUETYPE_TAG('n', 'k', 'u', 0), // nku = Bouna Kulango + TRUETYPE_TAG('n', 'k', 'v', 0), // nkv = Nyika (Malawi and Zambia) + TRUETYPE_TAG('n', 'k', 'w', 0), // nkw = Nkutu + TRUETYPE_TAG('n', 'k', 'x', 0), // nkx = Nkoroo + TRUETYPE_TAG('n', 'k', 'z', 0), // nkz = Nkari + TRUETYPE_TAG('n', 'l', 'a', 0), // nla = Ngombale + TRUETYPE_TAG('n', 'l', 'c', 0), // nlc = Nalca + TRUETYPE_TAG('n', 'l', 'e', 0), // nle = East Nyala + TRUETYPE_TAG('n', 'l', 'g', 0), // nlg = Gela + TRUETYPE_TAG('n', 'l', 'i', 0), // nli = Grangali + TRUETYPE_TAG('n', 'l', 'j', 0), // nlj = Nyali + TRUETYPE_TAG('n', 'l', 'k', 0), // nlk = Ninia Yali + TRUETYPE_TAG('n', 'l', 'l', 0), // nll = Nihali + TRUETYPE_TAG('n', 'l', 'n', 0), // nln = Durango Nahuatl + TRUETYPE_TAG('n', 'l', 'o', 0), // nlo = Ngul + TRUETYPE_TAG('n', 'l', 'r', 0), // nlr = Ngarla + TRUETYPE_TAG('n', 'l', 'u', 0), // nlu = Nchumbulu + TRUETYPE_TAG('n', 'l', 'v', 0), // nlv = Orizaba Nahuatl + TRUETYPE_TAG('n', 'l', 'x', 0), // nlx = Nahali + TRUETYPE_TAG('n', 'l', 'y', 0), // nly = Nyamal + TRUETYPE_TAG('n', 'l', 'z', 0), // nlz = Nalögo + TRUETYPE_TAG('n', 'm', 'a', 0), // nma = Maram Naga + TRUETYPE_TAG('n', 'm', 'b', 0), // nmb = Big Nambas + TRUETYPE_TAG('n', 'm', 'c', 0), // nmc = Ngam + TRUETYPE_TAG('n', 'm', 'd', 0), // nmd = Ndumu + TRUETYPE_TAG('n', 'm', 'e', 0), // nme = Mzieme Naga + TRUETYPE_TAG('n', 'm', 'f', 0), // nmf = Tangkhul Naga + TRUETYPE_TAG('n', 'm', 'g', 0), // nmg = Kwasio + TRUETYPE_TAG('n', 'm', 'h', 0), // nmh = Monsang Naga + TRUETYPE_TAG('n', 'm', 'i', 0), // nmi = Nyam + TRUETYPE_TAG('n', 'm', 'j', 0), // nmj = Ngombe (Central African Republic) + TRUETYPE_TAG('n', 'm', 'k', 0), // nmk = Namakura + TRUETYPE_TAG('n', 'm', 'l', 0), // nml = Ndemli + TRUETYPE_TAG('n', 'm', 'm', 0), // nmm = Manangba + TRUETYPE_TAG('n', 'm', 'n', 0), // nmn = !Xóõ + TRUETYPE_TAG('n', 'm', 'o', 0), // nmo = Moyon Naga + TRUETYPE_TAG('n', 'm', 'p', 0), // nmp = Nimanbur + TRUETYPE_TAG('n', 'm', 'q', 0), // nmq = Nambya + TRUETYPE_TAG('n', 'm', 'r', 0), // nmr = Nimbari + TRUETYPE_TAG('n', 'm', 's', 0), // nms = Letemboi + TRUETYPE_TAG('n', 'm', 't', 0), // nmt = Namonuito + TRUETYPE_TAG('n', 'm', 'u', 0), // nmu = Northeast Maidu + TRUETYPE_TAG('n', 'm', 'v', 0), // nmv = Ngamini + TRUETYPE_TAG('n', 'm', 'w', 0), // nmw = Nimoa + TRUETYPE_TAG('n', 'm', 'x', 0), // nmx = Nama (Papua New Guinea) + TRUETYPE_TAG('n', 'm', 'y', 0), // nmy = Namuyi + TRUETYPE_TAG('n', 'm', 'z', 0), // nmz = Nawdm + TRUETYPE_TAG('n', 'n', 'a', 0), // nna = Nyangumarta + TRUETYPE_TAG('n', 'n', 'b', 0), // nnb = Nande + TRUETYPE_TAG('n', 'n', 'c', 0), // nnc = Nancere + TRUETYPE_TAG('n', 'n', 'd', 0), // nnd = West Ambae + TRUETYPE_TAG('n', 'n', 'e', 0), // nne = Ngandyera + TRUETYPE_TAG('n', 'n', 'f', 0), // nnf = Ngaing + TRUETYPE_TAG('n', 'n', 'g', 0), // nng = Maring Naga + TRUETYPE_TAG('n', 'n', 'h', 0), // nnh = Ngiemboon + TRUETYPE_TAG('n', 'n', 'i', 0), // nni = North Nuaulu + TRUETYPE_TAG('n', 'n', 'j', 0), // nnj = Nyangatom + TRUETYPE_TAG('n', 'n', 'k', 0), // nnk = Nankina + TRUETYPE_TAG('n', 'n', 'l', 0), // nnl = Northern Rengma Naga + TRUETYPE_TAG('n', 'n', 'm', 0), // nnm = Namia + TRUETYPE_TAG('n', 'n', 'n', 0), // nnn = Ngete + TRUETYPE_TAG('n', 'n', 'p', 0), // nnp = Wancho Naga + TRUETYPE_TAG('n', 'n', 'q', 0), // nnq = Ngindo + TRUETYPE_TAG('n', 'n', 'r', 0), // nnr = Narungga + TRUETYPE_TAG('n', 'n', 's', 0), // nns = Ningye + TRUETYPE_TAG('n', 'n', 't', 0), // nnt = Nanticoke + TRUETYPE_TAG('n', 'n', 'u', 0), // nnu = Dwang + TRUETYPE_TAG('n', 'n', 'v', 0), // nnv = Nugunu (Australia) + TRUETYPE_TAG('n', 'n', 'w', 0), // nnw = Southern Nuni + TRUETYPE_TAG('n', 'n', 'x', 0), // nnx = Ngong + TRUETYPE_TAG('n', 'n', 'y', 0), // nny = Nyangga + TRUETYPE_TAG('n', 'n', 'z', 0), // nnz = Nda'nda' + TRUETYPE_TAG('n', 'o', 'a', 0), // noa = Woun Meu + TRUETYPE_TAG('n', 'o', 'c', 0), // noc = Nuk + TRUETYPE_TAG('n', 'o', 'd', 0), // nod = Northern Thai + TRUETYPE_TAG('n', 'o', 'e', 0), // noe = Nimadi + TRUETYPE_TAG('n', 'o', 'f', 0), // nof = Nomane + TRUETYPE_TAG('n', 'o', 'g', 0), // nog = Nogai + TRUETYPE_TAG('n', 'o', 'h', 0), // noh = Nomu + TRUETYPE_TAG('n', 'o', 'i', 0), // noi = Noiri + TRUETYPE_TAG('n', 'o', 'j', 0), // noj = Nonuya + TRUETYPE_TAG('n', 'o', 'k', 0), // nok = Nooksack + TRUETYPE_TAG('n', 'o', 'm', 0), // nom = Nocamán + TRUETYPE_TAG('n', 'o', 'n', 0), // non = Old Norse + TRUETYPE_TAG('n', 'o', 'o', 0), // noo = Nootka + TRUETYPE_TAG('n', 'o', 'p', 0), // nop = Numanggang + TRUETYPE_TAG('n', 'o', 'q', 0), // noq = Ngongo + TRUETYPE_TAG('n', 'o', 's', 0), // nos = Eastern Nisu + TRUETYPE_TAG('n', 'o', 't', 0), // not = Nomatsiguenga + TRUETYPE_TAG('n', 'o', 'u', 0), // nou = Ewage-Notu + TRUETYPE_TAG('n', 'o', 'v', 0), // nov = Novial + TRUETYPE_TAG('n', 'o', 'w', 0), // now = Nyambo + TRUETYPE_TAG('n', 'o', 'y', 0), // noy = Noy + TRUETYPE_TAG('n', 'o', 'z', 0), // noz = Nayi + TRUETYPE_TAG('n', 'p', 'a', 0), // npa = Nar Phu + TRUETYPE_TAG('n', 'p', 'b', 0), // npb = Nupbikha + TRUETYPE_TAG('n', 'p', 'h', 0), // nph = Phom Naga + TRUETYPE_TAG('n', 'p', 'l', 0), // npl = Southeastern Puebla Nahuatl + TRUETYPE_TAG('n', 'p', 'n', 0), // npn = Mondropolon + TRUETYPE_TAG('n', 'p', 'o', 0), // npo = Pochuri Naga + TRUETYPE_TAG('n', 'p', 's', 0), // nps = Nipsan + TRUETYPE_TAG('n', 'p', 'u', 0), // npu = Puimei Naga + TRUETYPE_TAG('n', 'p', 'y', 0), // npy = Napu + TRUETYPE_TAG('n', 'q', 'g', 0), // nqg = Southern Nago + TRUETYPE_TAG('n', 'q', 'k', 0), // nqk = Kura Ede Nago + TRUETYPE_TAG('n', 'q', 'm', 0), // nqm = Ndom + TRUETYPE_TAG('n', 'q', 'n', 0), // nqn = Nen + TRUETYPE_TAG('n', 'q', 'o', 0), // nqo = N'Ko + TRUETYPE_TAG('n', 'r', 'a', 0), // nra = Ngom + TRUETYPE_TAG('n', 'r', 'b', 0), // nrb = Nara + TRUETYPE_TAG('n', 'r', 'c', 0), // nrc = Noric + TRUETYPE_TAG('n', 'r', 'e', 0), // nre = Southern Rengma Naga + TRUETYPE_TAG('n', 'r', 'g', 0), // nrg = Narango + TRUETYPE_TAG('n', 'r', 'i', 0), // nri = Chokri Naga + TRUETYPE_TAG('n', 'r', 'l', 0), // nrl = Ngarluma + TRUETYPE_TAG('n', 'r', 'm', 0), // nrm = Narom + TRUETYPE_TAG('n', 'r', 'n', 0), // nrn = Norn + TRUETYPE_TAG('n', 'r', 'p', 0), // nrp = North Picene + TRUETYPE_TAG('n', 'r', 'r', 0), // nrr = Norra + TRUETYPE_TAG('n', 'r', 't', 0), // nrt = Northern Kalapuya + TRUETYPE_TAG('n', 'r', 'u', 0), // nru = Narua + TRUETYPE_TAG('n', 'r', 'x', 0), // nrx = Ngurmbur + TRUETYPE_TAG('n', 'r', 'z', 0), // nrz = Lala + TRUETYPE_TAG('n', 's', 'a', 0), // nsa = Sangtam Naga + TRUETYPE_TAG('n', 's', 'c', 0), // nsc = Nshi + TRUETYPE_TAG('n', 's', 'd', 0), // nsd = Southern Nisu + TRUETYPE_TAG('n', 's', 'e', 0), // nse = Nsenga + TRUETYPE_TAG('n', 's', 'g', 0), // nsg = Ngasa + TRUETYPE_TAG('n', 's', 'h', 0), // nsh = Ngoshie + TRUETYPE_TAG('n', 's', 'i', 0), // nsi = Nigerian Sign Language + TRUETYPE_TAG('n', 's', 'k', 0), // nsk = Naskapi + TRUETYPE_TAG('n', 's', 'l', 0), // nsl = Norwegian Sign Language + TRUETYPE_TAG('n', 's', 'm', 0), // nsm = Sumi Naga + TRUETYPE_TAG('n', 's', 'n', 0), // nsn = Nehan + TRUETYPE_TAG('n', 's', 'o', 0), // nso = Pedi + TRUETYPE_TAG('n', 's', 'p', 0), // nsp = Nepalese Sign Language + TRUETYPE_TAG('n', 's', 'q', 0), // nsq = Northern Sierra Miwok + TRUETYPE_TAG('n', 's', 'r', 0), // nsr = Maritime Sign Language + TRUETYPE_TAG('n', 's', 's', 0), // nss = Nali + TRUETYPE_TAG('n', 's', 't', 0), // nst = Tase Naga + TRUETYPE_TAG('n', 's', 'u', 0), // nsu = Sierra Negra Nahuatl + TRUETYPE_TAG('n', 's', 'v', 0), // nsv = Southwestern Nisu + TRUETYPE_TAG('n', 's', 'w', 0), // nsw = Navut + TRUETYPE_TAG('n', 's', 'x', 0), // nsx = Nsongo + TRUETYPE_TAG('n', 's', 'y', 0), // nsy = Nasal + TRUETYPE_TAG('n', 's', 'z', 0), // nsz = Nisenan + TRUETYPE_TAG('n', 't', 'e', 0), // nte = Nathembo + TRUETYPE_TAG('n', 't', 'i', 0), // nti = Natioro + TRUETYPE_TAG('n', 't', 'j', 0), // ntj = Ngaanyatjarra + TRUETYPE_TAG('n', 't', 'k', 0), // ntk = Ikoma-Nata-Isenye + TRUETYPE_TAG('n', 't', 'm', 0), // ntm = Nateni + TRUETYPE_TAG('n', 't', 'o', 0), // nto = Ntomba + TRUETYPE_TAG('n', 't', 'p', 0), // ntp = Northern Tepehuan + TRUETYPE_TAG('n', 't', 'r', 0), // ntr = Delo + TRUETYPE_TAG('n', 't', 's', 0), // nts = Natagaimas + TRUETYPE_TAG('n', 't', 'u', 0), // ntu = Natügu + TRUETYPE_TAG('n', 't', 'w', 0), // ntw = Nottoway + TRUETYPE_TAG('n', 't', 'y', 0), // nty = Mantsi + TRUETYPE_TAG('n', 't', 'z', 0), // ntz = Natanzi + TRUETYPE_TAG('n', 'u', 'a', 0), // nua = Yuaga + TRUETYPE_TAG('n', 'u', 'b', 0), // nub = Nubian languages + TRUETYPE_TAG('n', 'u', 'c', 0), // nuc = Nukuini + TRUETYPE_TAG('n', 'u', 'd', 0), // nud = Ngala + TRUETYPE_TAG('n', 'u', 'e', 0), // nue = Ngundu + TRUETYPE_TAG('n', 'u', 'f', 0), // nuf = Nusu + TRUETYPE_TAG('n', 'u', 'g', 0), // nug = Nungali + TRUETYPE_TAG('n', 'u', 'h', 0), // nuh = Ndunda + TRUETYPE_TAG('n', 'u', 'i', 0), // nui = Ngumbi + TRUETYPE_TAG('n', 'u', 'j', 0), // nuj = Nyole + TRUETYPE_TAG('n', 'u', 'k', 0), // nuk = Nuu-chah-nulth + TRUETYPE_TAG('n', 'u', 'l', 0), // nul = Nusa Laut + TRUETYPE_TAG('n', 'u', 'm', 0), // num = Niuafo'ou + TRUETYPE_TAG('n', 'u', 'n', 0), // nun = Anong + TRUETYPE_TAG('n', 'u', 'o', 0), // nuo = Nguôn + TRUETYPE_TAG('n', 'u', 'p', 0), // nup = Nupe-Nupe-Tako + TRUETYPE_TAG('n', 'u', 'q', 0), // nuq = Nukumanu + TRUETYPE_TAG('n', 'u', 'r', 0), // nur = Nukuria + TRUETYPE_TAG('n', 'u', 's', 0), // nus = Nuer + TRUETYPE_TAG('n', 'u', 't', 0), // nut = Nung (Viet Nam) + TRUETYPE_TAG('n', 'u', 'u', 0), // nuu = Ngbundu + TRUETYPE_TAG('n', 'u', 'v', 0), // nuv = Northern Nuni + TRUETYPE_TAG('n', 'u', 'w', 0), // nuw = Nguluwan + TRUETYPE_TAG('n', 'u', 'x', 0), // nux = Mehek + TRUETYPE_TAG('n', 'u', 'y', 0), // nuy = Nunggubuyu + TRUETYPE_TAG('n', 'u', 'z', 0), // nuz = Tlamacazapa Nahuatl + TRUETYPE_TAG('n', 'v', 'h', 0), // nvh = Nasarian + TRUETYPE_TAG('n', 'v', 'm', 0), // nvm = Namiae + TRUETYPE_TAG('n', 'w', 'a', 0), // nwa = Nawathinehena + TRUETYPE_TAG('n', 'w', 'b', 0), // nwb = Nyabwa + TRUETYPE_TAG('n', 'w', 'c', 0), // nwc = Classical Newari + TRUETYPE_TAG('n', 'w', 'e', 0), // nwe = Ngwe + TRUETYPE_TAG('n', 'w', 'i', 0), // nwi = Southwest Tanna + TRUETYPE_TAG('n', 'w', 'm', 0), // nwm = Nyamusa-Molo + TRUETYPE_TAG('n', 'w', 'r', 0), // nwr = Nawaru + TRUETYPE_TAG('n', 'w', 'x', 0), // nwx = Middle Newar + TRUETYPE_TAG('n', 'w', 'y', 0), // nwy = Nottoway-Meherrin + TRUETYPE_TAG('n', 'x', 'a', 0), // nxa = Nauete + TRUETYPE_TAG('n', 'x', 'd', + 0), // nxd = Ngando (Democratic Republic of Congo) + TRUETYPE_TAG('n', 'x', 'e', 0), // nxe = Nage + TRUETYPE_TAG('n', 'x', 'g', 0), // nxg = Ngad'a + TRUETYPE_TAG('n', 'x', 'i', 0), // nxi = Nindi + TRUETYPE_TAG('n', 'x', 'l', 0), // nxl = South Nuaulu + TRUETYPE_TAG('n', 'x', 'm', 0), // nxm = Numidian + TRUETYPE_TAG('n', 'x', 'n', 0), // nxn = Ngawun + TRUETYPE_TAG('n', 'x', 'q', 0), // nxq = Naxi + TRUETYPE_TAG('n', 'x', 'r', 0), // nxr = Ninggerum + TRUETYPE_TAG('n', 'x', 'u', 0), // nxu = Narau + TRUETYPE_TAG('n', 'x', 'x', 0), // nxx = Nafri + TRUETYPE_TAG('n', 'y', 'b', 0), // nyb = Nyangbo + TRUETYPE_TAG('n', 'y', 'c', 0), // nyc = Nyanga-li + TRUETYPE_TAG('n', 'y', 'd', 0), // nyd = Nyore + TRUETYPE_TAG('n', 'y', 'e', 0), // nye = Nyengo + TRUETYPE_TAG('n', 'y', 'f', 0), // nyf = Giryama + TRUETYPE_TAG('n', 'y', 'g', 0), // nyg = Nyindu + TRUETYPE_TAG('n', 'y', 'h', 0), // nyh = Nyigina + TRUETYPE_TAG('n', 'y', 'i', 0), // nyi = Ama (Sudan) + TRUETYPE_TAG('n', 'y', 'j', 0), // nyj = Nyanga + TRUETYPE_TAG('n', 'y', 'k', 0), // nyk = Nyaneka + TRUETYPE_TAG('n', 'y', 'l', 0), // nyl = Nyeu + TRUETYPE_TAG('n', 'y', 'm', 0), // nym = Nyamwezi + TRUETYPE_TAG('n', 'y', 'n', 0), // nyn = Nyankole + TRUETYPE_TAG('n', 'y', 'o', 0), // nyo = Nyoro + TRUETYPE_TAG('n', 'y', 'p', 0), // nyp = Nyang'i + TRUETYPE_TAG('n', 'y', 'q', 0), // nyq = Nayini + TRUETYPE_TAG('n', 'y', 'r', 0), // nyr = Nyiha (Malawi) + TRUETYPE_TAG('n', 'y', 's', 0), // nys = Nyunga + TRUETYPE_TAG('n', 'y', 't', 0), // nyt = Nyawaygi + TRUETYPE_TAG('n', 'y', 'u', 0), // nyu = Nyungwe + TRUETYPE_TAG('n', 'y', 'v', 0), // nyv = Nyulnyul + TRUETYPE_TAG('n', 'y', 'w', 0), // nyw = Nyaw + TRUETYPE_TAG('n', 'y', 'x', 0), // nyx = Nganyaywana + TRUETYPE_TAG('n', 'y', 'y', 0), // nyy = Nyakyusa-Ngonde + TRUETYPE_TAG('n', 'z', 'a', 0), // nza = Tigon Mbembe + TRUETYPE_TAG('n', 'z', 'b', 0), // nzb = Njebi + TRUETYPE_TAG('n', 'z', 'i', 0), // nzi = Nzima + TRUETYPE_TAG('n', 'z', 'k', 0), // nzk = Nzakara + TRUETYPE_TAG('n', 'z', 'm', 0), // nzm = Zeme Naga + TRUETYPE_TAG('n', 'z', 's', 0), // nzs = New Zealand Sign Language + TRUETYPE_TAG('n', 'z', 'u', 0), // nzu = Teke-Nzikou + TRUETYPE_TAG('n', 'z', 'y', 0), // nzy = Nzakambay + TRUETYPE_TAG('n', 'z', 'z', 0), // nzz = Nanga Dama Dogon + TRUETYPE_TAG('o', 'a', 'a', 0), // oaa = Orok + TRUETYPE_TAG('o', 'a', 'c', 0), // oac = Oroch + TRUETYPE_TAG('o', 'a', 'r', 0), // oar = Old Aramaic (up to 700 BCE) + TRUETYPE_TAG('o', 'a', 'v', 0), // oav = Old Avar + TRUETYPE_TAG('o', 'b', 'i', 0), // obi = Obispeño + TRUETYPE_TAG('o', 'b', 'k', 0), // obk = Southern Bontok + TRUETYPE_TAG('o', 'b', 'l', 0), // obl = Oblo + TRUETYPE_TAG('o', 'b', 'm', 0), // obm = Moabite + TRUETYPE_TAG('o', 'b', 'o', 0), // obo = Obo Manobo + TRUETYPE_TAG('o', 'b', 'r', 0), // obr = Old Burmese + TRUETYPE_TAG('o', 'b', 't', 0), // obt = Old Breton + TRUETYPE_TAG('o', 'b', 'u', 0), // obu = Obulom + TRUETYPE_TAG('o', 'c', 'a', 0), // oca = Ocaina + TRUETYPE_TAG('o', 'c', 'h', 0), // och = Old Chinese + TRUETYPE_TAG('o', 'c', 'o', 0), // oco = Old Cornish + TRUETYPE_TAG('o', 'c', 'u', 0), // ocu = Atzingo Matlatzinca + TRUETYPE_TAG('o', 'd', 'a', 0), // oda = Odut + TRUETYPE_TAG('o', 'd', 'k', 0), // odk = Od + TRUETYPE_TAG('o', 'd', 't', 0), // odt = Old Dutch + TRUETYPE_TAG('o', 'd', 'u', 0), // odu = Odual + TRUETYPE_TAG('o', 'f', 'o', 0), // ofo = Ofo + TRUETYPE_TAG('o', 'f', 's', 0), // ofs = Old Frisian + TRUETYPE_TAG('o', 'f', 'u', 0), // ofu = Efutop + TRUETYPE_TAG('o', 'g', 'b', 0), // ogb = Ogbia + TRUETYPE_TAG('o', 'g', 'c', 0), // ogc = Ogbah + TRUETYPE_TAG('o', 'g', 'e', 0), // oge = Old Georgian + TRUETYPE_TAG('o', 'g', 'g', 0), // ogg = Ogbogolo + TRUETYPE_TAG('o', 'g', 'o', 0), // ogo = Khana + TRUETYPE_TAG('o', 'g', 'u', 0), // ogu = Ogbronuagum + TRUETYPE_TAG('o', 'h', 't', 0), // oht = Old Hittite + TRUETYPE_TAG('o', 'h', 'u', 0), // ohu = Old Hungarian + TRUETYPE_TAG('o', 'i', 'a', 0), // oia = Oirata + TRUETYPE_TAG('o', 'i', 'n', 0), // oin = Inebu One + TRUETYPE_TAG('o', 'j', 'b', 0), // ojb = Northwestern Ojibwa + TRUETYPE_TAG('o', 'j', 'c', 0), // ojc = Central Ojibwa + TRUETYPE_TAG('o', 'j', 'g', 0), // ojg = Eastern Ojibwa + TRUETYPE_TAG('o', 'j', 'p', 0), // ojp = Old Japanese + TRUETYPE_TAG('o', 'j', 's', 0), // ojs = Severn Ojibwa + TRUETYPE_TAG('o', 'j', 'v', 0), // ojv = Ontong Java + TRUETYPE_TAG('o', 'j', 'w', 0), // ojw = Western Ojibwa + TRUETYPE_TAG('o', 'k', 'a', 0), // oka = Okanagan + TRUETYPE_TAG('o', 'k', 'b', 0), // okb = Okobo + TRUETYPE_TAG('o', 'k', 'd', 0), // okd = Okodia + TRUETYPE_TAG('o', 'k', 'e', 0), // oke = Okpe (Southwestern Edo) + TRUETYPE_TAG('o', 'k', 'h', 0), // okh = Koresh-e Rostam + TRUETYPE_TAG('o', 'k', 'i', 0), // oki = Okiek + TRUETYPE_TAG('o', 'k', 'j', 0), // okj = Oko-Juwoi + TRUETYPE_TAG('o', 'k', 'k', 0), // okk = Kwamtim One + TRUETYPE_TAG('o', 'k', 'l', 0), // okl = Old Kentish Sign Language + TRUETYPE_TAG('o', 'k', 'm', 0), // okm = Middle Korean (10th-16th cent.) + TRUETYPE_TAG('o', 'k', 'n', 0), // okn = Oki-No-Erabu + TRUETYPE_TAG('o', 'k', 'o', 0), // oko = Old Korean (3rd-9th cent.) + TRUETYPE_TAG('o', 'k', 'r', 0), // okr = Kirike + TRUETYPE_TAG('o', 'k', 's', 0), // oks = Oko-Eni-Osayen + TRUETYPE_TAG('o', 'k', 'u', 0), // oku = Oku + TRUETYPE_TAG('o', 'k', 'v', 0), // okv = Orokaiva + TRUETYPE_TAG('o', 'k', 'x', 0), // okx = Okpe (Northwestern Edo) + TRUETYPE_TAG('o', 'l', 'a', 0), // ola = Walungge + TRUETYPE_TAG('o', 'l', 'd', 0), // old = Mochi + TRUETYPE_TAG('o', 'l', 'e', 0), // ole = Olekha + TRUETYPE_TAG('o', 'l', 'm', 0), // olm = Oloma + TRUETYPE_TAG('o', 'l', 'o', 0), // olo = Livvi + TRUETYPE_TAG('o', 'l', 'r', 0), // olr = Olrat + TRUETYPE_TAG('o', 'm', 'a', 0), // oma = Omaha-Ponca + TRUETYPE_TAG('o', 'm', 'b', 0), // omb = East Ambae + TRUETYPE_TAG('o', 'm', 'c', 0), // omc = Mochica + TRUETYPE_TAG('o', 'm', 'e', 0), // ome = Omejes + TRUETYPE_TAG('o', 'm', 'g', 0), // omg = Omagua + TRUETYPE_TAG('o', 'm', 'i', 0), // omi = Omi + TRUETYPE_TAG('o', 'm', 'k', 0), // omk = Omok + TRUETYPE_TAG('o', 'm', 'l', 0), // oml = Ombo + TRUETYPE_TAG('o', 'm', 'n', 0), // omn = Minoan + TRUETYPE_TAG('o', 'm', 'o', 0), // omo = Utarmbung + TRUETYPE_TAG('o', 'm', 'p', 0), // omp = Old Manipuri + TRUETYPE_TAG('o', 'm', 'q', 0), // omq = Oto-Manguean languages + TRUETYPE_TAG('o', 'm', 'r', 0), // omr = Old Marathi + TRUETYPE_TAG('o', 'm', 't', 0), // omt = Omotik + TRUETYPE_TAG('o', 'm', 'u', 0), // omu = Omurano + TRUETYPE_TAG('o', 'm', 'v', 0), // omv = Omotic languages + TRUETYPE_TAG('o', 'm', 'w', 0), // omw = South Tairora + TRUETYPE_TAG('o', 'm', 'x', 0), // omx = Old Mon + TRUETYPE_TAG('o', 'n', 'a', 0), // ona = Ona + TRUETYPE_TAG('o', 'n', 'b', 0), // onb = Lingao + TRUETYPE_TAG('o', 'n', 'e', 0), // one = Oneida + TRUETYPE_TAG('o', 'n', 'g', 0), // ong = Olo + TRUETYPE_TAG('o', 'n', 'i', 0), // oni = Onin + TRUETYPE_TAG('o', 'n', 'j', 0), // onj = Onjob + TRUETYPE_TAG('o', 'n', 'k', 0), // onk = Kabore One + TRUETYPE_TAG('o', 'n', 'n', 0), // onn = Onobasulu + TRUETYPE_TAG('o', 'n', 'o', 0), // ono = Onondaga + TRUETYPE_TAG('o', 'n', 'p', 0), // onp = Sartang + TRUETYPE_TAG('o', 'n', 'r', 0), // onr = Northern One + TRUETYPE_TAG('o', 'n', 's', 0), // ons = Ono + TRUETYPE_TAG('o', 'n', 't', 0), // ont = Ontenu + TRUETYPE_TAG('o', 'n', 'u', 0), // onu = Unua + TRUETYPE_TAG('o', 'n', 'w', 0), // onw = Old Nubian + TRUETYPE_TAG('o', 'n', 'x', 0), // onx = Onin Based Pidgin + TRUETYPE_TAG('o', 'o', 'd', 0), // ood = Tohono O'odham + TRUETYPE_TAG('o', 'o', 'g', 0), // oog = Ong + TRUETYPE_TAG('o', 'o', 'n', 0), // oon = Önge + TRUETYPE_TAG('o', 'o', 'r', 0), // oor = Oorlams + TRUETYPE_TAG('o', 'o', 's', 0), // oos = Old Ossetic + TRUETYPE_TAG('o', 'p', 'a', 0), // opa = Okpamheri + TRUETYPE_TAG('o', 'p', 'k', 0), // opk = Kopkaka + TRUETYPE_TAG('o', 'p', 'm', 0), // opm = Oksapmin + TRUETYPE_TAG('o', 'p', 'o', 0), // opo = Opao + TRUETYPE_TAG('o', 'p', 't', 0), // opt = Opata + TRUETYPE_TAG('o', 'p', 'y', 0), // opy = Ofayé + TRUETYPE_TAG('o', 'r', 'a', 0), // ora = Oroha + TRUETYPE_TAG('o', 'r', 'c', 0), // orc = Orma + TRUETYPE_TAG('o', 'r', 'e', 0), // ore = Orejón + TRUETYPE_TAG('o', 'r', 'g', 0), // org = Oring + TRUETYPE_TAG('o', 'r', 'h', 0), // orh = Oroqen + TRUETYPE_TAG('o', 'r', 'n', 0), // orn = Orang Kanaq + TRUETYPE_TAG('o', 'r', 'o', 0), // oro = Orokolo + TRUETYPE_TAG('o', 'r', 'r', 0), // orr = Oruma + TRUETYPE_TAG('o', 'r', 's', 0), // ors = Orang Seletar + TRUETYPE_TAG('o', 'r', 't', 0), // ort = Adivasi Oriya + TRUETYPE_TAG('o', 'r', 'u', 0), // oru = Ormuri + TRUETYPE_TAG('o', 'r', 'v', 0), // orv = Old Russian + TRUETYPE_TAG('o', 'r', 'w', 0), // orw = Oro Win + TRUETYPE_TAG('o', 'r', 'x', 0), // orx = Oro + TRUETYPE_TAG('o', 'r', 'z', 0), // orz = Ormu + TRUETYPE_TAG('o', 's', 'a', 0), // osa = Osage + TRUETYPE_TAG('o', 's', 'c', 0), // osc = Oscan + TRUETYPE_TAG('o', 's', 'i', 0), // osi = Osing + TRUETYPE_TAG('o', 's', 'o', 0), // oso = Ososo + TRUETYPE_TAG('o', 's', 'p', 0), // osp = Old Spanish + TRUETYPE_TAG('o', 's', 't', 0), // ost = Osatu + TRUETYPE_TAG('o', 's', 'u', 0), // osu = Southern One + TRUETYPE_TAG('o', 's', 'x', 0), // osx = Old Saxon + TRUETYPE_TAG('o', 't', 'a', 0), // ota = Ottoman Turkish (1500-1928) + TRUETYPE_TAG('o', 't', 'b', 0), // otb = Old Tibetan + TRUETYPE_TAG('o', 't', 'd', 0), // otd = Ot Danum + TRUETYPE_TAG('o', 't', 'e', 0), // ote = Mezquital Otomi + TRUETYPE_TAG('o', 't', 'i', 0), // oti = Oti + TRUETYPE_TAG('o', 't', 'k', 0), // otk = Old Turkish + TRUETYPE_TAG('o', 't', 'l', 0), // otl = Tilapa Otomi + TRUETYPE_TAG('o', 't', 'm', 0), // otm = Eastern Highland Otomi + TRUETYPE_TAG('o', 't', 'n', 0), // otn = Tenango Otomi + TRUETYPE_TAG('o', 't', 'o', 0), // oto = Otomian languages + TRUETYPE_TAG('o', 't', 'q', 0), // otq = Querétaro Otomi + TRUETYPE_TAG('o', 't', 'r', 0), // otr = Otoro + TRUETYPE_TAG('o', 't', 's', 0), // ots = Estado de México Otomi + TRUETYPE_TAG('o', 't', 't', 0), // ott = Temoaya Otomi + TRUETYPE_TAG('o', 't', 'u', 0), // otu = Otuke + TRUETYPE_TAG('o', 't', 'w', 0), // otw = Ottawa + TRUETYPE_TAG('o', 't', 'x', 0), // otx = Texcatepec Otomi + TRUETYPE_TAG('o', 't', 'y', 0), // oty = Old Tamil + TRUETYPE_TAG('o', 't', 'z', 0), // otz = Ixtenco Otomi + TRUETYPE_TAG('o', 'u', 'a', 0), // oua = Tagargrent + TRUETYPE_TAG('o', 'u', 'b', 0), // oub = Glio-Oubi + TRUETYPE_TAG('o', 'u', 'e', 0), // oue = Oune + TRUETYPE_TAG('o', 'u', 'i', 0), // oui = Old Uighur + TRUETYPE_TAG('o', 'u', 'm', 0), // oum = Ouma + TRUETYPE_TAG('o', 'u', 'n', 0), // oun = !O!ung + TRUETYPE_TAG('o', 'w', 'i', 0), // owi = Owiniga + TRUETYPE_TAG('o', 'w', 'l', 0), // owl = Old Welsh + TRUETYPE_TAG('o', 'y', 'b', 0), // oyb = Oy + TRUETYPE_TAG('o', 'y', 'd', 0), // oyd = Oyda + TRUETYPE_TAG('o', 'y', 'm', 0), // oym = Wayampi + TRUETYPE_TAG('o', 'y', 'y', 0), // oyy = Oya'oya + TRUETYPE_TAG('o', 'z', 'm', 0), // ozm = Koonzime + TRUETYPE_TAG('p', 'a', 'a', 0), // paa = Papuan languages + TRUETYPE_TAG('p', 'a', 'b', 0), // pab = Parecís + TRUETYPE_TAG('p', 'a', 'c', 0), // pac = Pacoh + TRUETYPE_TAG('p', 'a', 'd', 0), // pad = Paumarí + TRUETYPE_TAG('p', 'a', 'e', 0), // pae = Pagibete + TRUETYPE_TAG('p', 'a', 'f', 0), // paf = Paranawát + TRUETYPE_TAG('p', 'a', 'g', 0), // pag = Pangasinan + TRUETYPE_TAG('p', 'a', 'h', 0), // pah = Tenharim + TRUETYPE_TAG('p', 'a', 'i', 0), // pai = Pe + TRUETYPE_TAG('p', 'a', 'k', 0), // pak = Parakanã + TRUETYPE_TAG('p', 'a', 'l', 0), // pal = Pahlavi + TRUETYPE_TAG('p', 'a', 'm', 0), // pam = Pampanga + TRUETYPE_TAG('p', 'a', 'o', 0), // pao = Northern Paiute + TRUETYPE_TAG('p', 'a', 'p', 0), // pap = Papiamento + TRUETYPE_TAG('p', 'a', 'q', 0), // paq = Parya + TRUETYPE_TAG('p', 'a', 'r', 0), // par = Panamint + TRUETYPE_TAG('p', 'a', 's', 0), // pas = Papasena + TRUETYPE_TAG('p', 'a', 't', 0), // pat = Papitalai + TRUETYPE_TAG('p', 'a', 'u', 0), // pau = Palauan + TRUETYPE_TAG('p', 'a', 'v', 0), // pav = Pakaásnovos + TRUETYPE_TAG('p', 'a', 'w', 0), // paw = Pawnee + TRUETYPE_TAG('p', 'a', 'x', 0), // pax = Pankararé + TRUETYPE_TAG('p', 'a', 'y', 0), // pay = Pech + TRUETYPE_TAG('p', 'a', 'z', 0), // paz = Pankararú + TRUETYPE_TAG('p', 'b', 'b', 0), // pbb = Páez + TRUETYPE_TAG('p', 'b', 'c', 0), // pbc = Patamona + TRUETYPE_TAG('p', 'b', 'e', 0), // pbe = Mezontla Popoloca + TRUETYPE_TAG('p', 'b', 'f', 0), // pbf = Coyotepec Popoloca + TRUETYPE_TAG('p', 'b', 'g', 0), // pbg = Paraujano + TRUETYPE_TAG('p', 'b', 'h', 0), // pbh = E'ñapa Woromaipu + TRUETYPE_TAG('p', 'b', 'i', 0), // pbi = Parkwa + TRUETYPE_TAG('p', 'b', 'l', 0), // pbl = Mak (Nigeria) + TRUETYPE_TAG('p', 'b', 'n', 0), // pbn = Kpasam + TRUETYPE_TAG('p', 'b', 'o', 0), // pbo = Papel + TRUETYPE_TAG('p', 'b', 'p', 0), // pbp = Badyara + TRUETYPE_TAG('p', 'b', 'r', 0), // pbr = Pangwa + TRUETYPE_TAG('p', 'b', 's', 0), // pbs = Central Pame + TRUETYPE_TAG('p', 'b', 't', 0), // pbt = Southern Pashto + TRUETYPE_TAG('p', 'b', 'u', 0), // pbu = Northern Pashto + TRUETYPE_TAG('p', 'b', 'v', 0), // pbv = Pnar + TRUETYPE_TAG('p', 'b', 'y', 0), // pby = Pyu + TRUETYPE_TAG('p', 'b', 'z', 0), // pbz = Palu + TRUETYPE_TAG('p', 'c', 'a', 0), // pca = Santa Inés Ahuatempan Popoloca + TRUETYPE_TAG('p', 'c', 'b', 0), // pcb = Pear + TRUETYPE_TAG('p', 'c', 'c', 0), // pcc = Bouyei + TRUETYPE_TAG('p', 'c', 'd', 0), // pcd = Picard + TRUETYPE_TAG('p', 'c', 'e', 0), // pce = Ruching Palaung + TRUETYPE_TAG('p', 'c', 'f', 0), // pcf = Paliyan + TRUETYPE_TAG('p', 'c', 'g', 0), // pcg = Paniya + TRUETYPE_TAG('p', 'c', 'h', 0), // pch = Pardhan + TRUETYPE_TAG('p', 'c', 'i', 0), // pci = Duruwa + TRUETYPE_TAG('p', 'c', 'j', 0), // pcj = Parenga + TRUETYPE_TAG('p', 'c', 'k', 0), // pck = Paite Chin + TRUETYPE_TAG('p', 'c', 'l', 0), // pcl = Pardhi + TRUETYPE_TAG('p', 'c', 'm', 0), // pcm = Nigerian Pidgin + TRUETYPE_TAG('p', 'c', 'n', 0), // pcn = Piti + TRUETYPE_TAG('p', 'c', 'p', 0), // pcp = Pacahuara + TRUETYPE_TAG('p', 'c', 'r', 0), // pcr = Panang + TRUETYPE_TAG('p', 'c', 'w', 0), // pcw = Pyapun + TRUETYPE_TAG('p', 'd', 'a', 0), // pda = Anam + TRUETYPE_TAG('p', 'd', 'c', 0), // pdc = Pennsylvania German + TRUETYPE_TAG('p', 'd', 'i', 0), // pdi = Pa Di + TRUETYPE_TAG('p', 'd', 'n', 0), // pdn = Podena + TRUETYPE_TAG('p', 'd', 'o', 0), // pdo = Padoe + TRUETYPE_TAG('p', 'd', 't', 0), // pdt = Plautdietsch + TRUETYPE_TAG('p', 'd', 'u', 0), // pdu = Kayan + TRUETYPE_TAG('p', 'e', 'a', 0), // pea = Peranakan Indonesian + TRUETYPE_TAG('p', 'e', 'b', 0), // peb = Eastern Pomo + TRUETYPE_TAG('p', 'e', 'd', 0), // ped = Mala (Papua New Guinea) + TRUETYPE_TAG('p', 'e', 'e', 0), // pee = Taje + TRUETYPE_TAG('p', 'e', 'f', 0), // pef = Northeastern Pomo + TRUETYPE_TAG('p', 'e', 'g', 0), // peg = Pengo + TRUETYPE_TAG('p', 'e', 'h', 0), // peh = Bonan + TRUETYPE_TAG('p', 'e', 'i', 0), // pei = Chichimeca-Jonaz + TRUETYPE_TAG('p', 'e', 'j', 0), // pej = Northern Pomo + TRUETYPE_TAG('p', 'e', 'k', 0), // pek = Penchal + TRUETYPE_TAG('p', 'e', 'l', 0), // pel = Pekal + TRUETYPE_TAG('p', 'e', 'm', 0), // pem = Phende + TRUETYPE_TAG('p', 'e', 'o', 0), // peo = Old Persian (ca. 600-400 B.C.) + TRUETYPE_TAG('p', 'e', 'p', 0), // pep = Kunja + TRUETYPE_TAG('p', 'e', 'q', 0), // peq = Southern Pomo + TRUETYPE_TAG('p', 'e', 's', 0), // pes = Iranian Persian + TRUETYPE_TAG('p', 'e', 'v', 0), // pev = Pémono + TRUETYPE_TAG('p', 'e', 'x', 0), // pex = Petats + TRUETYPE_TAG('p', 'e', 'y', 0), // pey = Petjo + TRUETYPE_TAG('p', 'e', 'z', 0), // pez = Eastern Penan + TRUETYPE_TAG('p', 'f', 'a', 0), // pfa = Pááfang + TRUETYPE_TAG('p', 'f', 'e', 0), // pfe = Peere + TRUETYPE_TAG('p', 'f', 'l', 0), // pfl = Pfaelzisch + TRUETYPE_TAG('p', 'g', 'a', 0), // pga = Sudanese Creole Arabic + TRUETYPE_TAG('p', 'g', 'g', 0), // pgg = Pangwali + TRUETYPE_TAG('p', 'g', 'i', 0), // pgi = Pagi + TRUETYPE_TAG('p', 'g', 'k', 0), // pgk = Rerep + TRUETYPE_TAG('p', 'g', 'l', 0), // pgl = Primitive Irish + TRUETYPE_TAG('p', 'g', 'n', 0), // pgn = Paelignian + TRUETYPE_TAG('p', 'g', 's', 0), // pgs = Pangseng + TRUETYPE_TAG('p', 'g', 'u', 0), // pgu = Pagu + TRUETYPE_TAG('p', 'g', 'y', 0), // pgy = Pongyong + TRUETYPE_TAG('p', 'h', 'a', 0), // pha = Pa-Hng + TRUETYPE_TAG('p', 'h', 'd', 0), // phd = Phudagi + TRUETYPE_TAG('p', 'h', 'g', 0), // phg = Phuong + TRUETYPE_TAG('p', 'h', 'h', 0), // phh = Phukha + TRUETYPE_TAG('p', 'h', 'i', 0), // phi = Philippine languages + TRUETYPE_TAG('p', 'h', 'k', 0), // phk = Phake + TRUETYPE_TAG('p', 'h', 'l', 0), // phl = Phalura + TRUETYPE_TAG('p', 'h', 'm', 0), // phm = Phimbi + TRUETYPE_TAG('p', 'h', 'n', 0), // phn = Phoenician + TRUETYPE_TAG('p', 'h', 'o', 0), // pho = Phunoi + TRUETYPE_TAG('p', 'h', 'q', 0), // phq = Phana' + TRUETYPE_TAG('p', 'h', 'r', 0), // phr = Pahari-Potwari + TRUETYPE_TAG('p', 'h', 't', 0), // pht = Phu Thai + TRUETYPE_TAG('p', 'h', 'u', 0), // phu = Phuan + TRUETYPE_TAG('p', 'h', 'v', 0), // phv = Pahlavani + TRUETYPE_TAG('p', 'h', 'w', 0), // phw = Phangduwali + TRUETYPE_TAG('p', 'i', 'a', 0), // pia = Pima Bajo + TRUETYPE_TAG('p', 'i', 'b', 0), // pib = Yine + TRUETYPE_TAG('p', 'i', 'c', 0), // pic = Pinji + TRUETYPE_TAG('p', 'i', 'd', 0), // pid = Piaroa + TRUETYPE_TAG('p', 'i', 'e', 0), // pie = Piro + TRUETYPE_TAG('p', 'i', 'f', 0), // pif = Pingelapese + TRUETYPE_TAG('p', 'i', 'g', 0), // pig = Pisabo + TRUETYPE_TAG('p', 'i', 'h', 0), // pih = Pitcairn-Norfolk + TRUETYPE_TAG('p', 'i', 'i', 0), // pii = Pini + TRUETYPE_TAG('p', 'i', 'j', 0), // pij = Pijao + TRUETYPE_TAG('p', 'i', 'l', 0), // pil = Yom + TRUETYPE_TAG('p', 'i', 'm', 0), // pim = Powhatan + TRUETYPE_TAG('p', 'i', 'n', 0), // pin = Piame + TRUETYPE_TAG('p', 'i', 'o', 0), // pio = Piapoco + TRUETYPE_TAG('p', 'i', 'p', 0), // pip = Pero + TRUETYPE_TAG('p', 'i', 'r', 0), // pir = Piratapuyo + TRUETYPE_TAG('p', 'i', 's', 0), // pis = Pijin + TRUETYPE_TAG('p', 'i', 't', 0), // pit = Pitta Pitta + TRUETYPE_TAG('p', 'i', 'u', 0), // piu = Pintupi-Luritja + TRUETYPE_TAG('p', 'i', 'v', 0), // piv = Pileni + TRUETYPE_TAG('p', 'i', 'w', 0), // piw = Pimbwe + TRUETYPE_TAG('p', 'i', 'x', 0), // pix = Piu + TRUETYPE_TAG('p', 'i', 'y', 0), // piy = Piya-Kwonci + TRUETYPE_TAG('p', 'i', 'z', 0), // piz = Pije + TRUETYPE_TAG('p', 'j', 't', 0), // pjt = Pitjantjatjara + TRUETYPE_TAG('p', 'k', 'a', 0), // pka = Ardhamāgadhī Prākrit + TRUETYPE_TAG('p', 'k', 'b', 0), // pkb = Pokomo + TRUETYPE_TAG('p', 'k', 'c', 0), // pkc = Paekche + TRUETYPE_TAG('p', 'k', 'g', 0), // pkg = Pak-Tong + TRUETYPE_TAG('p', 'k', 'h', 0), // pkh = Pankhu + TRUETYPE_TAG('p', 'k', 'n', 0), // pkn = Pakanha + TRUETYPE_TAG('p', 'k', 'o', 0), // pko = Pökoot + TRUETYPE_TAG('p', 'k', 'p', 0), // pkp = Pukapuka + TRUETYPE_TAG('p', 'k', 'r', 0), // pkr = Attapady Kurumba + TRUETYPE_TAG('p', 'k', 's', 0), // pks = Pakistan Sign Language + TRUETYPE_TAG('p', 'k', 't', 0), // pkt = Maleng + TRUETYPE_TAG('p', 'k', 'u', 0), // pku = Paku + TRUETYPE_TAG('p', 'l', 'a', 0), // pla = Miani + TRUETYPE_TAG('p', 'l', 'b', 0), // plb = Polonombauk + TRUETYPE_TAG('p', 'l', 'c', 0), // plc = Central Palawano + TRUETYPE_TAG('p', 'l', 'd', 0), // pld = Polari + TRUETYPE_TAG('p', 'l', 'e', 0), // ple = Palu'e + TRUETYPE_TAG('p', 'l', 'f', + 0), // plf = Central Malayo-Polynesian languages + TRUETYPE_TAG('p', 'l', 'g', 0), // plg = Pilagá + TRUETYPE_TAG('p', 'l', 'h', 0), // plh = Paulohi + TRUETYPE_TAG('p', 'l', 'j', 0), // plj = Polci + TRUETYPE_TAG('p', 'l', 'k', 0), // plk = Kohistani Shina + TRUETYPE_TAG('p', 'l', 'l', 0), // pll = Shwe Palaung + TRUETYPE_TAG('p', 'l', 'n', 0), // pln = Palenquero + TRUETYPE_TAG('p', 'l', 'o', 0), // plo = Oluta Popoluca + TRUETYPE_TAG('p', 'l', 'p', 0), // plp = Palpa + TRUETYPE_TAG('p', 'l', 'q', 0), // plq = Palaic + TRUETYPE_TAG('p', 'l', 'r', 0), // plr = Palaka Senoufo + TRUETYPE_TAG('p', 'l', 's', 0), // pls = San Marcos Tlalcoyalco Popoloca + TRUETYPE_TAG('p', 'l', 't', 0), // plt = Plateau Malagasy + TRUETYPE_TAG('p', 'l', 'u', 0), // plu = Palikúr + TRUETYPE_TAG('p', 'l', 'v', 0), // plv = Southwest Palawano + TRUETYPE_TAG('p', 'l', 'w', 0), // plw = Brooke's Point Palawano + TRUETYPE_TAG('p', 'l', 'y', 0), // ply = Bolyu + TRUETYPE_TAG('p', 'l', 'z', 0), // plz = Paluan + TRUETYPE_TAG('p', 'm', 'a', 0), // pma = Paama + TRUETYPE_TAG('p', 'm', 'b', 0), // pmb = Pambia + TRUETYPE_TAG('p', 'm', 'c', 0), // pmc = Palumata + TRUETYPE_TAG('p', 'm', 'e', 0), // pme = Pwaamei + TRUETYPE_TAG('p', 'm', 'f', 0), // pmf = Pamona + TRUETYPE_TAG('p', 'm', 'h', 0), // pmh = Māhārāṣṭri Prākrit + TRUETYPE_TAG('p', 'm', 'i', 0), // pmi = Northern Pumi + TRUETYPE_TAG('p', 'm', 'j', 0), // pmj = Southern Pumi + TRUETYPE_TAG('p', 'm', 'k', 0), // pmk = Pamlico + TRUETYPE_TAG('p', 'm', 'l', 0), // pml = Lingua Franca + TRUETYPE_TAG('p', 'm', 'm', 0), // pmm = Pomo + TRUETYPE_TAG('p', 'm', 'n', 0), // pmn = Pam + TRUETYPE_TAG('p', 'm', 'o', 0), // pmo = Pom + TRUETYPE_TAG('p', 'm', 'q', 0), // pmq = Northern Pame + TRUETYPE_TAG('p', 'm', 'r', 0), // pmr = Paynamar + TRUETYPE_TAG('p', 'm', 's', 0), // pms = Piemontese + TRUETYPE_TAG('p', 'm', 't', 0), // pmt = Tuamotuan + TRUETYPE_TAG('p', 'm', 'u', 0), // pmu = Mirpur Panjabi + TRUETYPE_TAG('p', 'm', 'w', 0), // pmw = Plains Miwok + TRUETYPE_TAG('p', 'm', 'x', 0), // pmx = Poumei Naga + TRUETYPE_TAG('p', 'm', 'y', 0), // pmy = Papuan Malay + TRUETYPE_TAG('p', 'm', 'z', 0), // pmz = Southern Pame + TRUETYPE_TAG('p', 'n', 'a', 0), // pna = Punan Bah-Biau + TRUETYPE_TAG('p', 'n', 'b', 0), // pnb = Western Panjabi + TRUETYPE_TAG('p', 'n', 'c', 0), // pnc = Pannei + TRUETYPE_TAG('p', 'n', 'e', 0), // pne = Western Penan + TRUETYPE_TAG('p', 'n', 'g', 0), // png = Pongu + TRUETYPE_TAG('p', 'n', 'h', 0), // pnh = Penrhyn + TRUETYPE_TAG('p', 'n', 'i', 0), // pni = Aoheng + TRUETYPE_TAG('p', 'n', 'm', 0), // pnm = Punan Batu 1 + TRUETYPE_TAG('p', 'n', 'n', 0), // pnn = Pinai-Hagahai + TRUETYPE_TAG('p', 'n', 'o', 0), // pno = Panobo + TRUETYPE_TAG('p', 'n', 'p', 0), // pnp = Pancana + TRUETYPE_TAG('p', 'n', 'q', 0), // pnq = Pana (Burkina Faso) + TRUETYPE_TAG('p', 'n', 'r', 0), // pnr = Panim + TRUETYPE_TAG('p', 'n', 's', 0), // pns = Ponosakan + TRUETYPE_TAG('p', 'n', 't', 0), // pnt = Pontic + TRUETYPE_TAG('p', 'n', 'u', 0), // pnu = Jiongnai Bunu + TRUETYPE_TAG('p', 'n', 'v', 0), // pnv = Pinigura + TRUETYPE_TAG('p', 'n', 'w', 0), // pnw = Panytyima + TRUETYPE_TAG('p', 'n', 'x', 0), // pnx = Phong-Kniang + TRUETYPE_TAG('p', 'n', 'y', 0), // pny = Pinyin + TRUETYPE_TAG('p', 'n', 'z', 0), // pnz = Pana (Central African Republic) + TRUETYPE_TAG('p', 'o', 'c', 0), // poc = Poqomam + TRUETYPE_TAG('p', 'o', 'd', 0), // pod = Ponares + TRUETYPE_TAG('p', 'o', 'e', 0), // poe = San Juan Atzingo Popoloca + TRUETYPE_TAG('p', 'o', 'f', 0), // pof = Poke + TRUETYPE_TAG('p', 'o', 'g', 0), // pog = Potiguára + TRUETYPE_TAG('p', 'o', 'h', 0), // poh = Poqomchi' + TRUETYPE_TAG('p', 'o', 'i', 0), // poi = Highland Popoluca + TRUETYPE_TAG('p', 'o', 'k', 0), // pok = Pokangá + TRUETYPE_TAG('p', 'o', 'm', 0), // pom = Southeastern Pomo + TRUETYPE_TAG('p', 'o', 'n', 0), // pon = Pohnpeian + TRUETYPE_TAG('p', 'o', 'o', 0), // poo = Central Pomo + TRUETYPE_TAG('p', 'o', 'p', 0), // pop = Pwapwa + TRUETYPE_TAG('p', 'o', 'q', 0), // poq = Texistepec Popoluca + TRUETYPE_TAG('p', 'o', 's', 0), // pos = Sayula Popoluca + TRUETYPE_TAG('p', 'o', 't', 0), // pot = Potawatomi + TRUETYPE_TAG('p', 'o', 'v', 0), // pov = Upper Guinea Crioulo + TRUETYPE_TAG('p', 'o', 'w', 0), // pow = San Felipe Otlaltepec Popoloca + TRUETYPE_TAG('p', 'o', 'x', 0), // pox = Polabian + TRUETYPE_TAG('p', 'o', 'y', 0), // poy = Pogolo + TRUETYPE_TAG('p', 'o', 'z', 0), // poz = Malayo-Polynesian languages + TRUETYPE_TAG('p', 'p', 'a', 0), // ppa = Pao + TRUETYPE_TAG('p', 'p', 'e', 0), // ppe = Papi + TRUETYPE_TAG('p', 'p', 'i', 0), // ppi = Paipai + TRUETYPE_TAG('p', 'p', 'k', 0), // ppk = Uma + TRUETYPE_TAG('p', 'p', 'l', 0), // ppl = Pipil + TRUETYPE_TAG('p', 'p', 'm', 0), // ppm = Papuma + TRUETYPE_TAG('p', 'p', 'n', 0), // ppn = Papapana + TRUETYPE_TAG('p', 'p', 'o', 0), // ppo = Folopa + TRUETYPE_TAG('p', 'p', 'p', 0), // ppp = Pelende + TRUETYPE_TAG('p', 'p', 'q', 0), // ppq = Pei + TRUETYPE_TAG('p', 'p', 'r', 0), // ppr = Piru + TRUETYPE_TAG('p', 'p', 's', 0), // pps = San Luís Temalacayuca Popoloca + TRUETYPE_TAG('p', 'p', 't', 0), // ppt = Pare + TRUETYPE_TAG('p', 'p', 'u', 0), // ppu = Papora + TRUETYPE_TAG('p', 'q', 'a', 0), // pqa = Pa'a + TRUETYPE_TAG('p', 'q', 'e', + 0), // pqe = Eastern Malayo-Polynesian languages + TRUETYPE_TAG('p', 'q', 'm', 0), // pqm = Malecite-Passamaquoddy + TRUETYPE_TAG('p', 'q', 'w', + 0), // pqw = Western Malayo-Polynesian languages + TRUETYPE_TAG('p', 'r', 'a', 0), // pra = Prakrit languages + TRUETYPE_TAG('p', 'r', 'b', 0), // prb = Lua' + TRUETYPE_TAG('p', 'r', 'c', 0), // prc = Parachi + TRUETYPE_TAG('p', 'r', 'd', 0), // prd = Parsi-Dari + TRUETYPE_TAG('p', 'r', 'e', 0), // pre = Principense + TRUETYPE_TAG('p', 'r', 'f', 0), // prf = Paranan + TRUETYPE_TAG('p', 'r', 'g', 0), // prg = Prussian + TRUETYPE_TAG('p', 'r', 'h', 0), // prh = Porohanon + TRUETYPE_TAG('p', 'r', 'i', 0), // pri = Paicî + TRUETYPE_TAG('p', 'r', 'k', 0), // prk = Parauk + TRUETYPE_TAG('p', 'r', 'l', 0), // prl = Peruvian Sign Language + TRUETYPE_TAG('p', 'r', 'm', 0), // prm = Kibiri + TRUETYPE_TAG('p', 'r', 'n', 0), // prn = Prasuni + TRUETYPE_TAG('p', 'r', 'o', 0), // pro = Old Provençal (to 1500) + TRUETYPE_TAG('p', 'r', 'p', 0), // prp = Parsi + TRUETYPE_TAG('p', 'r', 'q', 0), // prq = Ashéninka Perené + TRUETYPE_TAG('p', 'r', 'r', 0), // prr = Puri + TRUETYPE_TAG('p', 'r', 's', 0), // prs = Dari + TRUETYPE_TAG('p', 'r', 't', 0), // prt = Phai + TRUETYPE_TAG('p', 'r', 'u', 0), // pru = Puragi + TRUETYPE_TAG('p', 'r', 'w', 0), // prw = Parawen + TRUETYPE_TAG('p', 'r', 'x', 0), // prx = Purik + TRUETYPE_TAG('p', 'r', 'y', 0), // pry = Pray 3 + TRUETYPE_TAG('p', 'r', 'z', 0), // prz = Providencia Sign Language + TRUETYPE_TAG('p', 's', 'a', 0), // psa = Asue Awyu + TRUETYPE_TAG('p', 's', 'c', 0), // psc = Persian Sign Language + TRUETYPE_TAG('p', 's', 'd', 0), // psd = Plains Indian Sign Language + TRUETYPE_TAG('p', 's', 'e', 0), // pse = Central Malay + TRUETYPE_TAG('p', 's', 'g', 0), // psg = Penang Sign Language + TRUETYPE_TAG('p', 's', 'h', 0), // psh = Southwest Pashayi + TRUETYPE_TAG('p', 's', 'i', 0), // psi = Southeast Pashayi + TRUETYPE_TAG('p', 's', 'l', 0), // psl = Puerto Rican Sign Language + TRUETYPE_TAG('p', 's', 'm', 0), // psm = Pauserna + TRUETYPE_TAG('p', 's', 'n', 0), // psn = Panasuan + TRUETYPE_TAG('p', 's', 'o', 0), // pso = Polish Sign Language + TRUETYPE_TAG('p', 's', 'p', 0), // psp = Philippine Sign Language + TRUETYPE_TAG('p', 's', 'q', 0), // psq = Pasi + TRUETYPE_TAG('p', 's', 'r', 0), // psr = Portuguese Sign Language + TRUETYPE_TAG('p', 's', 's', 0), // pss = Kaulong + TRUETYPE_TAG('p', 's', 't', 0), // pst = Central Pashto + TRUETYPE_TAG('p', 's', 'u', 0), // psu = Sauraseni Prākrit + TRUETYPE_TAG('p', 's', 'w', 0), // psw = Port Sandwich + TRUETYPE_TAG('p', 's', 'y', 0), // psy = Piscataway + TRUETYPE_TAG('p', 't', 'a', 0), // pta = Pai Tavytera + TRUETYPE_TAG('p', 't', 'h', 0), // pth = Pataxó Hã-Ha-Hãe + TRUETYPE_TAG('p', 't', 'i', 0), // pti = Pintiini + TRUETYPE_TAG('p', 't', 'n', 0), // ptn = Patani + TRUETYPE_TAG('p', 't', 'o', 0), // pto = Zo'é + TRUETYPE_TAG('p', 't', 'p', 0), // ptp = Patep + TRUETYPE_TAG('p', 't', 'r', 0), // ptr = Piamatsina + TRUETYPE_TAG('p', 't', 't', 0), // ptt = Enrekang + TRUETYPE_TAG('p', 't', 'u', 0), // ptu = Bambam + TRUETYPE_TAG('p', 't', 'v', 0), // ptv = Port Vato + TRUETYPE_TAG('p', 't', 'w', 0), // ptw = Pentlatch + TRUETYPE_TAG('p', 't', 'y', 0), // pty = Pathiya + TRUETYPE_TAG('p', 'u', 'a', 0), // pua = Western Highland Purepecha + TRUETYPE_TAG('p', 'u', 'b', 0), // pub = Purum + TRUETYPE_TAG('p', 'u', 'c', 0), // puc = Punan Merap + TRUETYPE_TAG('p', 'u', 'd', 0), // pud = Punan Aput + TRUETYPE_TAG('p', 'u', 'e', 0), // pue = Puelche + TRUETYPE_TAG('p', 'u', 'f', 0), // puf = Punan Merah + TRUETYPE_TAG('p', 'u', 'g', 0), // pug = Phuie + TRUETYPE_TAG('p', 'u', 'i', 0), // pui = Puinave + TRUETYPE_TAG('p', 'u', 'j', 0), // puj = Punan Tubu + TRUETYPE_TAG('p', 'u', 'k', 0), // puk = Pu Ko + TRUETYPE_TAG('p', 'u', 'm', 0), // pum = Puma + TRUETYPE_TAG('p', 'u', 'o', 0), // puo = Puoc + TRUETYPE_TAG('p', 'u', 'p', 0), // pup = Pulabu + TRUETYPE_TAG('p', 'u', 'q', 0), // puq = Puquina + TRUETYPE_TAG('p', 'u', 'r', 0), // pur = Puruborá + TRUETYPE_TAG('p', 'u', 't', 0), // put = Putoh + TRUETYPE_TAG('p', 'u', 'u', 0), // puu = Punu + TRUETYPE_TAG('p', 'u', 'w', 0), // puw = Puluwatese + TRUETYPE_TAG('p', 'u', 'x', 0), // pux = Puare + TRUETYPE_TAG('p', 'u', 'y', 0), // puy = Purisimeño + TRUETYPE_TAG('p', 'u', 'z', 0), // puz = Purum Naga + TRUETYPE_TAG('p', 'w', 'a', 0), // pwa = Pawaia + TRUETYPE_TAG('p', 'w', 'b', 0), // pwb = Panawa + TRUETYPE_TAG('p', 'w', 'g', 0), // pwg = Gapapaiwa + TRUETYPE_TAG('p', 'w', 'm', 0), // pwm = Molbog + TRUETYPE_TAG('p', 'w', 'n', 0), // pwn = Paiwan + TRUETYPE_TAG('p', 'w', 'o', 0), // pwo = Pwo Western Karen + TRUETYPE_TAG('p', 'w', 'r', 0), // pwr = Powari + TRUETYPE_TAG('p', 'w', 'w', 0), // pww = Pwo Northern Karen + TRUETYPE_TAG('p', 'x', 'm', 0), // pxm = Quetzaltepec Mixe + TRUETYPE_TAG('p', 'y', 'e', 0), // pye = Pye Krumen + TRUETYPE_TAG('p', 'y', 'm', 0), // pym = Fyam + TRUETYPE_TAG('p', 'y', 'n', 0), // pyn = Poyanáwa + TRUETYPE_TAG('p', 'y', 's', 0), // pys = Paraguayan Sign Language + TRUETYPE_TAG('p', 'y', 'u', 0), // pyu = Puyuma + TRUETYPE_TAG('p', 'y', 'x', 0), // pyx = Pyu (Myanmar) + TRUETYPE_TAG('p', 'y', 'y', 0), // pyy = Pyen + TRUETYPE_TAG('p', 'z', 'n', 0), // pzn = Para Naga = Private use + TRUETYPE_TAG('q', 'u', 'a', 0), // qua = Quapaw + TRUETYPE_TAG('q', 'u', 'b', 0), // qub = Huallaga Huánuco Quechua + TRUETYPE_TAG('q', 'u', 'c', 0), // quc = K'iche' + TRUETYPE_TAG('q', 'u', 'd', 0), // qud = Calderón Highland Quichua + TRUETYPE_TAG('q', 'u', 'f', 0), // quf = Lambayeque Quechua + TRUETYPE_TAG('q', 'u', 'g', 0), // qug = Chimborazo Highland Quichua + TRUETYPE_TAG('q', 'u', 'h', 0), // quh = South Bolivian Quechua + TRUETYPE_TAG('q', 'u', 'i', 0), // qui = Quileute + TRUETYPE_TAG('q', 'u', 'k', 0), // quk = Chachapoyas Quechua + TRUETYPE_TAG('q', 'u', 'l', 0), // qul = North Bolivian Quechua + TRUETYPE_TAG('q', 'u', 'm', 0), // qum = Sipacapense + TRUETYPE_TAG('q', 'u', 'n', 0), // qun = Quinault + TRUETYPE_TAG('q', 'u', 'p', 0), // qup = Southern Pastaza Quechua + TRUETYPE_TAG('q', 'u', 'q', 0), // quq = Quinqui + TRUETYPE_TAG('q', 'u', 'r', 0), // qur = Yanahuanca Pasco Quechua + TRUETYPE_TAG('q', 'u', 's', 0), // qus = Santiago del Estero Quichua + TRUETYPE_TAG('q', 'u', 'v', 0), // quv = Sacapulteco + TRUETYPE_TAG('q', 'u', 'w', 0), // quw = Tena Lowland Quichua + TRUETYPE_TAG('q', 'u', 'x', 0), // qux = Yauyos Quechua + TRUETYPE_TAG('q', 'u', 'y', 0), // quy = Ayacucho Quechua + TRUETYPE_TAG('q', 'u', 'z', 0), // quz = Cusco Quechua + TRUETYPE_TAG('q', 'v', 'a', 0), // qva = Ambo-Pasco Quechua + TRUETYPE_TAG('q', 'v', 'c', 0), // qvc = Cajamarca Quechua + TRUETYPE_TAG('q', 'v', 'e', 0), // qve = Eastern Apurímac Quechua + TRUETYPE_TAG('q', 'v', 'h', + 0), // qvh = Huamalíes-Dos de Mayo Huánuco Quechua + TRUETYPE_TAG('q', 'v', 'i', 0), // qvi = Imbabura Highland Quichua + TRUETYPE_TAG('q', 'v', 'j', 0), // qvj = Loja Highland Quichua + TRUETYPE_TAG('q', 'v', 'l', 0), // qvl = Cajatambo North Lima Quechua + TRUETYPE_TAG('q', 'v', 'm', + 0), // qvm = Margos-Yarowilca-Lauricocha Quechua + TRUETYPE_TAG('q', 'v', 'n', 0), // qvn = North Junín Quechua + TRUETYPE_TAG('q', 'v', 'o', 0), // qvo = Napo Lowland Quechua + TRUETYPE_TAG('q', 'v', 'p', 0), // qvp = Pacaraos Quechua + TRUETYPE_TAG('q', 'v', 's', 0), // qvs = San Martín Quechua + TRUETYPE_TAG('q', 'v', 'w', 0), // qvw = Huaylla Wanca Quechua + TRUETYPE_TAG('q', 'v', 'y', 0), // qvy = Queyu + TRUETYPE_TAG('q', 'v', 'z', 0), // qvz = Northern Pastaza Quichua + TRUETYPE_TAG('q', 'w', 'a', 0), // qwa = Corongo Ancash Quechua + TRUETYPE_TAG('q', 'w', 'c', 0), // qwc = Classical Quechua + TRUETYPE_TAG('q', 'w', 'e', 0), // qwe = Quechuan (family) + TRUETYPE_TAG('q', 'w', 'h', 0), // qwh = Huaylas Ancash Quechua + TRUETYPE_TAG('q', 'w', 'm', 0), // qwm = Kuman (Russia) + TRUETYPE_TAG('q', 'w', 's', 0), // qws = Sihuas Ancash Quechua + TRUETYPE_TAG('q', 'w', 't', 0), // qwt = Kwalhioqua-Tlatskanai + TRUETYPE_TAG('q', 'x', 'a', 0), // qxa = Chiquián Ancash Quechua + TRUETYPE_TAG('q', 'x', 'c', 0), // qxc = Chincha Quechua + TRUETYPE_TAG('q', 'x', 'h', 0), // qxh = Panao Huánuco Quechua + TRUETYPE_TAG('q', 'x', 'l', 0), // qxl = Salasaca Highland Quichua + TRUETYPE_TAG('q', 'x', 'n', 0), // qxn = Northern Conchucos Ancash Quechua + TRUETYPE_TAG('q', 'x', 'o', 0), // qxo = Southern Conchucos Ancash Quechua + TRUETYPE_TAG('q', 'x', 'p', 0), // qxp = Puno Quechua + TRUETYPE_TAG('q', 'x', 'q', 0), // qxq = Qashqa'i + TRUETYPE_TAG('q', 'x', 'r', 0), // qxr = Cañar Highland Quichua + TRUETYPE_TAG('q', 'x', 's', 0), // qxs = Southern Qiang + TRUETYPE_TAG('q', 'x', 't', 0), // qxt = Santa Ana de Tusi Pasco Quechua + TRUETYPE_TAG('q', 'x', 'u', 0), // qxu = Arequipa-La Unión Quechua + TRUETYPE_TAG('q', 'x', 'w', 0), // qxw = Jauja Wanca Quechua + TRUETYPE_TAG('q', 'y', 'a', 0), // qya = Quenya + TRUETYPE_TAG('q', 'y', 'p', 0), // qyp = Quiripi + TRUETYPE_TAG('r', 'a', 'a', 0), // raa = Dungmali + TRUETYPE_TAG('r', 'a', 'b', 0), // rab = Camling + TRUETYPE_TAG('r', 'a', 'c', 0), // rac = Rasawa + TRUETYPE_TAG('r', 'a', 'd', 0), // rad = Rade + TRUETYPE_TAG('r', 'a', 'f', 0), // raf = Western Meohang + TRUETYPE_TAG('r', 'a', 'g', 0), // rag = Logooli + TRUETYPE_TAG('r', 'a', 'h', 0), // rah = Rabha + TRUETYPE_TAG('r', 'a', 'i', 0), // rai = Ramoaaina + TRUETYPE_TAG('r', 'a', 'j', 0), // raj = Rajasthani + TRUETYPE_TAG('r', 'a', 'k', 0), // rak = Tulu-Bohuai + TRUETYPE_TAG('r', 'a', 'l', 0), // ral = Ralte + TRUETYPE_TAG('r', 'a', 'm', 0), // ram = Canela + TRUETYPE_TAG('r', 'a', 'n', 0), // ran = Riantana + TRUETYPE_TAG('r', 'a', 'o', 0), // rao = Rao + TRUETYPE_TAG('r', 'a', 'p', 0), // rap = Rapanui + TRUETYPE_TAG('r', 'a', 'q', 0), // raq = Saam + TRUETYPE_TAG('r', 'a', 'r', 0), // rar = Rarotongan + TRUETYPE_TAG('r', 'a', 's', 0), // ras = Tegali + TRUETYPE_TAG('r', 'a', 't', 0), // rat = Razajerdi + TRUETYPE_TAG('r', 'a', 'u', 0), // rau = Raute + TRUETYPE_TAG('r', 'a', 'v', 0), // rav = Sampang + TRUETYPE_TAG('r', 'a', 'w', 0), // raw = Rawang + TRUETYPE_TAG('r', 'a', 'x', 0), // rax = Rang + TRUETYPE_TAG('r', 'a', 'y', 0), // ray = Rapa + TRUETYPE_TAG('r', 'a', 'z', 0), // raz = Rahambuu + TRUETYPE_TAG('r', 'b', 'b', 0), // rbb = Rumai Palaung + TRUETYPE_TAG('r', 'b', 'k', 0), // rbk = Northern Bontok + TRUETYPE_TAG('r', 'b', 'l', 0), // rbl = Miraya Bikol + TRUETYPE_TAG('r', 'c', 'f', 0), // rcf = Réunion Creole French + TRUETYPE_TAG('r', 'd', 'b', 0), // rdb = Rudbari + TRUETYPE_TAG('r', 'e', 'a', 0), // rea = Rerau + TRUETYPE_TAG('r', 'e', 'b', 0), // reb = Rembong + TRUETYPE_TAG('r', 'e', 'e', 0), // ree = Rejang Kayan + TRUETYPE_TAG('r', 'e', 'g', 0), // reg = Kara (Tanzania) + TRUETYPE_TAG('r', 'e', 'i', 0), // rei = Reli + TRUETYPE_TAG('r', 'e', 'j', 0), // rej = Rejang + TRUETYPE_TAG('r', 'e', 'l', 0), // rel = Rendille + TRUETYPE_TAG('r', 'e', 'm', 0), // rem = Remo + TRUETYPE_TAG('r', 'e', 'n', 0), // ren = Rengao + TRUETYPE_TAG('r', 'e', 'r', 0), // rer = Rer Bare + TRUETYPE_TAG('r', 'e', 's', 0), // res = Reshe + TRUETYPE_TAG('r', 'e', 't', 0), // ret = Retta + TRUETYPE_TAG('r', 'e', 'y', 0), // rey = Reyesano + TRUETYPE_TAG('r', 'g', 'a', 0), // rga = Roria + TRUETYPE_TAG('r', 'g', 'e', 0), // rge = Romano-Greek + TRUETYPE_TAG('r', 'g', 'k', 0), // rgk = Rangkas + TRUETYPE_TAG('r', 'g', 'n', 0), // rgn = Romagnol + TRUETYPE_TAG('r', 'g', 'r', 0), // rgr = Resígaro + TRUETYPE_TAG('r', 'g', 's', 0), // rgs = Southern Roglai + TRUETYPE_TAG('r', 'g', 'u', 0), // rgu = Ringgou + TRUETYPE_TAG('r', 'h', 'g', 0), // rhg = Rohingya + TRUETYPE_TAG('r', 'h', 'p', 0), // rhp = Yahang + TRUETYPE_TAG('r', 'i', 'a', 0), // ria = Riang (India) + TRUETYPE_TAG('r', 'i', 'e', 0), // rie = Rien + TRUETYPE_TAG('r', 'i', 'f', 0), // rif = Tarifit + TRUETYPE_TAG('r', 'i', 'l', 0), // ril = Riang (Myanmar) + TRUETYPE_TAG('r', 'i', 'm', 0), // rim = Nyaturu + TRUETYPE_TAG('r', 'i', 'n', 0), // rin = Nungu + TRUETYPE_TAG('r', 'i', 'r', 0), // rir = Ribun + TRUETYPE_TAG('r', 'i', 't', 0), // rit = Ritarungo + TRUETYPE_TAG('r', 'i', 'u', 0), // riu = Riung + TRUETYPE_TAG('r', 'j', 'g', 0), // rjg = Rajong + TRUETYPE_TAG('r', 'j', 'i', 0), // rji = Raji + TRUETYPE_TAG('r', 'j', 's', 0), // rjs = Rajbanshi + TRUETYPE_TAG('r', 'k', 'a', 0), // rka = Kraol + TRUETYPE_TAG('r', 'k', 'b', 0), // rkb = Rikbaktsa + TRUETYPE_TAG('r', 'k', 'h', 0), // rkh = Rakahanga-Manihiki + TRUETYPE_TAG('r', 'k', 'i', 0), // rki = Rakhine + TRUETYPE_TAG('r', 'k', 'm', 0), // rkm = Marka + TRUETYPE_TAG('r', 'k', 't', 0), // rkt = Rangpuri + TRUETYPE_TAG('r', 'm', 'a', 0), // rma = Rama + TRUETYPE_TAG('r', 'm', 'b', 0), // rmb = Rembarunga + TRUETYPE_TAG('r', 'm', 'c', 0), // rmc = Carpathian Romani + TRUETYPE_TAG('r', 'm', 'd', 0), // rmd = Traveller Danish + TRUETYPE_TAG('r', 'm', 'e', 0), // rme = Angloromani + TRUETYPE_TAG('r', 'm', 'f', 0), // rmf = Kalo Finnish Romani + TRUETYPE_TAG('r', 'm', 'g', 0), // rmg = Traveller Norwegian + TRUETYPE_TAG('r', 'm', 'h', 0), // rmh = Murkim + TRUETYPE_TAG('r', 'm', 'i', 0), // rmi = Lomavren + TRUETYPE_TAG('r', 'm', 'k', 0), // rmk = Romkun + TRUETYPE_TAG('r', 'm', 'l', 0), // rml = Baltic Romani + TRUETYPE_TAG('r', 'm', 'm', 0), // rmm = Roma + TRUETYPE_TAG('r', 'm', 'n', 0), // rmn = Balkan Romani + TRUETYPE_TAG('r', 'm', 'o', 0), // rmo = Sinte Romani + TRUETYPE_TAG('r', 'm', 'p', 0), // rmp = Rempi + TRUETYPE_TAG('r', 'm', 'q', 0), // rmq = Caló + TRUETYPE_TAG('r', 'm', 'r', 0), // rmr = Caló + TRUETYPE_TAG('r', 'm', 's', 0), // rms = Romanian Sign Language + TRUETYPE_TAG('r', 'm', 't', 0), // rmt = Domari + TRUETYPE_TAG('r', 'm', 'u', 0), // rmu = Tavringer Romani + TRUETYPE_TAG('r', 'm', 'v', 0), // rmv = Romanova + TRUETYPE_TAG('r', 'm', 'w', 0), // rmw = Welsh Romani + TRUETYPE_TAG('r', 'm', 'x', 0), // rmx = Romam + TRUETYPE_TAG('r', 'm', 'y', 0), // rmy = Vlax Romani + TRUETYPE_TAG('r', 'm', 'z', 0), // rmz = Marma + TRUETYPE_TAG('r', 'n', 'a', 0), // rna = Runa + TRUETYPE_TAG('r', 'n', 'd', 0), // rnd = Ruund + TRUETYPE_TAG('r', 'n', 'g', 0), // rng = Ronga + TRUETYPE_TAG('r', 'n', 'l', 0), // rnl = Ranglong + TRUETYPE_TAG('r', 'n', 'n', 0), // rnn = Roon + TRUETYPE_TAG('r', 'n', 'p', 0), // rnp = Rongpo + TRUETYPE_TAG('r', 'n', 'w', 0), // rnw = Rungwa + TRUETYPE_TAG('r', 'o', 'a', 0), // roa = Romance languages + TRUETYPE_TAG('r', 'o', 'b', 0), // rob = Tae' + TRUETYPE_TAG('r', 'o', 'c', 0), // roc = Cacgia Roglai + TRUETYPE_TAG('r', 'o', 'd', 0), // rod = Rogo + TRUETYPE_TAG('r', 'o', 'e', 0), // roe = Ronji + TRUETYPE_TAG('r', 'o', 'f', 0), // rof = Rombo + TRUETYPE_TAG('r', 'o', 'g', 0), // rog = Northern Roglai + TRUETYPE_TAG('r', 'o', 'l', 0), // rol = Romblomanon + TRUETYPE_TAG('r', 'o', 'm', 0), // rom = Romany + TRUETYPE_TAG('r', 'o', 'o', 0), // roo = Rotokas + TRUETYPE_TAG('r', 'o', 'p', 0), // rop = Kriol + TRUETYPE_TAG('r', 'o', 'r', 0), // ror = Rongga + TRUETYPE_TAG('r', 'o', 'u', 0), // rou = Runga + TRUETYPE_TAG('r', 'o', 'w', 0), // row = Dela-Oenale + TRUETYPE_TAG('r', 'p', 'n', 0), // rpn = Repanbitip + TRUETYPE_TAG('r', 'p', 't', 0), // rpt = Rapting + TRUETYPE_TAG('r', 'r', 'i', 0), // rri = Ririo + TRUETYPE_TAG('r', 'r', 'o', 0), // rro = Waima + TRUETYPE_TAG('r', 's', 'b', 0), // rsb = Romano-Serbian + TRUETYPE_TAG('r', 's', 'i', 0), // rsi = Rennellese Sign Language + TRUETYPE_TAG('r', 's', 'l', 0), // rsl = Russian Sign Language + TRUETYPE_TAG('r', 't', 'h', 0), // rth = Ratahan + TRUETYPE_TAG('r', 't', 'm', 0), // rtm = Rotuman + TRUETYPE_TAG('r', 't', 'w', 0), // rtw = Rathawi + TRUETYPE_TAG('r', 'u', 'b', 0), // rub = Gungu + TRUETYPE_TAG('r', 'u', 'c', 0), // ruc = Ruuli + TRUETYPE_TAG('r', 'u', 'e', 0), // rue = Rusyn + TRUETYPE_TAG('r', 'u', 'f', 0), // ruf = Luguru + TRUETYPE_TAG('r', 'u', 'g', 0), // rug = Roviana + TRUETYPE_TAG('r', 'u', 'h', 0), // ruh = Ruga + TRUETYPE_TAG('r', 'u', 'i', 0), // rui = Rufiji + TRUETYPE_TAG('r', 'u', 'k', 0), // ruk = Che + TRUETYPE_TAG('r', 'u', 'o', 0), // ruo = Istro Romanian + TRUETYPE_TAG('r', 'u', 'p', 0), // rup = Macedo-Romanian + TRUETYPE_TAG('r', 'u', 'q', 0), // ruq = Megleno Romanian + TRUETYPE_TAG('r', 'u', 't', 0), // rut = Rutul + TRUETYPE_TAG('r', 'u', 'u', 0), // ruu = Lanas Lobu + TRUETYPE_TAG('r', 'u', 'y', 0), // ruy = Mala (Nigeria) + TRUETYPE_TAG('r', 'u', 'z', 0), // ruz = Ruma + TRUETYPE_TAG('r', 'w', 'a', 0), // rwa = Rawo + TRUETYPE_TAG('r', 'w', 'k', 0), // rwk = Rwa + TRUETYPE_TAG('r', 'w', 'm', 0), // rwm = Amba (Uganda) + TRUETYPE_TAG('r', 'w', 'o', 0), // rwo = Rawa + TRUETYPE_TAG('r', 'w', 'r', 0), // rwr = Marwari (India) + TRUETYPE_TAG('r', 'y', 'n', 0), // ryn = Northern Amami-Oshima + TRUETYPE_TAG('r', 'y', 's', 0), // rys = Yaeyama + TRUETYPE_TAG('r', 'y', 'u', 0), // ryu = Central Okinawan + TRUETYPE_TAG('s', 'a', 'a', 0), // saa = Saba + TRUETYPE_TAG('s', 'a', 'b', 0), // sab = Buglere + TRUETYPE_TAG('s', 'a', 'c', 0), // sac = Meskwaki + TRUETYPE_TAG('s', 'a', 'd', 0), // sad = Sandawe + TRUETYPE_TAG('s', 'a', 'e', 0), // sae = Sabanê + TRUETYPE_TAG('s', 'a', 'f', 0), // saf = Safaliba + TRUETYPE_TAG('s', 'a', 'h', 0), // sah = Yakut + TRUETYPE_TAG('s', 'a', 'i', 0), // sai = South American Indian languages + TRUETYPE_TAG('s', 'a', 'j', 0), // saj = Sahu + TRUETYPE_TAG('s', 'a', 'k', 0), // sak = Sake + TRUETYPE_TAG('s', 'a', 'l', 0), // sal = Salishan languages + TRUETYPE_TAG('s', 'a', 'm', 0), // sam = Samaritan Aramaic + TRUETYPE_TAG('s', 'a', 'o', 0), // sao = Sause + TRUETYPE_TAG('s', 'a', 'p', 0), // sap = Sanapaná + TRUETYPE_TAG('s', 'a', 'q', 0), // saq = Samburu + TRUETYPE_TAG('s', 'a', 'r', 0), // sar = Saraveca + TRUETYPE_TAG('s', 'a', 's', 0), // sas = Sasak + TRUETYPE_TAG('s', 'a', 't', 0), // sat = Santali + TRUETYPE_TAG('s', 'a', 'u', 0), // sau = Saleman + TRUETYPE_TAG('s', 'a', 'v', 0), // sav = Saafi-Saafi + TRUETYPE_TAG('s', 'a', 'w', 0), // saw = Sawi + TRUETYPE_TAG('s', 'a', 'x', 0), // sax = Sa + TRUETYPE_TAG('s', 'a', 'y', 0), // say = Saya + TRUETYPE_TAG('s', 'a', 'z', 0), // saz = Saurashtra + TRUETYPE_TAG('s', 'b', 'a', 0), // sba = Ngambay + TRUETYPE_TAG('s', 'b', 'b', 0), // sbb = Simbo + TRUETYPE_TAG('s', 'b', 'c', 0), // sbc = Kele (Papua New Guinea) + TRUETYPE_TAG('s', 'b', 'd', 0), // sbd = Southern Samo + TRUETYPE_TAG('s', 'b', 'e', 0), // sbe = Saliba + TRUETYPE_TAG('s', 'b', 'f', 0), // sbf = Shabo + TRUETYPE_TAG('s', 'b', 'g', 0), // sbg = Seget + TRUETYPE_TAG('s', 'b', 'h', 0), // sbh = Sori-Harengan + TRUETYPE_TAG('s', 'b', 'i', 0), // sbi = Seti + TRUETYPE_TAG('s', 'b', 'j', 0), // sbj = Surbakhal + TRUETYPE_TAG('s', 'b', 'k', 0), // sbk = Safwa + TRUETYPE_TAG('s', 'b', 'l', 0), // sbl = Botolan Sambal + TRUETYPE_TAG('s', 'b', 'm', 0), // sbm = Sagala + TRUETYPE_TAG('s', 'b', 'n', 0), // sbn = Sindhi Bhil + TRUETYPE_TAG('s', 'b', 'o', 0), // sbo = Sabüm + TRUETYPE_TAG('s', 'b', 'p', 0), // sbp = Sangu (Tanzania) + TRUETYPE_TAG('s', 'b', 'q', 0), // sbq = Sileibi + TRUETYPE_TAG('s', 'b', 'r', 0), // sbr = Sembakung Murut + TRUETYPE_TAG('s', 'b', 's', 0), // sbs = Subiya + TRUETYPE_TAG('s', 'b', 't', 0), // sbt = Kimki + TRUETYPE_TAG('s', 'b', 'u', 0), // sbu = Stod Bhoti + TRUETYPE_TAG('s', 'b', 'v', 0), // sbv = Sabine + TRUETYPE_TAG('s', 'b', 'w', 0), // sbw = Simba + TRUETYPE_TAG('s', 'b', 'x', 0), // sbx = Seberuang + TRUETYPE_TAG('s', 'b', 'y', 0), // sby = Soli + TRUETYPE_TAG('s', 'b', 'z', 0), // sbz = Sara Kaba + TRUETYPE_TAG('s', 'c', 'a', 0), // sca = Sansu + TRUETYPE_TAG('s', 'c', 'b', 0), // scb = Chut + TRUETYPE_TAG('s', 'c', 'e', 0), // sce = Dongxiang + TRUETYPE_TAG('s', 'c', 'f', 0), // scf = San Miguel Creole French + TRUETYPE_TAG('s', 'c', 'g', 0), // scg = Sanggau + TRUETYPE_TAG('s', 'c', 'h', 0), // sch = Sakachep + TRUETYPE_TAG('s', 'c', 'i', 0), // sci = Sri Lankan Creole Malay + TRUETYPE_TAG('s', 'c', 'k', 0), // sck = Sadri + TRUETYPE_TAG('s', 'c', 'l', 0), // scl = Shina + TRUETYPE_TAG('s', 'c', 'n', 0), // scn = Sicilian + TRUETYPE_TAG('s', 'c', 'o', 0), // sco = Scots + TRUETYPE_TAG('s', 'c', 'p', 0), // scp = Helambu Sherpa + TRUETYPE_TAG('s', 'c', 'q', 0), // scq = Sa'och + TRUETYPE_TAG('s', 'c', 's', 0), // scs = North Slavey + TRUETYPE_TAG('s', 'c', 'u', 0), // scu = Shumcho + TRUETYPE_TAG('s', 'c', 'v', 0), // scv = Sheni + TRUETYPE_TAG('s', 'c', 'w', 0), // scw = Sha + TRUETYPE_TAG('s', 'c', 'x', 0), // scx = Sicel + TRUETYPE_TAG('s', 'd', 'a', 0), // sda = Toraja-Sa'dan + TRUETYPE_TAG('s', 'd', 'b', 0), // sdb = Shabak + TRUETYPE_TAG('s', 'd', 'c', 0), // sdc = Sassarese Sardinian + TRUETYPE_TAG('s', 'd', 'e', 0), // sde = Surubu + TRUETYPE_TAG('s', 'd', 'f', 0), // sdf = Sarli + TRUETYPE_TAG('s', 'd', 'g', 0), // sdg = Savi + TRUETYPE_TAG('s', 'd', 'h', 0), // sdh = Southern Kurdish + TRUETYPE_TAG('s', 'd', 'j', 0), // sdj = Suundi + TRUETYPE_TAG('s', 'd', 'k', 0), // sdk = Sos Kundi + TRUETYPE_TAG('s', 'd', 'l', 0), // sdl = Saudi Arabian Sign Language + TRUETYPE_TAG('s', 'd', 'm', 0), // sdm = Semandang + TRUETYPE_TAG('s', 'd', 'n', 0), // sdn = Gallurese Sardinian + TRUETYPE_TAG('s', 'd', 'o', 0), // sdo = Bukar-Sadung Bidayuh + TRUETYPE_TAG('s', 'd', 'p', 0), // sdp = Sherdukpen + TRUETYPE_TAG('s', 'd', 'r', 0), // sdr = Oraon Sadri + TRUETYPE_TAG('s', 'd', 's', 0), // sds = Sened + TRUETYPE_TAG('s', 'd', 't', 0), // sdt = Shuadit + TRUETYPE_TAG('s', 'd', 'u', 0), // sdu = Sarudu + TRUETYPE_TAG('s', 'd', 'v', 0), // sdv = Eastern Sudanic languages + TRUETYPE_TAG('s', 'd', 'x', 0), // sdx = Sibu Melanau + TRUETYPE_TAG('s', 'd', 'z', 0), // sdz = Sallands + TRUETYPE_TAG('s', 'e', 'a', 0), // sea = Semai + TRUETYPE_TAG('s', 'e', 'b', 0), // seb = Shempire Senoufo + TRUETYPE_TAG('s', 'e', 'c', 0), // sec = Sechelt + TRUETYPE_TAG('s', 'e', 'd', 0), // sed = Sedang + TRUETYPE_TAG('s', 'e', 'e', 0), // see = Seneca + TRUETYPE_TAG('s', 'e', 'f', 0), // sef = Cebaara Senoufo + TRUETYPE_TAG('s', 'e', 'g', 0), // seg = Segeju + TRUETYPE_TAG('s', 'e', 'h', 0), // seh = Sena + TRUETYPE_TAG('s', 'e', 'i', 0), // sei = Seri + TRUETYPE_TAG('s', 'e', 'j', 0), // sej = Sene + TRUETYPE_TAG('s', 'e', 'k', 0), // sek = Sekani + TRUETYPE_TAG('s', 'e', 'l', 0), // sel = Selkup + TRUETYPE_TAG('s', 'e', 'm', 0), // sem = Semitic languages + TRUETYPE_TAG('s', 'e', 'n', 0), // sen = Nanerigé Sénoufo + TRUETYPE_TAG('s', 'e', 'o', 0), // seo = Suarmin + TRUETYPE_TAG('s', 'e', 'p', 0), // sep = Sìcìté Sénoufo + TRUETYPE_TAG('s', 'e', 'q', 0), // seq = Senara Sénoufo + TRUETYPE_TAG('s', 'e', 'r', 0), // ser = Serrano + TRUETYPE_TAG('s', 'e', 's', 0), // ses = Koyraboro Senni Songhai + TRUETYPE_TAG('s', 'e', 't', 0), // set = Sentani + TRUETYPE_TAG('s', 'e', 'u', 0), // seu = Serui-Laut + TRUETYPE_TAG('s', 'e', 'v', 0), // sev = Nyarafolo Senoufo + TRUETYPE_TAG('s', 'e', 'w', 0), // sew = Sewa Bay + TRUETYPE_TAG('s', 'e', 'y', 0), // sey = Secoya + TRUETYPE_TAG('s', 'e', 'z', 0), // sez = Senthang Chin + TRUETYPE_TAG('s', 'f', 'b', + 0), // sfb = Langue des signes de Belgique Francophone + TRUETYPE_TAG('s', 'f', 'm', 0), // sfm = Small Flowery Miao + TRUETYPE_TAG('s', 'f', 's', 0), // sfs = South African Sign Language + TRUETYPE_TAG('s', 'f', 'w', 0), // sfw = Sehwi + TRUETYPE_TAG('s', 'g', 'a', 0), // sga = Old Irish (to 900) + TRUETYPE_TAG('s', 'g', 'b', 0), // sgb = Mag-antsi Ayta + TRUETYPE_TAG('s', 'g', 'c', 0), // sgc = Kipsigis + TRUETYPE_TAG('s', 'g', 'd', 0), // sgd = Surigaonon + TRUETYPE_TAG('s', 'g', 'e', 0), // sge = Segai + TRUETYPE_TAG('s', 'g', 'g', 0), // sgg = Swiss-German Sign Language + TRUETYPE_TAG('s', 'g', 'h', 0), // sgh = Shughni + TRUETYPE_TAG('s', 'g', 'i', 0), // sgi = Suga + TRUETYPE_TAG('s', 'g', 'k', 0), // sgk = Sangkong + TRUETYPE_TAG('s', 'g', 'l', 0), // sgl = Sanglechi-Ishkashimi + TRUETYPE_TAG('s', 'g', 'm', 0), // sgm = Singa + TRUETYPE_TAG('s', 'g', 'n', 0), // sgn = Sign languages + TRUETYPE_TAG('s', 'g', 'o', 0), // sgo = Songa + TRUETYPE_TAG('s', 'g', 'p', 0), // sgp = Singpho + TRUETYPE_TAG('s', 'g', 'r', 0), // sgr = Sangisari + TRUETYPE_TAG('s', 'g', 's', 0), // sgs = Samogitian + TRUETYPE_TAG('s', 'g', 't', 0), // sgt = Brokpake + TRUETYPE_TAG('s', 'g', 'u', 0), // sgu = Salas + TRUETYPE_TAG('s', 'g', 'w', 0), // sgw = Sebat Bet Gurage + TRUETYPE_TAG('s', 'g', 'x', 0), // sgx = Sierra Leone Sign Language + TRUETYPE_TAG('s', 'g', 'y', 0), // sgy = Sanglechi + TRUETYPE_TAG('s', 'g', 'z', 0), // sgz = Sursurunga + TRUETYPE_TAG('s', 'h', 'a', 0), // sha = Shall-Zwall + TRUETYPE_TAG('s', 'h', 'b', 0), // shb = Ninam + TRUETYPE_TAG('s', 'h', 'c', 0), // shc = Sonde + TRUETYPE_TAG('s', 'h', 'd', 0), // shd = Kundal Shahi + TRUETYPE_TAG('s', 'h', 'e', 0), // she = Sheko + TRUETYPE_TAG('s', 'h', 'g', 0), // shg = Shua + TRUETYPE_TAG('s', 'h', 'h', 0), // shh = Shoshoni + TRUETYPE_TAG('s', 'h', 'i', 0), // shi = Tachelhit + TRUETYPE_TAG('s', 'h', 'j', 0), // shj = Shatt + TRUETYPE_TAG('s', 'h', 'k', 0), // shk = Shilluk + TRUETYPE_TAG('s', 'h', 'l', 0), // shl = Shendu + TRUETYPE_TAG('s', 'h', 'm', 0), // shm = Shahrudi + TRUETYPE_TAG('s', 'h', 'n', 0), // shn = Shan + TRUETYPE_TAG('s', 'h', 'o', 0), // sho = Shanga + TRUETYPE_TAG('s', 'h', 'p', 0), // shp = Shipibo-Conibo + TRUETYPE_TAG('s', 'h', 'q', 0), // shq = Sala + TRUETYPE_TAG('s', 'h', 'r', 0), // shr = Shi + TRUETYPE_TAG('s', 'h', 's', 0), // shs = Shuswap + TRUETYPE_TAG('s', 'h', 't', 0), // sht = Shasta + TRUETYPE_TAG('s', 'h', 'u', 0), // shu = Chadian Arabic + TRUETYPE_TAG('s', 'h', 'v', 0), // shv = Shehri + TRUETYPE_TAG('s', 'h', 'w', 0), // shw = Shwai + TRUETYPE_TAG('s', 'h', 'x', 0), // shx = She + TRUETYPE_TAG('s', 'h', 'y', 0), // shy = Tachawit + TRUETYPE_TAG('s', 'h', 'z', 0), // shz = Syenara Senoufo + TRUETYPE_TAG('s', 'i', 'a', 0), // sia = Akkala Sami + TRUETYPE_TAG('s', 'i', 'b', 0), // sib = Sebop + TRUETYPE_TAG('s', 'i', 'd', 0), // sid = Sidamo + TRUETYPE_TAG('s', 'i', 'e', 0), // sie = Simaa + TRUETYPE_TAG('s', 'i', 'f', 0), // sif = Siamou + TRUETYPE_TAG('s', 'i', 'g', 0), // sig = Paasaal + TRUETYPE_TAG('s', 'i', 'h', 0), // sih = Zire + TRUETYPE_TAG('s', 'i', 'i', 0), // sii = Shom Peng + TRUETYPE_TAG('s', 'i', 'j', 0), // sij = Numbami + TRUETYPE_TAG('s', 'i', 'k', 0), // sik = Sikiana + TRUETYPE_TAG('s', 'i', 'l', 0), // sil = Tumulung Sisaala + TRUETYPE_TAG('s', 'i', 'm', 0), // sim = Mende (Papua New Guinea) + TRUETYPE_TAG('s', 'i', 'o', 0), // sio = Siouan languages + TRUETYPE_TAG('s', 'i', 'p', 0), // sip = Sikkimese + TRUETYPE_TAG('s', 'i', 'q', 0), // siq = Sonia + TRUETYPE_TAG('s', 'i', 'r', 0), // sir = Siri + TRUETYPE_TAG('s', 'i', 's', 0), // sis = Siuslaw + TRUETYPE_TAG('s', 'i', 't', 0), // sit = Sino-Tibetan languages + TRUETYPE_TAG('s', 'i', 'u', 0), // siu = Sinagen + TRUETYPE_TAG('s', 'i', 'v', 0), // siv = Sumariup + TRUETYPE_TAG('s', 'i', 'w', 0), // siw = Siwai + TRUETYPE_TAG('s', 'i', 'x', 0), // six = Sumau + TRUETYPE_TAG('s', 'i', 'y', 0), // siy = Sivandi + TRUETYPE_TAG('s', 'i', 'z', 0), // siz = Siwi + TRUETYPE_TAG('s', 'j', 'a', 0), // sja = Epena + TRUETYPE_TAG('s', 'j', 'b', 0), // sjb = Sajau Basap + TRUETYPE_TAG('s', 'j', 'd', 0), // sjd = Kildin Sami + TRUETYPE_TAG('s', 'j', 'e', 0), // sje = Pite Sami + TRUETYPE_TAG('s', 'j', 'g', 0), // sjg = Assangori + TRUETYPE_TAG('s', 'j', 'k', 0), // sjk = Kemi Sami + TRUETYPE_TAG('s', 'j', 'l', 0), // sjl = Sajalong + TRUETYPE_TAG('s', 'j', 'm', 0), // sjm = Mapun + TRUETYPE_TAG('s', 'j', 'n', 0), // sjn = Sindarin + TRUETYPE_TAG('s', 'j', 'o', 0), // sjo = Xibe + TRUETYPE_TAG('s', 'j', 'p', 0), // sjp = Surjapuri + TRUETYPE_TAG('s', 'j', 'r', 0), // sjr = Siar-Lak + TRUETYPE_TAG('s', 'j', 's', 0), // sjs = Senhaja De Srair + TRUETYPE_TAG('s', 'j', 't', 0), // sjt = Ter Sami + TRUETYPE_TAG('s', 'j', 'u', 0), // sju = Ume Sami + TRUETYPE_TAG('s', 'j', 'w', 0), // sjw = Shawnee + TRUETYPE_TAG('s', 'k', 'a', 0), // ska = Skagit + TRUETYPE_TAG('s', 'k', 'b', 0), // skb = Saek + TRUETYPE_TAG('s', 'k', 'c', 0), // skc = Sauk + TRUETYPE_TAG('s', 'k', 'd', 0), // skd = Southern Sierra Miwok + TRUETYPE_TAG('s', 'k', 'e', 0), // ske = Seke (Vanuatu) + TRUETYPE_TAG('s', 'k', 'f', 0), // skf = Sakirabiá + TRUETYPE_TAG('s', 'k', 'g', 0), // skg = Sakalava Malagasy + TRUETYPE_TAG('s', 'k', 'h', 0), // skh = Sikule + TRUETYPE_TAG('s', 'k', 'i', 0), // ski = Sika + TRUETYPE_TAG('s', 'k', 'j', 0), // skj = Seke (Nepal) + TRUETYPE_TAG('s', 'k', 'k', 0), // skk = Sok + TRUETYPE_TAG('s', 'k', 'm', 0), // skm = Sakam + TRUETYPE_TAG('s', 'k', 'n', 0), // skn = Kolibugan Subanon + TRUETYPE_TAG('s', 'k', 'o', 0), // sko = Seko Tengah + TRUETYPE_TAG('s', 'k', 'p', 0), // skp = Sekapan + TRUETYPE_TAG('s', 'k', 'q', 0), // skq = Sininkere + TRUETYPE_TAG('s', 'k', 'r', 0), // skr = Seraiki + TRUETYPE_TAG('s', 'k', 's', 0), // sks = Maia + TRUETYPE_TAG('s', 'k', 't', 0), // skt = Sakata + TRUETYPE_TAG('s', 'k', 'u', 0), // sku = Sakao + TRUETYPE_TAG('s', 'k', 'v', 0), // skv = Skou + TRUETYPE_TAG('s', 'k', 'w', 0), // skw = Skepi Creole Dutch + TRUETYPE_TAG('s', 'k', 'x', 0), // skx = Seko Padang + TRUETYPE_TAG('s', 'k', 'y', 0), // sky = Sikaiana + TRUETYPE_TAG('s', 'k', 'z', 0), // skz = Sekar + TRUETYPE_TAG('s', 'l', 'a', 0), // sla = Slavic languages + TRUETYPE_TAG('s', 'l', 'c', 0), // slc = Sáliba + TRUETYPE_TAG('s', 'l', 'd', 0), // sld = Sissala + TRUETYPE_TAG('s', 'l', 'e', 0), // sle = Sholaga + TRUETYPE_TAG('s', 'l', 'f', 0), // slf = Swiss-Italian Sign Language + TRUETYPE_TAG('s', 'l', 'g', 0), // slg = Selungai Murut + TRUETYPE_TAG('s', 'l', 'h', 0), // slh = Southern Puget Sound Salish + TRUETYPE_TAG('s', 'l', 'i', 0), // sli = Lower Silesian + TRUETYPE_TAG('s', 'l', 'j', 0), // slj = Salumá + TRUETYPE_TAG('s', 'l', 'l', 0), // sll = Salt-Yui + TRUETYPE_TAG('s', 'l', 'm', 0), // slm = Pangutaran Sama + TRUETYPE_TAG('s', 'l', 'n', 0), // sln = Salinan + TRUETYPE_TAG('s', 'l', 'p', 0), // slp = Lamaholot + TRUETYPE_TAG('s', 'l', 'q', 0), // slq = Salchuq + TRUETYPE_TAG('s', 'l', 'r', 0), // slr = Salar + TRUETYPE_TAG('s', 'l', 's', 0), // sls = Singapore Sign Language + TRUETYPE_TAG('s', 'l', 't', 0), // slt = Sila + TRUETYPE_TAG('s', 'l', 'u', 0), // slu = Selaru + TRUETYPE_TAG('s', 'l', 'w', 0), // slw = Sialum + TRUETYPE_TAG('s', 'l', 'x', 0), // slx = Salampasu + TRUETYPE_TAG('s', 'l', 'y', 0), // sly = Selayar + TRUETYPE_TAG('s', 'l', 'z', 0), // slz = Ma'ya + TRUETYPE_TAG('s', 'm', 'a', 0), // sma = Southern Sami + TRUETYPE_TAG('s', 'm', 'b', 0), // smb = Simbari + TRUETYPE_TAG('s', 'm', 'c', 0), // smc = Som + TRUETYPE_TAG('s', 'm', 'd', 0), // smd = Sama + TRUETYPE_TAG('s', 'm', 'f', 0), // smf = Auwe + TRUETYPE_TAG('s', 'm', 'g', 0), // smg = Simbali + TRUETYPE_TAG('s', 'm', 'h', 0), // smh = Samei + TRUETYPE_TAG('s', 'm', 'i', 0), // smi = Sami languages + TRUETYPE_TAG('s', 'm', 'j', 0), // smj = Lule Sami + TRUETYPE_TAG('s', 'm', 'k', 0), // smk = Bolinao + TRUETYPE_TAG('s', 'm', 'l', 0), // sml = Central Sama + TRUETYPE_TAG('s', 'm', 'm', 0), // smm = Musasa + TRUETYPE_TAG('s', 'm', 'n', 0), // smn = Inari Sami + TRUETYPE_TAG('s', 'm', 'p', 0), // smp = Samaritan + TRUETYPE_TAG('s', 'm', 'q', 0), // smq = Samo + TRUETYPE_TAG('s', 'm', 'r', 0), // smr = Simeulue + TRUETYPE_TAG('s', 'm', 's', 0), // sms = Skolt Sami + TRUETYPE_TAG('s', 'm', 't', 0), // smt = Simte + TRUETYPE_TAG('s', 'm', 'u', 0), // smu = Somray + TRUETYPE_TAG('s', 'm', 'v', 0), // smv = Samvedi + TRUETYPE_TAG('s', 'm', 'w', 0), // smw = Sumbawa + TRUETYPE_TAG('s', 'm', 'x', 0), // smx = Samba + TRUETYPE_TAG('s', 'm', 'y', 0), // smy = Semnani + TRUETYPE_TAG('s', 'm', 'z', 0), // smz = Simeku + TRUETYPE_TAG('s', 'n', 'b', 0), // snb = Sebuyau + TRUETYPE_TAG('s', 'n', 'c', 0), // snc = Sinaugoro + TRUETYPE_TAG('s', 'n', 'e', 0), // sne = Bau Bidayuh + TRUETYPE_TAG('s', 'n', 'f', 0), // snf = Noon + TRUETYPE_TAG('s', 'n', 'g', + 0), // sng = Sanga (Democratic Republic of Congo) + TRUETYPE_TAG('s', 'n', 'h', 0), // snh = Shinabo + TRUETYPE_TAG('s', 'n', 'i', 0), // sni = Sensi + TRUETYPE_TAG('s', 'n', 'j', 0), // snj = Riverain Sango + TRUETYPE_TAG('s', 'n', 'k', 0), // snk = Soninke + TRUETYPE_TAG('s', 'n', 'l', 0), // snl = Sangil + TRUETYPE_TAG('s', 'n', 'm', 0), // snm = Southern Ma'di + TRUETYPE_TAG('s', 'n', 'n', 0), // snn = Siona + TRUETYPE_TAG('s', 'n', 'o', 0), // sno = Snohomish + TRUETYPE_TAG('s', 'n', 'p', 0), // snp = Siane + TRUETYPE_TAG('s', 'n', 'q', 0), // snq = Sangu (Gabon) + TRUETYPE_TAG('s', 'n', 'r', 0), // snr = Sihan + TRUETYPE_TAG('s', 'n', 's', 0), // sns = South West Bay + TRUETYPE_TAG('s', 'n', 'u', 0), // snu = Senggi + TRUETYPE_TAG('s', 'n', 'v', 0), // snv = Sa'ban + TRUETYPE_TAG('s', 'n', 'w', 0), // snw = Selee + TRUETYPE_TAG('s', 'n', 'x', 0), // snx = Sam + TRUETYPE_TAG('s', 'n', 'y', 0), // sny = Saniyo-Hiyewe + TRUETYPE_TAG('s', 'n', 'z', 0), // snz = Sinsauru + TRUETYPE_TAG('s', 'o', 'a', 0), // soa = Thai Song + TRUETYPE_TAG('s', 'o', 'b', 0), // sob = Sobei + TRUETYPE_TAG('s', 'o', 'c', 0), // soc = So (Democratic Republic of Congo) + TRUETYPE_TAG('s', 'o', 'd', 0), // sod = Songoora + TRUETYPE_TAG('s', 'o', 'e', 0), // soe = Songomeno + TRUETYPE_TAG('s', 'o', 'g', 0), // sog = Sogdian + TRUETYPE_TAG('s', 'o', 'h', 0), // soh = Aka + TRUETYPE_TAG('s', 'o', 'i', 0), // soi = Sonha + TRUETYPE_TAG('s', 'o', 'j', 0), // soj = Soi + TRUETYPE_TAG('s', 'o', 'k', 0), // sok = Sokoro + TRUETYPE_TAG('s', 'o', 'l', 0), // sol = Solos + TRUETYPE_TAG('s', 'o', 'n', 0), // son = Songhai languages + TRUETYPE_TAG('s', 'o', 'o', 0), // soo = Songo + TRUETYPE_TAG('s', 'o', 'p', 0), // sop = Songe + TRUETYPE_TAG('s', 'o', 'q', 0), // soq = Kanasi + TRUETYPE_TAG('s', 'o', 'r', 0), // sor = Somrai + TRUETYPE_TAG('s', 'o', 's', 0), // sos = Seeku + TRUETYPE_TAG('s', 'o', 'u', 0), // sou = Southern Thai + TRUETYPE_TAG('s', 'o', 'v', 0), // sov = Sonsorol + TRUETYPE_TAG('s', 'o', 'w', 0), // sow = Sowanda + TRUETYPE_TAG('s', 'o', 'x', 0), // sox = So (Cameroon) + TRUETYPE_TAG('s', 'o', 'y', 0), // soy = Miyobe + TRUETYPE_TAG('s', 'o', 'z', 0), // soz = Temi + TRUETYPE_TAG('s', 'p', 'b', 0), // spb = Sepa (Indonesia) + TRUETYPE_TAG('s', 'p', 'c', 0), // spc = Sapé + TRUETYPE_TAG('s', 'p', 'd', 0), // spd = Saep + TRUETYPE_TAG('s', 'p', 'e', 0), // spe = Sepa (Papua New Guinea) + TRUETYPE_TAG('s', 'p', 'g', 0), // spg = Sian + TRUETYPE_TAG('s', 'p', 'i', 0), // spi = Saponi + TRUETYPE_TAG('s', 'p', 'k', 0), // spk = Sengo + TRUETYPE_TAG('s', 'p', 'l', 0), // spl = Selepet + TRUETYPE_TAG('s', 'p', 'm', 0), // spm = Sepen + TRUETYPE_TAG('s', 'p', 'o', 0), // spo = Spokane + TRUETYPE_TAG('s', 'p', 'p', 0), // spp = Supyire Senoufo + TRUETYPE_TAG('s', 'p', 'q', 0), // spq = Loreto-Ucayali Spanish + TRUETYPE_TAG('s', 'p', 'r', 0), // spr = Saparua + TRUETYPE_TAG('s', 'p', 's', 0), // sps = Saposa + TRUETYPE_TAG('s', 'p', 't', 0), // spt = Spiti Bhoti + TRUETYPE_TAG('s', 'p', 'u', 0), // spu = Sapuan + TRUETYPE_TAG('s', 'p', 'x', 0), // spx = South Picene + TRUETYPE_TAG('s', 'p', 'y', 0), // spy = Sabaot + TRUETYPE_TAG('s', 'q', 'a', 0), // sqa = Shama-Sambuga + TRUETYPE_TAG('s', 'q', 'h', 0), // sqh = Shau + TRUETYPE_TAG('s', 'q', 'j', 0), // sqj = Albanian languages + TRUETYPE_TAG('s', 'q', 'm', 0), // sqm = Suma + TRUETYPE_TAG('s', 'q', 'n', 0), // sqn = Susquehannock + TRUETYPE_TAG('s', 'q', 'o', 0), // sqo = Sorkhei + TRUETYPE_TAG('s', 'q', 'q', 0), // sqq = Sou + TRUETYPE_TAG('s', 'q', 'r', 0), // sqr = Siculo Arabic + TRUETYPE_TAG('s', 'q', 's', 0), // sqs = Sri Lankan Sign Language + TRUETYPE_TAG('s', 'q', 't', 0), // sqt = Soqotri + TRUETYPE_TAG('s', 'q', 'u', 0), // squ = Squamish + TRUETYPE_TAG('s', 'r', 'a', 0), // sra = Saruga + TRUETYPE_TAG('s', 'r', 'b', 0), // srb = Sora + TRUETYPE_TAG('s', 'r', 'c', 0), // src = Logudorese Sardinian + TRUETYPE_TAG('s', 'r', 'e', 0), // sre = Sara + TRUETYPE_TAG('s', 'r', 'f', 0), // srf = Nafi + TRUETYPE_TAG('s', 'r', 'g', 0), // srg = Sulod + TRUETYPE_TAG('s', 'r', 'h', 0), // srh = Sarikoli + TRUETYPE_TAG('s', 'r', 'i', 0), // sri = Siriano + TRUETYPE_TAG('s', 'r', 'k', 0), // srk = Serudung Murut + TRUETYPE_TAG('s', 'r', 'l', 0), // srl = Isirawa + TRUETYPE_TAG('s', 'r', 'm', 0), // srm = Saramaccan + TRUETYPE_TAG('s', 'r', 'n', 0), // srn = Sranan Tongo + TRUETYPE_TAG('s', 'r', 'o', 0), // sro = Campidanese Sardinian + TRUETYPE_TAG('s', 'r', 'q', 0), // srq = Sirionó + TRUETYPE_TAG('s', 'r', 'r', 0), // srr = Serer + TRUETYPE_TAG('s', 'r', 's', 0), // srs = Sarsi + TRUETYPE_TAG('s', 'r', 't', 0), // srt = Sauri + TRUETYPE_TAG('s', 'r', 'u', 0), // sru = Suruí + TRUETYPE_TAG('s', 'r', 'v', 0), // srv = Southern Sorsoganon + TRUETYPE_TAG('s', 'r', 'w', 0), // srw = Serua + TRUETYPE_TAG('s', 'r', 'x', 0), // srx = Sirmauri + TRUETYPE_TAG('s', 'r', 'y', 0), // sry = Sera + TRUETYPE_TAG('s', 'r', 'z', 0), // srz = Shahmirzadi + TRUETYPE_TAG('s', 's', 'a', 0), // ssa = Nilo-Saharan languages + TRUETYPE_TAG('s', 's', 'b', 0), // ssb = Southern Sama + TRUETYPE_TAG('s', 's', 'c', 0), // ssc = Suba-Simbiti + TRUETYPE_TAG('s', 's', 'd', 0), // ssd = Siroi + TRUETYPE_TAG('s', 's', 'e', 0), // sse = Balangingi + TRUETYPE_TAG('s', 's', 'f', 0), // ssf = Thao + TRUETYPE_TAG('s', 's', 'g', 0), // ssg = Seimat + TRUETYPE_TAG('s', 's', 'h', 0), // ssh = Shihhi Arabic + TRUETYPE_TAG('s', 's', 'i', 0), // ssi = Sansi + TRUETYPE_TAG('s', 's', 'j', 0), // ssj = Sausi + TRUETYPE_TAG('s', 's', 'k', 0), // ssk = Sunam + TRUETYPE_TAG('s', 's', 'l', 0), // ssl = Western Sisaala + TRUETYPE_TAG('s', 's', 'm', 0), // ssm = Semnam + TRUETYPE_TAG('s', 's', 'n', 0), // ssn = Waata + TRUETYPE_TAG('s', 's', 'o', 0), // sso = Sissano + TRUETYPE_TAG('s', 's', 'p', 0), // ssp = Spanish Sign Language + TRUETYPE_TAG('s', 's', 'q', 0), // ssq = So'a + TRUETYPE_TAG('s', 's', 'r', 0), // ssr = Swiss-French Sign Language + TRUETYPE_TAG('s', 's', 's', 0), // sss = Sô + TRUETYPE_TAG('s', 's', 't', 0), // sst = Sinasina + TRUETYPE_TAG('s', 's', 'u', 0), // ssu = Susuami + TRUETYPE_TAG('s', 's', 'v', 0), // ssv = Shark Bay + TRUETYPE_TAG('s', 's', 'x', 0), // ssx = Samberigi + TRUETYPE_TAG('s', 's', 'y', 0), // ssy = Saho + TRUETYPE_TAG('s', 's', 'z', 0), // ssz = Sengseng + TRUETYPE_TAG('s', 't', 'a', 0), // sta = Settla + TRUETYPE_TAG('s', 't', 'b', 0), // stb = Northern Subanen + TRUETYPE_TAG('s', 't', 'd', 0), // std = Sentinel + TRUETYPE_TAG('s', 't', 'e', 0), // ste = Liana-Seti + TRUETYPE_TAG('s', 't', 'f', 0), // stf = Seta + TRUETYPE_TAG('s', 't', 'g', 0), // stg = Trieng + TRUETYPE_TAG('s', 't', 'h', 0), // sth = Shelta + TRUETYPE_TAG('s', 't', 'i', 0), // sti = Bulo Stieng + TRUETYPE_TAG('s', 't', 'j', 0), // stj = Matya Samo + TRUETYPE_TAG('s', 't', 'k', 0), // stk = Arammba + TRUETYPE_TAG('s', 't', 'l', 0), // stl = Stellingwerfs + TRUETYPE_TAG('s', 't', 'm', 0), // stm = Setaman + TRUETYPE_TAG('s', 't', 'n', 0), // stn = Owa + TRUETYPE_TAG('s', 't', 'o', 0), // sto = Stoney + TRUETYPE_TAG('s', 't', 'p', 0), // stp = Southeastern Tepehuan + TRUETYPE_TAG('s', 't', 'q', 0), // stq = Saterfriesisch + TRUETYPE_TAG('s', 't', 'r', 0), // str = Straits Salish + TRUETYPE_TAG('s', 't', 's', 0), // sts = Shumashti + TRUETYPE_TAG('s', 't', 't', 0), // stt = Budeh Stieng + TRUETYPE_TAG('s', 't', 'u', 0), // stu = Samtao + TRUETYPE_TAG('s', 't', 'v', 0), // stv = Silt'e + TRUETYPE_TAG('s', 't', 'w', 0), // stw = Satawalese + TRUETYPE_TAG('s', 'u', 'a', 0), // sua = Sulka + TRUETYPE_TAG('s', 'u', 'b', 0), // sub = Suku + TRUETYPE_TAG('s', 'u', 'c', 0), // suc = Western Subanon + TRUETYPE_TAG('s', 'u', 'e', 0), // sue = Suena + TRUETYPE_TAG('s', 'u', 'g', 0), // sug = Suganga + TRUETYPE_TAG('s', 'u', 'i', 0), // sui = Suki + TRUETYPE_TAG('s', 'u', 'j', 0), // suj = Shubi + TRUETYPE_TAG('s', 'u', 'k', 0), // suk = Sukuma + TRUETYPE_TAG('s', 'u', 'l', 0), // sul = Surigaonon + TRUETYPE_TAG('s', 'u', 'm', 0), // sum = Sumo-Mayangna + TRUETYPE_TAG('s', 'u', 'q', 0), // suq = Suri + TRUETYPE_TAG('s', 'u', 'r', 0), // sur = Mwaghavul + TRUETYPE_TAG('s', 'u', 's', 0), // sus = Susu + TRUETYPE_TAG('s', 'u', 't', 0), // sut = Subtiaba + TRUETYPE_TAG('s', 'u', 'v', 0), // suv = Sulung + TRUETYPE_TAG('s', 'u', 'w', 0), // suw = Sumbwa + TRUETYPE_TAG('s', 'u', 'x', 0), // sux = Sumerian + TRUETYPE_TAG('s', 'u', 'y', 0), // suy = Suyá + TRUETYPE_TAG('s', 'u', 'z', 0), // suz = Sunwar + TRUETYPE_TAG('s', 'v', 'a', 0), // sva = Svan + TRUETYPE_TAG('s', 'v', 'b', 0), // svb = Ulau-Suain + TRUETYPE_TAG('s', 'v', 'c', 0), // svc = Vincentian Creole English + TRUETYPE_TAG('s', 'v', 'e', 0), // sve = Serili + TRUETYPE_TAG('s', 'v', 'k', 0), // svk = Slovakian Sign Language + TRUETYPE_TAG('s', 'v', 'r', 0), // svr = Savara + TRUETYPE_TAG('s', 'v', 's', 0), // svs = Savosavo + TRUETYPE_TAG('s', 'v', 'x', 0), // svx = Skalvian + TRUETYPE_TAG('s', 'w', 'b', 0), // swb = Maore Comorian + TRUETYPE_TAG('s', 'w', 'c', 0), // swc = Congo Swahili + TRUETYPE_TAG('s', 'w', 'f', 0), // swf = Sere + TRUETYPE_TAG('s', 'w', 'g', 0), // swg = Swabian + TRUETYPE_TAG('s', 'w', 'h', 0), // swh = Swahili (individual language) + TRUETYPE_TAG('s', 'w', 'i', 0), // swi = Sui + TRUETYPE_TAG('s', 'w', 'j', 0), // swj = Sira + TRUETYPE_TAG('s', 'w', 'k', 0), // swk = Malawi Sena + TRUETYPE_TAG('s', 'w', 'l', 0), // swl = Swedish Sign Language + TRUETYPE_TAG('s', 'w', 'm', 0), // swm = Samosa + TRUETYPE_TAG('s', 'w', 'n', 0), // swn = Sawknah + TRUETYPE_TAG('s', 'w', 'o', 0), // swo = Shanenawa + TRUETYPE_TAG('s', 'w', 'p', 0), // swp = Suau + TRUETYPE_TAG('s', 'w', 'q', 0), // swq = Sharwa + TRUETYPE_TAG('s', 'w', 'r', 0), // swr = Saweru + TRUETYPE_TAG('s', 'w', 's', 0), // sws = Seluwasan + TRUETYPE_TAG('s', 'w', 't', 0), // swt = Sawila + TRUETYPE_TAG('s', 'w', 'u', 0), // swu = Suwawa + TRUETYPE_TAG('s', 'w', 'v', 0), // swv = Shekhawati + TRUETYPE_TAG('s', 'w', 'w', 0), // sww = Sowa + TRUETYPE_TAG('s', 'w', 'x', 0), // swx = Suruahá + TRUETYPE_TAG('s', 'w', 'y', 0), // swy = Sarua + TRUETYPE_TAG('s', 'x', 'b', 0), // sxb = Suba + TRUETYPE_TAG('s', 'x', 'c', 0), // sxc = Sicanian + TRUETYPE_TAG('s', 'x', 'e', 0), // sxe = Sighu + TRUETYPE_TAG('s', 'x', 'g', 0), // sxg = Shixing + TRUETYPE_TAG('s', 'x', 'k', 0), // sxk = Southern Kalapuya + TRUETYPE_TAG('s', 'x', 'l', 0), // sxl = Selian + TRUETYPE_TAG('s', 'x', 'm', 0), // sxm = Samre + TRUETYPE_TAG('s', 'x', 'n', 0), // sxn = Sangir + TRUETYPE_TAG('s', 'x', 'o', 0), // sxo = Sorothaptic + TRUETYPE_TAG('s', 'x', 'r', 0), // sxr = Saaroa + TRUETYPE_TAG('s', 'x', 's', 0), // sxs = Sasaru + TRUETYPE_TAG('s', 'x', 'u', 0), // sxu = Upper Saxon + TRUETYPE_TAG('s', 'x', 'w', 0), // sxw = Saxwe Gbe + TRUETYPE_TAG('s', 'y', 'a', 0), // sya = Siang + TRUETYPE_TAG('s', 'y', 'b', 0), // syb = Central Subanen + TRUETYPE_TAG('s', 'y', 'c', 0), // syc = Classical Syriac + TRUETYPE_TAG('s', 'y', 'd', 0), // syd = Samoyedic languages + TRUETYPE_TAG('s', 'y', 'i', 0), // syi = Seki + TRUETYPE_TAG('s', 'y', 'k', 0), // syk = Sukur + TRUETYPE_TAG('s', 'y', 'l', 0), // syl = Sylheti + TRUETYPE_TAG('s', 'y', 'm', 0), // sym = Maya Samo + TRUETYPE_TAG('s', 'y', 'n', 0), // syn = Senaya + TRUETYPE_TAG('s', 'y', 'o', 0), // syo = Suoy + TRUETYPE_TAG('s', 'y', 'r', 0), // syr = Syriac + TRUETYPE_TAG('s', 'y', 's', 0), // sys = Sinyar + TRUETYPE_TAG('s', 'y', 'w', 0), // syw = Kagate + TRUETYPE_TAG('s', 'y', 'y', 0), // syy = Al-Sayyid Bedouin Sign Language + TRUETYPE_TAG('s', 'z', 'a', 0), // sza = Semelai + TRUETYPE_TAG('s', 'z', 'b', 0), // szb = Ngalum + TRUETYPE_TAG('s', 'z', 'c', 0), // szc = Semaq Beri + TRUETYPE_TAG('s', 'z', 'd', 0), // szd = Seru + TRUETYPE_TAG('s', 'z', 'e', 0), // sze = Seze + TRUETYPE_TAG('s', 'z', 'g', 0), // szg = Sengele + TRUETYPE_TAG('s', 'z', 'l', 0), // szl = Silesian + TRUETYPE_TAG('s', 'z', 'n', 0), // szn = Sula + TRUETYPE_TAG('s', 'z', 'p', 0), // szp = Suabo + TRUETYPE_TAG('s', 'z', 'v', 0), // szv = Isu (Fako Division) + TRUETYPE_TAG('s', 'z', 'w', 0), // szw = Sawai + TRUETYPE_TAG('t', 'a', 'a', 0), // taa = Lower Tanana + TRUETYPE_TAG('t', 'a', 'b', 0), // tab = Tabassaran + TRUETYPE_TAG('t', 'a', 'c', 0), // tac = Lowland Tarahumara + TRUETYPE_TAG('t', 'a', 'd', 0), // tad = Tause + TRUETYPE_TAG('t', 'a', 'e', 0), // tae = Tariana + TRUETYPE_TAG('t', 'a', 'f', 0), // taf = Tapirapé + TRUETYPE_TAG('t', 'a', 'g', 0), // tag = Tagoi + TRUETYPE_TAG('t', 'a', 'i', 0), // tai = Tai languages + TRUETYPE_TAG('t', 'a', 'j', 0), // taj = Eastern Tamang + TRUETYPE_TAG('t', 'a', 'k', 0), // tak = Tala + TRUETYPE_TAG('t', 'a', 'l', 0), // tal = Tal + TRUETYPE_TAG('t', 'a', 'n', 0), // tan = Tangale + TRUETYPE_TAG('t', 'a', 'o', 0), // tao = Yami + TRUETYPE_TAG('t', 'a', 'p', 0), // tap = Taabwa + TRUETYPE_TAG('t', 'a', 'q', 0), // taq = Tamasheq + TRUETYPE_TAG('t', 'a', 'r', 0), // tar = Central Tarahumara + TRUETYPE_TAG('t', 'a', 's', 0), // tas = Tay Boi + TRUETYPE_TAG('t', 'a', 'u', 0), // tau = Upper Tanana + TRUETYPE_TAG('t', 'a', 'v', 0), // tav = Tatuyo + TRUETYPE_TAG('t', 'a', 'w', 0), // taw = Tai + TRUETYPE_TAG('t', 'a', 'x', 0), // tax = Tamki + TRUETYPE_TAG('t', 'a', 'y', 0), // tay = Atayal + TRUETYPE_TAG('t', 'a', 'z', 0), // taz = Tocho + TRUETYPE_TAG('t', 'b', 'a', 0), // tba = Aikanã + TRUETYPE_TAG('t', 'b', 'b', 0), // tbb = Tapeba + TRUETYPE_TAG('t', 'b', 'c', 0), // tbc = Takia + TRUETYPE_TAG('t', 'b', 'd', 0), // tbd = Kaki Ae + TRUETYPE_TAG('t', 'b', 'e', 0), // tbe = Tanimbili + TRUETYPE_TAG('t', 'b', 'f', 0), // tbf = Mandara + TRUETYPE_TAG('t', 'b', 'g', 0), // tbg = North Tairora + TRUETYPE_TAG('t', 'b', 'h', 0), // tbh = Thurawal + TRUETYPE_TAG('t', 'b', 'i', 0), // tbi = Gaam + TRUETYPE_TAG('t', 'b', 'j', 0), // tbj = Tiang + TRUETYPE_TAG('t', 'b', 'k', 0), // tbk = Calamian Tagbanwa + TRUETYPE_TAG('t', 'b', 'l', 0), // tbl = Tboli + TRUETYPE_TAG('t', 'b', 'm', 0), // tbm = Tagbu + TRUETYPE_TAG('t', 'b', 'n', 0), // tbn = Barro Negro Tunebo + TRUETYPE_TAG('t', 'b', 'o', 0), // tbo = Tawala + TRUETYPE_TAG('t', 'b', 'p', 0), // tbp = Taworta + TRUETYPE_TAG('t', 'b', 'q', 0), // tbq = Tibeto-Burman languages + TRUETYPE_TAG('t', 'b', 'r', 0), // tbr = Tumtum + TRUETYPE_TAG('t', 'b', 's', 0), // tbs = Tanguat + TRUETYPE_TAG('t', 'b', 't', 0), // tbt = Tembo (Kitembo) + TRUETYPE_TAG('t', 'b', 'u', 0), // tbu = Tubar + TRUETYPE_TAG('t', 'b', 'v', 0), // tbv = Tobo + TRUETYPE_TAG('t', 'b', 'w', 0), // tbw = Tagbanwa + TRUETYPE_TAG('t', 'b', 'x', 0), // tbx = Kapin + TRUETYPE_TAG('t', 'b', 'y', 0), // tby = Tabaru + TRUETYPE_TAG('t', 'b', 'z', 0), // tbz = Ditammari + TRUETYPE_TAG('t', 'c', 'a', 0), // tca = Ticuna + TRUETYPE_TAG('t', 'c', 'b', 0), // tcb = Tanacross + TRUETYPE_TAG('t', 'c', 'c', 0), // tcc = Datooga + TRUETYPE_TAG('t', 'c', 'd', 0), // tcd = Tafi + TRUETYPE_TAG('t', 'c', 'e', 0), // tce = Southern Tutchone + TRUETYPE_TAG('t', 'c', 'f', 0), // tcf = Malinaltepec Me'phaa + TRUETYPE_TAG('t', 'c', 'g', 0), // tcg = Tamagario + TRUETYPE_TAG('t', 'c', 'h', 0), // tch = Turks And Caicos Creole English + TRUETYPE_TAG('t', 'c', 'i', 0), // tci = Wára + TRUETYPE_TAG('t', 'c', 'k', 0), // tck = Tchitchege + TRUETYPE_TAG('t', 'c', 'l', 0), // tcl = Taman (Myanmar) + TRUETYPE_TAG('t', 'c', 'm', 0), // tcm = Tanahmerah + TRUETYPE_TAG('t', 'c', 'n', 0), // tcn = Tichurong + TRUETYPE_TAG('t', 'c', 'o', 0), // tco = Taungyo + TRUETYPE_TAG('t', 'c', 'p', 0), // tcp = Tawr Chin + TRUETYPE_TAG('t', 'c', 'q', 0), // tcq = Kaiy + TRUETYPE_TAG('t', 'c', 's', 0), // tcs = Torres Strait Creole + TRUETYPE_TAG('t', 'c', 't', 0), // tct = T'en + TRUETYPE_TAG('t', 'c', 'u', 0), // tcu = Southeastern Tarahumara + TRUETYPE_TAG('t', 'c', 'w', 0), // tcw = Tecpatlán Totonac + TRUETYPE_TAG('t', 'c', 'x', 0), // tcx = Toda + TRUETYPE_TAG('t', 'c', 'y', 0), // tcy = Tulu + TRUETYPE_TAG('t', 'c', 'z', 0), // tcz = Thado Chin + TRUETYPE_TAG('t', 'd', 'a', 0), // tda = Tagdal + TRUETYPE_TAG('t', 'd', 'b', 0), // tdb = Panchpargania + TRUETYPE_TAG('t', 'd', 'c', 0), // tdc = Emberá-Tadó + TRUETYPE_TAG('t', 'd', 'd', 0), // tdd = Tai Nüa + TRUETYPE_TAG('t', 'd', 'e', 0), // tde = Tiranige Diga Dogon + TRUETYPE_TAG('t', 'd', 'f', 0), // tdf = Talieng + TRUETYPE_TAG('t', 'd', 'g', 0), // tdg = Western Tamang + TRUETYPE_TAG('t', 'd', 'h', 0), // tdh = Thulung + TRUETYPE_TAG('t', 'd', 'i', 0), // tdi = Tomadino + TRUETYPE_TAG('t', 'd', 'j', 0), // tdj = Tajio + TRUETYPE_TAG('t', 'd', 'k', 0), // tdk = Tambas + TRUETYPE_TAG('t', 'd', 'l', 0), // tdl = Sur + TRUETYPE_TAG('t', 'd', 'n', 0), // tdn = Tondano + TRUETYPE_TAG('t', 'd', 'o', 0), // tdo = Teme + TRUETYPE_TAG('t', 'd', 'q', 0), // tdq = Tita + TRUETYPE_TAG('t', 'd', 'r', 0), // tdr = Todrah + TRUETYPE_TAG('t', 'd', 's', 0), // tds = Doutai + TRUETYPE_TAG('t', 'd', 't', 0), // tdt = Tetun Dili + TRUETYPE_TAG('t', 'd', 'u', 0), // tdu = Tempasuk Dusun + TRUETYPE_TAG('t', 'd', 'v', 0), // tdv = Toro + TRUETYPE_TAG('t', 'd', 'x', 0), // tdx = Tandroy-Mahafaly Malagasy + TRUETYPE_TAG('t', 'd', 'y', 0), // tdy = Tadyawan + TRUETYPE_TAG('t', 'e', 'a', 0), // tea = Temiar + TRUETYPE_TAG('t', 'e', 'b', 0), // teb = Tetete + TRUETYPE_TAG('t', 'e', 'c', 0), // tec = Terik + TRUETYPE_TAG('t', 'e', 'd', 0), // ted = Tepo Krumen + TRUETYPE_TAG('t', 'e', 'e', 0), // tee = Huehuetla Tepehua + TRUETYPE_TAG('t', 'e', 'f', 0), // tef = Teressa + TRUETYPE_TAG('t', 'e', 'g', 0), // teg = Teke-Tege + TRUETYPE_TAG('t', 'e', 'h', 0), // teh = Tehuelche + TRUETYPE_TAG('t', 'e', 'i', 0), // tei = Torricelli + TRUETYPE_TAG('t', 'e', 'k', 0), // tek = Ibali Teke + TRUETYPE_TAG('t', 'e', 'm', 0), // tem = Timne + TRUETYPE_TAG('t', 'e', 'n', 0), // ten = Tama (Colombia) + TRUETYPE_TAG('t', 'e', 'o', 0), // teo = Teso + TRUETYPE_TAG('t', 'e', 'p', 0), // tep = Tepecano + TRUETYPE_TAG('t', 'e', 'q', 0), // teq = Temein + TRUETYPE_TAG('t', 'e', 'r', 0), // ter = Tereno + TRUETYPE_TAG('t', 'e', 's', 0), // tes = Tengger + TRUETYPE_TAG('t', 'e', 't', 0), // tet = Tetum + TRUETYPE_TAG('t', 'e', 'u', 0), // teu = Soo + TRUETYPE_TAG('t', 'e', 'v', 0), // tev = Teor + TRUETYPE_TAG('t', 'e', 'w', 0), // tew = Tewa (USA) + TRUETYPE_TAG('t', 'e', 'x', 0), // tex = Tennet + TRUETYPE_TAG('t', 'e', 'y', 0), // tey = Tulishi + TRUETYPE_TAG('t', 'f', 'i', 0), // tfi = Tofin Gbe + TRUETYPE_TAG('t', 'f', 'n', 0), // tfn = Tanaina + TRUETYPE_TAG('t', 'f', 'o', 0), // tfo = Tefaro + TRUETYPE_TAG('t', 'f', 'r', 0), // tfr = Teribe + TRUETYPE_TAG('t', 'f', 't', 0), // tft = Ternate + TRUETYPE_TAG('t', 'g', 'a', 0), // tga = Sagalla + TRUETYPE_TAG('t', 'g', 'b', 0), // tgb = Tobilung + TRUETYPE_TAG('t', 'g', 'c', 0), // tgc = Tigak + TRUETYPE_TAG('t', 'g', 'd', 0), // tgd = Ciwogai + TRUETYPE_TAG('t', 'g', 'e', 0), // tge = Eastern Gorkha Tamang + TRUETYPE_TAG('t', 'g', 'f', 0), // tgf = Chalikha + TRUETYPE_TAG('t', 'g', 'g', 0), // tgg = Tangga + TRUETYPE_TAG('t', 'g', 'h', 0), // tgh = Tobagonian Creole English + TRUETYPE_TAG('t', 'g', 'i', 0), // tgi = Lawunuia + TRUETYPE_TAG('t', 'g', 'n', 0), // tgn = Tandaganon + TRUETYPE_TAG('t', 'g', 'o', 0), // tgo = Sudest + TRUETYPE_TAG('t', 'g', 'p', 0), // tgp = Tangoa + TRUETYPE_TAG('t', 'g', 'q', 0), // tgq = Tring + TRUETYPE_TAG('t', 'g', 'r', 0), // tgr = Tareng + TRUETYPE_TAG('t', 'g', 's', 0), // tgs = Nume + TRUETYPE_TAG('t', 'g', 't', 0), // tgt = Central Tagbanwa + TRUETYPE_TAG('t', 'g', 'u', 0), // tgu = Tanggu + TRUETYPE_TAG('t', 'g', 'v', 0), // tgv = Tingui-Boto + TRUETYPE_TAG('t', 'g', 'w', 0), // tgw = Tagwana Senoufo + TRUETYPE_TAG('t', 'g', 'x', 0), // tgx = Tagish + TRUETYPE_TAG('t', 'g', 'y', 0), // tgy = Togoyo + TRUETYPE_TAG('t', 'h', 'c', 0), // thc = Tai Hang Tong + TRUETYPE_TAG('t', 'h', 'd', 0), // thd = Thayore + TRUETYPE_TAG('t', 'h', 'e', 0), // the = Chitwania Tharu + TRUETYPE_TAG('t', 'h', 'f', 0), // thf = Thangmi + TRUETYPE_TAG('t', 'h', 'h', 0), // thh = Northern Tarahumara + TRUETYPE_TAG('t', 'h', 'i', 0), // thi = Tai Long + TRUETYPE_TAG('t', 'h', 'k', 0), // thk = Tharaka + TRUETYPE_TAG('t', 'h', 'l', 0), // thl = Dangaura Tharu + TRUETYPE_TAG('t', 'h', 'm', 0), // thm = Aheu + TRUETYPE_TAG('t', 'h', 'n', 0), // thn = Thachanadan + TRUETYPE_TAG('t', 'h', 'p', 0), // thp = Thompson + TRUETYPE_TAG('t', 'h', 'q', 0), // thq = Kochila Tharu + TRUETYPE_TAG('t', 'h', 'r', 0), // thr = Rana Tharu + TRUETYPE_TAG('t', 'h', 's', 0), // ths = Thakali + TRUETYPE_TAG('t', 'h', 't', 0), // tht = Tahltan + TRUETYPE_TAG('t', 'h', 'u', 0), // thu = Thuri + TRUETYPE_TAG('t', 'h', 'v', 0), // thv = Tahaggart Tamahaq + TRUETYPE_TAG('t', 'h', 'w', 0), // thw = Thudam + TRUETYPE_TAG('t', 'h', 'x', 0), // thx = The + TRUETYPE_TAG('t', 'h', 'y', 0), // thy = Tha + TRUETYPE_TAG('t', 'h', 'z', 0), // thz = Tayart Tamajeq + TRUETYPE_TAG('t', 'i', 'a', 0), // tia = Tidikelt Tamazight + TRUETYPE_TAG('t', 'i', 'c', 0), // tic = Tira + TRUETYPE_TAG('t', 'i', 'd', 0), // tid = Tidong + TRUETYPE_TAG('t', 'i', 'e', 0), // tie = Tingal + TRUETYPE_TAG('t', 'i', 'f', 0), // tif = Tifal + TRUETYPE_TAG('t', 'i', 'g', 0), // tig = Tigre + TRUETYPE_TAG('t', 'i', 'h', 0), // tih = Timugon Murut + TRUETYPE_TAG('t', 'i', 'i', 0), // tii = Tiene + TRUETYPE_TAG('t', 'i', 'j', 0), // tij = Tilung + TRUETYPE_TAG('t', 'i', 'k', 0), // tik = Tikar + TRUETYPE_TAG('t', 'i', 'l', 0), // til = Tillamook + TRUETYPE_TAG('t', 'i', 'm', 0), // tim = Timbe + TRUETYPE_TAG('t', 'i', 'n', 0), // tin = Tindi + TRUETYPE_TAG('t', 'i', 'o', 0), // tio = Teop + TRUETYPE_TAG('t', 'i', 'p', 0), // tip = Trimuris + TRUETYPE_TAG('t', 'i', 'q', 0), // tiq = Tiéfo + TRUETYPE_TAG('t', 'i', 's', 0), // tis = Masadiit Itneg + TRUETYPE_TAG('t', 'i', 't', 0), // tit = Tinigua + TRUETYPE_TAG('t', 'i', 'u', 0), // tiu = Adasen + TRUETYPE_TAG('t', 'i', 'v', 0), // tiv = Tiv + TRUETYPE_TAG('t', 'i', 'w', 0), // tiw = Tiwi + TRUETYPE_TAG('t', 'i', 'x', 0), // tix = Southern Tiwa + TRUETYPE_TAG('t', 'i', 'y', 0), // tiy = Tiruray + TRUETYPE_TAG('t', 'i', 'z', 0), // tiz = Tai Hongjin + TRUETYPE_TAG('t', 'j', 'a', 0), // tja = Tajuasohn + TRUETYPE_TAG('t', 'j', 'g', 0), // tjg = Tunjung + TRUETYPE_TAG('t', 'j', 'i', 0), // tji = Northern Tujia + TRUETYPE_TAG('t', 'j', 'm', 0), // tjm = Timucua + TRUETYPE_TAG('t', 'j', 'n', 0), // tjn = Tonjon + TRUETYPE_TAG('t', 'j', 'o', 0), // tjo = Temacine Tamazight + TRUETYPE_TAG('t', 'j', 's', 0), // tjs = Southern Tujia + TRUETYPE_TAG('t', 'j', 'u', 0), // tju = Tjurruru + TRUETYPE_TAG('t', 'k', 'a', 0), // tka = Truká + TRUETYPE_TAG('t', 'k', 'b', 0), // tkb = Buksa + TRUETYPE_TAG('t', 'k', 'd', 0), // tkd = Tukudede + TRUETYPE_TAG('t', 'k', 'e', 0), // tke = Takwane + TRUETYPE_TAG('t', 'k', 'f', 0), // tkf = Tukumanféd + TRUETYPE_TAG('t', 'k', 'g', 0), // tkg = Tesaka Malagasy + TRUETYPE_TAG('t', 'k', 'k', 0), // tkk = Takpa + TRUETYPE_TAG('t', 'k', 'l', 0), // tkl = Tokelau + TRUETYPE_TAG('t', 'k', 'm', 0), // tkm = Takelma + TRUETYPE_TAG('t', 'k', 'n', 0), // tkn = Toku-No-Shima + TRUETYPE_TAG('t', 'k', 'p', 0), // tkp = Tikopia + TRUETYPE_TAG('t', 'k', 'q', 0), // tkq = Tee + TRUETYPE_TAG('t', 'k', 'r', 0), // tkr = Tsakhur + TRUETYPE_TAG('t', 'k', 's', 0), // tks = Takestani + TRUETYPE_TAG('t', 'k', 't', 0), // tkt = Kathoriya Tharu + TRUETYPE_TAG('t', 'k', 'u', 0), // tku = Upper Necaxa Totonac + TRUETYPE_TAG('t', 'k', 'w', 0), // tkw = Teanu + TRUETYPE_TAG('t', 'k', 'x', 0), // tkx = Tangko + TRUETYPE_TAG('t', 'k', 'z', 0), // tkz = Takua + TRUETYPE_TAG('t', 'l', 'a', 0), // tla = Southwestern Tepehuan + TRUETYPE_TAG('t', 'l', 'b', 0), // tlb = Tobelo + TRUETYPE_TAG('t', 'l', 'c', 0), // tlc = Yecuatla Totonac + TRUETYPE_TAG('t', 'l', 'd', 0), // tld = Talaud + TRUETYPE_TAG('t', 'l', 'f', 0), // tlf = Telefol + TRUETYPE_TAG('t', 'l', 'g', 0), // tlg = Tofanma + TRUETYPE_TAG('t', 'l', 'h', 0), // tlh = Klingon + TRUETYPE_TAG('t', 'l', 'i', 0), // tli = Tlingit + TRUETYPE_TAG('t', 'l', 'j', 0), // tlj = Talinga-Bwisi + TRUETYPE_TAG('t', 'l', 'k', 0), // tlk = Taloki + TRUETYPE_TAG('t', 'l', 'l', 0), // tll = Tetela + TRUETYPE_TAG('t', 'l', 'm', 0), // tlm = Tolomako + TRUETYPE_TAG('t', 'l', 'n', 0), // tln = Talondo' + TRUETYPE_TAG('t', 'l', 'o', 0), // tlo = Talodi + TRUETYPE_TAG('t', 'l', 'p', 0), // tlp = Filomena Mata-Coahuitlán Totonac + TRUETYPE_TAG('t', 'l', 'q', 0), // tlq = Tai Loi + TRUETYPE_TAG('t', 'l', 'r', 0), // tlr = Talise + TRUETYPE_TAG('t', 'l', 's', 0), // tls = Tambotalo + TRUETYPE_TAG('t', 'l', 't', 0), // tlt = Teluti + TRUETYPE_TAG('t', 'l', 'u', 0), // tlu = Tulehu + TRUETYPE_TAG('t', 'l', 'v', 0), // tlv = Taliabu + TRUETYPE_TAG('t', 'l', 'w', 0), // tlw = South Wemale + TRUETYPE_TAG('t', 'l', 'x', 0), // tlx = Khehek + TRUETYPE_TAG('t', 'l', 'y', 0), // tly = Talysh + TRUETYPE_TAG('t', 'm', 'a', 0), // tma = Tama (Chad) + TRUETYPE_TAG('t', 'm', 'b', 0), // tmb = Katbol + TRUETYPE_TAG('t', 'm', 'c', 0), // tmc = Tumak + TRUETYPE_TAG('t', 'm', 'd', 0), // tmd = Haruai + TRUETYPE_TAG('t', 'm', 'e', 0), // tme = Tremembé + TRUETYPE_TAG('t', 'm', 'f', 0), // tmf = Toba-Maskoy + TRUETYPE_TAG('t', 'm', 'g', 0), // tmg = Ternateño + TRUETYPE_TAG('t', 'm', 'h', 0), // tmh = Tamashek + TRUETYPE_TAG('t', 'm', 'i', 0), // tmi = Tutuba + TRUETYPE_TAG('t', 'm', 'j', 0), // tmj = Samarokena + TRUETYPE_TAG('t', 'm', 'k', 0), // tmk = Northwestern Tamang + TRUETYPE_TAG('t', 'm', 'l', 0), // tml = Tamnim Citak + TRUETYPE_TAG('t', 'm', 'm', 0), // tmm = Tai Thanh + TRUETYPE_TAG('t', 'm', 'n', 0), // tmn = Taman (Indonesia) + TRUETYPE_TAG('t', 'm', 'o', 0), // tmo = Temoq + TRUETYPE_TAG('t', 'm', 'p', 0), // tmp = Tai Mène + TRUETYPE_TAG('t', 'm', 'q', 0), // tmq = Tumleo + TRUETYPE_TAG('t', 'm', 'r', + 0), // tmr = Jewish Babylonian Aramaic (ca. 200-1200 CE) + TRUETYPE_TAG('t', 'm', 's', 0), // tms = Tima + TRUETYPE_TAG('t', 'm', 't', 0), // tmt = Tasmate + TRUETYPE_TAG('t', 'm', 'u', 0), // tmu = Iau + TRUETYPE_TAG('t', 'm', 'v', 0), // tmv = Tembo (Motembo) + TRUETYPE_TAG('t', 'm', 'w', 0), // tmw = Temuan + TRUETYPE_TAG('t', 'm', 'y', 0), // tmy = Tami + TRUETYPE_TAG('t', 'm', 'z', 0), // tmz = Tamanaku + TRUETYPE_TAG('t', 'n', 'a', 0), // tna = Tacana + TRUETYPE_TAG('t', 'n', 'b', 0), // tnb = Western Tunebo + TRUETYPE_TAG('t', 'n', 'c', 0), // tnc = Tanimuca-Retuarã + TRUETYPE_TAG('t', 'n', 'd', 0), // tnd = Angosturas Tunebo + TRUETYPE_TAG('t', 'n', 'e', 0), // tne = Tinoc Kallahan + TRUETYPE_TAG('t', 'n', 'f', 0), // tnf = Tangshewi + TRUETYPE_TAG('t', 'n', 'g', 0), // tng = Tobanga + TRUETYPE_TAG('t', 'n', 'h', 0), // tnh = Maiani + TRUETYPE_TAG('t', 'n', 'i', 0), // tni = Tandia + TRUETYPE_TAG('t', 'n', 'k', 0), // tnk = Kwamera + TRUETYPE_TAG('t', 'n', 'l', 0), // tnl = Lenakel + TRUETYPE_TAG('t', 'n', 'm', 0), // tnm = Tabla + TRUETYPE_TAG('t', 'n', 'n', 0), // tnn = North Tanna + TRUETYPE_TAG('t', 'n', 'o', 0), // tno = Toromono + TRUETYPE_TAG('t', 'n', 'p', 0), // tnp = Whitesands + TRUETYPE_TAG('t', 'n', 'q', 0), // tnq = Taino + TRUETYPE_TAG('t', 'n', 'r', 0), // tnr = Bedik + TRUETYPE_TAG('t', 'n', 's', 0), // tns = Tenis + TRUETYPE_TAG('t', 'n', 't', 0), // tnt = Tontemboan + TRUETYPE_TAG('t', 'n', 'u', 0), // tnu = Tay Khang + TRUETYPE_TAG('t', 'n', 'v', 0), // tnv = Tangchangya + TRUETYPE_TAG('t', 'n', 'w', 0), // tnw = Tonsawang + TRUETYPE_TAG('t', 'n', 'x', 0), // tnx = Tanema + TRUETYPE_TAG('t', 'n', 'y', 0), // tny = Tongwe + TRUETYPE_TAG('t', 'n', 'z', 0), // tnz = Tonga (Thailand) + TRUETYPE_TAG('t', 'o', 'b', 0), // tob = Toba + TRUETYPE_TAG('t', 'o', 'c', 0), // toc = Coyutla Totonac + TRUETYPE_TAG('t', 'o', 'd', 0), // tod = Toma + TRUETYPE_TAG('t', 'o', 'e', 0), // toe = Tomedes + TRUETYPE_TAG('t', 'o', 'f', 0), // tof = Gizrra + TRUETYPE_TAG('t', 'o', 'g', 0), // tog = Tonga (Nyasa) + TRUETYPE_TAG('t', 'o', 'h', 0), // toh = Gitonga + TRUETYPE_TAG('t', 'o', 'i', 0), // toi = Tonga (Zambia) + TRUETYPE_TAG('t', 'o', 'j', 0), // toj = Tojolabal + TRUETYPE_TAG('t', 'o', 'l', 0), // tol = Tolowa + TRUETYPE_TAG('t', 'o', 'm', 0), // tom = Tombulu + TRUETYPE_TAG('t', 'o', 'o', 0), // too = Xicotepec De Juárez Totonac + TRUETYPE_TAG('t', 'o', 'p', 0), // top = Papantla Totonac + TRUETYPE_TAG('t', 'o', 'q', 0), // toq = Toposa + TRUETYPE_TAG('t', 'o', 'r', 0), // tor = Togbo-Vara Banda + TRUETYPE_TAG('t', 'o', 's', 0), // tos = Highland Totonac + TRUETYPE_TAG('t', 'o', 'u', 0), // tou = Tho + TRUETYPE_TAG('t', 'o', 'v', 0), // tov = Upper Taromi + TRUETYPE_TAG('t', 'o', 'w', 0), // tow = Jemez + TRUETYPE_TAG('t', 'o', 'x', 0), // tox = Tobian + TRUETYPE_TAG('t', 'o', 'y', 0), // toy = Topoiyo + TRUETYPE_TAG('t', 'o', 'z', 0), // toz = To + TRUETYPE_TAG('t', 'p', 'a', 0), // tpa = Taupota + TRUETYPE_TAG('t', 'p', 'c', 0), // tpc = Azoyú Me'phaa + TRUETYPE_TAG('t', 'p', 'e', 0), // tpe = Tippera + TRUETYPE_TAG('t', 'p', 'f', 0), // tpf = Tarpia + TRUETYPE_TAG('t', 'p', 'g', 0), // tpg = Kula + TRUETYPE_TAG('t', 'p', 'i', 0), // tpi = Tok Pisin + TRUETYPE_TAG('t', 'p', 'j', 0), // tpj = Tapieté + TRUETYPE_TAG('t', 'p', 'k', 0), // tpk = Tupinikin + TRUETYPE_TAG('t', 'p', 'l', 0), // tpl = Tlacoapa Me'phaa + TRUETYPE_TAG('t', 'p', 'm', 0), // tpm = Tampulma + TRUETYPE_TAG('t', 'p', 'n', 0), // tpn = Tupinambá + TRUETYPE_TAG('t', 'p', 'o', 0), // tpo = Tai Pao + TRUETYPE_TAG('t', 'p', 'p', 0), // tpp = Pisaflores Tepehua + TRUETYPE_TAG('t', 'p', 'q', 0), // tpq = Tukpa + TRUETYPE_TAG('t', 'p', 'r', 0), // tpr = Tuparí + TRUETYPE_TAG('t', 'p', 't', 0), // tpt = Tlachichilco Tepehua + TRUETYPE_TAG('t', 'p', 'u', 0), // tpu = Tampuan + TRUETYPE_TAG('t', 'p', 'v', 0), // tpv = Tanapag + TRUETYPE_TAG('t', 'p', 'w', 0), // tpw = Tupí + TRUETYPE_TAG('t', 'p', 'x', 0), // tpx = Acatepec Me'phaa + TRUETYPE_TAG('t', 'p', 'y', 0), // tpy = Trumai + TRUETYPE_TAG('t', 'p', 'z', 0), // tpz = Tinputz + TRUETYPE_TAG('t', 'q', 'b', 0), // tqb = Tembé + TRUETYPE_TAG('t', 'q', 'l', 0), // tql = Lehali + TRUETYPE_TAG('t', 'q', 'm', 0), // tqm = Turumsa + TRUETYPE_TAG('t', 'q', 'n', 0), // tqn = Tenino + TRUETYPE_TAG('t', 'q', 'o', 0), // tqo = Toaripi + TRUETYPE_TAG('t', 'q', 'p', 0), // tqp = Tomoip + TRUETYPE_TAG('t', 'q', 'q', 0), // tqq = Tunni + TRUETYPE_TAG('t', 'q', 'r', 0), // tqr = Torona + TRUETYPE_TAG('t', 'q', 't', 0), // tqt = Western Totonac + TRUETYPE_TAG('t', 'q', 'u', 0), // tqu = Touo + TRUETYPE_TAG('t', 'q', 'w', 0), // tqw = Tonkawa + TRUETYPE_TAG('t', 'r', 'a', 0), // tra = Tirahi + TRUETYPE_TAG('t', 'r', 'b', 0), // trb = Terebu + TRUETYPE_TAG('t', 'r', 'c', 0), // trc = Copala Triqui + TRUETYPE_TAG('t', 'r', 'd', 0), // trd = Turi + TRUETYPE_TAG('t', 'r', 'e', 0), // tre = East Tarangan + TRUETYPE_TAG('t', 'r', 'f', 0), // trf = Trinidadian Creole English + TRUETYPE_TAG('t', 'r', 'g', 0), // trg = Lishán Didán + TRUETYPE_TAG('t', 'r', 'h', 0), // trh = Turaka + TRUETYPE_TAG('t', 'r', 'i', 0), // tri = Trió + TRUETYPE_TAG('t', 'r', 'j', 0), // trj = Toram + TRUETYPE_TAG('t', 'r', 'k', 0), // trk = Turkic languages + TRUETYPE_TAG('t', 'r', 'l', 0), // trl = Traveller Scottish + TRUETYPE_TAG('t', 'r', 'm', 0), // trm = Tregami + TRUETYPE_TAG('t', 'r', 'n', 0), // trn = Trinitario + TRUETYPE_TAG('t', 'r', 'o', 0), // tro = Tarao Naga + TRUETYPE_TAG('t', 'r', 'p', 0), // trp = Kok Borok + TRUETYPE_TAG('t', 'r', 'q', 0), // trq = San Martín Itunyoso Triqui + TRUETYPE_TAG('t', 'r', 'r', 0), // trr = Taushiro + TRUETYPE_TAG('t', 'r', 's', 0), // trs = Chicahuaxtla Triqui + TRUETYPE_TAG('t', 'r', 't', 0), // trt = Tunggare + TRUETYPE_TAG('t', 'r', 'u', 0), // tru = Turoyo + TRUETYPE_TAG('t', 'r', 'v', 0), // trv = Taroko + TRUETYPE_TAG('t', 'r', 'w', 0), // trw = Torwali + TRUETYPE_TAG('t', 'r', 'x', 0), // trx = Tringgus-Sembaan Bidayuh + TRUETYPE_TAG('t', 'r', 'y', 0), // try = Turung + TRUETYPE_TAG('t', 'r', 'z', 0), // trz = Torá + TRUETYPE_TAG('t', 's', 'a', 0), // tsa = Tsaangi + TRUETYPE_TAG('t', 's', 'b', 0), // tsb = Tsamai + TRUETYPE_TAG('t', 's', 'c', 0), // tsc = Tswa + TRUETYPE_TAG('t', 's', 'd', 0), // tsd = Tsakonian + TRUETYPE_TAG('t', 's', 'e', 0), // tse = Tunisian Sign Language + TRUETYPE_TAG('t', 's', 'f', 0), // tsf = Southwestern Tamang + TRUETYPE_TAG('t', 's', 'g', 0), // tsg = Tausug + TRUETYPE_TAG('t', 's', 'h', 0), // tsh = Tsuvan + TRUETYPE_TAG('t', 's', 'i', 0), // tsi = Tsimshian + TRUETYPE_TAG('t', 's', 'j', 0), // tsj = Tshangla + TRUETYPE_TAG('t', 's', 'k', 0), // tsk = Tseku + TRUETYPE_TAG('t', 's', 'l', 0), // tsl = Ts'ün-Lao + TRUETYPE_TAG('t', 's', 'm', 0), // tsm = Turkish Sign Language + TRUETYPE_TAG('t', 's', 'p', 0), // tsp = Northern Toussian + TRUETYPE_TAG('t', 's', 'q', 0), // tsq = Thai Sign Language + TRUETYPE_TAG('t', 's', 'r', 0), // tsr = Akei + TRUETYPE_TAG('t', 's', 's', 0), // tss = Taiwan Sign Language + TRUETYPE_TAG('t', 's', 't', 0), // tst = Tondi Songway Kiini + TRUETYPE_TAG('t', 's', 'u', 0), // tsu = Tsou + TRUETYPE_TAG('t', 's', 'v', 0), // tsv = Tsogo + TRUETYPE_TAG('t', 's', 'w', 0), // tsw = Tsishingini + TRUETYPE_TAG('t', 's', 'x', 0), // tsx = Mubami + TRUETYPE_TAG('t', 's', 'y', 0), // tsy = Tebul Sign Language + TRUETYPE_TAG('t', 's', 'z', 0), // tsz = Purepecha + TRUETYPE_TAG('t', 't', 'a', 0), // tta = Tutelo + TRUETYPE_TAG('t', 't', 'b', 0), // ttb = Gaa + TRUETYPE_TAG('t', 't', 'c', 0), // ttc = Tektiteko + TRUETYPE_TAG('t', 't', 'd', 0), // ttd = Tauade + TRUETYPE_TAG('t', 't', 'e', 0), // tte = Bwanabwana + TRUETYPE_TAG('t', 't', 'f', 0), // ttf = Tuotomb + TRUETYPE_TAG('t', 't', 'g', 0), // ttg = Tutong + TRUETYPE_TAG('t', 't', 'h', 0), // tth = Upper Ta'oih + TRUETYPE_TAG('t', 't', 'i', 0), // tti = Tobati + TRUETYPE_TAG('t', 't', 'j', 0), // ttj = Tooro + TRUETYPE_TAG('t', 't', 'k', 0), // ttk = Totoro + TRUETYPE_TAG('t', 't', 'l', 0), // ttl = Totela + TRUETYPE_TAG('t', 't', 'm', 0), // ttm = Northern Tutchone + TRUETYPE_TAG('t', 't', 'n', 0), // ttn = Towei + TRUETYPE_TAG('t', 't', 'o', 0), // tto = Lower Ta'oih + TRUETYPE_TAG('t', 't', 'p', 0), // ttp = Tombelala + TRUETYPE_TAG('t', 't', 'q', 0), // ttq = Tawallammat Tamajaq + TRUETYPE_TAG('t', 't', 'r', 0), // ttr = Tera + TRUETYPE_TAG('t', 't', 's', 0), // tts = Northeastern Thai + TRUETYPE_TAG('t', 't', 't', 0), // ttt = Muslim Tat + TRUETYPE_TAG('t', 't', 'u', 0), // ttu = Torau + TRUETYPE_TAG('t', 't', 'v', 0), // ttv = Titan + TRUETYPE_TAG('t', 't', 'w', 0), // ttw = Long Wat + TRUETYPE_TAG('t', 't', 'y', 0), // tty = Sikaritai + TRUETYPE_TAG('t', 't', 'z', 0), // ttz = Tsum + TRUETYPE_TAG('t', 'u', 'a', 0), // tua = Wiarumus + TRUETYPE_TAG('t', 'u', 'b', 0), // tub = Tübatulabal + TRUETYPE_TAG('t', 'u', 'c', 0), // tuc = Mutu + TRUETYPE_TAG('t', 'u', 'd', 0), // tud = Tuxá + TRUETYPE_TAG('t', 'u', 'e', 0), // tue = Tuyuca + TRUETYPE_TAG('t', 'u', 'f', 0), // tuf = Central Tunebo + TRUETYPE_TAG('t', 'u', 'g', 0), // tug = Tunia + TRUETYPE_TAG('t', 'u', 'h', 0), // tuh = Taulil + TRUETYPE_TAG('t', 'u', 'i', 0), // tui = Tupuri + TRUETYPE_TAG('t', 'u', 'j', 0), // tuj = Tugutil + TRUETYPE_TAG('t', 'u', 'l', 0), // tul = Tula + TRUETYPE_TAG('t', 'u', 'm', 0), // tum = Tumbuka + TRUETYPE_TAG('t', 'u', 'n', 0), // tun = Tunica + TRUETYPE_TAG('t', 'u', 'o', 0), // tuo = Tucano + TRUETYPE_TAG('t', 'u', 'p', 0), // tup = Tupi languages + TRUETYPE_TAG('t', 'u', 'q', 0), // tuq = Tedaga + TRUETYPE_TAG('t', 'u', 's', 0), // tus = Tuscarora + TRUETYPE_TAG('t', 'u', 't', 0), // tut = Altaic languages + TRUETYPE_TAG('t', 'u', 'u', 0), // tuu = Tututni + TRUETYPE_TAG('t', 'u', 'v', 0), // tuv = Turkana + TRUETYPE_TAG('t', 'u', 'w', 0), // tuw = Tungus languages + TRUETYPE_TAG('t', 'u', 'x', 0), // tux = Tuxináwa + TRUETYPE_TAG('t', 'u', 'y', 0), // tuy = Tugen + TRUETYPE_TAG('t', 'u', 'z', 0), // tuz = Turka + TRUETYPE_TAG('t', 'v', 'a', 0), // tva = Vaghua + TRUETYPE_TAG('t', 'v', 'd', 0), // tvd = Tsuvadi + TRUETYPE_TAG('t', 'v', 'e', 0), // tve = Te'un + TRUETYPE_TAG('t', 'v', 'k', 0), // tvk = Southeast Ambrym + TRUETYPE_TAG('t', 'v', 'l', 0), // tvl = Tuvalu + TRUETYPE_TAG('t', 'v', 'm', 0), // tvm = Tela-Masbuar + TRUETYPE_TAG('t', 'v', 'n', 0), // tvn = Tavoyan + TRUETYPE_TAG('t', 'v', 'o', 0), // tvo = Tidore + TRUETYPE_TAG('t', 'v', 's', 0), // tvs = Taveta + TRUETYPE_TAG('t', 'v', 't', 0), // tvt = Tutsa Naga + TRUETYPE_TAG('t', 'v', 'w', 0), // tvw = Sedoa + TRUETYPE_TAG('t', 'v', 'y', 0), // tvy = Timor Pidgin + TRUETYPE_TAG('t', 'w', 'a', 0), // twa = Twana + TRUETYPE_TAG('t', 'w', 'b', 0), // twb = Western Tawbuid + TRUETYPE_TAG('t', 'w', 'c', 0), // twc = Teshenawa + TRUETYPE_TAG('t', 'w', 'd', 0), // twd = Twents + TRUETYPE_TAG('t', 'w', 'e', 0), // twe = Tewa (Indonesia) + TRUETYPE_TAG('t', 'w', 'f', 0), // twf = Northern Tiwa + TRUETYPE_TAG('t', 'w', 'g', 0), // twg = Tereweng + TRUETYPE_TAG('t', 'w', 'h', 0), // twh = Tai Dón + TRUETYPE_TAG('t', 'w', 'l', 0), // twl = Tawara + TRUETYPE_TAG('t', 'w', 'm', 0), // twm = Tawang Monpa + TRUETYPE_TAG('t', 'w', 'n', 0), // twn = Twendi + TRUETYPE_TAG('t', 'w', 'o', 0), // two = Tswapong + TRUETYPE_TAG('t', 'w', 'p', 0), // twp = Ere + TRUETYPE_TAG('t', 'w', 'q', 0), // twq = Tasawaq + TRUETYPE_TAG('t', 'w', 'r', 0), // twr = Southwestern Tarahumara + TRUETYPE_TAG('t', 'w', 't', 0), // twt = Turiwára + TRUETYPE_TAG('t', 'w', 'u', 0), // twu = Termanu + TRUETYPE_TAG('t', 'w', 'w', 0), // tww = Tuwari + TRUETYPE_TAG('t', 'w', 'x', 0), // twx = Tewe + TRUETYPE_TAG('t', 'w', 'y', 0), // twy = Tawoyan + TRUETYPE_TAG('t', 'x', 'a', 0), // txa = Tombonuo + TRUETYPE_TAG('t', 'x', 'b', 0), // txb = Tokharian B + TRUETYPE_TAG('t', 'x', 'c', 0), // txc = Tsetsaut + TRUETYPE_TAG('t', 'x', 'e', 0), // txe = Totoli + TRUETYPE_TAG('t', 'x', 'g', 0), // txg = Tangut + TRUETYPE_TAG('t', 'x', 'h', 0), // txh = Thracian + TRUETYPE_TAG('t', 'x', 'i', 0), // txi = Ikpeng + TRUETYPE_TAG('t', 'x', 'm', 0), // txm = Tomini + TRUETYPE_TAG('t', 'x', 'n', 0), // txn = West Tarangan + TRUETYPE_TAG('t', 'x', 'o', 0), // txo = Toto + TRUETYPE_TAG('t', 'x', 'q', 0), // txq = Tii + TRUETYPE_TAG('t', 'x', 'r', 0), // txr = Tartessian + TRUETYPE_TAG('t', 'x', 's', 0), // txs = Tonsea + TRUETYPE_TAG('t', 'x', 't', 0), // txt = Citak + TRUETYPE_TAG('t', 'x', 'u', 0), // txu = Kayapó + TRUETYPE_TAG('t', 'x', 'x', 0), // txx = Tatana + TRUETYPE_TAG('t', 'x', 'y', 0), // txy = Tanosy Malagasy + TRUETYPE_TAG('t', 'y', 'a', 0), // tya = Tauya + TRUETYPE_TAG('t', 'y', 'e', 0), // tye = Kyenga + TRUETYPE_TAG('t', 'y', 'h', 0), // tyh = O'du + TRUETYPE_TAG('t', 'y', 'i', 0), // tyi = Teke-Tsaayi + TRUETYPE_TAG('t', 'y', 'j', 0), // tyj = Tai Do + TRUETYPE_TAG('t', 'y', 'l', 0), // tyl = Thu Lao + TRUETYPE_TAG('t', 'y', 'n', 0), // tyn = Kombai + TRUETYPE_TAG('t', 'y', 'p', 0), // typ = Thaypan + TRUETYPE_TAG('t', 'y', 'r', 0), // tyr = Tai Daeng + TRUETYPE_TAG('t', 'y', 's', 0), // tys = Tày Sa Pa + TRUETYPE_TAG('t', 'y', 't', 0), // tyt = Tày Tac + TRUETYPE_TAG('t', 'y', 'u', 0), // tyu = Kua + TRUETYPE_TAG('t', 'y', 'v', 0), // tyv = Tuvinian + TRUETYPE_TAG('t', 'y', 'x', 0), // tyx = Teke-Tyee + TRUETYPE_TAG('t', 'y', 'z', 0), // tyz = Tày + TRUETYPE_TAG('t', 'z', 'a', 0), // tza = Tanzanian Sign Language + TRUETYPE_TAG('t', 'z', 'h', 0), // tzh = Tzeltal + TRUETYPE_TAG('t', 'z', 'j', 0), // tzj = Tz'utujil + TRUETYPE_TAG('t', 'z', 'm', 0), // tzm = Central Atlas Tamazight + TRUETYPE_TAG('t', 'z', 'n', 0), // tzn = Tugun + TRUETYPE_TAG('t', 'z', 'o', 0), // tzo = Tzotzil + TRUETYPE_TAG('t', 'z', 'x', 0), // tzx = Tabriak + TRUETYPE_TAG('u', 'a', 'm', 0), // uam = Uamué + TRUETYPE_TAG('u', 'a', 'n', 0), // uan = Kuan + TRUETYPE_TAG('u', 'a', 'r', 0), // uar = Tairuma + TRUETYPE_TAG('u', 'b', 'a', 0), // uba = Ubang + TRUETYPE_TAG('u', 'b', 'i', 0), // ubi = Ubi + TRUETYPE_TAG('u', 'b', 'l', 0), // ubl = Buhi'non Bikol + TRUETYPE_TAG('u', 'b', 'r', 0), // ubr = Ubir + TRUETYPE_TAG('u', 'b', 'u', 0), // ubu = Umbu-Ungu + TRUETYPE_TAG('u', 'b', 'y', 0), // uby = Ubykh + TRUETYPE_TAG('u', 'd', 'a', 0), // uda = Uda + TRUETYPE_TAG('u', 'd', 'e', 0), // ude = Udihe + TRUETYPE_TAG('u', 'd', 'g', 0), // udg = Muduga + TRUETYPE_TAG('u', 'd', 'i', 0), // udi = Udi + TRUETYPE_TAG('u', 'd', 'j', 0), // udj = Ujir + TRUETYPE_TAG('u', 'd', 'l', 0), // udl = Wuzlam + TRUETYPE_TAG('u', 'd', 'm', 0), // udm = Udmurt + TRUETYPE_TAG('u', 'd', 'u', 0), // udu = Uduk + TRUETYPE_TAG('u', 'e', 's', 0), // ues = Kioko + TRUETYPE_TAG('u', 'f', 'i', 0), // ufi = Ufim + TRUETYPE_TAG('u', 'g', 'a', 0), // uga = Ugaritic + TRUETYPE_TAG('u', 'g', 'b', 0), // ugb = Kuku-Ugbanh + TRUETYPE_TAG('u', 'g', 'e', 0), // uge = Ughele + TRUETYPE_TAG('u', 'g', 'n', 0), // ugn = Ugandan Sign Language + TRUETYPE_TAG('u', 'g', 'o', 0), // ugo = Ugong + TRUETYPE_TAG('u', 'g', 'y', 0), // ugy = Uruguayan Sign Language + TRUETYPE_TAG('u', 'h', 'a', 0), // uha = Uhami + TRUETYPE_TAG('u', 'h', 'n', 0), // uhn = Damal + TRUETYPE_TAG('u', 'i', 's', 0), // uis = Uisai + TRUETYPE_TAG('u', 'i', 'v', 0), // uiv = Iyive + TRUETYPE_TAG('u', 'j', 'i', 0), // uji = Tanjijili + TRUETYPE_TAG('u', 'k', 'a', 0), // uka = Kaburi + TRUETYPE_TAG('u', 'k', 'g', 0), // ukg = Ukuriguma + TRUETYPE_TAG('u', 'k', 'h', 0), // ukh = Ukhwejo + TRUETYPE_TAG('u', 'k', 'l', 0), // ukl = Ukrainian Sign Language + TRUETYPE_TAG('u', 'k', 'p', 0), // ukp = Ukpe-Bayobiri + TRUETYPE_TAG('u', 'k', 'q', 0), // ukq = Ukwa + TRUETYPE_TAG('u', 'k', 's', 0), // uks = Urubú-Kaapor Sign Language + TRUETYPE_TAG('u', 'k', 'u', 0), // uku = Ukue + TRUETYPE_TAG('u', 'k', 'w', 0), // ukw = Ukwuani-Aboh-Ndoni + TRUETYPE_TAG('u', 'l', 'a', 0), // ula = Fungwa + TRUETYPE_TAG('u', 'l', 'b', 0), // ulb = Ulukwumi + TRUETYPE_TAG('u', 'l', 'c', 0), // ulc = Ulch + TRUETYPE_TAG('u', 'l', 'f', 0), // ulf = Usku + TRUETYPE_TAG('u', 'l', 'i', 0), // uli = Ulithian + TRUETYPE_TAG('u', 'l', 'k', 0), // ulk = Meriam + TRUETYPE_TAG('u', 'l', 'l', 0), // ull = Ullatan + TRUETYPE_TAG('u', 'l', 'm', 0), // ulm = Ulumanda' + TRUETYPE_TAG('u', 'l', 'n', 0), // uln = Unserdeutsch + TRUETYPE_TAG('u', 'l', 'u', 0), // ulu = Uma' Lung + TRUETYPE_TAG('u', 'l', 'w', 0), // ulw = Ulwa + TRUETYPE_TAG('u', 'm', 'a', 0), // uma = Umatilla + TRUETYPE_TAG('u', 'm', 'b', 0), // umb = Umbundu + TRUETYPE_TAG('u', 'm', 'c', 0), // umc = Marrucinian + TRUETYPE_TAG('u', 'm', 'd', 0), // umd = Umbindhamu + TRUETYPE_TAG('u', 'm', 'g', 0), // umg = Umbuygamu + TRUETYPE_TAG('u', 'm', 'i', 0), // umi = Ukit + TRUETYPE_TAG('u', 'm', 'm', 0), // umm = Umon + TRUETYPE_TAG('u', 'm', 'n', 0), // umn = Makyan Naga + TRUETYPE_TAG('u', 'm', 'o', 0), // umo = Umotína + TRUETYPE_TAG('u', 'm', 'p', 0), // ump = Umpila + TRUETYPE_TAG('u', 'm', 'r', 0), // umr = Umbugarla + TRUETYPE_TAG('u', 'm', 's', 0), // ums = Pendau + TRUETYPE_TAG('u', 'm', 'u', 0), // umu = Munsee + TRUETYPE_TAG('u', 'n', 'a', 0), // una = North Watut + TRUETYPE_TAG('u', 'n', 'd', 0), // und = Undetermined + TRUETYPE_TAG('u', 'n', 'e', 0), // une = Uneme + TRUETYPE_TAG('u', 'n', 'g', 0), // ung = Ngarinyin + TRUETYPE_TAG('u', 'n', 'k', 0), // unk = Enawené-Nawé + TRUETYPE_TAG('u', 'n', 'm', 0), // unm = Unami + TRUETYPE_TAG('u', 'n', 'p', 0), // unp = Worora + TRUETYPE_TAG('u', 'n', 'r', 0), // unr = Mundari + TRUETYPE_TAG('u', 'n', 'x', 0), // unx = Munda + TRUETYPE_TAG('u', 'n', 'z', 0), // unz = Unde Kaili + TRUETYPE_TAG('u', 'o', 'k', 0), // uok = Uokha + TRUETYPE_TAG('u', 'p', 'i', 0), // upi = Umeda + TRUETYPE_TAG('u', 'p', 'v', 0), // upv = Uripiv-Wala-Rano-Atchin + TRUETYPE_TAG('u', 'r', 'a', 0), // ura = Urarina + TRUETYPE_TAG('u', 'r', 'b', 0), // urb = Urubú-Kaapor + TRUETYPE_TAG('u', 'r', 'c', 0), // urc = Urningangg + TRUETYPE_TAG('u', 'r', 'e', 0), // ure = Uru + TRUETYPE_TAG('u', 'r', 'f', 0), // urf = Uradhi + TRUETYPE_TAG('u', 'r', 'g', 0), // urg = Urigina + TRUETYPE_TAG('u', 'r', 'h', 0), // urh = Urhobo + TRUETYPE_TAG('u', 'r', 'i', 0), // uri = Urim + TRUETYPE_TAG('u', 'r', 'j', 0), // urj = Uralic languages + TRUETYPE_TAG('u', 'r', 'k', 0), // urk = Urak Lawoi' + TRUETYPE_TAG('u', 'r', 'l', 0), // url = Urali + TRUETYPE_TAG('u', 'r', 'm', 0), // urm = Urapmin + TRUETYPE_TAG('u', 'r', 'n', 0), // urn = Uruangnirin + TRUETYPE_TAG('u', 'r', 'o', 0), // uro = Ura (Papua New Guinea) + TRUETYPE_TAG('u', 'r', 'p', 0), // urp = Uru-Pa-In + TRUETYPE_TAG('u', 'r', 'r', 0), // urr = Lehalurup + TRUETYPE_TAG('u', 'r', 't', 0), // urt = Urat + TRUETYPE_TAG('u', 'r', 'u', 0), // uru = Urumi + TRUETYPE_TAG('u', 'r', 'v', 0), // urv = Uruava + TRUETYPE_TAG('u', 'r', 'w', 0), // urw = Sop + TRUETYPE_TAG('u', 'r', 'x', 0), // urx = Urimo + TRUETYPE_TAG('u', 'r', 'y', 0), // ury = Orya + TRUETYPE_TAG('u', 'r', 'z', 0), // urz = Uru-Eu-Wau-Wau + TRUETYPE_TAG('u', 's', 'a', 0), // usa = Usarufa + TRUETYPE_TAG('u', 's', 'h', 0), // ush = Ushojo + TRUETYPE_TAG('u', 's', 'i', 0), // usi = Usui + TRUETYPE_TAG('u', 's', 'k', 0), // usk = Usaghade + TRUETYPE_TAG('u', 's', 'p', 0), // usp = Uspanteco + TRUETYPE_TAG('u', 's', 'u', 0), // usu = Uya + TRUETYPE_TAG('u', 't', 'a', 0), // uta = Otank + TRUETYPE_TAG('u', 't', 'e', 0), // ute = Ute-Southern Paiute + TRUETYPE_TAG('u', 't', 'p', 0), // utp = Amba (Solomon Islands) + TRUETYPE_TAG('u', 't', 'r', 0), // utr = Etulo + TRUETYPE_TAG('u', 't', 'u', 0), // utu = Utu + TRUETYPE_TAG('u', 'u', 'm', 0), // uum = Urum + TRUETYPE_TAG('u', 'u', 'n', 0), // uun = Kulon-Pazeh + TRUETYPE_TAG('u', 'u', 'r', 0), // uur = Ura (Vanuatu) + TRUETYPE_TAG('u', 'u', 'u', 0), // uuu = U + TRUETYPE_TAG('u', 'v', 'e', 0), // uve = West Uvean + TRUETYPE_TAG('u', 'v', 'h', 0), // uvh = Uri + TRUETYPE_TAG('u', 'v', 'l', 0), // uvl = Lote + TRUETYPE_TAG('u', 'w', 'a', 0), // uwa = Kuku-Uwanh + TRUETYPE_TAG('u', 'y', 'a', 0), // uya = Doko-Uyanga + TRUETYPE_TAG('u', 'z', 'n', 0), // uzn = Northern Uzbek + TRUETYPE_TAG('u', 'z', 's', 0), // uzs = Southern Uzbek + TRUETYPE_TAG('v', 'a', 'a', 0), // vaa = Vaagri Booli + TRUETYPE_TAG('v', 'a', 'e', 0), // vae = Vale + TRUETYPE_TAG('v', 'a', 'f', 0), // vaf = Vafsi + TRUETYPE_TAG('v', 'a', 'g', 0), // vag = Vagla + TRUETYPE_TAG('v', 'a', 'h', 0), // vah = Varhadi-Nagpuri + TRUETYPE_TAG('v', 'a', 'i', 0), // vai = Vai + TRUETYPE_TAG('v', 'a', 'j', 0), // vaj = Vasekela Bushman + TRUETYPE_TAG('v', 'a', 'l', 0), // val = Vehes + TRUETYPE_TAG('v', 'a', 'm', 0), // vam = Vanimo + TRUETYPE_TAG('v', 'a', 'n', 0), // van = Valman + TRUETYPE_TAG('v', 'a', 'o', 0), // vao = Vao + TRUETYPE_TAG('v', 'a', 'p', 0), // vap = Vaiphei + TRUETYPE_TAG('v', 'a', 'r', 0), // var = Huarijio + TRUETYPE_TAG('v', 'a', 's', 0), // vas = Vasavi + TRUETYPE_TAG('v', 'a', 'u', 0), // vau = Vanuma + TRUETYPE_TAG('v', 'a', 'v', 0), // vav = Varli + TRUETYPE_TAG('v', 'a', 'y', 0), // vay = Wayu + TRUETYPE_TAG('v', 'b', 'b', 0), // vbb = Southeast Babar + TRUETYPE_TAG('v', 'b', 'k', 0), // vbk = Southwestern Bontok + TRUETYPE_TAG('v', 'e', 'c', 0), // vec = Venetian + TRUETYPE_TAG('v', 'e', 'd', 0), // ved = Veddah + TRUETYPE_TAG('v', 'e', 'l', 0), // vel = Veluws + TRUETYPE_TAG('v', 'e', 'm', 0), // vem = Vemgo-Mabas + TRUETYPE_TAG('v', 'e', 'o', 0), // veo = Ventureño + TRUETYPE_TAG('v', 'e', 'p', 0), // vep = Veps + TRUETYPE_TAG('v', 'e', 'r', 0), // ver = Mom Jango + TRUETYPE_TAG('v', 'g', 'r', 0), // vgr = Vaghri + TRUETYPE_TAG('v', 'g', 't', 0), // vgt = Vlaamse Gebarentaal + TRUETYPE_TAG('v', 'i', 'c', 0), // vic = Virgin Islands Creole English + TRUETYPE_TAG('v', 'i', 'd', 0), // vid = Vidunda + TRUETYPE_TAG('v', 'i', 'f', 0), // vif = Vili + TRUETYPE_TAG('v', 'i', 'g', 0), // vig = Viemo + TRUETYPE_TAG('v', 'i', 'l', 0), // vil = Vilela + TRUETYPE_TAG('v', 'i', 'n', 0), // vin = Vinza + TRUETYPE_TAG('v', 'i', 's', 0), // vis = Vishavan + TRUETYPE_TAG('v', 'i', 't', 0), // vit = Viti + TRUETYPE_TAG('v', 'i', 'v', 0), // viv = Iduna + TRUETYPE_TAG('v', 'k', 'a', 0), // vka = Kariyarra + TRUETYPE_TAG('v', 'k', 'i', 0), // vki = Ija-Zuba + TRUETYPE_TAG('v', 'k', 'j', 0), // vkj = Kujarge + TRUETYPE_TAG('v', 'k', 'k', 0), // vkk = Kaur + TRUETYPE_TAG('v', 'k', 'l', 0), // vkl = Kulisusu + TRUETYPE_TAG('v', 'k', 'm', 0), // vkm = Kamakan + TRUETYPE_TAG('v', 'k', 'o', 0), // vko = Kodeoha + TRUETYPE_TAG('v', 'k', 'p', 0), // vkp = Korlai Creole Portuguese + TRUETYPE_TAG('v', 'k', 't', 0), // vkt = Tenggarong Kutai Malay + TRUETYPE_TAG('v', 'k', 'u', 0), // vku = Kurrama + TRUETYPE_TAG('v', 'l', 'p', 0), // vlp = Valpei + TRUETYPE_TAG('v', 'l', 's', 0), // vls = Vlaams + TRUETYPE_TAG('v', 'm', 'a', 0), // vma = Martuyhunira + TRUETYPE_TAG('v', 'm', 'b', 0), // vmb = Mbabaram + TRUETYPE_TAG('v', 'm', 'c', 0), // vmc = Juxtlahuaca Mixtec + TRUETYPE_TAG('v', 'm', 'd', 0), // vmd = Mudu Koraga + TRUETYPE_TAG('v', 'm', 'e', 0), // vme = East Masela + TRUETYPE_TAG('v', 'm', 'f', 0), // vmf = Mainfränkisch + TRUETYPE_TAG('v', 'm', 'g', 0), // vmg = Minigir + TRUETYPE_TAG('v', 'm', 'h', 0), // vmh = Maraghei + TRUETYPE_TAG('v', 'm', 'i', 0), // vmi = Miwa + TRUETYPE_TAG('v', 'm', 'j', 0), // vmj = Ixtayutla Mixtec + TRUETYPE_TAG('v', 'm', 'k', 0), // vmk = Makhuwa-Shirima + TRUETYPE_TAG('v', 'm', 'l', 0), // vml = Malgana + TRUETYPE_TAG('v', 'm', 'm', 0), // vmm = Mitlatongo Mixtec + TRUETYPE_TAG('v', 'm', 'p', 0), // vmp = Soyaltepec Mazatec + TRUETYPE_TAG('v', 'm', 'q', 0), // vmq = Soyaltepec Mixtec + TRUETYPE_TAG('v', 'm', 'r', 0), // vmr = Marenje + TRUETYPE_TAG('v', 'm', 's', 0), // vms = Moksela + TRUETYPE_TAG('v', 'm', 'u', 0), // vmu = Muluridyi + TRUETYPE_TAG('v', 'm', 'v', 0), // vmv = Valley Maidu + TRUETYPE_TAG('v', 'm', 'w', 0), // vmw = Makhuwa + TRUETYPE_TAG('v', 'm', 'x', 0), // vmx = Tamazola Mixtec + TRUETYPE_TAG('v', 'm', 'y', 0), // vmy = Ayautla Mazatec + TRUETYPE_TAG('v', 'm', 'z', 0), // vmz = Mazatlán Mazatec + TRUETYPE_TAG('v', 'n', 'k', 0), // vnk = Vano + TRUETYPE_TAG('v', 'n', 'm', 0), // vnm = Vinmavis + TRUETYPE_TAG('v', 'n', 'p', 0), // vnp = Vunapu + TRUETYPE_TAG('v', 'o', 'r', 0), // vor = Voro + TRUETYPE_TAG('v', 'o', 't', 0), // vot = Votic + TRUETYPE_TAG('v', 'r', 'a', 0), // vra = Vera'a + TRUETYPE_TAG('v', 'r', 'o', 0), // vro = Võro + TRUETYPE_TAG('v', 'r', 's', 0), // vrs = Varisi + TRUETYPE_TAG('v', 'r', 't', 0), // vrt = Burmbar + TRUETYPE_TAG('v', 's', 'i', 0), // vsi = Moldova Sign Language + TRUETYPE_TAG('v', 's', 'l', 0), // vsl = Venezuelan Sign Language + TRUETYPE_TAG('v', 's', 'v', 0), // vsv = Valencian Sign Language + TRUETYPE_TAG('v', 't', 'o', 0), // vto = Vitou + TRUETYPE_TAG('v', 'u', 'm', 0), // vum = Vumbu + TRUETYPE_TAG('v', 'u', 'n', 0), // vun = Vunjo + TRUETYPE_TAG('v', 'u', 't', 0), // vut = Vute + TRUETYPE_TAG('v', 'w', 'a', 0), // vwa = Awa (China) + TRUETYPE_TAG('w', 'a', 'a', 0), // waa = Walla Walla + TRUETYPE_TAG('w', 'a', 'b', 0), // wab = Wab + TRUETYPE_TAG('w', 'a', 'c', 0), // wac = Wasco-Wishram + TRUETYPE_TAG('w', 'a', 'd', 0), // wad = Wandamen + TRUETYPE_TAG('w', 'a', 'e', 0), // wae = Walser + TRUETYPE_TAG('w', 'a', 'f', 0), // waf = Wakoná + TRUETYPE_TAG('w', 'a', 'g', 0), // wag = Wa'ema + TRUETYPE_TAG('w', 'a', 'h', 0), // wah = Watubela + TRUETYPE_TAG('w', 'a', 'i', 0), // wai = Wares + TRUETYPE_TAG('w', 'a', 'j', 0), // waj = Waffa + TRUETYPE_TAG('w', 'a', 'k', 0), // wak = Wakashan languages + TRUETYPE_TAG('w', 'a', 'l', 0), // wal = Wolaytta + TRUETYPE_TAG('w', 'a', 'm', 0), // wam = Wampanoag + TRUETYPE_TAG('w', 'a', 'n', 0), // wan = Wan + TRUETYPE_TAG('w', 'a', 'o', 0), // wao = Wappo + TRUETYPE_TAG('w', 'a', 'p', 0), // wap = Wapishana + TRUETYPE_TAG('w', 'a', 'q', 0), // waq = Wageman + TRUETYPE_TAG('w', 'a', 'r', 0), // war = Waray (Philippines) + TRUETYPE_TAG('w', 'a', 's', 0), // was = Washo + TRUETYPE_TAG('w', 'a', 't', 0), // wat = Kaninuwa + TRUETYPE_TAG('w', 'a', 'u', 0), // wau = Waurá + TRUETYPE_TAG('w', 'a', 'v', 0), // wav = Waka + TRUETYPE_TAG('w', 'a', 'w', 0), // waw = Waiwai + TRUETYPE_TAG('w', 'a', 'x', 0), // wax = Watam + TRUETYPE_TAG('w', 'a', 'y', 0), // way = Wayana + TRUETYPE_TAG('w', 'a', 'z', 0), // waz = Wampur + TRUETYPE_TAG('w', 'b', 'a', 0), // wba = Warao + TRUETYPE_TAG('w', 'b', 'b', 0), // wbb = Wabo + TRUETYPE_TAG('w', 'b', 'e', 0), // wbe = Waritai + TRUETYPE_TAG('w', 'b', 'f', 0), // wbf = Wara + TRUETYPE_TAG('w', 'b', 'h', 0), // wbh = Wanda + TRUETYPE_TAG('w', 'b', 'i', 0), // wbi = Vwanji + TRUETYPE_TAG('w', 'b', 'j', 0), // wbj = Alagwa + TRUETYPE_TAG('w', 'b', 'k', 0), // wbk = Waigali + TRUETYPE_TAG('w', 'b', 'l', 0), // wbl = Wakhi + TRUETYPE_TAG('w', 'b', 'm', 0), // wbm = Wa + TRUETYPE_TAG('w', 'b', 'p', 0), // wbp = Warlpiri + TRUETYPE_TAG('w', 'b', 'q', 0), // wbq = Waddar + TRUETYPE_TAG('w', 'b', 'r', 0), // wbr = Wagdi + TRUETYPE_TAG('w', 'b', 't', 0), // wbt = Wanman + TRUETYPE_TAG('w', 'b', 'v', 0), // wbv = Wajarri + TRUETYPE_TAG('w', 'b', 'w', 0), // wbw = Woi + TRUETYPE_TAG('w', 'c', 'a', 0), // wca = Yanomámi + TRUETYPE_TAG('w', 'c', 'i', 0), // wci = Waci Gbe + TRUETYPE_TAG('w', 'd', 'd', 0), // wdd = Wandji + TRUETYPE_TAG('w', 'd', 'g', 0), // wdg = Wadaginam + TRUETYPE_TAG('w', 'd', 'j', 0), // wdj = Wadjiginy + TRUETYPE_TAG('w', 'd', 'u', 0), // wdu = Wadjigu + TRUETYPE_TAG('w', 'e', 'a', 0), // wea = Wewaw + TRUETYPE_TAG('w', 'e', 'c', 0), // wec = Wè Western + TRUETYPE_TAG('w', 'e', 'd', 0), // wed = Wedau + TRUETYPE_TAG('w', 'e', 'h', 0), // weh = Weh + TRUETYPE_TAG('w', 'e', 'i', 0), // wei = Kiunum + TRUETYPE_TAG('w', 'e', 'm', 0), // wem = Weme Gbe + TRUETYPE_TAG('w', 'e', 'n', 0), // wen = Sorbian languages + TRUETYPE_TAG('w', 'e', 'o', 0), // weo = North Wemale + TRUETYPE_TAG('w', 'e', 'p', 0), // wep = Westphalien + TRUETYPE_TAG('w', 'e', 'r', 0), // wer = Weri + TRUETYPE_TAG('w', 'e', 's', 0), // wes = Cameroon Pidgin + TRUETYPE_TAG('w', 'e', 't', 0), // wet = Perai + TRUETYPE_TAG('w', 'e', 'u', 0), // weu = Welaung + TRUETYPE_TAG('w', 'e', 'w', 0), // wew = Wejewa + TRUETYPE_TAG('w', 'f', 'g', 0), // wfg = Yafi + TRUETYPE_TAG('w', 'g', 'a', 0), // wga = Wagaya + TRUETYPE_TAG('w', 'g', 'b', 0), // wgb = Wagawaga + TRUETYPE_TAG('w', 'g', 'g', 0), // wgg = Wangganguru + TRUETYPE_TAG('w', 'g', 'i', 0), // wgi = Wahgi + TRUETYPE_TAG('w', 'g', 'o', 0), // wgo = Waigeo + TRUETYPE_TAG('w', 'g', 'w', 0), // wgw = Wagawaga + TRUETYPE_TAG('w', 'g', 'y', 0), // wgy = Warrgamay + TRUETYPE_TAG('w', 'h', 'a', 0), // wha = Manusela + TRUETYPE_TAG('w', 'h', 'g', 0), // whg = North Wahgi + TRUETYPE_TAG('w', 'h', 'k', 0), // whk = Wahau Kenyah + TRUETYPE_TAG('w', 'h', 'u', 0), // whu = Wahau Kayan + TRUETYPE_TAG('w', 'i', 'b', 0), // wib = Southern Toussian + TRUETYPE_TAG('w', 'i', 'c', 0), // wic = Wichita + TRUETYPE_TAG('w', 'i', 'e', 0), // wie = Wik-Epa + TRUETYPE_TAG('w', 'i', 'f', 0), // wif = Wik-Keyangan + TRUETYPE_TAG('w', 'i', 'g', 0), // wig = Wik-Ngathana + TRUETYPE_TAG('w', 'i', 'h', 0), // wih = Wik-Me'anha + TRUETYPE_TAG('w', 'i', 'i', 0), // wii = Minidien + TRUETYPE_TAG('w', 'i', 'j', 0), // wij = Wik-Iiyanh + TRUETYPE_TAG('w', 'i', 'k', 0), // wik = Wikalkan + TRUETYPE_TAG('w', 'i', 'l', 0), // wil = Wilawila + TRUETYPE_TAG('w', 'i', 'm', 0), // wim = Wik-Mungkan + TRUETYPE_TAG('w', 'i', 'n', 0), // win = Ho-Chunk + TRUETYPE_TAG('w', 'i', 'r', 0), // wir = Wiraféd + TRUETYPE_TAG('w', 'i', 't', 0), // wit = Wintu + TRUETYPE_TAG('w', 'i', 'u', 0), // wiu = Wiru + TRUETYPE_TAG('w', 'i', 'v', 0), // wiv = Muduapa + TRUETYPE_TAG('w', 'i', 'w', 0), // wiw = Wirangu + TRUETYPE_TAG('w', 'i', 'y', 0), // wiy = Wiyot + TRUETYPE_TAG('w', 'j', 'a', 0), // wja = Waja + TRUETYPE_TAG('w', 'j', 'i', 0), // wji = Warji + TRUETYPE_TAG('w', 'k', 'a', 0), // wka = Kw'adza + TRUETYPE_TAG('w', 'k', 'b', 0), // wkb = Kumbaran + TRUETYPE_TAG('w', 'k', 'd', 0), // wkd = Wakde + TRUETYPE_TAG('w', 'k', 'l', 0), // wkl = Kalanadi + TRUETYPE_TAG('w', 'k', 'u', 0), // wku = Kunduvadi + TRUETYPE_TAG('w', 'k', 'w', 0), // wkw = Wakawaka + TRUETYPE_TAG('w', 'l', 'a', 0), // wla = Walio + TRUETYPE_TAG('w', 'l', 'c', 0), // wlc = Mwali Comorian + TRUETYPE_TAG('w', 'l', 'e', 0), // wle = Wolane + TRUETYPE_TAG('w', 'l', 'g', 0), // wlg = Kunbarlang + TRUETYPE_TAG('w', 'l', 'i', 0), // wli = Waioli + TRUETYPE_TAG('w', 'l', 'k', 0), // wlk = Wailaki + TRUETYPE_TAG('w', 'l', 'l', 0), // wll = Wali (Sudan) + TRUETYPE_TAG('w', 'l', 'm', 0), // wlm = Middle Welsh + TRUETYPE_TAG('w', 'l', 'o', 0), // wlo = Wolio + TRUETYPE_TAG('w', 'l', 'r', 0), // wlr = Wailapa + TRUETYPE_TAG('w', 'l', 's', 0), // wls = Wallisian + TRUETYPE_TAG('w', 'l', 'u', 0), // wlu = Wuliwuli + TRUETYPE_TAG('w', 'l', 'v', 0), // wlv = Wichí Lhamtés Vejoz + TRUETYPE_TAG('w', 'l', 'w', 0), // wlw = Walak + TRUETYPE_TAG('w', 'l', 'x', 0), // wlx = Wali (Ghana) + TRUETYPE_TAG('w', 'l', 'y', 0), // wly = Waling + TRUETYPE_TAG('w', 'm', 'a', 0), // wma = Mawa (Nigeria) + TRUETYPE_TAG('w', 'm', 'b', 0), // wmb = Wambaya + TRUETYPE_TAG('w', 'm', 'c', 0), // wmc = Wamas + TRUETYPE_TAG('w', 'm', 'd', 0), // wmd = Mamaindé + TRUETYPE_TAG('w', 'm', 'e', 0), // wme = Wambule + TRUETYPE_TAG('w', 'm', 'h', 0), // wmh = Waima'a + TRUETYPE_TAG('w', 'm', 'i', 0), // wmi = Wamin + TRUETYPE_TAG('w', 'm', 'm', 0), // wmm = Maiwa (Indonesia) + TRUETYPE_TAG('w', 'm', 'n', 0), // wmn = Waamwang + TRUETYPE_TAG('w', 'm', 'o', 0), // wmo = Wom (Papua New Guinea) + TRUETYPE_TAG('w', 'm', 's', 0), // wms = Wambon + TRUETYPE_TAG('w', 'm', 't', 0), // wmt = Walmajarri + TRUETYPE_TAG('w', 'm', 'w', 0), // wmw = Mwani + TRUETYPE_TAG('w', 'm', 'x', 0), // wmx = Womo + TRUETYPE_TAG('w', 'n', 'b', 0), // wnb = Wanambre + TRUETYPE_TAG('w', 'n', 'c', 0), // wnc = Wantoat + TRUETYPE_TAG('w', 'n', 'd', 0), // wnd = Wandarang + TRUETYPE_TAG('w', 'n', 'e', 0), // wne = Waneci + TRUETYPE_TAG('w', 'n', 'g', 0), // wng = Wanggom + TRUETYPE_TAG('w', 'n', 'i', 0), // wni = Ndzwani Comorian + TRUETYPE_TAG('w', 'n', 'k', 0), // wnk = Wanukaka + TRUETYPE_TAG('w', 'n', 'm', 0), // wnm = Wanggamala + TRUETYPE_TAG('w', 'n', 'o', 0), // wno = Wano + TRUETYPE_TAG('w', 'n', 'p', 0), // wnp = Wanap + TRUETYPE_TAG('w', 'n', 'u', 0), // wnu = Usan + TRUETYPE_TAG('w', 'o', 'a', 0), // woa = Tyaraity + TRUETYPE_TAG('w', 'o', 'b', 0), // wob = Wè Northern + TRUETYPE_TAG('w', 'o', 'c', 0), // woc = Wogeo + TRUETYPE_TAG('w', 'o', 'd', 0), // wod = Wolani + TRUETYPE_TAG('w', 'o', 'e', 0), // woe = Woleaian + TRUETYPE_TAG('w', 'o', 'f', 0), // wof = Gambian Wolof + TRUETYPE_TAG('w', 'o', 'g', 0), // wog = Wogamusin + TRUETYPE_TAG('w', 'o', 'i', 0), // woi = Kamang + TRUETYPE_TAG('w', 'o', 'k', 0), // wok = Longto + TRUETYPE_TAG('w', 'o', 'm', 0), // wom = Wom (Nigeria) + TRUETYPE_TAG('w', 'o', 'n', 0), // won = Wongo + TRUETYPE_TAG('w', 'o', 'o', 0), // woo = Manombai + TRUETYPE_TAG('w', 'o', 'r', 0), // wor = Woria + TRUETYPE_TAG('w', 'o', 's', 0), // wos = Hanga Hundi + TRUETYPE_TAG('w', 'o', 'w', 0), // wow = Wawonii + TRUETYPE_TAG('w', 'o', 'y', 0), // woy = Weyto + TRUETYPE_TAG('w', 'p', 'c', 0), // wpc = Maco + TRUETYPE_TAG('w', 'r', 'a', 0), // wra = Warapu + TRUETYPE_TAG('w', 'r', 'b', 0), // wrb = Warluwara + TRUETYPE_TAG('w', 'r', 'd', 0), // wrd = Warduji + TRUETYPE_TAG('w', 'r', 'g', 0), // wrg = Warungu + TRUETYPE_TAG('w', 'r', 'h', 0), // wrh = Wiradhuri + TRUETYPE_TAG('w', 'r', 'i', 0), // wri = Wariyangga + TRUETYPE_TAG('w', 'r', 'l', 0), // wrl = Warlmanpa + TRUETYPE_TAG('w', 'r', 'm', 0), // wrm = Warumungu + TRUETYPE_TAG('w', 'r', 'n', 0), // wrn = Warnang + TRUETYPE_TAG('w', 'r', 'p', 0), // wrp = Waropen + TRUETYPE_TAG('w', 'r', 'r', 0), // wrr = Wardaman + TRUETYPE_TAG('w', 'r', 's', 0), // wrs = Waris + TRUETYPE_TAG('w', 'r', 'u', 0), // wru = Waru + TRUETYPE_TAG('w', 'r', 'v', 0), // wrv = Waruna + TRUETYPE_TAG('w', 'r', 'w', 0), // wrw = Gugu Warra + TRUETYPE_TAG('w', 'r', 'x', 0), // wrx = Wae Rana + TRUETYPE_TAG('w', 'r', 'y', 0), // wry = Merwari + TRUETYPE_TAG('w', 'r', 'z', 0), // wrz = Waray (Australia) + TRUETYPE_TAG('w', 's', 'a', 0), // wsa = Warembori + TRUETYPE_TAG('w', 's', 'i', 0), // wsi = Wusi + TRUETYPE_TAG('w', 's', 'k', 0), // wsk = Waskia + TRUETYPE_TAG('w', 's', 'r', 0), // wsr = Owenia + TRUETYPE_TAG('w', 's', 's', 0), // wss = Wasa + TRUETYPE_TAG('w', 's', 'u', 0), // wsu = Wasu + TRUETYPE_TAG('w', 's', 'v', 0), // wsv = Wotapuri-Katarqalai + TRUETYPE_TAG('w', 't', 'f', 0), // wtf = Watiwa + TRUETYPE_TAG('w', 't', 'i', 0), // wti = Berta + TRUETYPE_TAG('w', 't', 'k', 0), // wtk = Watakataui + TRUETYPE_TAG('w', 't', 'm', 0), // wtm = Mewati + TRUETYPE_TAG('w', 't', 'w', 0), // wtw = Wotu + TRUETYPE_TAG('w', 'u', 'a', 0), // wua = Wikngenchera + TRUETYPE_TAG('w', 'u', 'b', 0), // wub = Wunambal + TRUETYPE_TAG('w', 'u', 'd', 0), // wud = Wudu + TRUETYPE_TAG('w', 'u', 'h', 0), // wuh = Wutunhua + TRUETYPE_TAG('w', 'u', 'l', 0), // wul = Silimo + TRUETYPE_TAG('w', 'u', 'm', 0), // wum = Wumbvu + TRUETYPE_TAG('w', 'u', 'n', 0), // wun = Bungu + TRUETYPE_TAG('w', 'u', 'r', 0), // wur = Wurrugu + TRUETYPE_TAG('w', 'u', 't', 0), // wut = Wutung + TRUETYPE_TAG('w', 'u', 'u', 0), // wuu = Wu Chinese + TRUETYPE_TAG('w', 'u', 'v', 0), // wuv = Wuvulu-Aua + TRUETYPE_TAG('w', 'u', 'x', 0), // wux = Wulna + TRUETYPE_TAG('w', 'u', 'y', 0), // wuy = Wauyai + TRUETYPE_TAG('w', 'w', 'a', 0), // wwa = Waama + TRUETYPE_TAG('w', 'w', 'o', 0), // wwo = Wetamut + TRUETYPE_TAG('w', 'w', 'r', 0), // wwr = Warrwa + TRUETYPE_TAG('w', 'w', 'w', 0), // www = Wawa + TRUETYPE_TAG('w', 'x', 'a', 0), // wxa = Waxianghua + TRUETYPE_TAG('w', 'y', 'a', 0), // wya = Wyandot + TRUETYPE_TAG('w', 'y', 'b', 0), // wyb = Wangaaybuwan-Ngiyambaa + TRUETYPE_TAG('w', 'y', 'm', 0), // wym = Wymysorys + TRUETYPE_TAG('w', 'y', 'r', 0), // wyr = Wayoró + TRUETYPE_TAG('w', 'y', 'y', 0), // wyy = Western Fijian + TRUETYPE_TAG('x', 'a', 'a', 0), // xaa = Andalusian Arabic + TRUETYPE_TAG('x', 'a', 'b', 0), // xab = Sambe + TRUETYPE_TAG('x', 'a', 'c', 0), // xac = Kachari + TRUETYPE_TAG('x', 'a', 'd', 0), // xad = Adai + TRUETYPE_TAG('x', 'a', 'e', 0), // xae = Aequian + TRUETYPE_TAG('x', 'a', 'g', 0), // xag = Aghwan + TRUETYPE_TAG('x', 'a', 'i', 0), // xai = Kaimbé + TRUETYPE_TAG('x', 'a', 'l', 0), // xal = Kalmyk + TRUETYPE_TAG('x', 'a', 'm', 0), // xam = /Xam + TRUETYPE_TAG('x', 'a', 'n', 0), // xan = Xamtanga + TRUETYPE_TAG('x', 'a', 'o', 0), // xao = Khao + TRUETYPE_TAG('x', 'a', 'p', 0), // xap = Apalachee + TRUETYPE_TAG('x', 'a', 'q', 0), // xaq = Aquitanian + TRUETYPE_TAG('x', 'a', 'r', 0), // xar = Karami + TRUETYPE_TAG('x', 'a', 's', 0), // xas = Kamas + TRUETYPE_TAG('x', 'a', 't', 0), // xat = Katawixi + TRUETYPE_TAG('x', 'a', 'u', 0), // xau = Kauwera + TRUETYPE_TAG('x', 'a', 'v', 0), // xav = Xavánte + TRUETYPE_TAG('x', 'a', 'w', 0), // xaw = Kawaiisu + TRUETYPE_TAG('x', 'a', 'y', 0), // xay = Kayan Mahakam + TRUETYPE_TAG('x', 'b', 'a', 0), // xba = Kamba (Brazil) + TRUETYPE_TAG('x', 'b', 'b', 0), // xbb = Lower Burdekin + TRUETYPE_TAG('x', 'b', 'c', 0), // xbc = Bactrian + TRUETYPE_TAG('x', 'b', 'i', 0), // xbi = Kombio + TRUETYPE_TAG('x', 'b', 'm', 0), // xbm = Middle Breton + TRUETYPE_TAG('x', 'b', 'n', 0), // xbn = Kenaboi + TRUETYPE_TAG('x', 'b', 'o', 0), // xbo = Bolgarian + TRUETYPE_TAG('x', 'b', 'r', 0), // xbr = Kambera + TRUETYPE_TAG('x', 'b', 'w', 0), // xbw = Kambiwá + TRUETYPE_TAG('x', 'b', 'x', 0), // xbx = Kabixí + TRUETYPE_TAG('x', 'c', 'b', 0), // xcb = Cumbric + TRUETYPE_TAG('x', 'c', 'c', 0), // xcc = Camunic + TRUETYPE_TAG('x', 'c', 'e', 0), // xce = Celtiberian + TRUETYPE_TAG('x', 'c', 'g', 0), // xcg = Cisalpine Gaulish + TRUETYPE_TAG('x', 'c', 'h', 0), // xch = Chemakum + TRUETYPE_TAG('x', 'c', 'l', 0), // xcl = Classical Armenian + TRUETYPE_TAG('x', 'c', 'm', 0), // xcm = Comecrudo + TRUETYPE_TAG('x', 'c', 'n', 0), // xcn = Cotoname + TRUETYPE_TAG('x', 'c', 'o', 0), // xco = Chorasmian + TRUETYPE_TAG('x', 'c', 'r', 0), // xcr = Carian + TRUETYPE_TAG('x', 'c', 't', 0), // xct = Classical Tibetan + TRUETYPE_TAG('x', 'c', 'u', 0), // xcu = Curonian + TRUETYPE_TAG('x', 'c', 'v', 0), // xcv = Chuvantsy + TRUETYPE_TAG('x', 'c', 'w', 0), // xcw = Coahuilteco + TRUETYPE_TAG('x', 'c', 'y', 0), // xcy = Cayuse + TRUETYPE_TAG('x', 'd', 'c', 0), // xdc = Dacian + TRUETYPE_TAG('x', 'd', 'm', 0), // xdm = Edomite + TRUETYPE_TAG('x', 'd', 'y', 0), // xdy = Malayic Dayak + TRUETYPE_TAG('x', 'e', 'b', 0), // xeb = Eblan + TRUETYPE_TAG('x', 'e', 'd', 0), // xed = Hdi + TRUETYPE_TAG('x', 'e', 'g', 0), // xeg = //Xegwi + TRUETYPE_TAG('x', 'e', 'l', 0), // xel = Kelo + TRUETYPE_TAG('x', 'e', 'm', 0), // xem = Kembayan + TRUETYPE_TAG('x', 'e', 'p', 0), // xep = Epi-Olmec + TRUETYPE_TAG('x', 'e', 'r', 0), // xer = Xerénte + TRUETYPE_TAG('x', 'e', 's', 0), // xes = Kesawai + TRUETYPE_TAG('x', 'e', 't', 0), // xet = Xetá + TRUETYPE_TAG('x', 'e', 'u', 0), // xeu = Keoru-Ahia + TRUETYPE_TAG('x', 'f', 'a', 0), // xfa = Faliscan + TRUETYPE_TAG('x', 'g', 'a', 0), // xga = Galatian + TRUETYPE_TAG('x', 'g', 'f', 0), // xgf = Gabrielino-Fernandeño + TRUETYPE_TAG('x', 'g', 'l', 0), // xgl = Galindan + TRUETYPE_TAG('x', 'g', 'n', 0), // xgn = Mongolian languages + TRUETYPE_TAG('x', 'g', 'r', 0), // xgr = Garza + TRUETYPE_TAG('x', 'h', 'a', 0), // xha = Harami + TRUETYPE_TAG('x', 'h', 'c', 0), // xhc = Hunnic + TRUETYPE_TAG('x', 'h', 'd', 0), // xhd = Hadrami + TRUETYPE_TAG('x', 'h', 'e', 0), // xhe = Khetrani + TRUETYPE_TAG('x', 'h', 'r', 0), // xhr = Hernican + TRUETYPE_TAG('x', 'h', 't', 0), // xht = Hattic + TRUETYPE_TAG('x', 'h', 'u', 0), // xhu = Hurrian + TRUETYPE_TAG('x', 'h', 'v', 0), // xhv = Khua + TRUETYPE_TAG('x', 'i', 'a', 0), // xia = Xiandao + TRUETYPE_TAG('x', 'i', 'b', 0), // xib = Iberian + TRUETYPE_TAG('x', 'i', 'i', 0), // xii = Xiri + TRUETYPE_TAG('x', 'i', 'l', 0), // xil = Illyrian + TRUETYPE_TAG('x', 'i', 'n', 0), // xin = Xinca + TRUETYPE_TAG('x', 'i', 'p', 0), // xip = Xipináwa + TRUETYPE_TAG('x', 'i', 'r', 0), // xir = Xiriâna + TRUETYPE_TAG('x', 'i', 'v', 0), // xiv = Indus Valley Language + TRUETYPE_TAG('x', 'i', 'y', 0), // xiy = Xipaya + TRUETYPE_TAG('x', 'k', 'a', 0), // xka = Kalkoti + TRUETYPE_TAG('x', 'k', 'b', 0), // xkb = Northern Nago + TRUETYPE_TAG('x', 'k', 'c', 0), // xkc = Kho'ini + TRUETYPE_TAG('x', 'k', 'd', 0), // xkd = Mendalam Kayan + TRUETYPE_TAG('x', 'k', 'e', 0), // xke = Kereho + TRUETYPE_TAG('x', 'k', 'f', 0), // xkf = Khengkha + TRUETYPE_TAG('x', 'k', 'g', 0), // xkg = Kagoro + TRUETYPE_TAG('x', 'k', 'h', 0), // xkh = Karahawyana + TRUETYPE_TAG('x', 'k', 'i', 0), // xki = Kenyan Sign Language + TRUETYPE_TAG('x', 'k', 'j', 0), // xkj = Kajali + TRUETYPE_TAG('x', 'k', 'k', 0), // xkk = Kaco' + TRUETYPE_TAG('x', 'k', 'l', 0), // xkl = Mainstream Kenyah + TRUETYPE_TAG('x', 'k', 'n', 0), // xkn = Kayan River Kayan + TRUETYPE_TAG('x', 'k', 'o', 0), // xko = Kiorr + TRUETYPE_TAG('x', 'k', 'p', 0), // xkp = Kabatei + TRUETYPE_TAG('x', 'k', 'q', 0), // xkq = Koroni + TRUETYPE_TAG('x', 'k', 'r', 0), // xkr = Xakriabá + TRUETYPE_TAG('x', 'k', 's', 0), // xks = Kumbewaha + TRUETYPE_TAG('x', 'k', 't', 0), // xkt = Kantosi + TRUETYPE_TAG('x', 'k', 'u', 0), // xku = Kaamba + TRUETYPE_TAG('x', 'k', 'v', 0), // xkv = Kgalagadi + TRUETYPE_TAG('x', 'k', 'w', 0), // xkw = Kembra + TRUETYPE_TAG('x', 'k', 'x', 0), // xkx = Karore + TRUETYPE_TAG('x', 'k', 'y', 0), // xky = Uma' Lasan + TRUETYPE_TAG('x', 'k', 'z', 0), // xkz = Kurtokha + TRUETYPE_TAG('x', 'l', 'a', 0), // xla = Kamula + TRUETYPE_TAG('x', 'l', 'b', 0), // xlb = Loup B + TRUETYPE_TAG('x', 'l', 'c', 0), // xlc = Lycian + TRUETYPE_TAG('x', 'l', 'd', 0), // xld = Lydian + TRUETYPE_TAG('x', 'l', 'e', 0), // xle = Lemnian + TRUETYPE_TAG('x', 'l', 'g', 0), // xlg = Ligurian (Ancient) + TRUETYPE_TAG('x', 'l', 'i', 0), // xli = Liburnian + TRUETYPE_TAG('x', 'l', 'n', 0), // xln = Alanic + TRUETYPE_TAG('x', 'l', 'o', 0), // xlo = Loup A + TRUETYPE_TAG('x', 'l', 'p', 0), // xlp = Lepontic + TRUETYPE_TAG('x', 'l', 's', 0), // xls = Lusitanian + TRUETYPE_TAG('x', 'l', 'u', 0), // xlu = Cuneiform Luwian + TRUETYPE_TAG('x', 'l', 'y', 0), // xly = Elymian + TRUETYPE_TAG('x', 'm', 'a', 0), // xma = Mushungulu + TRUETYPE_TAG('x', 'm', 'b', 0), // xmb = Mbonga + TRUETYPE_TAG('x', 'm', 'c', 0), // xmc = Makhuwa-Marrevone + TRUETYPE_TAG('x', 'm', 'd', 0), // xmd = Mbudum + TRUETYPE_TAG('x', 'm', 'e', 0), // xme = Median + TRUETYPE_TAG('x', 'm', 'f', 0), // xmf = Mingrelian + TRUETYPE_TAG('x', 'm', 'g', 0), // xmg = Mengaka + TRUETYPE_TAG('x', 'm', 'h', 0), // xmh = Kuku-Muminh + TRUETYPE_TAG('x', 'm', 'j', 0), // xmj = Majera + TRUETYPE_TAG('x', 'm', 'k', 0), // xmk = Ancient Macedonian + TRUETYPE_TAG('x', 'm', 'l', 0), // xml = Malaysian Sign Language + TRUETYPE_TAG('x', 'm', 'm', 0), // xmm = Manado Malay + TRUETYPE_TAG('x', 'm', 'n', 0), // xmn = Manichaean Middle Persian + TRUETYPE_TAG('x', 'm', 'o', 0), // xmo = Morerebi + TRUETYPE_TAG('x', 'm', 'p', 0), // xmp = Kuku-Mu'inh + TRUETYPE_TAG('x', 'm', 'q', 0), // xmq = Kuku-Mangk + TRUETYPE_TAG('x', 'm', 'r', 0), // xmr = Meroitic + TRUETYPE_TAG('x', 'm', 's', 0), // xms = Moroccan Sign Language + TRUETYPE_TAG('x', 'm', 't', 0), // xmt = Matbat + TRUETYPE_TAG('x', 'm', 'u', 0), // xmu = Kamu + TRUETYPE_TAG('x', 'm', 'v', 0), // xmv = Antankarana Malagasy + TRUETYPE_TAG('x', 'm', 'w', 0), // xmw = Tsimihety Malagasy + TRUETYPE_TAG('x', 'm', 'x', 0), // xmx = Maden + TRUETYPE_TAG('x', 'm', 'y', 0), // xmy = Mayaguduna + TRUETYPE_TAG('x', 'm', 'z', 0), // xmz = Mori Bawah + TRUETYPE_TAG('x', 'n', 'a', 0), // xna = Ancient North Arabian + TRUETYPE_TAG('x', 'n', 'b', 0), // xnb = Kanakanabu + TRUETYPE_TAG('x', 'n', 'd', 0), // xnd = Na-Dene languages + TRUETYPE_TAG('x', 'n', 'g', 0), // xng = Middle Mongolian + TRUETYPE_TAG('x', 'n', 'h', 0), // xnh = Kuanhua + TRUETYPE_TAG('x', 'n', 'n', 0), // xnn = Northern Kankanay + TRUETYPE_TAG('x', 'n', 'o', 0), // xno = Anglo-Norman + TRUETYPE_TAG('x', 'n', 'r', 0), // xnr = Kangri + TRUETYPE_TAG('x', 'n', 's', 0), // xns = Kanashi + TRUETYPE_TAG('x', 'n', 't', 0), // xnt = Narragansett + TRUETYPE_TAG('x', 'o', 'c', 0), // xoc = O'chi'chi' + TRUETYPE_TAG('x', 'o', 'd', 0), // xod = Kokoda + TRUETYPE_TAG('x', 'o', 'g', 0), // xog = Soga + TRUETYPE_TAG('x', 'o', 'i', 0), // xoi = Kominimung + TRUETYPE_TAG('x', 'o', 'k', 0), // xok = Xokleng + TRUETYPE_TAG('x', 'o', 'm', 0), // xom = Komo (Sudan) + TRUETYPE_TAG('x', 'o', 'n', 0), // xon = Konkomba + TRUETYPE_TAG('x', 'o', 'o', 0), // xoo = Xukurú + TRUETYPE_TAG('x', 'o', 'p', 0), // xop = Kopar + TRUETYPE_TAG('x', 'o', 'r', 0), // xor = Korubo + TRUETYPE_TAG('x', 'o', 'w', 0), // xow = Kowaki + TRUETYPE_TAG('x', 'p', 'c', 0), // xpc = Pecheneg + TRUETYPE_TAG('x', 'p', 'e', 0), // xpe = Liberia Kpelle + TRUETYPE_TAG('x', 'p', 'g', 0), // xpg = Phrygian + TRUETYPE_TAG('x', 'p', 'i', 0), // xpi = Pictish + TRUETYPE_TAG('x', 'p', 'k', 0), // xpk = Kulina Pano + TRUETYPE_TAG('x', 'p', 'm', 0), // xpm = Pumpokol + TRUETYPE_TAG('x', 'p', 'n', 0), // xpn = Kapinawá + TRUETYPE_TAG('x', 'p', 'o', 0), // xpo = Pochutec + TRUETYPE_TAG('x', 'p', 'p', 0), // xpp = Puyo-Paekche + TRUETYPE_TAG('x', 'p', 'q', 0), // xpq = Mohegan-Pequot + TRUETYPE_TAG('x', 'p', 'r', 0), // xpr = Parthian + TRUETYPE_TAG('x', 'p', 's', 0), // xps = Pisidian + TRUETYPE_TAG('x', 'p', 'u', 0), // xpu = Punic + TRUETYPE_TAG('x', 'p', 'y', 0), // xpy = Puyo + TRUETYPE_TAG('x', 'q', 'a', 0), // xqa = Karakhanid + TRUETYPE_TAG('x', 'q', 't', 0), // xqt = Qatabanian + TRUETYPE_TAG('x', 'r', 'a', 0), // xra = Krahô + TRUETYPE_TAG('x', 'r', 'b', 0), // xrb = Eastern Karaboro + TRUETYPE_TAG('x', 'r', 'e', 0), // xre = Kreye + TRUETYPE_TAG('x', 'r', 'i', 0), // xri = Krikati-Timbira + TRUETYPE_TAG('x', 'r', 'm', 0), // xrm = Armazic + TRUETYPE_TAG('x', 'r', 'n', 0), // xrn = Arin + TRUETYPE_TAG('x', 'r', 'r', 0), // xrr = Raetic + TRUETYPE_TAG('x', 'r', 't', 0), // xrt = Aranama-Tamique + TRUETYPE_TAG('x', 'r', 'u', 0), // xru = Marriammu + TRUETYPE_TAG('x', 'r', 'w', 0), // xrw = Karawa + TRUETYPE_TAG('x', 's', 'a', 0), // xsa = Sabaean + TRUETYPE_TAG('x', 's', 'b', 0), // xsb = Tinà Sambal + TRUETYPE_TAG('x', 's', 'c', 0), // xsc = Scythian + TRUETYPE_TAG('x', 's', 'd', 0), // xsd = Sidetic + TRUETYPE_TAG('x', 's', 'e', 0), // xse = Sempan + TRUETYPE_TAG('x', 's', 'h', 0), // xsh = Shamang + TRUETYPE_TAG('x', 's', 'i', 0), // xsi = Sio + TRUETYPE_TAG('x', 's', 'j', 0), // xsj = Subi + TRUETYPE_TAG('x', 's', 'l', 0), // xsl = South Slavey + TRUETYPE_TAG('x', 's', 'm', 0), // xsm = Kasem + TRUETYPE_TAG('x', 's', 'n', 0), // xsn = Sanga (Nigeria) + TRUETYPE_TAG('x', 's', 'o', 0), // xso = Solano + TRUETYPE_TAG('x', 's', 'p', 0), // xsp = Silopi + TRUETYPE_TAG('x', 's', 'q', 0), // xsq = Makhuwa-Saka + TRUETYPE_TAG('x', 's', 'r', 0), // xsr = Sherpa + TRUETYPE_TAG('x', 's', 's', 0), // xss = Assan + TRUETYPE_TAG('x', 's', 'u', 0), // xsu = Sanumá + TRUETYPE_TAG('x', 's', 'v', 0), // xsv = Sudovian + TRUETYPE_TAG('x', 's', 'y', 0), // xsy = Saisiyat + TRUETYPE_TAG('x', 't', 'a', 0), // xta = Alcozauca Mixtec + TRUETYPE_TAG('x', 't', 'b', 0), // xtb = Chazumba Mixtec + TRUETYPE_TAG('x', 't', 'c', 0), // xtc = Katcha-Kadugli-Miri + TRUETYPE_TAG('x', 't', 'd', 0), // xtd = Diuxi-Tilantongo Mixtec + TRUETYPE_TAG('x', 't', 'e', 0), // xte = Ketengban + TRUETYPE_TAG('x', 't', 'g', 0), // xtg = Transalpine Gaulish + TRUETYPE_TAG('x', 't', 'i', 0), // xti = Sinicahua Mixtec + TRUETYPE_TAG('x', 't', 'j', 0), // xtj = San Juan Teita Mixtec + TRUETYPE_TAG('x', 't', 'l', 0), // xtl = Tijaltepec Mixtec + TRUETYPE_TAG('x', 't', 'm', 0), // xtm = Magdalena Peñasco Mixtec + TRUETYPE_TAG('x', 't', 'n', 0), // xtn = Northern Tlaxiaco Mixtec + TRUETYPE_TAG('x', 't', 'o', 0), // xto = Tokharian A + TRUETYPE_TAG('x', 't', 'p', 0), // xtp = San Miguel Piedras Mixtec + TRUETYPE_TAG('x', 't', 'q', 0), // xtq = Tumshuqese + TRUETYPE_TAG('x', 't', 'r', 0), // xtr = Early Tripuri + TRUETYPE_TAG('x', 't', 's', 0), // xts = Sindihui Mixtec + TRUETYPE_TAG('x', 't', 't', 0), // xtt = Tacahua Mixtec + TRUETYPE_TAG('x', 't', 'u', 0), // xtu = Cuyamecalco Mixtec + TRUETYPE_TAG('x', 't', 'w', 0), // xtw = Tawandê + TRUETYPE_TAG('x', 't', 'y', 0), // xty = Yoloxochitl Mixtec + TRUETYPE_TAG('x', 't', 'z', 0), // xtz = Tasmanian + TRUETYPE_TAG('x', 'u', 'a', 0), // xua = Alu Kurumba + TRUETYPE_TAG('x', 'u', 'b', 0), // xub = Betta Kurumba + TRUETYPE_TAG('x', 'u', 'g', 0), // xug = Kunigami + TRUETYPE_TAG('x', 'u', 'j', 0), // xuj = Jennu Kurumba + TRUETYPE_TAG('x', 'u', 'm', 0), // xum = Umbrian + TRUETYPE_TAG('x', 'u', 'o', 0), // xuo = Kuo + TRUETYPE_TAG('x', 'u', 'p', 0), // xup = Upper Umpqua + TRUETYPE_TAG('x', 'u', 'r', 0), // xur = Urartian + TRUETYPE_TAG('x', 'u', 't', 0), // xut = Kuthant + TRUETYPE_TAG('x', 'u', 'u', 0), // xuu = Kxoe + TRUETYPE_TAG('x', 'v', 'e', 0), // xve = Venetic + TRUETYPE_TAG('x', 'v', 'i', 0), // xvi = Kamviri + TRUETYPE_TAG('x', 'v', 'n', 0), // xvn = Vandalic + TRUETYPE_TAG('x', 'v', 'o', 0), // xvo = Volscian + TRUETYPE_TAG('x', 'v', 's', 0), // xvs = Vestinian + TRUETYPE_TAG('x', 'w', 'a', 0), // xwa = Kwaza + TRUETYPE_TAG('x', 'w', 'c', 0), // xwc = Woccon + TRUETYPE_TAG('x', 'w', 'e', 0), // xwe = Xwela Gbe + TRUETYPE_TAG('x', 'w', 'g', 0), // xwg = Kwegu + TRUETYPE_TAG('x', 'w', 'l', 0), // xwl = Western Xwla Gbe + TRUETYPE_TAG('x', 'w', 'o', 0), // xwo = Written Oirat + TRUETYPE_TAG('x', 'w', 'r', 0), // xwr = Kwerba Mamberamo + TRUETYPE_TAG('x', 'x', 'b', 0), // xxb = Boro (Ghana) + TRUETYPE_TAG('x', 'x', 'k', 0), // xxk = Ke'o + TRUETYPE_TAG('x', 'x', 'r', 0), // xxr = Koropó + TRUETYPE_TAG('x', 'x', 't', 0), // xxt = Tambora + TRUETYPE_TAG('x', 'y', 'l', 0), // xyl = Yalakalore + TRUETYPE_TAG('x', 'z', 'h', 0), // xzh = Zhang-Zhung + TRUETYPE_TAG('x', 'z', 'm', 0), // xzm = Zemgalian + TRUETYPE_TAG('x', 'z', 'p', 0), // xzp = Ancient Zapotec + TRUETYPE_TAG('y', 'a', 'a', 0), // yaa = Yaminahua + TRUETYPE_TAG('y', 'a', 'b', 0), // yab = Yuhup + TRUETYPE_TAG('y', 'a', 'c', 0), // yac = Pass Valley Yali + TRUETYPE_TAG('y', 'a', 'd', 0), // yad = Yagua + TRUETYPE_TAG('y', 'a', 'e', 0), // yae = Pumé + TRUETYPE_TAG('y', 'a', 'f', + 0), // yaf = Yaka (Democratic Republic of Congo) + TRUETYPE_TAG('y', 'a', 'g', 0), // yag = Yámana + TRUETYPE_TAG('y', 'a', 'h', 0), // yah = Yazgulyam + TRUETYPE_TAG('y', 'a', 'i', 0), // yai = Yagnobi + TRUETYPE_TAG('y', 'a', 'j', 0), // yaj = Banda-Yangere + TRUETYPE_TAG('y', 'a', 'k', 0), // yak = Yakama + TRUETYPE_TAG('y', 'a', 'l', 0), // yal = Yalunka + TRUETYPE_TAG('y', 'a', 'm', 0), // yam = Yamba + TRUETYPE_TAG('y', 'a', 'n', 0), // yan = Mayangna + TRUETYPE_TAG('y', 'a', 'o', 0), // yao = Yao + TRUETYPE_TAG('y', 'a', 'p', 0), // yap = Yapese + TRUETYPE_TAG('y', 'a', 'q', 0), // yaq = Yaqui + TRUETYPE_TAG('y', 'a', 'r', 0), // yar = Yabarana + TRUETYPE_TAG('y', 'a', 's', 0), // yas = Nugunu (Cameroon) + TRUETYPE_TAG('y', 'a', 't', 0), // yat = Yambeta + TRUETYPE_TAG('y', 'a', 'u', 0), // yau = Yuwana + TRUETYPE_TAG('y', 'a', 'v', 0), // yav = Yangben + TRUETYPE_TAG('y', 'a', 'w', 0), // yaw = Yawalapití + TRUETYPE_TAG('y', 'a', 'x', 0), // yax = Yauma + TRUETYPE_TAG('y', 'a', 'y', 0), // yay = Agwagwune + TRUETYPE_TAG('y', 'a', 'z', 0), // yaz = Lokaa + TRUETYPE_TAG('y', 'b', 'a', 0), // yba = Yala + TRUETYPE_TAG('y', 'b', 'b', 0), // ybb = Yemba + TRUETYPE_TAG('y', 'b', 'd', 0), // ybd = Yangbye + TRUETYPE_TAG('y', 'b', 'e', 0), // ybe = West Yugur + TRUETYPE_TAG('y', 'b', 'h', 0), // ybh = Yakha + TRUETYPE_TAG('y', 'b', 'i', 0), // ybi = Yamphu + TRUETYPE_TAG('y', 'b', 'j', 0), // ybj = Hasha + TRUETYPE_TAG('y', 'b', 'k', 0), // ybk = Bokha + TRUETYPE_TAG('y', 'b', 'l', 0), // ybl = Yukuben + TRUETYPE_TAG('y', 'b', 'm', 0), // ybm = Yaben + TRUETYPE_TAG('y', 'b', 'n', 0), // ybn = Yabaâna + TRUETYPE_TAG('y', 'b', 'o', 0), // ybo = Yabong + TRUETYPE_TAG('y', 'b', 'x', 0), // ybx = Yawiyo + TRUETYPE_TAG('y', 'b', 'y', 0), // yby = Yaweyuha + TRUETYPE_TAG('y', 'c', 'h', 0), // ych = Chesu + TRUETYPE_TAG('y', 'c', 'l', 0), // ycl = Lolopo + TRUETYPE_TAG('y', 'c', 'n', 0), // ycn = Yucuna + TRUETYPE_TAG('y', 'c', 'p', 0), // ycp = Chepya + TRUETYPE_TAG('y', 'd', 'd', 0), // ydd = Eastern Yiddish + TRUETYPE_TAG('y', 'd', 'e', 0), // yde = Yangum Dey + TRUETYPE_TAG('y', 'd', 'g', 0), // ydg = Yidgha + TRUETYPE_TAG('y', 'd', 'k', 0), // ydk = Yoidik + TRUETYPE_TAG('y', 'd', 's', 0), // yds = Yiddish Sign Language + TRUETYPE_TAG('y', 'e', 'a', 0), // yea = Ravula + TRUETYPE_TAG('y', 'e', 'c', 0), // yec = Yeniche + TRUETYPE_TAG('y', 'e', 'e', 0), // yee = Yimas + TRUETYPE_TAG('y', 'e', 'i', 0), // yei = Yeni + TRUETYPE_TAG('y', 'e', 'j', 0), // yej = Yevanic + TRUETYPE_TAG('y', 'e', 'l', 0), // yel = Yela + TRUETYPE_TAG('y', 'e', 'n', 0), // yen = Yendang + TRUETYPE_TAG('y', 'e', 'r', 0), // yer = Tarok + TRUETYPE_TAG('y', 'e', 's', 0), // yes = Yeskwa + TRUETYPE_TAG('y', 'e', 't', 0), // yet = Yetfa + TRUETYPE_TAG('y', 'e', 'u', 0), // yeu = Yerukula + TRUETYPE_TAG('y', 'e', 'v', 0), // yev = Yapunda + TRUETYPE_TAG('y', 'e', 'y', 0), // yey = Yeyi + TRUETYPE_TAG('y', 'g', 'l', 0), // ygl = Yangum Gel + TRUETYPE_TAG('y', 'g', 'm', 0), // ygm = Yagomi + TRUETYPE_TAG('y', 'g', 'p', 0), // ygp = Gepo + TRUETYPE_TAG('y', 'g', 'r', 0), // ygr = Yagaria + TRUETYPE_TAG('y', 'g', 'w', 0), // ygw = Yagwoia + TRUETYPE_TAG('y', 'h', 'a', 0), // yha = Baha Buyang + TRUETYPE_TAG('y', 'h', 'd', 0), // yhd = Judeo-Iraqi Arabic + TRUETYPE_TAG('y', 'h', 'l', 0), // yhl = Hlepho Phowa + TRUETYPE_TAG('y', 'i', 'a', 0), // yia = Yinggarda + TRUETYPE_TAG('y', 'i', 'f', 0), // yif = Ache + TRUETYPE_TAG('y', 'i', 'g', 0), // yig = Wusa Nasu + TRUETYPE_TAG('y', 'i', 'h', 0), // yih = Western Yiddish + TRUETYPE_TAG('y', 'i', 'i', 0), // yii = Yidiny + TRUETYPE_TAG('y', 'i', 'j', 0), // yij = Yindjibarndi + TRUETYPE_TAG('y', 'i', 'k', 0), // yik = Dongshanba Lalo + TRUETYPE_TAG('y', 'i', 'l', 0), // yil = Yindjilandji + TRUETYPE_TAG('y', 'i', 'm', 0), // yim = Yimchungru Naga + TRUETYPE_TAG('y', 'i', 'n', 0), // yin = Yinchia + TRUETYPE_TAG('y', 'i', 'p', 0), // yip = Pholo + TRUETYPE_TAG('y', 'i', 'q', 0), // yiq = Miqie + TRUETYPE_TAG('y', 'i', 'r', 0), // yir = North Awyu + TRUETYPE_TAG('y', 'i', 's', 0), // yis = Yis + TRUETYPE_TAG('y', 'i', 't', 0), // yit = Eastern Lalu + TRUETYPE_TAG('y', 'i', 'u', 0), // yiu = Awu + TRUETYPE_TAG('y', 'i', 'v', 0), // yiv = Northern Nisu + TRUETYPE_TAG('y', 'i', 'x', 0), // yix = Axi Yi + TRUETYPE_TAG('y', 'i', 'y', 0), // yiy = Yir Yoront + TRUETYPE_TAG('y', 'i', 'z', 0), // yiz = Azhe + TRUETYPE_TAG('y', 'k', 'a', 0), // yka = Yakan + TRUETYPE_TAG('y', 'k', 'g', 0), // ykg = Northern Yukaghir + TRUETYPE_TAG('y', 'k', 'i', 0), // yki = Yoke + TRUETYPE_TAG('y', 'k', 'k', 0), // ykk = Yakaikeke + TRUETYPE_TAG('y', 'k', 'l', 0), // ykl = Khlula + TRUETYPE_TAG('y', 'k', 'm', 0), // ykm = Kap + TRUETYPE_TAG('y', 'k', 'o', 0), // yko = Yasa + TRUETYPE_TAG('y', 'k', 'r', 0), // ykr = Yekora + TRUETYPE_TAG('y', 'k', 't', 0), // ykt = Kathu + TRUETYPE_TAG('y', 'k', 'y', 0), // yky = Yakoma + TRUETYPE_TAG('y', 'l', 'a', 0), // yla = Yaul + TRUETYPE_TAG('y', 'l', 'b', 0), // ylb = Yaleba + TRUETYPE_TAG('y', 'l', 'e', 0), // yle = Yele + TRUETYPE_TAG('y', 'l', 'g', 0), // ylg = Yelogu + TRUETYPE_TAG('y', 'l', 'i', 0), // yli = Angguruk Yali + TRUETYPE_TAG('y', 'l', 'l', 0), // yll = Yil + TRUETYPE_TAG('y', 'l', 'm', 0), // ylm = Limi + TRUETYPE_TAG('y', 'l', 'n', 0), // yln = Langnian Buyang + TRUETYPE_TAG('y', 'l', 'o', 0), // ylo = Naluo Yi + TRUETYPE_TAG('y', 'l', 'r', 0), // ylr = Yalarnnga + TRUETYPE_TAG('y', 'l', 'u', 0), // ylu = Aribwaung + TRUETYPE_TAG('y', 'l', 'y', 0), // yly = Nyâlayu + TRUETYPE_TAG('y', 'm', 'a', 0), // yma = Yamphe + TRUETYPE_TAG('y', 'm', 'b', 0), // ymb = Yambes + TRUETYPE_TAG('y', 'm', 'c', 0), // ymc = Southern Muji + TRUETYPE_TAG('y', 'm', 'd', 0), // ymd = Muda + TRUETYPE_TAG('y', 'm', 'e', 0), // yme = Yameo + TRUETYPE_TAG('y', 'm', 'g', 0), // ymg = Yamongeri + TRUETYPE_TAG('y', 'm', 'h', 0), // ymh = Mili + TRUETYPE_TAG('y', 'm', 'i', 0), // ymi = Moji + TRUETYPE_TAG('y', 'm', 'k', 0), // ymk = Makwe + TRUETYPE_TAG('y', 'm', 'l', 0), // yml = Iamalele + TRUETYPE_TAG('y', 'm', 'm', 0), // ymm = Maay + TRUETYPE_TAG('y', 'm', 'n', 0), // ymn = Yamna + TRUETYPE_TAG('y', 'm', 'o', 0), // ymo = Yangum Mon + TRUETYPE_TAG('y', 'm', 'p', 0), // ymp = Yamap + TRUETYPE_TAG('y', 'm', 'q', 0), // ymq = Qila Muji + TRUETYPE_TAG('y', 'm', 'r', 0), // ymr = Malasar + TRUETYPE_TAG('y', 'm', 's', 0), // yms = Mysian + TRUETYPE_TAG('y', 'm', 't', 0), // ymt = Mator-Taygi-Karagas + TRUETYPE_TAG('y', 'm', 'x', 0), // ymx = Northern Muji + TRUETYPE_TAG('y', 'm', 'z', 0), // ymz = Muzi + TRUETYPE_TAG('y', 'n', 'a', 0), // yna = Aluo + TRUETYPE_TAG('y', 'n', 'd', 0), // ynd = Yandruwandha + TRUETYPE_TAG('y', 'n', 'e', 0), // yne = Lang'e + TRUETYPE_TAG('y', 'n', 'g', 0), // yng = Yango + TRUETYPE_TAG('y', 'n', 'h', 0), // ynh = Yangho + TRUETYPE_TAG('y', 'n', 'k', 0), // ynk = Naukan Yupik + TRUETYPE_TAG('y', 'n', 'l', 0), // ynl = Yangulam + TRUETYPE_TAG('y', 'n', 'n', 0), // ynn = Yana + TRUETYPE_TAG('y', 'n', 'o', 0), // yno = Yong + TRUETYPE_TAG('y', 'n', 's', 0), // yns = Yansi + TRUETYPE_TAG('y', 'n', 'u', 0), // ynu = Yahuna + TRUETYPE_TAG('y', 'o', 'b', 0), // yob = Yoba + TRUETYPE_TAG('y', 'o', 'g', 0), // yog = Yogad + TRUETYPE_TAG('y', 'o', 'i', 0), // yoi = Yonaguni + TRUETYPE_TAG('y', 'o', 'k', 0), // yok = Yokuts + TRUETYPE_TAG('y', 'o', 'l', 0), // yol = Yola + TRUETYPE_TAG('y', 'o', 'm', 0), // yom = Yombe + TRUETYPE_TAG('y', 'o', 'n', 0), // yon = Yongkom + TRUETYPE_TAG('y', 'o', 's', 0), // yos = Yos + TRUETYPE_TAG('y', 'o', 'x', 0), // yox = Yoron + TRUETYPE_TAG('y', 'o', 'y', 0), // yoy = Yoy + TRUETYPE_TAG('y', 'p', 'a', 0), // ypa = Phala + TRUETYPE_TAG('y', 'p', 'b', 0), // ypb = Labo Phowa + TRUETYPE_TAG('y', 'p', 'g', 0), // ypg = Phola + TRUETYPE_TAG('y', 'p', 'h', 0), // yph = Phupha + TRUETYPE_TAG('y', 'p', 'k', 0), // ypk = Yupik languages + TRUETYPE_TAG('y', 'p', 'm', 0), // ypm = Phuma + TRUETYPE_TAG('y', 'p', 'n', 0), // ypn = Ani Phowa + TRUETYPE_TAG('y', 'p', 'o', 0), // ypo = Alo Phola + TRUETYPE_TAG('y', 'p', 'p', 0), // ypp = Phupa + TRUETYPE_TAG('y', 'p', 'z', 0), // ypz = Phuza + TRUETYPE_TAG('y', 'r', 'a', 0), // yra = Yerakai + TRUETYPE_TAG('y', 'r', 'b', 0), // yrb = Yareba + TRUETYPE_TAG('y', 'r', 'e', 0), // yre = Yaouré + TRUETYPE_TAG('y', 'r', 'i', 0), // yri = Yarí + TRUETYPE_TAG('y', 'r', 'k', 0), // yrk = Nenets + TRUETYPE_TAG('y', 'r', 'l', 0), // yrl = Nhengatu + TRUETYPE_TAG('y', 'r', 'n', 0), // yrn = Yerong + TRUETYPE_TAG('y', 'r', 's', 0), // yrs = Yarsun + TRUETYPE_TAG('y', 'r', 'w', 0), // yrw = Yarawata + TRUETYPE_TAG('y', 's', 'c', 0), // ysc = Yassic + TRUETYPE_TAG('y', 's', 'd', 0), // ysd = Samatao + TRUETYPE_TAG('y', 's', 'l', 0), // ysl = Yugoslavian Sign Language + TRUETYPE_TAG('y', 's', 'n', 0), // ysn = Sani + TRUETYPE_TAG('y', 's', 'o', 0), // yso = Nisi (China) + TRUETYPE_TAG('y', 's', 'p', 0), // ysp = Southern Lolopo + TRUETYPE_TAG('y', 's', 'r', 0), // ysr = Sirenik Yupik + TRUETYPE_TAG('y', 's', 's', 0), // yss = Yessan-Mayo + TRUETYPE_TAG('y', 's', 'y', 0), // ysy = Sanie + TRUETYPE_TAG('y', 't', 'a', 0), // yta = Talu + TRUETYPE_TAG('y', 't', 'l', 0), // ytl = Tanglang + TRUETYPE_TAG('y', 't', 'p', 0), // ytp = Thopho + TRUETYPE_TAG('y', 't', 'w', 0), // ytw = Yout Wam + TRUETYPE_TAG('y', 'u', 'a', 0), // yua = Yucateco + TRUETYPE_TAG('y', 'u', 'b', 0), // yub = Yugambal + TRUETYPE_TAG('y', 'u', 'c', 0), // yuc = Yuchi + TRUETYPE_TAG('y', 'u', 'd', 0), // yud = Judeo-Tripolitanian Arabic + TRUETYPE_TAG('y', 'u', 'e', 0), // yue = Yue Chinese + TRUETYPE_TAG('y', 'u', 'f', 0), // yuf = Havasupai-Walapai-Yavapai + TRUETYPE_TAG('y', 'u', 'g', 0), // yug = Yug + TRUETYPE_TAG('y', 'u', 'i', 0), // yui = Yurutí + TRUETYPE_TAG('y', 'u', 'j', 0), // yuj = Karkar-Yuri + TRUETYPE_TAG('y', 'u', 'k', 0), // yuk = Yuki + TRUETYPE_TAG('y', 'u', 'l', 0), // yul = Yulu + TRUETYPE_TAG('y', 'u', 'm', 0), // yum = Quechan + TRUETYPE_TAG('y', 'u', 'n', 0), // yun = Bena (Nigeria) + TRUETYPE_TAG('y', 'u', 'p', 0), // yup = Yukpa + TRUETYPE_TAG('y', 'u', 'q', 0), // yuq = Yuqui + TRUETYPE_TAG('y', 'u', 'r', 0), // yur = Yurok + TRUETYPE_TAG('y', 'u', 't', 0), // yut = Yopno + TRUETYPE_TAG('y', 'u', 'u', 0), // yuu = Yugh + TRUETYPE_TAG('y', 'u', 'w', 0), // yuw = Yau (Morobe Province) + TRUETYPE_TAG('y', 'u', 'x', 0), // yux = Southern Yukaghir + TRUETYPE_TAG('y', 'u', 'y', 0), // yuy = East Yugur + TRUETYPE_TAG('y', 'u', 'z', 0), // yuz = Yuracare + TRUETYPE_TAG('y', 'v', 'a', 0), // yva = Yawa + TRUETYPE_TAG('y', 'v', 't', 0), // yvt = Yavitero + TRUETYPE_TAG('y', 'w', 'a', 0), // ywa = Kalou + TRUETYPE_TAG('y', 'w', 'l', 0), // ywl = Western Lalu + TRUETYPE_TAG('y', 'w', 'n', 0), // ywn = Yawanawa + TRUETYPE_TAG('y', 'w', 'q', 0), // ywq = Wuding-Luquan Yi + TRUETYPE_TAG('y', 'w', 'r', 0), // ywr = Yawuru + TRUETYPE_TAG('y', 'w', 't', 0), // ywt = Xishanba Lalo + TRUETYPE_TAG('y', 'w', 'u', 0), // ywu = Wumeng Nasu + TRUETYPE_TAG('y', 'w', 'w', 0), // yww = Yawarawarga + TRUETYPE_TAG('y', 'y', 'u', 0), // yyu = Yau (Sandaun Province) + TRUETYPE_TAG('y', 'y', 'z', 0), // yyz = Ayizi + TRUETYPE_TAG('y', 'z', 'g', 0), // yzg = E'ma Buyang + TRUETYPE_TAG('y', 'z', 'k', 0), // yzk = Zokhuo + TRUETYPE_TAG('z', 'a', 'a', 0), // zaa = Sierra de Juárez Zapotec + TRUETYPE_TAG('z', 'a', 'b', 0), // zab = San Juan Guelavía Zapotec + TRUETYPE_TAG('z', 'a', 'c', 0), // zac = Ocotlán Zapotec + TRUETYPE_TAG('z', 'a', 'd', 0), // zad = Cajonos Zapotec + TRUETYPE_TAG('z', 'a', 'e', 0), // zae = Yareni Zapotec + TRUETYPE_TAG('z', 'a', 'f', 0), // zaf = Ayoquesco Zapotec + TRUETYPE_TAG('z', 'a', 'g', 0), // zag = Zaghawa + TRUETYPE_TAG('z', 'a', 'h', 0), // zah = Zangwal + TRUETYPE_TAG('z', 'a', 'i', 0), // zai = Isthmus Zapotec + TRUETYPE_TAG('z', 'a', 'j', 0), // zaj = Zaramo + TRUETYPE_TAG('z', 'a', 'k', 0), // zak = Zanaki + TRUETYPE_TAG('z', 'a', 'l', 0), // zal = Zauzou + TRUETYPE_TAG('z', 'a', 'm', 0), // zam = Miahuatlán Zapotec + TRUETYPE_TAG('z', 'a', 'o', 0), // zao = Ozolotepec Zapotec + TRUETYPE_TAG('z', 'a', 'p', 0), // zap = Zapotec + TRUETYPE_TAG('z', 'a', 'q', 0), // zaq = Aloápam Zapotec + TRUETYPE_TAG('z', 'a', 'r', 0), // zar = Rincón Zapotec + TRUETYPE_TAG('z', 'a', 's', 0), // zas = Santo Domingo Albarradas Zapotec + TRUETYPE_TAG('z', 'a', 't', 0), // zat = Tabaa Zapotec + TRUETYPE_TAG('z', 'a', 'u', 0), // zau = Zangskari + TRUETYPE_TAG('z', 'a', 'v', 0), // zav = Yatzachi Zapotec + TRUETYPE_TAG('z', 'a', 'w', 0), // zaw = Mitla Zapotec + TRUETYPE_TAG('z', 'a', 'x', 0), // zax = Xadani Zapotec + TRUETYPE_TAG('z', 'a', 'y', 0), // zay = Zayse-Zergulla + TRUETYPE_TAG('z', 'a', 'z', 0), // zaz = Zari + TRUETYPE_TAG('z', 'b', 'c', 0), // zbc = Central Berawan + TRUETYPE_TAG('z', 'b', 'e', 0), // zbe = East Berawan + TRUETYPE_TAG('z', 'b', 'l', 0), // zbl = Blissymbols + TRUETYPE_TAG('z', 'b', 't', 0), // zbt = Batui + TRUETYPE_TAG('z', 'b', 'w', 0), // zbw = West Berawan + TRUETYPE_TAG('z', 'c', 'a', 0), // zca = Coatecas Altas Zapotec + TRUETYPE_TAG('z', 'c', 'h', 0), // zch = Central Hongshuihe Zhuang + TRUETYPE_TAG('z', 'd', 'j', 0), // zdj = Ngazidja Comorian + TRUETYPE_TAG('z', 'e', 'a', 0), // zea = Zeeuws + TRUETYPE_TAG('z', 'e', 'g', 0), // zeg = Zenag + TRUETYPE_TAG('z', 'e', 'h', 0), // zeh = Eastern Hongshuihe Zhuang + TRUETYPE_TAG('z', 'e', 'n', 0), // zen = Zenaga + TRUETYPE_TAG('z', 'g', 'a', 0), // zga = Kinga + TRUETYPE_TAG('z', 'g', 'b', 0), // zgb = Guibei Zhuang + TRUETYPE_TAG('z', 'g', 'm', 0), // zgm = Minz Zhuang + TRUETYPE_TAG('z', 'g', 'n', 0), // zgn = Guibian Zhuang + TRUETYPE_TAG('z', 'g', 'r', 0), // zgr = Magori + TRUETYPE_TAG('z', 'h', 'b', 0), // zhb = Zhaba + TRUETYPE_TAG('z', 'h', 'd', 0), // zhd = Dai Zhuang + TRUETYPE_TAG('z', 'h', 'i', 0), // zhi = Zhire + TRUETYPE_TAG('z', 'h', 'n', 0), // zhn = Nong Zhuang + TRUETYPE_TAG('z', 'h', 'w', 0), // zhw = Zhoa + TRUETYPE_TAG('z', 'h', 'x', 0), // zhx = Chinese (family) + TRUETYPE_TAG('z', 'i', 'a', 0), // zia = Zia + TRUETYPE_TAG('z', 'i', 'b', 0), // zib = Zimbabwe Sign Language + TRUETYPE_TAG('z', 'i', 'k', 0), // zik = Zimakani + TRUETYPE_TAG('z', 'i', 'l', 0), // zil = Zialo + TRUETYPE_TAG('z', 'i', 'm', 0), // zim = Mesme + TRUETYPE_TAG('z', 'i', 'n', 0), // zin = Zinza + TRUETYPE_TAG('z', 'i', 'r', 0), // zir = Ziriya + TRUETYPE_TAG('z', 'i', 'w', 0), // ziw = Zigula + TRUETYPE_TAG('z', 'i', 'z', 0), // ziz = Zizilivakan + TRUETYPE_TAG('z', 'k', 'a', 0), // zka = Kaimbulawa + TRUETYPE_TAG('z', 'k', 'b', 0), // zkb = Koibal + TRUETYPE_TAG('z', 'k', 'g', 0), // zkg = Koguryo + TRUETYPE_TAG('z', 'k', 'h', 0), // zkh = Khorezmian + TRUETYPE_TAG('z', 'k', 'k', 0), // zkk = Karankawa + TRUETYPE_TAG('z', 'k', 'o', 0), // zko = Kott + TRUETYPE_TAG('z', 'k', 'p', 0), // zkp = São Paulo Kaingáng + TRUETYPE_TAG('z', 'k', 'r', 0), // zkr = Zakhring + TRUETYPE_TAG('z', 'k', 't', 0), // zkt = Kitan + TRUETYPE_TAG('z', 'k', 'u', 0), // zku = Kaurna + TRUETYPE_TAG('z', 'k', 'v', 0), // zkv = Krevinian + TRUETYPE_TAG('z', 'k', 'z', 0), // zkz = Khazar + TRUETYPE_TAG('z', 'l', 'e', 0), // zle = East Slavic languages + TRUETYPE_TAG('z', 'l', 'j', 0), // zlj = Liujiang Zhuang + TRUETYPE_TAG('z', 'l', 'm', 0), // zlm = Malay (individual language) + TRUETYPE_TAG('z', 'l', 'n', 0), // zln = Lianshan Zhuang + TRUETYPE_TAG('z', 'l', 'q', 0), // zlq = Liuqian Zhuang + TRUETYPE_TAG('z', 'l', 's', 0), // zls = South Slavic languages + TRUETYPE_TAG('z', 'l', 'w', 0), // zlw = West Slavic languages + TRUETYPE_TAG('z', 'm', 'a', 0), // zma = Manda (Australia) + TRUETYPE_TAG('z', 'm', 'b', 0), // zmb = Zimba + TRUETYPE_TAG('z', 'm', 'c', 0), // zmc = Margany + TRUETYPE_TAG('z', 'm', 'd', 0), // zmd = Maridan + TRUETYPE_TAG('z', 'm', 'e', 0), // zme = Mangerr + TRUETYPE_TAG('z', 'm', 'f', 0), // zmf = Mfinu + TRUETYPE_TAG('z', 'm', 'g', 0), // zmg = Marti Ke + TRUETYPE_TAG('z', 'm', 'h', 0), // zmh = Makolkol + TRUETYPE_TAG('z', 'm', 'i', 0), // zmi = Negeri Sembilan Malay + TRUETYPE_TAG('z', 'm', 'j', 0), // zmj = Maridjabin + TRUETYPE_TAG('z', 'm', 'k', 0), // zmk = Mandandanyi + TRUETYPE_TAG('z', 'm', 'l', 0), // zml = Madngele + TRUETYPE_TAG('z', 'm', 'm', 0), // zmm = Marimanindji + TRUETYPE_TAG('z', 'm', 'n', 0), // zmn = Mbangwe + TRUETYPE_TAG('z', 'm', 'o', 0), // zmo = Molo + TRUETYPE_TAG('z', 'm', 'p', 0), // zmp = Mpuono + TRUETYPE_TAG('z', 'm', 'q', 0), // zmq = Mituku + TRUETYPE_TAG('z', 'm', 'r', 0), // zmr = Maranunggu + TRUETYPE_TAG('z', 'm', 's', 0), // zms = Mbesa + TRUETYPE_TAG('z', 'm', 't', 0), // zmt = Maringarr + TRUETYPE_TAG('z', 'm', 'u', 0), // zmu = Muruwari + TRUETYPE_TAG('z', 'm', 'v', 0), // zmv = Mbariman-Gudhinma + TRUETYPE_TAG('z', 'm', 'w', 0), // zmw = Mbo (Democratic Republic of Congo) + TRUETYPE_TAG('z', 'm', 'x', 0), // zmx = Bomitaba + TRUETYPE_TAG('z', 'm', 'y', 0), // zmy = Mariyedi + TRUETYPE_TAG('z', 'm', 'z', 0), // zmz = Mbandja + TRUETYPE_TAG('z', 'n', 'a', 0), // zna = Zan Gula + TRUETYPE_TAG('z', 'n', 'd', 0), // znd = Zande languages + TRUETYPE_TAG('z', 'n', 'e', 0), // zne = Zande (individual language) + TRUETYPE_TAG('z', 'n', 'g', 0), // zng = Mang + TRUETYPE_TAG('z', 'n', 'k', 0), // znk = Manangkari + TRUETYPE_TAG('z', 'n', 's', 0), // zns = Mangas + TRUETYPE_TAG('z', 'o', 'c', 0), // zoc = Copainalá Zoque + TRUETYPE_TAG('z', 'o', 'h', 0), // zoh = Chimalapa Zoque + TRUETYPE_TAG('z', 'o', 'm', 0), // zom = Zou + TRUETYPE_TAG('z', 'o', 'o', 0), // zoo = Asunción Mixtepec Zapotec + TRUETYPE_TAG('z', 'o', 'q', 0), // zoq = Tabasco Zoque + TRUETYPE_TAG('z', 'o', 'r', 0), // zor = Rayón Zoque + TRUETYPE_TAG('z', 'o', 's', 0), // zos = Francisco León Zoque + TRUETYPE_TAG('z', 'p', 'a', 0), // zpa = Lachiguiri Zapotec + TRUETYPE_TAG('z', 'p', 'b', 0), // zpb = Yautepec Zapotec + TRUETYPE_TAG('z', 'p', 'c', 0), // zpc = Choapan Zapotec + TRUETYPE_TAG('z', 'p', 'd', 0), // zpd = Southeastern Ixtlán Zapotec + TRUETYPE_TAG('z', 'p', 'e', 0), // zpe = Petapa Zapotec + TRUETYPE_TAG('z', 'p', 'f', 0), // zpf = San Pedro Quiatoni Zapotec + TRUETYPE_TAG('z', 'p', 'g', 0), // zpg = Guevea De Humboldt Zapotec + TRUETYPE_TAG('z', 'p', 'h', 0), // zph = Totomachapan Zapotec + TRUETYPE_TAG('z', 'p', 'i', 0), // zpi = Santa María Quiegolani Zapotec + TRUETYPE_TAG('z', 'p', 'j', 0), // zpj = Quiavicuzas Zapotec + TRUETYPE_TAG('z', 'p', 'k', 0), // zpk = Tlacolulita Zapotec + TRUETYPE_TAG('z', 'p', 'l', 0), // zpl = Lachixío Zapotec + TRUETYPE_TAG('z', 'p', 'm', 0), // zpm = Mixtepec Zapotec + TRUETYPE_TAG('z', 'p', 'n', 0), // zpn = Santa Inés Yatzechi Zapotec + TRUETYPE_TAG('z', 'p', 'o', 0), // zpo = Amatlán Zapotec + TRUETYPE_TAG('z', 'p', 'p', 0), // zpp = El Alto Zapotec + TRUETYPE_TAG('z', 'p', 'q', 0), // zpq = Zoogocho Zapotec + TRUETYPE_TAG('z', 'p', 'r', 0), // zpr = Santiago Xanica Zapotec + TRUETYPE_TAG('z', 'p', 's', 0), // zps = Coatlán Zapotec + TRUETYPE_TAG('z', 'p', 't', 0), // zpt = San Vicente Coatlán Zapotec + TRUETYPE_TAG('z', 'p', 'u', 0), // zpu = Yalálag Zapotec + TRUETYPE_TAG('z', 'p', 'v', 0), // zpv = Chichicapan Zapotec + TRUETYPE_TAG('z', 'p', 'w', 0), // zpw = Zaniza Zapotec + TRUETYPE_TAG('z', 'p', 'x', 0), // zpx = San Baltazar Loxicha Zapotec + TRUETYPE_TAG('z', 'p', 'y', 0), // zpy = Mazaltepec Zapotec + TRUETYPE_TAG('z', 'p', 'z', 0), // zpz = Texmelucan Zapotec + TRUETYPE_TAG('z', 'q', 'e', 0), // zqe = Qiubei Zhuang + TRUETYPE_TAG('z', 'r', 'a', 0), // zra = Kara (Korea) + TRUETYPE_TAG('z', 'r', 'g', 0), // zrg = Mirgan + TRUETYPE_TAG('z', 'r', 'n', 0), // zrn = Zerenkel + TRUETYPE_TAG('z', 'r', 'o', 0), // zro = Záparo + TRUETYPE_TAG('z', 'r', 'p', 0), // zrp = Zarphatic + TRUETYPE_TAG('z', 'r', 's', 0), // zrs = Mairasi + TRUETYPE_TAG('z', 's', 'a', 0), // zsa = Sarasira + TRUETYPE_TAG('z', 's', 'k', 0), // zsk = Kaskean + TRUETYPE_TAG('z', 's', 'l', 0), // zsl = Zambian Sign Language + TRUETYPE_TAG('z', 's', 'm', 0), // zsm = Standard Malay + TRUETYPE_TAG('z', 's', 'r', 0), // zsr = Southern Rincon Zapotec + TRUETYPE_TAG('z', 's', 'u', 0), // zsu = Sukurum + TRUETYPE_TAG('z', 't', 'e', 0), // zte = Elotepec Zapotec + TRUETYPE_TAG('z', 't', 'g', 0), // ztg = Xanaguía Zapotec + TRUETYPE_TAG('z', 't', 'l', 0), // ztl = Lapaguía-Guivini Zapotec + TRUETYPE_TAG('z', 't', 'm', 0), // ztm = San Agustín Mixtepec Zapotec + TRUETYPE_TAG('z', 't', 'n', 0), // ztn = Santa Catarina Albarradas Zapotec + TRUETYPE_TAG('z', 't', 'p', 0), // ztp = Loxicha Zapotec + TRUETYPE_TAG('z', 't', 'q', 0), // ztq = Quioquitani-Quierí Zapotec + TRUETYPE_TAG('z', 't', 's', 0), // zts = Tilquiapan Zapotec + TRUETYPE_TAG('z', 't', 't', 0), // ztt = Tejalapan Zapotec + TRUETYPE_TAG('z', 't', 'u', 0), // ztu = Güilá Zapotec + TRUETYPE_TAG('z', 't', 'x', 0), // ztx = Zaachila Zapotec + TRUETYPE_TAG('z', 't', 'y', 0), // zty = Yatee Zapotec + TRUETYPE_TAG('z', 'u', 'a', 0), // zua = Zeem + TRUETYPE_TAG('z', 'u', 'h', 0), // zuh = Tokano + TRUETYPE_TAG('z', 'u', 'm', 0), // zum = Kumzari + TRUETYPE_TAG('z', 'u', 'n', 0), // zun = Zuni + TRUETYPE_TAG('z', 'u', 'y', 0), // zuy = Zumaya + TRUETYPE_TAG('z', 'w', 'a', 0), // zwa = Zay + TRUETYPE_TAG('z', 'x', 'x', 0), // zxx = No linguistic content + TRUETYPE_TAG('z', 'y', 'b', 0), // zyb = Yongbei Zhuang + TRUETYPE_TAG('z', 'y', 'g', 0), // zyg = Yang Zhuang + TRUETYPE_TAG('z', 'y', 'j', 0), // zyj = Youjiang Zhuang + TRUETYPE_TAG('z', 'y', 'n', 0), // zyn = Yongnan Zhuang + TRUETYPE_TAG('z', 'y', 'p', 0), // zyp = Zyphe + TRUETYPE_TAG('z', 'z', 'a', 0), // zza = Zaza + TRUETYPE_TAG('z', 'z', 'j', 0), // zzj = Zuojiang Zhuang + 0x0 // end of language code list +}; + +/* + * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * * + */ diff --git a/gfx/thebes/gfxLineSegment.h b/gfx/thebes/gfxLineSegment.h new file mode 100644 index 0000000000..b54d3097f2 --- /dev/null +++ b/gfx/thebes/gfxLineSegment.h @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_LINESEGMENT_H +#define GFX_LINESEGMENT_H + +#include "gfxTypes.h" +#include "gfxPoint.h" + +struct gfxLineSegment { + gfxLineSegment() : mStart(gfxPoint()), mEnd(gfxPoint()) {} + gfxLineSegment(const gfxPoint& aStart, const gfxPoint& aEnd) + : mStart(aStart), mEnd(aEnd) {} + + bool PointsOnSameSide(const gfxPoint& aOne, const gfxPoint& aTwo) { + // Solve the equation + // y - mStart.y - ((mEnd.y - mStart.y)/(mEnd.x - mStart.x))(x - mStart.x) + // for both points + + gfxFloat deltaY = (mEnd.y - mStart.y); + gfxFloat deltaX = (mEnd.x - mStart.x); + + gfxFloat one = deltaX * (aOne.y - mStart.y) - deltaY * (aOne.x - mStart.x); + gfxFloat two = deltaX * (aTwo.y - mStart.y) - deltaY * (aTwo.x - mStart.x); + + // If both results have the same sign, then we're on the correct side of the + // line. 0 (on the line) is always considered in. + + if ((one >= 0 && two >= 0) || (one <= 0 && two <= 0)) return true; + return false; + } + + /** + * Determines if two line segments intersect, and returns the intersection + * point in aIntersection if they do. + * + * Coincident lines are considered not intersecting as they don't have an + * intersection point. + */ + bool Intersects(const gfxLineSegment& aOther, gfxPoint& aIntersection) { + gfxFloat denominator = + (aOther.mEnd.y - aOther.mStart.y) * (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 0000000000..8780030a60 --- /dev/null +++ b/gfx/thebes/gfxMacFont.cpp @@ -0,0 +1,565 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxMacFont.h" + +#include "mozilla/MemoryReporting.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticPrefs_gfx.h" + +#include "gfxCoreTextShaper.h" +#include <algorithm> +#include "gfxPlatformMac.h" +#include "gfxContext.h" +#include "gfxFontUtils.h" +#include "gfxMacPlatformFontList.h" +#include "gfxFontConstants.h" +#include "gfxTextRun.h" +#include "gfxUtils.h" +#include "nsCocoaFeatures.h" +#include "cairo-quartz.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +template <class T> +struct TagEquals { + bool Equals(const T& aIter, uint32_t aTag) const { + return aIter.mTag == aTag; + } +}; + +gfxMacFont::gfxMacFont(const RefPtr<UnscaledFontMac>& aUnscaledFont, + MacOSFontEntry* aFontEntry, + const gfxFontStyle* aFontStyle) + : gfxFont(aUnscaledFont, aFontEntry, aFontStyle), + mCGFont(nullptr), + mCTFont(nullptr), + mFontSmoothingBackgroundColor(aFontStyle->fontSmoothingBackgroundColor), + mVariationFont(aFontEntry->HasVariations()) { + mApplySyntheticBold = aFontStyle->NeedsSyntheticBold(aFontEntry); + + if (mVariationFont) { + CGFontRef baseFont = aUnscaledFont->GetFont(); + if (!baseFont) { + mIsValid = false; + return; + } + + // Get the variation settings needed to instantiate the fontEntry + // for a particular fontStyle. + AutoTArray<gfxFontVariation, 4> vars; + aFontEntry->GetVariationsForStyle(vars, *aFontStyle); + + // Because of a Core Text bug, we need to ensure that if the font has + // an 'opsz' axis, it is always explicitly set, and NOT to the font's + // default value. (See bug 1457417, bug 1478720.) + // We record the result of searching the font's axes in the font entry, + // so that this only has to be done by the first instance created for + // a given font resource. + const uint32_t kOpszTag = HB_TAG('o', 'p', 's', 'z'); + const float kOpszFudgeAmount = 0.01f; + + if (!aFontEntry->mCheckedForOpszAxis) { + aFontEntry->mCheckedForOpszAxis = true; + AutoTArray<gfxFontVariationAxis, 4> axes; + aFontEntry->GetVariationAxes(axes); + auto index = axes.IndexOf(kOpszTag, 0, TagEquals<gfxFontVariationAxis>()); + if (index == axes.NoIndex) { + aFontEntry->mHasOpszAxis = false; + } else { + const auto& axis = axes[index]; + aFontEntry->mHasOpszAxis = true; + aFontEntry->mOpszAxis = axis; + // Pick a slightly-adjusted version of the default that we'll + // use to work around Core Text's habit of ignoring any attempt + // to explicitly set the default value. + aFontEntry->mAdjustedDefaultOpsz = + axis.mDefaultValue == axis.mMinValue + ? axis.mDefaultValue + kOpszFudgeAmount + : axis.mDefaultValue - kOpszFudgeAmount; + } + } + + // Add 'opsz' if not present, or tweak its value if it looks too close + // to the default (after clamping to the font's available range). + if (aFontEntry->mHasOpszAxis) { + auto index = vars.IndexOf(kOpszTag, 0, TagEquals<gfxFontVariation>()); + if (index == vars.NoIndex) { + gfxFontVariation opsz{kOpszTag, aFontEntry->mAdjustedDefaultOpsz}; + vars.AppendElement(opsz); + } else { + // Figure out a "safe" value that Core Text won't ignore. + auto& value = vars[index].mValue; + auto& axis = aFontEntry->mOpszAxis; + value = fmin(fmax(value, axis.mMinValue), axis.mMaxValue); + if (std::abs(value - axis.mDefaultValue) < kOpszFudgeAmount) { + value = aFontEntry->mAdjustedDefaultOpsz; + } + } + } + + mCGFont = UnscaledFontMac::CreateCGFontWithVariations( + baseFont, aUnscaledFont->AxesCache(), vars.Length(), vars.Elements()); + if (!mCGFont) { + ::CFRetain(baseFont); + mCGFont = baseFont; + } + } else { + mCGFont = aUnscaledFont->GetFont(); + if (!mCGFont) { + mIsValid = false; + return; + } + ::CFRetain(mCGFont); + } + + // InitMetrics will handle the sizeAdjust factor and set mAdjustedSize + InitMetrics(); + if (!mIsValid) { + return; + } + + // turn off font anti-aliasing based on user pref setting + if (mAdjustedSize <= + (gfxFloat)gfxPlatformMac::GetPlatform()->GetAntiAliasingThreshold()) { + mAntialiasOption = kAntialiasNone; + } else if (mStyle.useGrayscaleAntialiasing) { + mAntialiasOption = kAntialiasGrayscale; + } +} + +gfxMacFont::~gfxMacFont() { + if (mCGFont) { + ::CFRelease(mCGFont); + } + if (mCTFont) { + ::CFRelease(mCTFont); + } +} + +bool gfxMacFont::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, + RoundingFlags aRounding, + gfxShapedText* aShapedText) { + if (!mIsValid) { + NS_WARNING("invalid font! expect incorrect text rendering"); + return false; + } + + // Currently, we don't support vertical shaping via CoreText, + // so we ignore RequiresAATLayout if vertical is requested. + auto macFontEntry = static_cast<MacOSFontEntry*>(GetFontEntry()); + if (macFontEntry->RequiresAATLayout() && !aVertical && + StaticPrefs::gfx_font_rendering_coretext_enabled()) { + if (!mCoreTextShaper) { + mCoreTextShaper = MakeUnique<gfxCoreTextShaper>(this); + } + if (mCoreTextShaper->ShapeText(aDrawTarget, aText, aOffset, aLength, + aScript, aLanguage, aVertical, aRounding, + aShapedText)) { + PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical, + aShapedText); + if (GetFontEntry()->HasTrackingTable()) { + // Convert font size from device pixels back to CSS px + // to use in selecting tracking value + float trackSize = GetAdjustedSize() * + aShapedText->GetAppUnitsPerDevUnit() / + AppUnitsPerCSSPixel(); + float tracking = + GetFontEntry()->TrackingForCSSPx(trackSize) * mFUnitsConvFactor; + // Applying tracking is a lot like the adjustment we do for + // synthetic bold: we want to apply between clusters, not to + // non-spacing glyphs within a cluster. So we can reuse that + // helper here. + aShapedText->AdjustAdvancesForSyntheticBold(tracking, aOffset, aLength); + } + return true; + } + } + + return gfxFont::ShapeText(aDrawTarget, aText, aOffset, aLength, aScript, + aLanguage, aVertical, aRounding, aShapedText); +} + +gfxFont::RunMetrics gfxMacFont::Measure(const gfxTextRun* aTextRun, + uint32_t aStart, uint32_t aEnd, + BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, + Spacing* aSpacing, + gfx::ShapedTextFlags aOrientation) { + gfxFont::RunMetrics metrics = + gfxFont::Measure(aTextRun, aStart, aEnd, aBoundingBoxType, aRefDrawTarget, + aSpacing, aOrientation); + + // if aBoundingBoxType is not TIGHT_HINTED_OUTLINE_EXTENTS then we need to add + // a pixel column each side of the bounding box in case of antialiasing + // "bleed" + if (aBoundingBoxType != TIGHT_HINTED_OUTLINE_EXTENTS && + metrics.mBoundingBox.width > 0) { + metrics.mBoundingBox.x -= aTextRun->GetAppUnitsPerDevUnit(); + metrics.mBoundingBox.width += aTextRun->GetAppUnitsPerDevUnit() * 2; + } + + return metrics; +} + +void gfxMacFont::InitMetrics() { + mIsValid = false; + ::memset(&mMetrics, 0, sizeof(mMetrics)); + + uint32_t upem = 0; + + // try to get unitsPerEm from sfnt head table, to avoid calling CGFont + // if possible (bug 574368) and because CGFontGetUnitsPerEm does not + // return the true value for OpenType/CFF fonts (it normalizes to 1000, + // which then leads to metrics errors when we read the 'hmtx' table to + // get glyph advances for HarfBuzz, see bug 580863) + CFDataRef headData = + ::CGFontCopyTableForTag(mCGFont, TRUETYPE_TAG('h', 'e', 'a', 'd')); + if (headData) { + if (size_t(::CFDataGetLength(headData)) >= sizeof(HeadTable)) { + const HeadTable* head = + reinterpret_cast<const HeadTable*>(::CFDataGetBytePtr(headData)); + upem = head->unitsPerEm; + } + ::CFRelease(headData); + } + if (!upem) { + upem = ::CGFontGetUnitsPerEm(mCGFont); + } + + if (upem < 16 || upem > 16384) { + // See http://www.microsoft.com/typography/otspec/head.htm +#ifdef DEBUG + char warnBuf[1024]; + SprintfLiteral(warnBuf, + "Bad font metrics for: %s (invalid unitsPerEm value)", + mFontEntry->Name().get()); + NS_WARNING(warnBuf); +#endif + return; + } + + mAdjustedSize = std::max(mStyle.size, 1.0); + mFUnitsConvFactor = mAdjustedSize / upem; + + // For CFF fonts, when scaling values read from CGFont* APIs, we need to + // use CG's idea of unitsPerEm, which may differ from the "true" value in + // the head table of the font (see bug 580863) + gfxFloat cgConvFactor; + if (static_cast<MacOSFontEntry*>(mFontEntry.get())->IsCFF()) { + cgConvFactor = mAdjustedSize / ::CGFontGetUnitsPerEm(mCGFont); + } else { + cgConvFactor = mFUnitsConvFactor; + } + + // Try to read 'sfnt' metrics; for local, non-sfnt fonts ONLY, fall back to + // platform APIs. The InitMetrics...() functions will set mIsValid on success. + if (!InitMetricsFromSfntTables(mMetrics) && + (!mFontEntry->IsUserFont() || mFontEntry->IsLocalUserFont())) { + InitMetricsFromPlatform(); + } + if (!mIsValid) { + return; + } + + if (mMetrics.xHeight == 0.0) { + mMetrics.xHeight = ::CGFontGetXHeight(mCGFont) * cgConvFactor; + } + + if (mMetrics.capHeight == 0.0) { + mMetrics.capHeight = ::CGFontGetCapHeight(mCGFont) * cgConvFactor; + } + + if (mStyle.sizeAdjust > 0.0 && mStyle.size > 0.0 && mMetrics.xHeight > 0.0) { + // apply font-size-adjust, and recalculate metrics + gfxFloat aspect = mMetrics.xHeight / mStyle.size; + mAdjustedSize = mStyle.GetAdjustedSize(aspect); + mFUnitsConvFactor = mAdjustedSize / upem; + if (static_cast<MacOSFontEntry*>(mFontEntry.get())->IsCFF()) { + cgConvFactor = mAdjustedSize / ::CGFontGetUnitsPerEm(mCGFont); + } else { + cgConvFactor = mFUnitsConvFactor; + } + mMetrics.xHeight = 0.0; + if (!InitMetricsFromSfntTables(mMetrics) && + (!mFontEntry->IsUserFont() || mFontEntry->IsLocalUserFont())) { + InitMetricsFromPlatform(); + } + if (!mIsValid) { + // this shouldn't happen, as we succeeded earlier before applying + // the size-adjust factor! But check anyway, for paranoia's sake. + return; + } + if (mMetrics.xHeight == 0.0) { + mMetrics.xHeight = ::CGFontGetXHeight(mCGFont) * cgConvFactor; + } + } + + // Once we reach here, we've got basic metrics and set mIsValid = TRUE; + // there should be no further points of actual failure in InitMetrics(). + // (If one is introduced, be sure to reset mIsValid to FALSE!) + + mMetrics.emHeight = mAdjustedSize; + + // Measure/calculate additional metrics, independent of whether we used + // the tables directly or ATS metrics APIs + + CFDataRef cmap = + ::CGFontCopyTableForTag(mCGFont, TRUETYPE_TAG('c', 'm', 'a', 'p')); + + uint32_t glyphID; + if (mMetrics.aveCharWidth <= 0) { + mMetrics.aveCharWidth = GetCharWidth(cmap, 'x', &glyphID, cgConvFactor); + if (glyphID == 0) { + // we didn't find 'x', so use maxAdvance rather than zero + mMetrics.aveCharWidth = mMetrics.maxAdvance; + } + } + if (IsSyntheticBold()) { + mMetrics.aveCharWidth += GetSyntheticBoldOffset(); + mMetrics.maxAdvance += GetSyntheticBoldOffset(); + } + + mMetrics.spaceWidth = GetCharWidth(cmap, ' ', &glyphID, cgConvFactor); + if (glyphID == 0) { + // no space glyph?! + mMetrics.spaceWidth = mMetrics.aveCharWidth; + } + mSpaceGlyph = glyphID; + + mMetrics.zeroWidth = GetCharWidth(cmap, '0', &glyphID, cgConvFactor); + if (glyphID == 0) { + mMetrics.zeroWidth = -1.0; // indicates not found + } + + 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; +} + +/* static */ +CTFontRef gfxMacFont::CreateCTFontFromCGFontWithVariations( + CGFontRef aCGFont, CGFloat aSize, bool aInstalledFont, + CTFontDescriptorRef aFontDesc) { + // Avoid calling potentially buggy variation APIs on pre-Sierra macOS + // versions (see bug 1331683). + // + // And on HighSierra, CTFontCreateWithGraphicsFont properly carries over + // variation settings from the CGFont to CTFont, so we don't need to do + // the extra work here -- and this seems to avoid Core Text crashiness + // seen in bug 1454094. + // + // However, for installed fonts it seems we DO need to copy the variations + // explicitly even on 10.13, otherwise fonts fail to render (as in bug + // 1455494) when non-default values are used. Fortunately, the crash + // mentioned above occurs with data fonts, not (AFAICT) with system- + // installed fonts. + // + // So we only need to do this "the hard way" on Sierra, and on HighSierra + // for system-installed fonts; in other cases just let the standard CTFont + // function do its thing. + // + // NOTE in case this ever needs further adjustment: there is similar logic + // in four places in the tree (sadly): + // CreateCTFontFromCGFontWithVariations in gfxMacFont.cpp + // CreateCTFontFromCGFontWithVariations in ScaledFontMac.cpp + // CreateCTFontFromCGFontWithVariations in cairo-quartz-font.c + // ctfont_create_exact_copy in SkFontHost_mac.cpp + + CTFontRef ctFont; + if (nsCocoaFeatures::OnSierraExactly() || + (aInstalledFont && nsCocoaFeatures::OnHighSierraOrLater())) { + CFDictionaryRef variations = ::CGFontCopyVariations(aCGFont); + if (variations) { + CFDictionaryRef varAttr = ::CFDictionaryCreate( + nullptr, (const void**)&kCTFontVariationAttribute, + (const void**)&variations, 1, &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + ::CFRelease(variations); + + CTFontDescriptorRef varDesc = + aFontDesc + ? ::CTFontDescriptorCreateCopyWithAttributes(aFontDesc, varAttr) + : ::CTFontDescriptorCreateWithAttributes(varAttr); + ::CFRelease(varAttr); + + ctFont = ::CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, varDesc); + ::CFRelease(varDesc); + } else { + ctFont = + ::CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, aFontDesc); + } + } else { + ctFont = ::CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, aFontDesc); + } + + return ctFont; +} + +int32_t gfxMacFont::GetGlyphWidth(uint16_t aGID) { + if (mVariationFont) { + // Avoid a potential Core Text crash (bug 1450209) by using + // CoreGraphics glyph advance API. This is inferior for 'sbix' + // fonts, but those won't have variations, so it's OK. + int cgAdvance; + if (::CGFontGetGlyphAdvances(mCGFont, &aGID, 1, &cgAdvance)) { + return cgAdvance * mFUnitsConvFactor * 0x10000; + } + } + + if (!mCTFont) { + bool isInstalledFont = + !mFontEntry->IsUserFont() || mFontEntry->IsLocalUserFont(); + mCTFont = CreateCTFontFromCGFontWithVariations(mCGFont, mAdjustedSize, + isInstalledFont); + if (!mCTFont) { // shouldn't happen, but let's be safe + NS_WARNING("failed to create CTFontRef to measure glyph width"); + return 0; + } + } + + CGSize advance; + ::CTFontGetAdvancesForGlyphs(mCTFont, kCTFontDefaultOrientation, &aGID, + &advance, 1); + return advance.width * 0x10000; +} + +bool gfxMacFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) { + CGRect bb; + if (!::CGFontGetGlyphBBoxes(mCGFont, &aGID, 1, &bb)) { + return false; + } + + // broken fonts can return incorrect bounds for some null characters, + // see https://bugzilla.mozilla.org/show_bug.cgi?id=534260 + if (bb.origin.x == -32767 && bb.origin.y == -32767 && + bb.size.width == 65534 && bb.size.height == 65534) { + *aBounds = gfxRect(0, 0, 0, 0); + return true; + } + + gfxRect bounds(bb.origin.x, -(bb.origin.y + bb.size.height), bb.size.width, + bb.size.height); + bounds.Scale(mFUnitsConvFactor); + *aBounds = bounds; + return true; +} + +// Try to initialize font metrics via platform APIs (CG/CT), +// and set mIsValid = TRUE on success. +// We ONLY call this for local (platform) fonts that are not sfnt format; +// for sfnts, including ALL downloadable fonts, we prefer to use +// InitMetricsFromSfntTables and avoid platform APIs. +void gfxMacFont::InitMetricsFromPlatform() { + CTFontRef ctFont = + ::CTFontCreateWithGraphicsFont(mCGFont, mAdjustedSize, nullptr, nullptr); + if (!ctFont) { + return; + } + + mMetrics.underlineOffset = ::CTFontGetUnderlinePosition(ctFont); + mMetrics.underlineSize = ::CTFontGetUnderlineThickness(ctFont); + + mMetrics.externalLeading = ::CTFontGetLeading(ctFont); + + mMetrics.maxAscent = ::CTFontGetAscent(ctFont); + mMetrics.maxDescent = ::CTFontGetDescent(ctFont); + + // this is not strictly correct, but neither CTFont nor CGFont seems to + // provide maxAdvance, unless we were to iterate over all the glyphs + // (which isn't worth the cost here) + CGRect r = ::CTFontGetBoundingBox(ctFont); + mMetrics.maxAdvance = r.size.width; + + // aveCharWidth is also not provided, so leave it at zero + // (fallback code in gfxMacFont::InitMetrics will then try measuring 'x'); + // this could lead to less-than-"perfect" text field sizing when width is + // specified as a number of characters, and the font in use is a non-sfnt + // legacy font, but that's a sufficiently obscure edge case that we can + // ignore the potential discrepancy. + mMetrics.aveCharWidth = 0; + + mMetrics.xHeight = ::CTFontGetXHeight(ctFont); + mMetrics.capHeight = ::CTFontGetCapHeight(ctFont); + + ::CFRelease(ctFont); + + mIsValid = true; +} + +already_AddRefed<ScaledFont> gfxMacFont::GetScaledFont(DrawTarget* aTarget) { + if (!mAzureScaledFont) { + mAzureScaledFont = Factory::CreateScaledFontForMacFont( + GetCGFontRef(), GetUnscaledFont(), GetAdjustedSize(), + ToDeviceColor(mFontSmoothingBackgroundColor), + !mStyle.useGrayscaleAntialiasing, IsSyntheticBold()); + if (!mAzureScaledFont) { + return nullptr; + } + InitializeScaledFont(); + } + + RefPtr<ScaledFont> scaledFont(mAzureScaledFont); + return scaledFont.forget(); +} + +bool gfxMacFont::ShouldRoundXOffset(cairo_t* aCairo) const { + // Quartz surfaces implement show_glyphs for Quartz fonts + return aCairo && cairo_surface_get_type(cairo_get_target(aCairo)) != + CAIRO_SURFACE_TYPE_QUARTZ; +} + +void gfxMacFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const { + gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); + // mCGFont is shared with the font entry, so not counted here; +} + +void gfxMacFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const { + aSizes->mFontInstances += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} diff --git a/gfx/thebes/gfxMacFont.h b/gfx/thebes/gfxMacFont.h new file mode 100644 index 0000000000..3d7157b957 --- /dev/null +++ b/gfx/thebes/gfxMacFont.h @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_MACFONT_H +#define GFX_MACFONT_H + +#include "mozilla/MemoryReporting.h" +#include "gfxFont.h" +#include <ApplicationServices/ApplicationServices.h> + +#include "mozilla/gfx/UnscaledFontMac.h" + +class MacOSFontEntry; + +class gfxMacFont : public gfxFont { + public: + gfxMacFont(const RefPtr<mozilla::gfx::UnscaledFontMac>& aUnscaledFont, MacOSFontEntry* aFontEntry, + const gfxFontStyle* aFontStyle); + + virtual ~gfxMacFont(); + + CGFontRef GetCGFontRef() const { return mCGFont; } + + /* override Measure to add padding for antialiasing */ + RunMetrics Measure(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd, + BoundingBoxType aBoundingBoxType, DrawTarget* aDrawTargetForTightBoundingBox, + Spacing* aSpacing, mozilla::gfx::ShapedTextFlags aOrientation) override; + + // We need to provide hinted (non-linear) glyph widths if using a font + // with embedded color bitmaps (Apple Color Emoji), as Core Text renders + // the glyphs with non-linear scaling at small pixel sizes. + bool ProvidesGlyphWidths() const override { + return mVariationFont || mFontEntry->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x')); + } + + int32_t GetGlyphWidth(uint16_t aGID) override; + + bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override; + + already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont( + mozilla::gfx::DrawTarget* aTarget) override; + + bool ShouldRoundXOffset(cairo_t* aCairo) const override; + + void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const override; + void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const override; + + FontType GetType() const override { return FONT_TYPE_MAC; } + + // Helper to create a CTFont from a CGFont, with optional font descriptor + // (for features), and copying any variations that were set on the CGFont. + // This is public so that gfxCoreTextShaper can also use it. + static CTFontRef CreateCTFontFromCGFontWithVariations(CGFontRef aCGFont, CGFloat aSize, + bool aInstalledFont, + CTFontDescriptorRef aFontDesc = nullptr); + + protected: + const Metrics& GetHorizontalMetrics() override { return mMetrics; } + + // override to prefer CoreText shaping with fonts that depend on AAT + bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, uint32_t aOffset, uint32_t aLength, + Script aScript, nsAtom* aLanguage, bool aVertical, RoundingFlags aRounding, + gfxShapedText* aShapedText) override; + + void InitMetrics(); + void InitMetricsFromPlatform(); + + // Get width and glyph ID for a character; uses aConvFactor + // to convert font units as returned by CG to actual dimensions + gfxFloat GetCharWidth(CFDataRef aCmap, char16_t aUniChar, uint32_t* aGlyphID, + gfxFloat aConvFactor); + + // a strong reference to the CoreGraphics font + CGFontRef mCGFont; + + // a Core Text font reference, created only if we're using CT to measure + // glyph widths; otherwise null. + CTFontRef mCTFont; + + mozilla::UniquePtr<gfxFontShaper> mCoreTextShaper; + + Metrics mMetrics; + nscolor mFontSmoothingBackgroundColor; + + bool mVariationFont; // true if font has OpenType variations +}; + +#endif /* GFX_MACFONT_H */ diff --git a/gfx/thebes/gfxMacPlatformFontList.h b/gfx/thebes/gfxMacPlatformFontList.h new file mode 100644 index 0000000000..302dc937b3 --- /dev/null +++ b/gfx/thebes/gfxMacPlatformFontList.h @@ -0,0 +1,248 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef gfxMacPlatformFontList_H_ +#define gfxMacPlatformFontList_H_ + +#include <CoreFoundation/CoreFoundation.h> + +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/MemoryReporting.h" +#include "nsDataHashtable.h" +#include "nsRefPtrHashtable.h" + +#include "gfxPlatformFontList.h" +#include "gfxPlatform.h" +#include "gfxPlatformMac.h" + +#include "nsUnicharUtils.h" +#include "nsTArray.h" +#include "mozilla/LookAndFeel.h" + +#include "mozilla/gfx/UnscaledFontMac.h" + +class gfxMacPlatformFontList; + +// a single member of a font family (i.e. a single face, such as Times Italic) +class MacOSFontEntry final : public gfxFontEntry { + public: + friend class gfxMacPlatformFontList; + friend class gfxMacFont; + + MacOSFontEntry(const nsACString& aPostscriptName, WeightRange aWeight, + bool aIsStandardFace = false, double aSizeHint = 0.0); + + // for use with data fonts + MacOSFontEntry(const nsACString& aPostscriptName, CGFontRef aFontRef, + WeightRange aWeight, StretchRange aStretch, + SlantStyleRange aStyle, bool aIsDataUserFont, bool aIsLocal); + + virtual ~MacOSFontEntry() { ::CGFontRelease(mFontRef); } + + gfxFontEntry* Clone() const override; + + // Return a non-owning reference to our CGFont; caller must not release it. + // This will cause the fontEntry to create & retain a CGFont for the life + // of the entry. + // Note that in the case of a broken font, this could return null. + CGFontRef GetFontRef(); + + // Return a new reference to our CGFont. Caller is responsible to release + // this reference. + // (If the entry has a cached CGFont, this just bumps its refcount and + // returns it; if not, the instance returned will be owned solely by the + // caller.) + // Note that in the case of a broken font, this could return null. + CGFontRef CreateOrCopyFontRef(); + + // override gfxFontEntry table access function to bypass table cache, + // use CGFontRef API to get direct access to system font data + hb_blob_t* GetFontTable(uint32_t aTag) override; + + void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const override; + + nsresult ReadCMAP(FontInfoData* aFontInfoData = nullptr) override; + + bool RequiresAATLayout() const { return mRequiresAAT; } + + bool HasVariations() override; + void GetVariationAxes( + nsTArray<gfxFontVariationAxis>& aVariationAxes) override; + void GetVariationInstances( + nsTArray<gfxFontVariationInstance>& aInstances) override; + + bool IsCFF(); + + bool SupportsOpenTypeFeature(Script aScript, uint32_t aFeatureTag) override; + + protected: + gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle) override; + + bool HasFontTable(uint32_t aTableTag) override; + + static void DestroyBlobFunc(void* aUserData); + + CGFontRef + mFontRef; // owning reference to the CGFont, released on destruction + + double mSizeHint; + + bool mFontRefInitialized; + bool mRequiresAAT; + bool mIsCFF; + bool mIsCFFInitialized; + bool mHasVariations; + bool mHasVariationsInitialized; + bool mHasAATSmallCaps; + bool mHasAATSmallCapsInitialized; + + // To work around Core Text's mishandling of the default value for 'opsz', + // we need to record whether the font has an a optical size axis, what its + // range and default values are, and a usable close-to-default alternative. + // (See bug 1457417 for details.) + // These fields are used by gfxMacFont, but stored in the font entry so + // that only a single font instance needs to inspect the available + // variations. + bool mCheckedForOpszAxis; + bool mHasOpszAxis; + gfxFontVariationAxis mOpszAxis; + float mAdjustedDefaultOpsz; + + nsTHashtable<nsUint32HashKey> mAvailableTables; + + mozilla::ThreadSafeWeakPtr<mozilla::gfx::UnscaledFontMac> mUnscaledFont; +}; + +class gfxMacPlatformFontList final : public gfxPlatformFontList { + using FontFamilyListEntry = mozilla::dom::SystemFontListEntry; + + public: + static gfxMacPlatformFontList* PlatformFontList() { + return static_cast<gfxMacPlatformFontList*>(sPlatformFontList); + } + + gfxFontFamily* CreateFontFamily(const nsACString& aName, + FontVisibility aVisibility) const override; + + static int32_t AppleWeightToCSSWeight(int32_t aAppleWeight); + + gfxFontEntry* LookupLocalFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) override; + + gfxFontEntry* MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, + uint32_t aLength) override; + + bool FindAndAddFamilies(mozilla::StyleGenericFontFamily aGeneric, + const nsACString& aFamily, + nsTArray<FamilyAndGeneric>* aOutput, + FindFamiliesFlags aFlags, + gfxFontStyle* aStyle = nullptr, + nsAtom* aLanguage = 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, + nsACString& aSystemFontName, gfxFontStyle& aFontStyle); + + // Values for the entryType field in FontFamilyListEntry records passed + // from chrome to content process. + enum FontFamilyEntryType { + kStandardFontFamily = 0, // a standard installed font family + kTextSizeSystemFontFamily = 1, // name of 'system' font at text sizes + kDisplaySizeSystemFontFamily = 2 // 'system' font at display sizes + }; + void ReadSystemFontList(nsTArray<FontFamilyListEntry>* aList); + + protected: + FontFamily GetDefaultFontForPlatform(const gfxFontStyle* aStyle, + nsAtom* aLanguage = nullptr) override; + + private: + friend class gfxPlatformMac; + + gfxMacPlatformFontList(); + virtual ~gfxMacPlatformFontList(); + + // initialize font lists + nsresult InitFontListForPlatform() override; + void InitSharedFontListForPlatform() override; + + // special case font faces treated as font families (set via prefs) + void InitSingleFaceList(); + void InitAliasesForSingleFaceList(); + + // initialize system fonts + void InitSystemFontNames(); + + // helper function to lookup in both hidden system fonts and normal fonts + gfxFontFamily* FindSystemFontFamily(const nsACString& aFamily); + + FontVisibility GetVisibilityForFamily(const nsACString& aName) const; + + static void RegisteredFontsChangedNotificationCallback( + CFNotificationCenterRef center, void* observer, CFStringRef name, + const void* object, CFDictionaryRef userInfo); + + // attempt to use platform-specific fallback for the given character + // return null if no usable result found + gfxFontEntry* PlatformGlobalFontFallback(const uint32_t aCh, + Script aRunScript, + const gfxFontStyle* aMatchStyle, + FontFamily& aMatchedFamily) override; + + bool UsesSystemFallback() override { return true; } + + already_AddRefed<FontInfoData> CreateFontInfoData() override; + + // Add the specified family to mFontFamilies. + // Ideally we'd use NSString* instead of CFStringRef here, but this header + // file is included in .cpp files, so we can't use objective C classes here. + // But CFStringRef and NSString* are the same thing anyway (they're + // toll-free bridged). + void AddFamily(CFStringRef aFamily); + + void AddFamily(const nsACString& aFamilyName, FontVisibility aVisibility); + + void ActivateFontsFromDir(nsIFile* aDir); + + gfxFontEntry* CreateFontEntry( + mozilla::fontlist::Face* aFace, + const mozilla::fontlist::Family* aFamily) override; + + void GetFacesInitDataForFamily( + const mozilla::fontlist::Family* aFamily, + nsTArray<mozilla::fontlist::Face::InitData>& aFaces, + bool aLoadCmaps) const override; + + void ReadFaceNamesForFamily(mozilla::fontlist::Family* aFamily, + bool aNeedFullnamePostscriptNames) override; + +#ifdef MOZ_BUNDLED_FONTS + void ActivateBundledFonts(); +#endif + + enum { kATSGenerationInitial = -1 }; + + // default font for use with system-wide font fallback + CTFontRef mDefaultFont; + + // font families that -apple-system maps to + // Pre-10.11 this was always a single font family, such as Lucida Grande + // or Helvetica Neue. For OSX 10.11, Apple uses pair of families + // for the UI, one for text sizes and another for display sizes + bool mUseSizeSensitiveSystemFont; + nsCString mSystemTextFontFamilyName; + nsCString mSystemDisplayFontFamilyName; // only used on OSX 10.11 +}; + +#endif /* gfxMacPlatformFontList_H_ */ diff --git a/gfx/thebes/gfxMacPlatformFontList.mm b/gfx/thebes/gfxMacPlatformFontList.mm new file mode 100644 index 0000000000..9472c53785 --- /dev/null +++ b/gfx/thebes/gfxMacPlatformFontList.mm @@ -0,0 +1,1900 @@ +/* -*- Mode: ObjC; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: BSD + * + * Copyright (C) 2006-2009 Mozilla Corporation. All rights reserved. + * + * Contributor(s): + * Vladimir Vukicevic <vladimir@pobox.com> + * Masayuki Nakano <masayuki@d-toybox.com> + * John Daggett <jdaggett@mozilla.com> + * Jonathan Kew <jfkthame@gmail.com> + * + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ***** END LICENSE BLOCK ***** */ + +#include "mozilla/Logging.h" + +#include <algorithm> + +#import <AppKit/AppKit.h> + +#include "gfxFontConstants.h" +#include "gfxPlatformMac.h" +#include "gfxMacPlatformFontList.h" +#include "gfxMacFont.h" +#include "gfxUserFontSet.h" +#include "SharedFontList-impl.h" + +#include "harfbuzz/hb.h" + +#include "MainThreadUtils.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsIDirectoryEnumerator.h" +#include "nsCharTraits.h" +#include "nsCocoaFeatures.h" +#include "nsCocoaUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsTArray.h" + +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Preferences.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/Telemetry.h" +#include "mozilla/gfx/2D.h" + +#include <unistd.h> +#include <time.h> +#include <dlfcn.h> + +#include "StandardFonts-macos.inc" + +using namespace mozilla; +using namespace mozilla::gfx; + +// indexes into the NSArray objects that the Cocoa font manager returns +// as the available members of a family +#define INDEX_FONT_POSTSCRIPT_NAME 0 +#define INDEX_FONT_FACE_NAME 1 +#define INDEX_FONT_WEIGHT 2 +#define INDEX_FONT_TRAITS 3 + +static const int kAppleMaxWeight = 14; +static const int kAppleExtraLightWeight = 3; +static const int kAppleUltraLightWeight = 2; + +static const int gAppleWeightToCSSWeight[] = { + 0, + 1, // 1. + 1, // 2. W1, ultralight + 2, // 3. W2, extralight + 3, // 4. W3, light + 4, // 5. W4, semilight + 5, // 6. W5, medium + 6, // 7. + 6, // 8. W6, semibold + 7, // 9. W7, bold + 8, // 10. W8, extrabold + 8, // 11. + 9, // 12. W9, ultrabold + 9, // 13 + 9 // 14 +}; + +// cache Cocoa's "shared font manager" for performance +static NSFontManager* sFontManager; + +static void GetStringForNSString(const NSString* aSrc, nsAString& aDest) { + aDest.SetLength([aSrc length]); + [aSrc getCharacters:reinterpret_cast<unichar*>(aDest.BeginWriting()) + range:NSMakeRange(0, [aSrc length])]; +} + +static NSString* GetNSStringForString(const nsAString& aSrc) { + return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(aSrc.BeginReading()) + length:aSrc.Length()]; +} + +#define LOG_FONTLIST(args) \ + MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), mozilla::LogLevel::Debug, args) +#define LOG_FONTLIST_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontlist), mozilla::LogLevel::Debug) +#define LOG_CMAPDATA_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_cmapdata), mozilla::LogLevel::Debug) + +#pragma mark - + +// Complex scripts will not render correctly unless appropriate AAT or OT +// layout tables are present. +// For OpenType, we also check that the GSUB table supports the relevant +// script tag, to avoid using things like Arial Unicode MS for Lao (it has +// the characters, but lacks OpenType support). + +// TODO: consider whether we should move this to gfxFontEntry and do similar +// cmap-masking on other platforms to avoid using fonts that won't shape +// properly. + +nsresult MacOSFontEntry::ReadCMAP(FontInfoData* aFontInfoData) { + // attempt this once, if errors occur leave a blank cmap + if (mCharacterMap || mShmemCharacterMap) { + return NS_OK; + } + + RefPtr<gfxCharacterMap> charmap; + nsresult rv; + + if (aFontInfoData && (charmap = GetCMAPFromFontInfo(aFontInfoData, mUVSOffset))) { + rv = NS_OK; + } else { + uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p'); + charmap = new gfxCharacterMap(); + AutoTable cmapTable(this, kCMAP); + + if (cmapTable) { + uint32_t cmapLen; + const uint8_t* cmapData = + reinterpret_cast<const uint8_t*>(hb_blob_get_data(cmapTable, &cmapLen)); + rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, *charmap, mUVSOffset); + } else { + rv = NS_ERROR_NOT_AVAILABLE; + } + } + + if (NS_SUCCEEDED(rv) && !mIsDataUserFont && !HasGraphiteTables()) { + // For downloadable fonts, trust the author and don't + // try to munge the cmap based on script shaping support. + + // We also assume a Graphite font knows what it's doing, + // and provides whatever shaping is needed for the + // characters it supports, so only check/clear the + // complex-script ranges for non-Graphite fonts + + // for layout support, check for the presence of mort/morx/kerx and/or + // opentype layout tables + bool hasAATLayout = HasFontTable(TRUETYPE_TAG('m', 'o', 'r', 'x')) || + HasFontTable(TRUETYPE_TAG('m', 'o', 'r', 't')); + bool hasAppleKerning = HasFontTable(TRUETYPE_TAG('k', 'e', 'r', 'x')); + bool hasGSUB = HasFontTable(TRUETYPE_TAG('G', 'S', 'U', 'B')); + bool hasGPOS = HasFontTable(TRUETYPE_TAG('G', 'P', 'O', 'S')); + if ((hasAATLayout && !(hasGSUB || hasGPOS)) || hasAppleKerning) { + mRequiresAAT = true; // prefer CoreText if font has no OTL tables, + // or if it uses the Apple-specific 'kerx' + // variant of kerning table + } + + for (const ScriptRange* sr = gfxPlatformFontList::sComplexScriptRanges; sr->rangeStart; sr++) { + // check to see if the cmap includes complex script codepoints + if (charmap->TestRange(sr->rangeStart, sr->rangeEnd)) { + if (hasAATLayout) { + // prefer CoreText for Apple's complex-script fonts, + // even if they also have some OpenType tables + // (e.g. Geeza Pro Bold on 10.6; see bug 614903) + mRequiresAAT = true; + // and don't mask off complex-script ranges, we assume + // the AAT tables will provide the necessary shaping + continue; + } + + // We check for GSUB here, as GPOS alone would not be ok. + if (hasGSUB && SupportsScriptInGSUB(sr->tags, sr->numTags)) { + continue; + } + + charmap->ClearRange(sr->rangeStart, sr->rangeEnd); + } + } + + // Bug 1360309, 1393624: several of Apple's Chinese fonts have spurious + // blank glyphs for obscure Tibetan and Arabic-script codepoints. + // Blocklist these so that font fallback will not use them. + if (mRequiresAAT && + (FamilyName().EqualsLiteral("Songti SC") || FamilyName().EqualsLiteral("Songti TC") || + FamilyName().EqualsLiteral("STSong") || + // Bug 1390980: on 10.11, the Kaiti fonts are also affected. + FamilyName().EqualsLiteral("Kaiti SC") || FamilyName().EqualsLiteral("Kaiti TC") || + FamilyName().EqualsLiteral("STKaiti"))) { + charmap->ClearRange(0x0f6b, 0x0f70); + charmap->ClearRange(0x0f8c, 0x0f8f); + charmap->clear(0x0f98); + charmap->clear(0x0fbd); + charmap->ClearRange(0x0fcd, 0x0fff); + charmap->clear(0x0620); + charmap->clear(0x065f); + charmap->ClearRange(0x06ee, 0x06ef); + charmap->clear(0x06ff); + } + } + + mHasCmapTable = NS_SUCCEEDED(rv); + if (mHasCmapTable) { + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + fontlist::FontList* sharedFontList = pfl->SharedFontList(); + if (!IsUserFont() && mShmemFace) { + mShmemFace->SetCharacterMap(sharedFontList, charmap); // async + if (!TrySetShmemCharacterMap()) { + // Temporarily retain charmap, until the shared version is + // ready for use. + mCharacterMap = charmap; + } + } else { + mCharacterMap = pfl->FindCharMap(charmap); + } + } else { + // if error occurred, initialize to null cmap + mCharacterMap = new gfxCharacterMap(); + } + + LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %zu hash: %8.8x%s\n", mName.get(), + charmap->SizeOfIncludingThis(moz_malloc_size_of), charmap->mHash, + mCharacterMap == charmap ? " new" : "")); + if (LOG_CMAPDATA_ENABLED()) { + char prefix[256]; + SprintfLiteral(prefix, "(cmapdata) name: %.220s", mName.get()); + charmap->Dump(prefix, eGfxLog_cmapdata); + } + + return rv; +} + +gfxFont* MacOSFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle) { + RefPtr<UnscaledFontMac> unscaledFont(mUnscaledFont); + if (!unscaledFont) { + CGFontRef baseFont = GetFontRef(); + if (!baseFont) { + return nullptr; + } + unscaledFont = new UnscaledFontMac(baseFont, mIsDataUserFont); + mUnscaledFont = unscaledFont; + } + + return new gfxMacFont(unscaledFont, this, aFontStyle); +} + +bool MacOSFontEntry::HasVariations() { + if (!mHasVariationsInitialized) { + mHasVariationsInitialized = true; + mHasVariations = gfxPlatform::GetPlatform()->HasVariationFontSupport() && + HasFontTable(TRUETYPE_TAG('f', 'v', 'a', 'r')); + } + + return mHasVariations; +} + +void MacOSFontEntry::GetVariationAxes(nsTArray<gfxFontVariationAxis>& aVariationAxes) { + // We could do this by creating a CTFont and calling CTFontCopyVariationAxes, + // but it is expensive to instantiate a CTFont for every face just to set up + // the axis information. + // Instead we use gfxFontUtils to read the font tables directly. + gfxFontUtils::GetVariationData(this, &aVariationAxes, nullptr); +} + +void MacOSFontEntry::GetVariationInstances(nsTArray<gfxFontVariationInstance>& aInstances) { + // Core Text doesn't offer API for this, so we use gfxFontUtils to read the + // font tables directly. + gfxFontUtils::GetVariationData(this, nullptr, &aInstances); +} + +bool MacOSFontEntry::IsCFF() { + if (!mIsCFFInitialized) { + mIsCFFInitialized = true; + mIsCFF = HasFontTable(TRUETYPE_TAG('C', 'F', 'F', ' ')); + } + + return mIsCFF; +} + +MacOSFontEntry::MacOSFontEntry(const nsACString& aPostscriptName, WeightRange aWeight, + bool aIsStandardFace, double aSizeHint) + : gfxFontEntry(aPostscriptName, aIsStandardFace), + mFontRef(NULL), + mSizeHint(aSizeHint), + mFontRefInitialized(false), + mRequiresAAT(false), + mIsCFF(false), + mIsCFFInitialized(false), + mHasVariations(false), + mHasVariationsInitialized(false), + mHasAATSmallCaps(false), + mHasAATSmallCapsInitialized(false), + mCheckedForOpszAxis(false) { + mWeightRange = aWeight; +} + +MacOSFontEntry::MacOSFontEntry(const nsACString& aPostscriptName, CGFontRef aFontRef, + WeightRange aWeight, StretchRange aStretch, SlantStyleRange aStyle, + bool aIsDataUserFont, bool aIsLocalUserFont) + : gfxFontEntry(aPostscriptName, false), + mFontRef(NULL), + mSizeHint(0.0), + mFontRefInitialized(false), + mRequiresAAT(false), + mIsCFF(false), + mIsCFFInitialized(false), + mHasVariations(false), + mHasVariationsInitialized(false), + mHasAATSmallCaps(false), + mHasAATSmallCapsInitialized(false), + mCheckedForOpszAxis(false) { + mFontRef = aFontRef; + mFontRefInitialized = true; + ::CFRetain(mFontRef); + + mWeightRange = aWeight; + mStretchRange = aStretch; + mFixedPitch = false; // xxx - do we need this for downloaded fonts? + mStyleRange = aStyle; + + NS_ASSERTION(!(aIsDataUserFont && aIsLocalUserFont), + "userfont is either a data font or a local font"); + mIsDataUserFont = aIsDataUserFont; + mIsLocalUserFont = aIsLocalUserFont; +} + +gfxFontEntry* MacOSFontEntry::Clone() const { + MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!"); + MacOSFontEntry* fe = new MacOSFontEntry(Name(), Weight(), mStandardFace, mSizeHint); + fe->mStyleRange = mStyleRange; + fe->mStretchRange = mStretchRange; + fe->mFixedPitch = mFixedPitch; + return fe; +} + +CGFontRef MacOSFontEntry::GetFontRef() { + if (!mFontRefInitialized) { + // Cache the CGFontRef, to be released by our destructor. + mFontRef = CreateOrCopyFontRef(); + mFontRefInitialized = true; + } + // Return a non-retained reference; caller does not need to release. + return mFontRef; +} + +CGFontRef MacOSFontEntry::CreateOrCopyFontRef() { + if (mFontRef) { + // We have a cached CGFont, just add a reference. Caller must + // release, but we'll still own our reference. + ::CGFontRetain(mFontRef); + return mFontRef; + } + // Create a new CGFont; caller will own the only reference to it. + NSString* psname = GetNSStringForString(NS_ConvertUTF8toUTF16(mName)); + CGFontRef ref = CGFontCreateWithFontName(CFStringRef(psname)); + if (!ref) { + // This happens on macOS 10.12 for font entry names that start with + // .AppleSystemUIFont. For those fonts, we need to go through NSFont + // to get the correct CGFontRef. + // Both the Text and the Display variant of the display font use + // .AppleSystemUIFontSomethingSomething as their member names. + // That's why we're carrying along mSizeHint to this place so that + // we get the variant that we want for this family. + NSFont* font = [NSFont fontWithName:psname size:mSizeHint]; + if (font) { + ref = CTFontCopyGraphicsFont((CTFontRef)font, nullptr); + } + } + return ref; // Not saved in mFontRef; caller will own the reference +} + +// For a logging build, we wrap the CFDataRef in a FontTableRec so that we can +// use the MOZ_COUNT_[CD]TOR macros in it. A release build without logging +// does not get this overhead. +class FontTableRec { + public: + explicit FontTableRec(CFDataRef aDataRef) : mDataRef(aDataRef) { MOZ_COUNT_CTOR(FontTableRec); } + + ~FontTableRec() { + MOZ_COUNT_DTOR(FontTableRec); + ::CFRelease(mDataRef); + } + + private: + CFDataRef mDataRef; +}; + +/*static*/ void MacOSFontEntry::DestroyBlobFunc(void* aUserData) { +#ifdef NS_BUILD_REFCNT_LOGGING + FontTableRec* ftr = static_cast<FontTableRec*>(aUserData); + delete ftr; +#else + ::CFRelease((CFDataRef)aUserData); +#endif +} + +hb_blob_t* MacOSFontEntry::GetFontTable(uint32_t aTag) { + CGFontRef fontRef = CreateOrCopyFontRef(); + if (!fontRef) { + return nullptr; + } + + CFDataRef dataRef = ::CGFontCopyTableForTag(fontRef, aTag); + ::CGFontRelease(fontRef); + 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 = CreateOrCopyFontRef(); + if (!fontRef) { + return false; + } + CFArrayRef tags = ::CGFontCopyTableTags(fontRef); + ::CGFontRelease(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); +} + +static bool CheckForAATSmallCaps(CFArrayRef aFeatures) { + // Walk the array of feature descriptors from the font, and see whether + // a small-caps feature setting is available. + // Just bail out (returning false) if at any point we fail to find the + // expected dictionary keys, etc; if the font has bad data, we don't even + // try to search the rest of it. + auto numFeatures = CFArrayGetCount(aFeatures); + for (auto f = 0; f < numFeatures; ++f) { + auto featureDict = (CFDictionaryRef)CFArrayGetValueAtIndex(aFeatures, f); + if (!featureDict) { + return false; + } + auto featureNum = + (CFNumberRef)CFDictionaryGetValue(featureDict, CFSTR("CTFeatureTypeIdentifier")); + if (!featureNum) { + return false; + } + int16_t featureType; + if (!CFNumberGetValue(featureNum, kCFNumberSInt16Type, &featureType)) { + return false; + } + if (featureType == kLetterCaseType || featureType == kLowerCaseType) { + // Which selector to look for, depending whether we've found the + // legacy LetterCase feature or the new LowerCase one. + const uint16_t smallCaps = + (featureType == kLetterCaseType) ? kSmallCapsSelector : kLowerCaseSmallCapsSelector; + auto selectors = + (CFArrayRef)CFDictionaryGetValue(featureDict, CFSTR("CTFeatureTypeSelectors")); + if (!selectors) { + return false; + } + auto numSelectors = CFArrayGetCount(selectors); + for (auto s = 0; s < numSelectors; s++) { + auto selectorDict = (CFDictionaryRef)CFArrayGetValueAtIndex(selectors, s); + if (!selectorDict) { + return false; + } + auto selectorNum = + (CFNumberRef)CFDictionaryGetValue(selectorDict, CFSTR("CTFeatureSelectorIdentifier")); + if (!selectorNum) { + return false; + } + int16_t selectorValue; + if (!CFNumberGetValue(selectorNum, kCFNumberSInt16Type, &selectorValue)) { + return false; + } + if (selectorValue == smallCaps) { + return true; + } + } + } + } + return false; +} + +bool MacOSFontEntry::SupportsOpenTypeFeature(Script aScript, uint32_t aFeatureTag) { + // If we're going to shape with Core Text, we don't support added + // OpenType features (aside from any CT applies by default), except + // for 'smcp' which we map to an AAT feature selector. + if (RequiresAATLayout()) { + if (aFeatureTag != HB_TAG('s', 'm', 'c', 'p')) { + return false; + } + if (mHasAATSmallCapsInitialized) { + return mHasAATSmallCaps; + } + mHasAATSmallCapsInitialized = true; + CGFontRef cgFont = GetFontRef(); + if (!cgFont) { + return mHasAATSmallCaps; + } + CTFontRef ctFont = CTFontCreateWithGraphicsFont(cgFont, 0.0, nullptr, nullptr); + if (ctFont) { + CFArrayRef features = CTFontCopyFeatures(ctFont); + CFRelease(ctFont); + if (features) { + mHasAATSmallCaps = CheckForAATSmallCaps(features); + CFRelease(features); + } + } + return mHasAATSmallCaps; + } + return gfxFontEntry::SupportsOpenTypeFeature(aScript, aFeatureTag); +} + +void MacOSFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +/* gfxMacFontFamily */ +#pragma mark - + +class gfxMacFontFamily final : public gfxFontFamily { + public: + gfxMacFontFamily(const nsACString& aName, FontVisibility aVisibility, double aSizeHint) + : gfxFontFamily(aName, aVisibility), mSizeHint(aSizeHint) {} + + virtual ~gfxMacFontFamily() = default; + + virtual void LocalizedName(nsACString& aLocalizedName); + + virtual void FindStyleVariations(FontInfoData* aFontInfoData = nullptr); + + virtual bool IsSingleFaceFamily() const { return false; } + + protected: + double mSizeHint; +}; + +void gfxMacFontFamily::LocalizedName(nsACString& aLocalizedName) { + nsAutoreleasePool localPool; + + // It's unsafe to call HasOtherFamilyNames off the main thread because + // it entrains FindStyleVariations, which calls GetWeightOverride, which + // retrieves prefs. And the pref names can change (via user overrides), + // so we can't use StaticPrefs to access them. + if (NS_IsMainThread() && !HasOtherFamilyNames()) { + aLocalizedName = mName; + return; + } + + NSString* family = GetNSStringForString(NS_ConvertUTF8toUTF16(mName)); + NSString* localized = [sFontManager localizedNameForFamily:family face:nil]; + + if (localized) { + nsAutoString locName; + GetStringForNSString(localized, locName); + CopyUTF16toUTF8(locName, aLocalizedName); + return; + } + + // failed to get localized name, just use the canonical one + aLocalizedName = mName; +} + +// Return the CSS weight value to use for the given face, overriding what +// AppKit gives us (used to adjust families with bad weight values, see +// bug 931426). +// A return value of 0 indicates no override - use the existing weight. +static inline int GetWeightOverride(const nsAString& aPSName) { + nsAutoCString prefName("font.weight-override."); + // The PostScript name is required to be ASCII; if it's not, the font is + // broken anyway, so we really don't care that this is lossy. + LossyAppendUTF16toASCII(aPSName, prefName); + return Preferences::GetInt(prefName.get(), 0); +} + +void gfxMacFontFamily::FindStyleVariations(FontInfoData* aFontInfoData) { + if (mHasStyles) { + return; + } + + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("gfxMacFontFamily::FindStyleVariations", LAYOUT, mName); + + nsAutoreleasePool localPool; + + NSString* family = GetNSStringForString(NS_ConvertUTF8toUTF16(mName)); + + // create a font entry for each face + NSArray* fontfaces = [sFontManager + availableMembersOfFontFamily:family]; // returns an array of [psname, style name, weight, + // traits] elements, goofy api + int faceCount = [fontfaces count]; + int faceIndex; + + for (faceIndex = 0; faceIndex < faceCount; faceIndex++) { + NSArray* face = [fontfaces objectAtIndex:faceIndex]; + NSString* psname = [face objectAtIndex:INDEX_FONT_POSTSCRIPT_NAME]; + int32_t appKitWeight = [[face objectAtIndex:INDEX_FONT_WEIGHT] unsignedIntValue]; + uint32_t macTraits = [[face objectAtIndex:INDEX_FONT_TRAITS] unsignedIntValue]; + NSString* facename = [face objectAtIndex:INDEX_FONT_FACE_NAME]; + bool isStandardFace = false; + + if (appKitWeight == kAppleExtraLightWeight) { + // if the facename contains UltraLight, set the weight to the ultralight weight value + NSRange range = [facename rangeOfString:@"ultralight" options:NSCaseInsensitiveSearch]; + if (range.location != NSNotFound) { + appKitWeight = kAppleUltraLightWeight; + } + } + + // make a nsString + nsAutoString postscriptFontName; + GetStringForNSString(psname, postscriptFontName); + + int32_t cssWeight = 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(NS_ConvertUTF16toUTF8(postscriptFontName), + WeightRange(FontWeight(cssWeight)), isStandardFace, mSizeHint); + if (!fontEntry) { + break; + } + + // set additional properties based on the traits reported by Cocoa + if (macTraits & (NSCondensedFontMask | NSNarrowFontMask | NSCompressedFontMask)) { + fontEntry->mStretchRange = StretchRange(FontStretch::Condensed()); + } else if (macTraits & NSExpandedFontMask) { + fontEntry->mStretchRange = StretchRange(FontStretch::Expanded()); + } + // Cocoa fails to set the Italic traits bit for HelveticaLightItalic, + // at least (see bug 611855), so check for style name endings as well + if ((macTraits & NSItalicFontMask) || [facename hasSuffix:@"Italic"] || + [facename hasSuffix:@"Oblique"]) { + fontEntry->mStyleRange = SlantStyleRange(FontSlantStyle::Italic()); + } + if (macTraits & NSFixedPitchFontMask) { + fontEntry->mFixedPitch = true; + } + + if (gfxPlatform::GetPlatform()->HasVariationFontSupport()) { + fontEntry->SetupVariationRanges(); + } + + if (LOG_FONTLIST_ENABLED()) { + nsAutoCString weightString; + fontEntry->Weight().ToString(weightString); + nsAutoCString stretchString; + fontEntry->Stretch().ToString(stretchString); + LOG_FONTLIST(("(fontlist) added (%s) to family (%s)" + " with style: %s weight: %s stretch: %s" + " (apple-weight: %d macTraits: %8.8x)", + fontEntry->Name().get(), Name().get(), + fontEntry->IsItalic() ? "italic" : "normal", weightString.get(), + stretchString.get(), appKitWeight, macTraits)); + } + + // insert into font entry array of family + AddFontEntry(fontEntry); + } + + SortAvailableFonts(); + SetHasStyles(true); + + if (mIsBadUnderlineFamily) { + SetBadUnderlineFonts(); + } + + CheckForSimpleFamily(); +} + +/* gfxSingleFaceMacFontFamily */ +#pragma mark - + +class gfxSingleFaceMacFontFamily final : public gfxFontFamily { + public: + explicit gfxSingleFaceMacFontFamily(const nsACString& aName) + : gfxFontFamily(aName, FontVisibility::Unknown) { + mFaceNamesInitialized = true; // omit from face name lists + } + + virtual ~gfxSingleFaceMacFontFamily() = default; + + virtual void LocalizedName(nsACString& aLocalizedName); + + virtual void ReadOtherFamilyNames(gfxPlatformFontList* aPlatformFontList); + + virtual bool IsSingleFaceFamily() const { return true; } +}; + +void gfxSingleFaceMacFontFamily::LocalizedName(nsACString& aLocalizedName) { + nsAutoreleasePool localPool; + + if (!HasOtherFamilyNames()) { + aLocalizedName = mName; + return; + } + + gfxFontEntry* fe = mAvailableFonts[0]; + NSFont* font = [NSFont fontWithName:GetNSStringForString(NS_ConvertUTF8toUTF16(fe->Name())) + size:0.0]; + if (font) { + NSString* localized = [font displayName]; + if (localized) { + nsAutoString locName; + GetStringForNSString(localized, locName); + CopyUTF16toUTF8(locName, aLocalizedName); + return; + } + } + + // failed to get localized name, just use the canonical one + aLocalizedName = mName; +} + +void gfxSingleFaceMacFontFamily::ReadOtherFamilyNames(gfxPlatformFontList* aPlatformFontList) { + 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 - + +// A bunch of fonts for "additional language support" are shipped in a +// "Language Support" directory, and don't show up in the standard font +// list returned by CTFontManagerCopyAvailableFontFamilyNames unless +// we explicitly activate them. +#define LANG_FONTS_DIR "/Library/Application Support/Apple/Fonts/Language Support" + +gfxMacPlatformFontList::gfxMacPlatformFontList() + : gfxPlatformFontList(false), mDefaultFont(nullptr), mUseSizeSensitiveSystemFont(false) { + CheckFamilyList(kBaseFonts, ArrayLength(kBaseFonts)); + +#ifdef MOZ_BUNDLED_FONTS + // We activate bundled fonts if the pref is > 0 (on) or < 0 (auto), only an + // explicit value of 0 (off) will disable them. + if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) { + ActivateBundledFonts(); + } +#endif + + nsresult rv; + nsCOMPtr<nsIFile> langFonts(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = langFonts->InitWithNativePath(nsLiteralCString(LANG_FONTS_DIR)); + if (NS_SUCCEEDED(rv)) { + ActivateFontsFromDir(langFonts); + } + } + + // Only the parent process listens for OS font-changed notifications; + // after rebuilding its list, it will update the content processes. + if (XRE_IsParentProcess()) { + ::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 (XRE_IsParentProcess()) { + ::CFNotificationCenterRemoveObserver(::CFNotificationCenterGetLocalCenter(), this, + kCTFontManagerRegisteredFontsChangedNotification, 0); + } + + if (mDefaultFont) { + ::CFRelease(mDefaultFont); + } +} + +void gfxMacPlatformFontList::AddFamily(const nsACString& aFamilyName, FontVisibility aVisibility) { + double sizeHint = 0.0; + if (aVisibility == FontVisibility::Hidden && mUseSizeSensitiveSystemFont && + mSystemDisplayFontFamilyName.Equals(aFamilyName)) { + sizeHint = 128.0; + } + + nsAutoCString key; + ToLowerCase(aFamilyName, key); + + RefPtr<gfxFontFamily> familyEntry = new gfxMacFontFamily(aFamilyName, aVisibility, sizeHint); + mFontFamilies.Put(key, RefPtr{familyEntry}); + + // check the bad underline blocklist + if (mBadUnderlineFamilyNames.ContainsSorted(key)) { + familyEntry->SetBadUnderlineFamily(); + } +} + +FontVisibility gfxMacPlatformFontList::GetVisibilityForFamily(const nsACString& aName) const { + if (aName[0] == '.' || aName.LowerCaseEqualsLiteral("lastresort")) { + return FontVisibility::Hidden; + } + if (FamilyInList(aName, kBaseFonts, ArrayLength(kBaseFonts))) { + return FontVisibility::Base; + } + return FontVisibility::User; +} + +void gfxMacPlatformFontList::AddFamily(CFStringRef aFamily) { + NSString* family = (NSString*)aFamily; + + // CTFontManager includes weird internal family names and + // LastResort, skip over those + if (!family || [family caseInsensitiveCompare:@"LastResort"] == NSOrderedSame || + [family caseInsensitiveCompare:@".LastResort"] == NSOrderedSame) { + return; + } + + nsAutoString familyName; + nsCocoaUtils::GetStringForNSString(family, familyName); + + NS_ConvertUTF16toUTF8 nameUtf8(familyName); + AddFamily(nameUtf8, GetVisibilityForFamily(nameUtf8)); +} + +void gfxMacPlatformFontList::ReadSystemFontList(nsTArray<FontFamilyListEntry>* aList) { + // Note: We rely on the records for mSystemTextFontFamilyName and + // mSystemDisplayFontFamilyName (if present) being *before* the main + // font list, so that those names are known in the content process + // by the time we add the actual family records to the font list. + aList->AppendElement(FontFamilyListEntry(mSystemTextFontFamilyName, FontVisibility::Unknown, + kTextSizeSystemFontFamily)); + if (mUseSizeSensitiveSystemFont) { + aList->AppendElement(FontFamilyListEntry(mSystemDisplayFontFamilyName, FontVisibility::Unknown, + kDisplaySizeSystemFontFamily)); + } + // Now collect the list of available families, with visibility attributes. + for (auto f = mFontFamilies.Iter(); !f.Done(); f.Next()) { + auto macFamily = static_cast<gfxMacFontFamily*>(f.Data().get()); + if (macFamily->IsSingleFaceFamily()) { + continue; // skip, this will be recreated separately in the child + } + aList->AppendElement( + FontFamilyListEntry(macFamily->Name(), macFamily->Visibility(), kStandardFontFamily)); + } +} + +nsresult gfxMacPlatformFontList::InitFontListForPlatform() { + nsAutoreleasePool localPool; + + Telemetry::AutoTimer<Telemetry::MAC_INITFONTLIST_TOTAL> timer; + + InitSystemFontNames(); + + if (XRE_IsContentProcess()) { + // Content process: use font list passed from the chrome process via + // the GetXPCOMProcessAttributes message, because it's much faster than + // querying Core Text again in the child. + auto& fontList = dom::ContentChild::GetSingleton()->SystemFontList(); + for (FontFamilyListEntry& ffe : fontList) { + switch (ffe.entryType()) { + case kStandardFontFamily: + // On Catalina or later, we pre-initialize system font-family entries + // in InitSystemFontNames(), so we can just skip them here. + if (nsCocoaFeatures::OnCatalinaOrLater() && + (ffe.familyName() == mSystemTextFontFamilyName || + ffe.familyName() == mSystemDisplayFontFamilyName)) { + continue; + } + AddFamily(ffe.familyName(), ffe.visibility()); + break; + case kTextSizeSystemFontFamily: + mSystemTextFontFamilyName = ffe.familyName(); + break; + case kDisplaySizeSystemFontFamily: + mSystemDisplayFontFamilyName = ffe.familyName(); + mUseSizeSensitiveSystemFont = true; + break; + } + } + fontList.Clear(); + } else { + // We're not a content process, so get the available fonts directly + // from Core Text. + 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::InitSharedFontListForPlatform() { + nsAutoreleasePool localPool; + + InitSystemFontNames(); + if (XRE_IsParentProcess()) { + CFArrayRef familyNames = CTFontManagerCopyAvailableFontFamilyNames(); + nsTArray<fontlist::Family::InitData> families; + for (NSString* familyName in (NSArray*)familyNames) { + nsAutoString name16; + GetStringForNSString(familyName, name16); + NS_ConvertUTF16toUTF8 name(name16); + nsAutoCString key; + GenerateFontListKey(name, key); + families.AppendElement(fontlist::Family::InitData(key, name, fontlist::Family::kNoIndex, + GetVisibilityForFamily(name))); + } + CFRelease(familyNames); + SharedFontList()->SetFamilyNames(families); + InitAliasesForSingleFaceList(); + GetPrefsAndStartLoader(); + } +} + +void gfxMacPlatformFontList::InitAliasesForSingleFaceList() { + AutoTArray<nsCString, 10> singleFaceFonts; + gfxFontUtils::GetPrefsFontList("font.single-face-list", singleFaceFonts); + + for (auto& familyName : singleFaceFonts) { + LOG_FONTLIST(("(fontlist-singleface) face name: %s\n", familyName.get())); + // Each entry in the "single face families" list is expected to be a + // colon-separated pair of FaceName:Family, + // where FaceName is the individual face name (psname) of a font + // that should be exposed as a separate family name, + // and Family is the standard family to which that face belongs. + // The only such face listed by default is + // Osaka-Mono:Osaka + auto colon = familyName.FindChar(':'); + if (colon == kNotFound) { + continue; + } + + // Look for the parent family in the main font family list, + // and ensure we have loaded its list of available faces. + nsAutoCString key; + GenerateFontListKey(Substring(familyName, colon + 1), key); + fontlist::Family* family = SharedFontList()->FindFamily(key); + if (!family) { + // The parent family is not present, so just ignore this entry. + continue; + } + if (!family->IsInitialized()) { + if (!gfxPlatformFontList::InitializeFamily(family)) { + // This shouldn't ever fail, but if it does, we can safely ignore it. + MOZ_ASSERT(false, "failed to initialize font family"); + continue; + } + } + + // Truncate the entry from prefs at the colon, so now it is just the + // desired single-face-family name. + familyName.Truncate(colon); + + // Look through the family's faces to see if this one is present. + fontlist::FontList* list = SharedFontList(); + const fontlist::Pointer* facePtrs = family->Faces(list); + for (size_t i = 0; i < family->NumFaces(); i++) { + if (facePtrs[i].IsNull()) { + continue; + } + auto face = static_cast<const fontlist::Face*>(facePtrs[i].ToPtr(list)); + if (face->mDescriptor.AsString(list).Equals(familyName)) { + // Found it! Create an entry in the Alias table. + GenerateFontListKey(familyName, key); + if (SharedFontList()->FindFamily(key) || mAliasTable.Get(key)) { + // If the family name is already known, something's misconfigured; + // just ignore it. + MOZ_ASSERT(false, "single-face family already known"); + break; + } + auto aliasData = mAliasTable.LookupOrAdd(key); + // The "alias" here isn't based on an existing family, so we don't call + // aliasData->InitFromFamily(); the various flags are left as defaults. + aliasData->mFaces.AppendElement(facePtrs[i]); + aliasData->mBaseFamily = familyName; + break; + } + } + } +} + +void gfxMacPlatformFontList::InitSingleFaceList() { + AutoTArray<nsCString, 10> singleFaceFonts; + gfxFontUtils::GetPrefsFontList("font.single-face-list", singleFaceFonts); + + for (auto& familyName : singleFaceFonts) { + LOG_FONTLIST(("(fontlist-singleface) face name: %s\n", familyName.get())); + // Each entry in the "single face families" list is expected to be a + // colon-separated pair of FaceName:Family, + // where FaceName is the individual face name (psname) of a font + // that should be exposed as a separate family name, + // and Family is the standard family to which that face belongs. + // The only such face listed by default is + // Osaka-Mono:Osaka + auto colon = familyName.FindChar(':'); + if (colon == kNotFound) { + continue; + } + + // Look for the parent family in the main font family list, + // and ensure we have loaded its list of available faces. + nsAutoCString key(Substring(familyName, colon + 1)); + ToLowerCase(key); + gfxFontFamily* family = mFontFamilies.GetWeak(key); + if (!family || family->IsHidden()) { + continue; + } + family->FindStyleVariations(); + + // Truncate the entry from prefs at the colon, so now it is just the + // desired single-face-family name. + 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", familyName.get(), key.get())); + + // add only if doesn't exist already + if (!mFontFamilies.GetWeak(key)) { + RefPtr<gfxFontFamily> familyEntry = new gfxSingleFaceMacFontFamily(familyName); + // We need a separate font entry, because its family name will + // differ from the one we found in the main list. + MacOSFontEntry* fontEntry = new MacOSFontEntry( + fe->Name(), fe->Weight(), true, static_cast<const MacOSFontEntry*>(fe)->mSizeHint); + familyEntry->AddFontEntry(fontEntry); + familyEntry->SetHasStyles(true); + mFontFamilies.Put(key, std::move(familyEntry)); + LOG_FONTLIST( + ("(fontlist-singleface) added new family: %s, key: %s\n", familyName.get(), key.get())); + } + } +} + +// System fonts under OSX may contain weird "meta" names but if we create +// a new font using just the Postscript name, the NSFont api returns an object +// with the actual real family name. For example, under OSX 10.11: +// +// [[NSFont menuFontOfSize:8.0] familyName] ==> .AppleSystemUIFont +// [[NSFont fontWithName:[[[NSFont menuFontOfSize:8.0] fontDescriptor] postscriptName] +// size:8.0] familyName] ==> .SF NS Text + +static NSString* GetRealFamilyName(NSFont* aFont) { + NSString* psName = [[aFont fontDescriptor] postscriptName]; + // With newer macOS versions and SDKs (e.g. when compiled against SDK 10.15), + // [NSFont fontWithName:] fails for hidden system fonts, because the underlying + // Core Text functions it uses reject such names and tell us to use the special + // CTFontCreateUIFontForLanguage API instead. + // To work around this, as we don't yet work directly with the CTFontUIFontType + // identifiers, we create a Core Graphics font (as it doesn't reject system font + // names), and use this to create a Core Text font that we can query for the + // family name. + // Eventually we should move to using CTFontUIFontType constants to identify + // system fonts, and eliminate the need to instantiate them (indirectly) from + // their postscript names. + CGFontRef cgFont = CGFontCreateWithFontName(CFStringRef(psName)); + if (!cgFont) { + return [aFont familyName]; + } + + CTFontRef ctFont = CTFontCreateWithGraphicsFont(cgFont, 0.0, nullptr, nullptr); + CFRelease(cgFont); + if (!ctFont) { + return [aFont familyName]; + } + NSString* familyName = (NSString*)CTFontCopyFamilyName(ctFont); + CFRelease(ctFont); + + return [familyName autorelease]; +} + +// Create a gfxFontFamily that corresponds to the "system" font name, +// and populate it with the given font face. We only use this on Catalina or later, +// so we expect the system font to be a variable-weight face rather than requiring +// a number of discrete faces of different weights. +static gfxFontFamily* CreateFamilyForSystemFont(NSFont* aFont, const nsACString& aFamilyName) { + gfxFontFamily* familyEntry = new gfxFontFamily(aFamilyName, FontVisibility::Unknown); + + NSString* psNameNS = [[aFont fontDescriptor] postscriptName]; + nsAutoString nameUTF16; + nsAutoCString psName; + nsCocoaUtils::GetStringForNSString(psNameNS, nameUTF16); + CopyUTF16toUTF8(nameUTF16, psName); + + MacOSFontEntry* fe = new MacOSFontEntry(psName, WeightRange(FontWeight::Normal()), true, 0.0); + MOZ_ASSERT(gfxPlatform::GetPlatform()->HasVariationFontSupport()); + fe->SetupVariationRanges(); + + familyEntry->AddFontEntry(fe); + familyEntry->SetHasStyles(true); + + return familyEntry; +} + +// 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() { + mUseSizeSensitiveSystemFont = true; + + // text font family + NSFont* sys = [NSFont systemFontOfSize:0.0]; + NSString* textFamilyName = GetRealFamilyName(sys); + nsAutoString familyName; + nsCocoaUtils::GetStringForNSString(textFamilyName, familyName); + CopyUTF16toUTF8(familyName, mSystemTextFontFamilyName); + + // On Catalina or later, we store an in-process gfxFontFamily for the system font + // even if using the shared fontlist to manage "normal" fonts, because the hidden + // system fonts may be excluded from the font list altogether. + if (nsCocoaFeatures::OnCatalinaOrLater()) { + RefPtr<gfxFontFamily> fam = CreateFamilyForSystemFont(sys, mSystemTextFontFamilyName); + if (fam) { + nsAutoCString key; + GenerateFontListKey(mSystemTextFontFamilyName, key); + mFontFamilies.Put(key, std::move(fam)); + } + } + + // display font family, if on OSX 10.11 + if (mUseSizeSensitiveSystemFont) { + NSFont* displaySys = [NSFont systemFontOfSize:128.0]; + NSString* displayFamilyName = GetRealFamilyName(displaySys); + if ([displayFamilyName isEqualToString:textFamilyName]) { + mUseSizeSensitiveSystemFont = false; + } else { + nsCocoaUtils::GetStringForNSString(displayFamilyName, familyName); + CopyUTF16toUTF8(familyName, mSystemDisplayFontFamilyName); + if (nsCocoaFeatures::OnCatalinaOrLater()) { + // This will probably never be used, as Catalina has an optically-sized system font + // rather than separate text and display faces. + RefPtr<gfxFontFamily> fam = CreateFamilyForSystemFont(sys, mSystemDisplayFontFamilyName); + if (fam) { + nsAutoCString key; + GenerateFontListKey(mSystemDisplayFontFamilyName, key); + mFontFamilies.Put(key, std::move(fam)); + } + } + } + } + +#ifdef DEBUG + // different system font API's always map to the same family under OSX, so + // just assume that and emit a warning if that ever changes + NSString* sysFamily = GetRealFamilyName([NSFont systemFontOfSize:0.0]); + if ([sysFamily compare:GetRealFamilyName([NSFont boldSystemFontOfSize:0.0])] != NSOrderedSame || + [sysFamily compare:GetRealFamilyName([NSFont controlContentFontOfSize:0.0])] != + NSOrderedSame || + [sysFamily compare:GetRealFamilyName([NSFont menuBarFontOfSize:0.0])] != NSOrderedSame || + [sysFamily compare:GetRealFamilyName([NSFont toolTipsFontOfSize:0.0])] != NSOrderedSame) { + NS_WARNING("system font types map to different font families" + " -- please log a bug!!"); + } +#endif +} + +gfxFontFamily* gfxMacPlatformFontList::FindSystemFontFamily(const nsACString& aFamily) { + nsAutoCString key; + GenerateFontListKey(aFamily, key); + + gfxFontFamily* familyEntry; + if ((familyEntry = mFontFamilies.GetWeak(key))) { + return CheckFamily(familyEntry); + } + + return nullptr; +} + +void gfxMacPlatformFontList::RegisteredFontsChangedNotificationCallback( + CFNotificationCenterRef center, void* observer, CFStringRef name, const void* object, + CFDictionaryRef userInfo) { + if (!::CFEqual(name, kCTFontManagerRegisteredFontsChangedNotification)) { + return; + } + + gfxMacPlatformFontList* fl = static_cast<gfxMacPlatformFontList*>(observer); + + // 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(); + + dom::ContentParent::NotifyUpdatedFonts(true); +} + +gfxFontEntry* gfxMacPlatformFontList::PlatformGlobalFontFallback(const uint32_t aCh, + Script aRunScript, + const gfxFontStyle* aMatchStyle, + FontFamily& aMatchedFamily) { + CFStringRef str; + UniChar ch[2]; + CFIndex length = 1; + + if (IS_IN_BMP(aCh)) { + ch[0] = aCh; + str = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, ch, 1, kCFAllocatorNull); + } else { + ch[0] = H_SURROGATE(aCh); + ch[1] = L_SURROGATE(aCh); + str = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, ch, 2, kCFAllocatorNull); + if (!str) { + return nullptr; + } + length = 2; + } + + // use CoreText to find the fallback family + + gfxFontEntry* fontEntry = nullptr; + 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 && + ::CFStringCompare(familyNameRef, CFSTR(".LastResort"), kCFCompareCaseInsensitive) != + kCFCompareEqualTo) { + AutoTArray<UniChar, 1024> buffer; + CFIndex familyNameLen = ::CFStringGetLength(familyNameRef); + buffer.SetLength(familyNameLen + 1); + ::CFStringGetCharacters(familyNameRef, ::CFRangeMake(0, familyNameLen), buffer.Elements()); + buffer[familyNameLen] = 0; + NS_ConvertUTF16toUTF8 familyNameString(reinterpret_cast<char16_t*>(buffer.Elements()), + familyNameLen); + + if (SharedFontList()) { + fontlist::Family* family = FindSharedFamily(familyNameString); + if (family) { + fontlist::Face* face = family->FindFaceForStyle(SharedFontList(), *aMatchStyle); + if (face) { + fontEntry = GetOrCreateFontEntry(face, family); + } + if (fontEntry) { + if (fontEntry->HasCharacter(aCh)) { + aMatchedFamily = FontFamily(family); + } else { + fontEntry = nullptr; + cantUseFallbackFont = true; + } + } + } + } else { + gfxFontFamily* family = FindSystemFontFamily(familyNameString); + if (family) { + fontEntry = family->FindFontForStyle(*aMatchStyle); + if (fontEntry) { + if (fontEntry->HasCharacter(aCh)) { + aMatchedFamily = FontFamily(family); + } else { + fontEntry = nullptr; + cantUseFallbackFont = true; + } + } + } + } + } + + if (familyNameRef) { + ::CFRelease(familyNameRef); + } + } + + if (cantUseFallbackFont) { + Telemetry::Accumulate(Telemetry::BAD_FALLBACK_FONT, cantUseFallbackFont); + } + + ::CFRelease(str); + + return fontEntry; +} + +FontFamily gfxMacPlatformFontList::GetDefaultFontForPlatform(const gfxFontStyle* aStyle, + nsAtom* aLanguage) { + nsAutoreleasePool localPool; + + NSString* defaultFamily = [[NSFont userFontOfSize:aStyle->size] familyName]; + nsAutoString familyName; + + GetStringForNSString(defaultFamily, familyName); + return FindFamily(NS_ConvertUTF16toUTF8(familyName)); +} + +int32_t gfxMacPlatformFontList::AppleWeightToCSSWeight(int32_t aAppleWeight) { + if (aAppleWeight < 1) + aAppleWeight = 1; + else if (aAppleWeight > kAppleMaxWeight) + aAppleWeight = kAppleMaxWeight; + return gAppleWeightToCSSWeight[aAppleWeight]; +} + +gfxFontEntry* gfxMacPlatformFontList::LookupLocalFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) { + if (aFontName.IsEmpty() || aFontName[0] == '.') { + return nullptr; + } + + nsAutoreleasePool localPool; + + NSString* faceName = GetNSStringForString(NS_ConvertUTF8toUTF16(aFontName)); + MacOSFontEntry* newFontEntry; + + // lookup face based on postscript or full name + CGFontRef fontRef = ::CGFontCreateWithFontName(CFStringRef(faceName)); + if (!fontRef) { + return nullptr; + } + + newFontEntry = new MacOSFontEntry(aFontName, fontRef, aWeightForEntry, aStretchForEntry, + aStyleForEntry, false, true); + ::CFRelease(fontRef); + + return newFontEntry; +} + +static void ReleaseData(void* info, const void* data, size_t size) { free((void*)data); } + +gfxFontEntry* gfxMacPlatformFontList::MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, uint32_t aLength) { + NS_ASSERTION(aFontData, "MakePlatformFont called with null data"); + + // create the font entry + nsAutoString uniqueName; + + nsresult rv = gfxFontUtils::MakeUniqueUserFontName(uniqueName); + if (NS_FAILED(rv)) { + return nullptr; + } + + CGDataProviderRef provider = + ::CGDataProviderCreateWithData(nullptr, aFontData, aLength, &ReleaseData); + CGFontRef fontRef = ::CGFontCreateWithDataProvider(provider); + ::CGDataProviderRelease(provider); + + if (!fontRef) { + return nullptr; + } + + auto newFontEntry = + MakeUnique<MacOSFontEntry>(NS_ConvertUTF16toUTF8(uniqueName), fontRef, aWeightForEntry, + aStretchForEntry, aStyleForEntry, true, false); + ::CFRelease(fontRef); + return newFontEntry.release(); +} + +// Webkit code uses a system font meta name, so mimic that here +// WebCore/platform/graphics/mac/FontCacheMac.mm +static const char kSystemFont_system[] = "-apple-system"; + +bool gfxMacPlatformFontList::FindAndAddFamilies(mozilla::StyleGenericFontFamily aGeneric, + const nsACString& aFamily, + nsTArray<FamilyAndGeneric>* aOutput, + FindFamiliesFlags aFlags, gfxFontStyle* aStyle, + nsAtom* aLanguage, gfxFloat aDevToCssSize) { + if (aFamily.EqualsLiteral(kSystemFont_system)) { + // Search for special system font name, -apple-system. This is not done via + // the shared fontlist on Catalina or later, because the hidden system font + // may not be included there; we create a separate gfxFontFamily to manage + // this family. + const nsCString& systemFontFamilyName = + mUseSizeSensitiveSystemFont && aStyle && + (aStyle->size * aDevToCssSize) >= kTextDisplayCrossover + ? mSystemDisplayFontFamilyName + : mSystemTextFontFamilyName; + if (SharedFontList() && !nsCocoaFeatures::OnCatalinaOrLater()) { + FindFamiliesFlags flags = aFlags | FindFamiliesFlags::eSearchHiddenFamilies; + return gfxPlatformFontList::FindAndAddFamilies(aGeneric, systemFontFamilyName, aOutput, flags, + aStyle, aLanguage, aDevToCssSize); + } else { + if (auto* fam = FindSystemFontFamily(systemFontFamilyName)) { + aOutput->AppendElement(fam); + return true; + } + } + return false; + } + + return gfxPlatformFontList::FindAndAddFamilies(aGeneric, aFamily, aOutput, aFlags, aStyle, + aLanguage, aDevToCssSize); +} + +void gfxMacPlatformFontList::LookupSystemFont(LookAndFeel::FontID aSystemFontID, + nsACString& aSystemFontName, + gfxFontStyle& aFontStyle) { + // Provide a local pool because we may be called from stylo threads. + nsAutoreleasePool localPool; + + // code moved here from widget/cocoa/nsLookAndFeel.mm + NSFont* font = nullptr; + char* systemFontName = nullptr; + switch (aSystemFontID) { + case LookAndFeel::FontID::MessageBox: + case LookAndFeel::FontID::StatusBar: + case LookAndFeel::FontID::List: + case LookAndFeel::FontID::Field: + case LookAndFeel::FontID::Button: + case LookAndFeel::FontID::Widget: + font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; + systemFontName = (char*)kSystemFont_system; + break; + + case LookAndFeel::FontID::SmallCaption: + font = [NSFont boldSystemFontOfSize:[NSFont smallSystemFontSize]]; + systemFontName = (char*)kSystemFont_system; + break; + + case LookAndFeel::FontID::Icon: // used in urlbar; tried labelFont, but too small + case LookAndFeel::FontID::Workspace: + case LookAndFeel::FontID::Desktop: + case LookAndFeel::FontID::Info: + font = [NSFont controlContentFontOfSize:0.0]; + systemFontName = (char*)kSystemFont_system; + break; + + case LookAndFeel::FontID::PullDownMenu: + font = [NSFont menuBarFontOfSize:0.0]; + systemFontName = (char*)kSystemFont_system; + break; + + case LookAndFeel::FontID::Tooltips: + font = [NSFont toolTipsFontOfSize:0.0]; + systemFontName = (char*)kSystemFont_system; + break; + + case LookAndFeel::FontID::Caption: + case LookAndFeel::FontID::Menu: + case LookAndFeel::FontID::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) ? FontSlantStyle::Italic() : FontSlantStyle::Normal(); + aFontStyle.weight = (traits & NSFontBoldTrait) ? FontWeight::Bold() : FontWeight::Normal(); + aFontStyle.stretch = (traits & NSFontExpandedTrait) ? FontStretch::Expanded() + : (traits & NSFontCondensedTrait) ? FontStretch::Condensed() + : FontStretch::Normal(); + aFontStyle.size = [font pointSize]; + aFontStyle.systemFont = true; +} + +// used to load system-wide font info on off-main thread +class MacFontInfo final : public FontInfoData { + public: + MacFontInfo(bool aLoadOtherNames, bool aLoadFaceNames, bool aLoadCmaps) + : FontInfoData(aLoadOtherNames, aLoadFaceNames, aLoadCmaps) {} + + virtual ~MacFontInfo() = default; + + virtual void Load() { + nsAutoreleasePool localPool; + FontInfoData::Load(); + } + + // loads font data for all members of a given family + virtual void LoadFontFamilyData(const nsACString& aFamilyName); +}; + +void MacFontInfo::LoadFontFamilyData(const nsACString& aFamilyName) { + // family name ==> CTFontDescriptor + NSString* famName = GetNSStringForString(NS_ConvertUTF8toUTF16(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<nsCString> otherFamilyNames; + bool hasOtherFamilyNames = true; + + // iterate over faces in the family + int f, numFaces = (int)CFArrayGetCount(matchingFonts); + for (f = 0; f < numFaces; f++) { + mLoadStats.fonts++; + + CTFontDescriptorRef faceDesc = (CTFontDescriptorRef)CFArrayGetValueAtIndex(matchingFonts, f); + if (!faceDesc) { + continue; + } + CTFontRef fontRef = CTFontCreateWithFontDescriptor(faceDesc, 0.0, nullptr); + if (!fontRef) { + NS_WARNING("failed to create a CTFontRef"); + continue; + } + + if (mLoadCmaps) { + // face name + CFStringRef faceName = + (CFStringRef)CTFontDescriptorCopyAttribute(faceDesc, kCTFontNameAttribute); + + AutoTArray<UniChar, 1024> buffer; + CFIndex len = CFStringGetLength(faceName); + buffer.SetLength(len + 1); + CFStringGetCharacters(faceName, ::CFRangeMake(0, len), buffer.Elements()); + buffer[len] = 0; + NS_ConvertUTF16toUTF8 fontName(reinterpret_cast<char16_t*>(buffer.Elements()), len); + + // load the cmap data + FontFaceData fontData; + CFDataRef cmapTable = CTFontCopyTable(fontRef, kCTFontTableCmap, kCTFontTableOptionNoOptions); + + if (cmapTable) { + const uint8_t* cmapData = (const uint8_t*)CFDataGetBytePtr(cmapTable); + uint32_t cmapLen = CFDataGetLength(cmapTable); + RefPtr<gfxCharacterMap> charmap = new gfxCharacterMap(); + uint32_t offset; + nsresult rv; + + rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, *charmap, offset); + if (NS_SUCCEEDED(rv)) { + fontData.mCharacterMap = charmap; + fontData.mUVSOffset = offset; + 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); + gfxFontUtils::ReadOtherFamilyNamesForFace(aFamilyName, nameData, nameLen, otherFamilyNames, + false); + hasOtherFamilyNames = otherFamilyNames.Length() != 0; + CFRelease(nameTable); + } + } + + CFRelease(fontRef); + } + CFRelease(matchingFonts); + + // if found other names, insert them in the hash table + if (otherFamilyNames.Length() != 0) { + mOtherFamilyNames.Put(aFamilyName, otherFamilyNames); + mLoadStats.othernames += otherFamilyNames.Length(); + } +} + +already_AddRefed<FontInfoData> gfxMacPlatformFontList::CreateFontInfoData() { + bool loadCmaps = + !UsesSystemFallback() || gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback(); + + RefPtr<MacFontInfo> fi = new MacFontInfo(true, NeedFullnamePostscriptNames(), loadCmaps); + return fi.forget(); +} + +gfxFontFamily* gfxMacPlatformFontList::CreateFontFamily(const nsACString& aName, + FontVisibility aVisibility) const { + return new gfxMacFontFamily(aName, aVisibility, 0.0); +} + +gfxFontEntry* gfxMacPlatformFontList::CreateFontEntry(fontlist::Face* aFace, + const fontlist::Family* aFamily) { + MacOSFontEntry* fe = + new MacOSFontEntry(aFace->mDescriptor.AsString(SharedFontList()), aFace->mWeight, false, + 0.0); // XXX standardFace, sizeHint + fe->InitializeFrom(aFace, aFamily); + return fe; +} + +void gfxMacPlatformFontList::ActivateFontsFromDir(nsIFile* aDir) { + bool isDir; + if (NS_FAILED(aDir->IsDirectory(&isDir)) || !isDir) { + return; + } + + nsCOMPtr<nsIDirectoryEnumerator> e; + if (NS_FAILED(aDir->GetDirectoryEntries(getter_AddRefs(e)))) { + return; + } + + CFMutableArrayRef urls = ::CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + + bool hasMore; + while (NS_SUCCEEDED(e->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr<nsISupports> entry; + if (NS_FAILED(e->GetNext(getter_AddRefs(entry)))) { + break; + } + nsCOMPtr<nsIFile> file = do_QueryInterface(entry); + if (!file) { + continue; + } + nsCString path; + if (NS_FAILED(file->GetNativePath(path))) { + continue; + } + CFURLRef fontURL = ::CFURLCreateFromFileSystemRepresentation( + kCFAllocatorDefault, (uint8_t*)path.get(), path.Length(), false); + if (fontURL) { + ::CFArrayAppendValue(urls, fontURL); + ::CFRelease(fontURL); + } + } + + ::CTFontManagerRegisterFontsForURLs(urls, kCTFontManagerScopeProcess, nullptr); + ::CFRelease(urls); +} + +void gfxMacPlatformFontList::GetFacesInitDataForFamily(const fontlist::Family* aFamily, + nsTArray<fontlist::Face::InitData>& aFaces, + bool aLoadCmaps) const { + nsAutoreleasePool localPool; + + NS_ConvertUTF8toUTF16 name(aFamily->Key().AsString(SharedFontList())); + NSString* family = GetNSStringForString(name); + + // returns an array of [psname, style name, weight, traits] elements, goofy api + NSArray* fontfaces = [sFontManager availableMembersOfFontFamily:family]; + int faceCount = [fontfaces count]; + for (int faceIndex = 0; faceIndex < faceCount; faceIndex++) { + NSArray* face = [fontfaces objectAtIndex:faceIndex]; + NSString* psname = [face objectAtIndex:INDEX_FONT_POSTSCRIPT_NAME]; + int32_t appKitWeight = [[face objectAtIndex:INDEX_FONT_WEIGHT] unsignedIntValue]; + uint32_t macTraits = [[face objectAtIndex:INDEX_FONT_TRAITS] unsignedIntValue]; + NSString* facename = [face objectAtIndex:INDEX_FONT_FACE_NAME]; + 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; + } + + StretchRange stretch(FontStretch::Normal()); + if (macTraits & (NSCondensedFontMask | NSNarrowFontMask | NSCompressedFontMask)) { + stretch = StretchRange(FontStretch::Condensed()); + } else if (macTraits & NSExpandedFontMask) { + stretch = StretchRange(FontStretch::Expanded()); + } + // Cocoa fails to set the Italic traits bit for HelveticaLightItalic, + // at least (see bug 611855), so check for style name endings as well + SlantStyleRange slantStyle(FontSlantStyle::Normal()); + if ((macTraits & NSItalicFontMask) || [facename hasSuffix:@"Italic"] || + [facename hasSuffix:@"Oblique"]) { + slantStyle = SlantStyleRange(FontSlantStyle::Italic()); + } + + bool fixedPitch = (macTraits & NSFixedPitchFontMask) ? true : false; + + RefPtr<gfxCharacterMap> charmap; + if (aLoadCmaps) { + CGFontRef font = CGFontCreateWithFontName(CFStringRef(psname)); + if (font) { + uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p'); + CFDataRef data = CGFontCopyTableForTag(font, kCMAP); + if (data) { + uint32_t offset; + charmap = new gfxCharacterMap(); + gfxFontUtils::ReadCMAP(CFDataGetBytePtr(data), CFDataGetLength(data), *charmap, offset); + CFRelease(data); + } + CGFontRelease(font); + } + } + + aFaces.AppendElement(fontlist::Face::InitData{ + NS_ConvertUTF16toUTF8(postscriptFontName), + 0, + fixedPitch, + WeightRange(FontWeight(cssWeight)), + stretch, + slantStyle, + charmap, + }); + } +} + +void gfxMacPlatformFontList::ReadFaceNamesForFamily(fontlist::Family* aFamily, + bool aNeedFullnamePostscriptNames) { + if (!aFamily->IsInitialized()) { + if (!InitializeFamily(aFamily)) { + return; + } + } + const uint32_t kNAME = TRUETYPE_TAG('n', 'a', 'm', 'e'); + fontlist::FontList* list = SharedFontList(); + nsAutoCString canonicalName(aFamily->DisplayName().AsString(list)); + const fontlist::Pointer* facePtrs = aFamily->Faces(list); + for (uint32_t i = 0, n = aFamily->NumFaces(); i < n; i++) { + auto face = static_cast<fontlist::Face*>(facePtrs[i].ToPtr(list)); + if (!face) { + continue; + } + nsAutoCString name(face->mDescriptor.AsString(list)); + // We create a temporary MacOSFontEntry just to read family names from the + // 'name' table in the font resource. The style attributes here are ignored + // as this entry is not used for font style matching. + // The size hint might be used to select which face is accessed in the case + // of the macOS UI font; see MacOSFontEntry::GetFontRef(). We pass 16.0 in + // order to get a standard text-size face in this case, although it's + // unlikely to matter for the purpose of just reading family names. + auto fe = MakeUnique<MacOSFontEntry>(name, WeightRange(FontWeight::Normal()), false, 16.0); + if (!fe) { + continue; + } + gfxFontEntry::AutoTable nameTable(fe.get(), kNAME); + if (!nameTable) { + continue; + } + uint32_t dataLength; + const char* nameData = hb_blob_get_data(nameTable, &dataLength); + AutoTArray<nsCString, 4> otherFamilyNames; + gfxFontUtils::ReadOtherFamilyNamesForFace(canonicalName, nameData, dataLength, otherFamilyNames, + false); + for (const auto& alias : otherFamilyNames) { + nsAutoCString key; + GenerateFontListKey(alias, key); + auto aliasData = mAliasTable.LookupOrAdd(key); + aliasData->InitFromFamily(aFamily, canonicalName); + aliasData->mFaces.AppendElement(facePtrs[i]); + } + } +} + +#ifdef MOZ_BUNDLED_FONTS + +void gfxMacPlatformFontList::ActivateBundledFonts() { + nsCOMPtr<nsIFile> localDir; + if (NS_FAILED(NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(localDir)))) { + return; + } + if (NS_FAILED(localDir->Append(u"fonts"_ns))) { + return; + } + + ActivateFontsFromDir(localDir); +} + +#endif diff --git a/gfx/thebes/gfxMathTable.cpp b/gfx/thebes/gfxMathTable.cpp new file mode 100644 index 0000000000..6a27efff21 --- /dev/null +++ b/gfx/thebes/gfxMathTable.cpp @@ -0,0 +1,200 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxMathTable.h" + +#include "harfbuzz/hb.h" +#include "harfbuzz/hb-ot.h" + +#define FloatToFixed(f) (65536 * (f)) +#define FixedToFloat(f) ((f) * (1.0 / 65536.0)) + +using namespace mozilla; + +gfxMathTable::gfxMathTable(hb_face_t* aFace, gfxFloat aSize) { + mMathVariantCache.vertical = false; + mHBFont = hb_font_create(aFace); + if (mHBFont) { + hb_font_set_ppem(mHBFont, aSize, aSize); + uint32_t scale = FloatToFixed(aSize); + hb_font_set_scale(mHBFont, scale, scale); + } + + mMathVariantCache.glyphID = 0; + ClearCache(); +} + +gfxMathTable::~gfxMathTable() { + if (mHBFont) { + hb_font_destroy(mHBFont); + } +} + +gfxFloat gfxMathTable::Constant(MathConstant aConstant) const { + int32_t value = hb_ot_math_get_constant( + mHBFont, static_cast<hb_ot_math_constant_t>(aConstant)); + if (aConstant == ScriptPercentScaleDown || + aConstant == ScriptScriptPercentScaleDown || + aConstant == RadicalDegreeBottomRaisePercent) { + return value / 100.0; + } + return FixedToFloat(value); +} + +gfxFloat gfxMathTable::ItalicsCorrection(uint32_t aGlyphID) const { + return FixedToFloat( + hb_ot_math_get_glyph_italics_correction(mHBFont, aGlyphID)); +} + +uint32_t gfxMathTable::VariantsSize(uint32_t aGlyphID, bool aVertical, + uint16_t aSize) const { + UpdateMathVariantCache(aGlyphID, aVertical); + if (aSize < kMaxCachedSizeCount) { + return mMathVariantCache.sizes[aSize]; + } + + // If the size index exceeds the cache size, we just read the value with + // hb_ot_math_get_glyph_variants. + hb_direction_t direction = aVertical ? HB_DIRECTION_BTT : HB_DIRECTION_LTR; + hb_ot_math_glyph_variant_t variant; + unsigned int count = 1; + hb_ot_math_get_glyph_variants(mHBFont, aGlyphID, direction, aSize, &count, + &variant); + return count > 0 ? variant.glyph : 0; +} + +bool gfxMathTable::VariantsParts(uint32_t aGlyphID, bool aVertical, + uint32_t aGlyphs[4]) const { + UpdateMathVariantCache(aGlyphID, aVertical); + memcpy(aGlyphs, mMathVariantCache.parts, sizeof(mMathVariantCache.parts)); + return mMathVariantCache.arePartsValid; +} + +void gfxMathTable::ClearCache() const { + memset(mMathVariantCache.sizes, 0, sizeof(mMathVariantCache.sizes)); + memset(mMathVariantCache.parts, 0, sizeof(mMathVariantCache.parts)); + mMathVariantCache.arePartsValid = false; +} + +void gfxMathTable::UpdateMathVariantCache(uint32_t aGlyphID, + bool aVertical) const { + if (aGlyphID == mMathVariantCache.glyphID && + aVertical == mMathVariantCache.vertical) + return; + + mMathVariantCache.glyphID = aGlyphID; + mMathVariantCache.vertical = aVertical; + ClearCache(); + + // Cache the first size variants. + hb_direction_t direction = aVertical ? HB_DIRECTION_BTT : HB_DIRECTION_LTR; + hb_ot_math_glyph_variant_t variant[kMaxCachedSizeCount]; + unsigned int count = kMaxCachedSizeCount; + hb_ot_math_get_glyph_variants(mHBFont, aGlyphID, direction, 0, &count, + variant); + for (unsigned int i = 0; i < count; i++) { + mMathVariantCache.sizes[i] = variant[i].glyph; + } + + // Try and cache the parts of the glyph assembly. + // XXXfredw The structure of the Open Type Math table is a bit more general + // than the one currently used by the nsMathMLChar code, so we try to fallback + // in reasonable way. We use the approach of the copyComponents function in + // github.com/mathjax/MathJax-dev/blob/master/fonts/OpenTypeMath/fontUtil.py + // + // The nsMathMLChar code can use at most 3 non extender pieces (aGlyphs[0], + // aGlyphs[1] and aGlyphs[2]) and the extenders between these pieces should + // all be the same (aGlyphs[4]). Also, the parts of vertical assembly are + // stored from bottom to top in the Open Type MATH table while they are + // stored from top to bottom in nsMathMLChar. + + hb_ot_math_glyph_part_t parts[5]; + count = MOZ_ARRAY_LENGTH(parts); + unsigned int offset = 0; + if (hb_ot_math_get_glyph_assembly(mHBFont, aGlyphID, direction, offset, + &count, parts, + NULL) > MOZ_ARRAY_LENGTH(parts)) + return; // Not supported: Too many pieces. + if (count <= 0) return; // Not supported: No pieces. + + // Count the number of non extender pieces + uint16_t nonExtenderCount = 0; + for (uint16_t i = 0; i < count; i++) { + if (!(parts[i].flags & HB_MATH_GLYPH_PART_FLAG_EXTENDER)) { + nonExtenderCount++; + } + } + if (nonExtenderCount > 3) { + // Not supported: too many pieces + return; + } + + // Now browse the list of pieces + + // 0 = look for a left/bottom glyph + // 1 = look for an extender between left/bottom and mid + // 2 = look for a middle glyph + // 3 = look for an extender between middle and right/top + // 4 = look for a right/top glyph + // 5 = no more piece expected + uint8_t state = 0; + + // First extender char found. + uint32_t extenderChar = 0; + + for (uint16_t i = 0; i < count; i++) { + bool isExtender = parts[i].flags & HB_MATH_GLYPH_PART_FLAG_EXTENDER; + uint32_t glyph = parts[i].glyph; + + if ((state == 1 || state == 2) && nonExtenderCount < 3) { + // do not try to find a middle glyph + state += 2; + } + + if (isExtender) { + if (!extenderChar) { + extenderChar = glyph; + mMathVariantCache.parts[3] = extenderChar; + } else if (extenderChar != glyph) { + // Not supported: different extenders + return; + } + + if (state == 0) { // or state == 1 + // ignore left/bottom piece and multiple successive extenders + state = 1; + } else if (state == 2) { // or state == 3 + // ignore middle piece and multiple successive extenders + state = 3; + } else if (state >= 4) { + // Not supported: unexpected extender + return; + } + + continue; + } + + if (state == 0) { + // copy left/bottom part + mMathVariantCache.parts[aVertical ? 2 : 0] = glyph; + state = 1; + continue; + } + + if (state == 1 || state == 2) { + // copy middle part + mMathVariantCache.parts[1] = glyph; + state = 3; + continue; + } + + if (state == 3 || state == 4) { + // copy right/top part + mMathVariantCache.parts[aVertical ? 0 : 2] = glyph; + state = 5; + } + } + + mMathVariantCache.arePartsValid = true; +} diff --git a/gfx/thebes/gfxMathTable.h b/gfx/thebes/gfxMathTable.h new file mode 100644 index 0000000000..bb7595c36d --- /dev/null +++ b/gfx/thebes/gfxMathTable.h @@ -0,0 +1,152 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_MATH_TABLE_H +#define GFX_MATH_TABLE_H + +#include "gfxFont.h" + +/** + * Used by |gfxFont| to represent the MATH table of an OpenType font. + * Each |gfxFont| owns at most one |gfxMathTable| instance. + */ +class gfxMathTable { + public: + /** + * @param aFace The HarfBuzz face containing the math table. + * @param aSize The font size to pass to HarfBuzz. + */ + gfxMathTable(hb_face_t* aFace, gfxFloat aSize); + + /** + * Releases our reference to the MATH table and cleans up everything else. + */ + ~gfxMathTable(); + + enum MathConstant { + // The order of the constants must match the order of the fields + // defined in the MATH table. + ScriptPercentScaleDown, + ScriptScriptPercentScaleDown, + DelimitedSubFormulaMinHeight, + DisplayOperatorMinHeight, + MathLeading, + AxisHeight, + AccentBaseHeight, + FlattenedAccentBaseHeight, + SubscriptShiftDown, + SubscriptTopMax, + SubscriptBaselineDropMin, + SuperscriptShiftUp, + SuperscriptShiftUpCramped, + SuperscriptBottomMin, + SuperscriptBaselineDropMax, + SubSuperscriptGapMin, + SuperscriptBottomMaxWithSubscript, + SpaceAfterScript, + UpperLimitGapMin, + UpperLimitBaselineRiseMin, + LowerLimitGapMin, + LowerLimitBaselineDropMin, + StackTopShiftUp, + StackTopDisplayStyleShiftUp, + StackBottomShiftDown, + StackBottomDisplayStyleShiftDown, + StackGapMin, + StackDisplayStyleGapMin, + StretchStackTopShiftUp, + StretchStackBottomShiftDown, + StretchStackGapAboveMin, + StretchStackGapBelowMin, + FractionNumeratorShiftUp, + FractionNumeratorDisplayStyleShiftUp, + FractionDenominatorShiftDown, + FractionDenominatorDisplayStyleShiftDown, + FractionNumeratorGapMin, + FractionNumDisplayStyleGapMin, + FractionRuleThickness, + FractionDenominatorGapMin, + FractionDenomDisplayStyleGapMin, + SkewedFractionHorizontalGap, + SkewedFractionVerticalGap, + OverbarVerticalGap, + OverbarRuleThickness, + OverbarExtraAscender, + UnderbarVerticalGap, + UnderbarRuleThickness, + UnderbarExtraDescender, + RadicalVerticalGap, + RadicalDisplayStyleVerticalGap, + RadicalRuleThickness, + RadicalExtraAscender, + RadicalKernBeforeDegree, + RadicalKernAfterDegree, + RadicalDegreeBottomRaisePercent + }; + + /** + * Returns the value of the specified constant from the MATH table. + */ + gfxFloat Constant(MathConstant aConstant) const; + + /** + * Returns the value of the specified constant in app units. + */ + nscoord Constant(MathConstant aConstant, + uint32_t aAppUnitsPerDevPixel) const { + return NSToCoordRound(Constant(aConstant) * aAppUnitsPerDevPixel); + } + + /** + * If the MATH table contains an italic correction for that glyph, this + * function returns the corresponding value. Otherwise it returns 0. + */ + gfxFloat ItalicsCorrection(uint32_t aGlyphID) const; + + /** + * @param aGlyphID glyph index of the character we want to stretch + * @param aVertical direction of the stretching (vertical/horizontal) + * @param aSize the desired size variant + * + * Returns the glyph index of the desired size variant or 0 if there is not + * any such size variant. + */ + uint32_t VariantsSize(uint32_t aGlyphID, bool aVertical, + uint16_t aSize) const; + + /** + * @param aGlyphID glyph index of the character we want to stretch + * @param aVertical direction of the stretching (vertical/horizontal) + * @param aGlyphs pre-allocated buffer of 4 elements where the glyph + * indexes (or 0 for absent parts) will be stored. The parts are stored in + * the order expected by the nsMathMLChar: Top (or Left), Middle, Bottom + * (or Right), Glue. + * + * Tries to fill-in aGlyphs with the relevant glyph indexes and returns + * whether the operation was successful. The function returns false if + * there is not any assembly for the character we want to stretch or if + * the format is not supported by the nsMathMLChar code. + * + */ + bool VariantsParts(uint32_t aGlyphID, bool aVertical, + uint32_t aGlyphs[4]) const; + + private: + // size-specific font object, owned by the gfxMathTable + hb_font_t* mHBFont; + + static const unsigned int kMaxCachedSizeCount = 10; + struct MathVariantCacheEntry { + uint32_t glyphID; + bool vertical; + uint32_t sizes[kMaxCachedSizeCount]; + uint32_t parts[4]; + bool arePartsValid; + }; + mutable MathVariantCacheEntry mMathVariantCache; + void ClearCache() const; + void UpdateMathVariantCache(uint32_t aGlyphID, bool aVertical) const; +}; + +#endif diff --git a/gfx/thebes/gfxMatrix.h b/gfx/thebes/gfxMatrix.h new file mode 100644 index 0000000000..7ff90c8f39 --- /dev/null +++ b/gfx/thebes/gfxMatrix.h @@ -0,0 +1,13 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_MATRIX_H +#define GFX_MATRIX_H + +#include "mozilla/gfx/MatrixFwd.h" + +typedef mozilla::gfx::MatrixDouble gfxMatrix; + +#endif /* GFX_MATRIX_H */ diff --git a/gfx/thebes/gfxOTSUtils.h b/gfx/thebes/gfxOTSUtils.h new file mode 100644 index 0000000000..ec402c303b --- /dev/null +++ b/gfx/thebes/gfxOTSUtils.h @@ -0,0 +1,164 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_OTS_UTILS_H +#define GFX_OTS_UTILS_H + +#include "gfxFontUtils.h" + +#include "opentype-sanitiser.h" + +struct gfxOTSMozAlloc { + void* Grow(void* aPtr, size_t aLength) { return moz_xrealloc(aPtr, aLength); } + void* ShrinkToFit(void* aPtr, size_t aLength) { + return moz_xrealloc(aPtr, aLength); + } + void Free(void* aPtr) { free(aPtr); } +}; + +// Based on ots::ExpandingMemoryStream from ots-memory-stream.h, +// adapted to use Mozilla allocators and to allow the final +// memory buffer to be adopted by the client. +template <typename AllocT = gfxOTSMozAlloc> +class gfxOTSExpandingMemoryStream : public ots::OTSStream { + public: + // limit output/expansion to 256MB by default + enum { DEFAULT_LIMIT = 256 * 1024 * 1024 }; + + explicit gfxOTSExpandingMemoryStream(size_t initial, + size_t limit = DEFAULT_LIMIT) + : mLength(initial), mLimit(limit), mOff(0) { + mPtr = mAlloc.Grow(nullptr, mLength); + } + + ~gfxOTSExpandingMemoryStream() { mAlloc.Free(mPtr); } + + // Return the buffer, resized to fit its contents (as it may have been + // over-allocated during growth), and give up ownership of it so the + // caller becomes responsible to call free() when finished with it. + auto forget() { + auto p = mAlloc.ShrinkToFit(mPtr, mOff); + mPtr = nullptr; + return p; + } + + bool WriteRaw(const void* data, size_t length) override { + if ((mOff + length > mLength) || + (mLength > std::numeric_limits<size_t>::max() - mOff)) { + if (mLength == mLimit) { + return false; + } + size_t newLength = (mLength + 1) * 2; + if (newLength < mLength) { + return false; + } + if (newLength > mLimit) { + newLength = mLimit; + } + mPtr = mAlloc.Grow(mPtr, newLength); + mLength = newLength; + return WriteRaw(data, length); + } + std::memcpy(static_cast<char*>(mPtr) + mOff, data, length); + mOff += length; + return true; + } + + bool Seek(off_t position) override { + if (position < 0) { + return false; + } + if (static_cast<size_t>(position) > mLength) { + return false; + } + mOff = position; + return true; + } + + off_t Tell() const override { return mOff; } + + private: + AllocT mAlloc; + void* mPtr; + size_t mLength; + const size_t mLimit; + off_t mOff; +}; + +class MOZ_STACK_CLASS gfxOTSContext : public ots::OTSContext { + public: + gfxOTSContext() { + using namespace mozilla; + + // Whether to apply OTS validation to OpenType Layout tables + mCheckOTLTables = StaticPrefs::gfx_downloadable_fonts_otl_validation(); + // Whether to preserve Variation tables in downloaded fonts + mCheckVariationTables = + StaticPrefs::gfx_downloadable_fonts_validate_variation_tables(); + // Whether to preserve color bitmap glyphs + mKeepColorBitmaps = + StaticPrefs::gfx_downloadable_fonts_keep_color_bitmaps(); + } + + virtual ots::TableAction GetTableAction(uint32_t aTag) override { + // Preserve Graphite, color glyph and SVG tables, + // and possibly OTL and Variation tables (depending on prefs) + if ((!mCheckOTLTables && (aTag == TRUETYPE_TAG('G', 'D', 'E', 'F') || + aTag == TRUETYPE_TAG('G', 'P', 'O', 'S') || + aTag == TRUETYPE_TAG('G', 'S', 'U', 'B'))) || + (!mCheckVariationTables && + (aTag == TRUETYPE_TAG('a', 'v', 'a', 'r') || + aTag == TRUETYPE_TAG('c', 'v', 'a', 'r') || + aTag == TRUETYPE_TAG('f', 'v', 'a', 'r') || + aTag == TRUETYPE_TAG('g', 'v', 'a', 'r') || + aTag == TRUETYPE_TAG('H', 'V', 'A', 'R') || + aTag == TRUETYPE_TAG('M', 'V', 'A', 'R') || + aTag == TRUETYPE_TAG('S', 'T', 'A', 'T') || + aTag == TRUETYPE_TAG('V', 'V', 'A', 'R'))) || + aTag == TRUETYPE_TAG('S', 'V', 'G', ' ') || + aTag == TRUETYPE_TAG('C', 'O', 'L', 'R') || + aTag == TRUETYPE_TAG('C', 'P', 'A', 'L') || + (mKeepColorBitmaps && (aTag == TRUETYPE_TAG('C', 'B', 'D', 'T') || + aTag == TRUETYPE_TAG('C', 'B', 'L', 'C'))) || + false) { + return ots::TABLE_ACTION_PASSTHRU; + } + return ots::TABLE_ACTION_DEFAULT; + } + + static size_t GuessSanitizedFontSize(size_t aLength, + gfxUserFontType aFontType, + bool aStrict = true) { + switch (aFontType) { + case GFX_USERFONT_UNKNOWN: + // If being permissive of unknown types, make a reasonable guess + // at how much room the sanitized font may take, if it passes. Just + // enough extra space to accomodate some growth without excessive + // bloat in case of large fonts. 1.5x is a reasonable compromise + // for growable vectors in general. + return aStrict || !aLength ? 0 : (aLength * 3) / 2; + case GFX_USERFONT_WOFF: + return aLength * 2; + case GFX_USERFONT_WOFF2: + return aLength * 3; + default: + return aLength; + } + } + + static size_t GuessSanitizedFontSize(const uint8_t* aData, size_t aLength, + bool aStrict = true) { + gfxUserFontType fontType = + gfxFontUtils::DetermineFontDataType(aData, aLength); + return GuessSanitizedFontSize(aLength, fontType, aStrict); + } + + private: + bool mCheckOTLTables; + bool mCheckVariationTables; + bool mKeepColorBitmaps; +}; + +#endif /* GFX_OTS_UTILS_H */ diff --git a/gfx/thebes/gfxPattern.cpp b/gfx/thebes/gfxPattern.cpp new file mode 100644 index 0000000000..5f063fc484 --- /dev/null +++ b/gfx/thebes/gfxPattern.cpp @@ -0,0 +1,204 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxPattern.h" + +#include "gfxUtils.h" +#include "gfxTypes.h" +#include "gfxASurface.h" +#include "gfxPlatform.h" +#include "gfx2DGlue.h" +#include "gfxGradientCache.h" +#include "mozilla/gfx/2D.h" + +#include "cairo.h" + +#include <vector> + +using namespace mozilla::gfx; + +gfxPattern::gfxPattern(const DeviceColor& aColor) : mExtend(ExtendMode::CLAMP) { + mGfxPattern.InitColorPattern(aColor); +} + +// linear +gfxPattern::gfxPattern(gfxFloat x0, gfxFloat y0, gfxFloat x1, gfxFloat y1) + : mExtend(ExtendMode::CLAMP) { + mGfxPattern.InitLinearGradientPattern(Point(x0, y0), Point(x1, y1), nullptr); +} + +// radial +gfxPattern::gfxPattern(gfxFloat cx0, gfxFloat cy0, gfxFloat radius0, + gfxFloat cx1, gfxFloat cy1, gfxFloat radius1) + : mExtend(ExtendMode::CLAMP) { + mGfxPattern.InitRadialGradientPattern(Point(cx0, cy0), Point(cx1, cy1), + radius0, radius1, nullptr); +} + +// conic +gfxPattern::gfxPattern(gfxFloat cx, gfxFloat cy, gfxFloat angle, + gfxFloat startOffset, gfxFloat endOffset) + : mExtend(ExtendMode::CLAMP) { + mGfxPattern.InitConicGradientPattern(Point(cx, cy), angle, startOffset, + endOffset, nullptr); +} + +// Azure +gfxPattern::gfxPattern(SourceSurface* aSurface, + const Matrix& aPatternToUserSpace) + : mPatternToUserSpace(aPatternToUserSpace), mExtend(ExtendMode::CLAMP) { + mGfxPattern.InitSurfacePattern( + aSurface, mExtend, Matrix(), // matrix is overridden in GetPattern() + mozilla::gfx::SamplingFilter::GOOD); +} + +void gfxPattern::AddColorStop(gfxFloat offset, const DeviceColor& c) { + if (mGfxPattern.GetPattern()->GetType() != PatternType::LINEAR_GRADIENT && + mGfxPattern.GetPattern()->GetType() != PatternType::RADIAL_GRADIENT && + mGfxPattern.GetPattern()->GetType() != PatternType::CONIC_GRADIENT) { + return; + } + + mStops = nullptr; + + GradientStop stop; + stop.offset = offset; + stop.color = c; + mStopsList.AppendElement(stop); +} + +void gfxPattern::SetColorStops(GradientStops* aStops) { mStops = aStops; } + +void gfxPattern::CacheColorStops(const DrawTarget* aDT) { + mStops = gfxGradientCache::GetOrCreateGradientStops(aDT, mStopsList, mExtend); +} + +void gfxPattern::SetMatrix(const gfxMatrix& aPatternToUserSpace) { + mPatternToUserSpace = ToMatrix(aPatternToUserSpace); + // Cairo-pattern matrices specify the conversion from DrawTarget to pattern + // space. Azure pattern matrices specify the conversion from pattern to + // DrawTarget space. + mPatternToUserSpace.Invert(); +} + +gfxMatrix gfxPattern::GetMatrix() const { + // invert at the higher precision of gfxMatrix + // cause we need to convert at some point anyways + gfxMatrix mat = ThebesMatrix(mPatternToUserSpace); + mat.Invert(); + return mat; +} + +gfxMatrix gfxPattern::GetInverseMatrix() const { + return ThebesMatrix(mPatternToUserSpace); +} + +Pattern* gfxPattern::GetPattern(const DrawTarget* aTarget, + const Matrix* aOriginalUserToDevice) { + Matrix patternToUser = mPatternToUserSpace; + + if (aOriginalUserToDevice && + !aOriginalUserToDevice->FuzzyEquals(aTarget->GetTransform())) { + // mPatternToUserSpace maps from pattern space to the original user space, + // but aTarget now has a transform to a different user space. In order for + // the Pattern* that we return to be usable in aTarget's new user space we + // need the Pattern's mMatrix to be the transform from pattern space to + // aTarget's -new- user space. That transform is equivalent to the + // transform from pattern space to original user space (patternToUser), + // multiplied by the transform from original user space to device space, + // multiplied by the transform from device space to current user space. + + Matrix deviceToCurrentUser = aTarget->GetTransform(); + deviceToCurrentUser.Invert(); + + patternToUser = + patternToUser * *aOriginalUserToDevice * deviceToCurrentUser; + } + patternToUser.NudgeToIntegers(); + + if (!mStops && !mStopsList.IsEmpty()) { + mStops = aTarget->CreateGradientStops(mStopsList.Elements(), + mStopsList.Length(), mExtend); + } + + switch (mGfxPattern.GetPattern()->GetType()) { + case PatternType::SURFACE: { + SurfacePattern* surfacePattern = + static_cast<SurfacePattern*>(mGfxPattern.GetPattern()); + surfacePattern->mMatrix = patternToUser; + surfacePattern->mExtendMode = mExtend; + break; + } + case PatternType::LINEAR_GRADIENT: { + LinearGradientPattern* linearGradientPattern = + static_cast<LinearGradientPattern*>(mGfxPattern.GetPattern()); + linearGradientPattern->mMatrix = patternToUser; + linearGradientPattern->mStops = mStops; + break; + } + case PatternType::RADIAL_GRADIENT: { + RadialGradientPattern* radialGradientPattern = + static_cast<RadialGradientPattern*>(mGfxPattern.GetPattern()); + radialGradientPattern->mMatrix = patternToUser; + radialGradientPattern->mStops = mStops; + break; + } + case PatternType::CONIC_GRADIENT: { + ConicGradientPattern* conicGradientPattern = + static_cast<ConicGradientPattern*>(mGfxPattern.GetPattern()); + conicGradientPattern->mMatrix = patternToUser; + conicGradientPattern->mStops = mStops; + break; + } + default: + /* Reassure the compiler we are handling all the enum values. */ + break; + } + + return mGfxPattern.GetPattern(); +} + +void gfxPattern::SetExtend(ExtendMode aExtend) { + mExtend = aExtend; + mStops = nullptr; +} + +bool gfxPattern::IsOpaque() { + if (mGfxPattern.GetPattern()->GetType() != PatternType::SURFACE) { + return false; + } + + if (static_cast<SurfacePattern*>(mGfxPattern.GetPattern()) + ->mSurface->GetFormat() == SurfaceFormat::B8G8R8X8) { + return true; + } + return false; +} + +void gfxPattern::SetSamplingFilter(mozilla::gfx::SamplingFilter filter) { + if (mGfxPattern.GetPattern()->GetType() != PatternType::SURFACE) { + return; + } + + static_cast<SurfacePattern*>(mGfxPattern.GetPattern())->mSamplingFilter = + filter; +} + +SamplingFilter gfxPattern::SamplingFilter() const { + if (mGfxPattern.GetPattern()->GetType() != PatternType::SURFACE) { + return mozilla::gfx::SamplingFilter::GOOD; + } + return static_cast<const SurfacePattern*>(mGfxPattern.GetPattern()) + ->mSamplingFilter; +} + +bool gfxPattern::GetSolidColor(DeviceColor& aColorOut) { + if (mGfxPattern.GetPattern()->GetType() == PatternType::COLOR) { + aColorOut = static_cast<ColorPattern*>(mGfxPattern.GetPattern())->mColor; + return true; + } + + return false; +} diff --git a/gfx/thebes/gfxPattern.h b/gfx/thebes/gfxPattern.h new file mode 100644 index 0000000000..cc8a7d5231 --- /dev/null +++ b/gfx/thebes/gfxPattern.h @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_PATTERN_H +#define GFX_PATTERN_H + +#include "gfxTypes.h" + +#include "gfxMatrix.h" +#include "mozilla/Alignment.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/PatternHelpers.h" +#include "nsISupportsImpl.h" +#include "nsTArray.h" + +typedef struct _cairo_pattern cairo_pattern_t; + +class gfxPattern final { + NS_INLINE_DECL_REFCOUNTING(gfxPattern) + + public: + explicit gfxPattern(const mozilla::gfx::DeviceColor& aColor); + // gradients + gfxPattern(gfxFloat x0, gfxFloat y0, gfxFloat x1, gfxFloat y1); // linear + gfxPattern(gfxFloat cx0, gfxFloat cy0, gfxFloat radius0, gfxFloat cx1, + gfxFloat cy1, gfxFloat radius1); // radial + gfxPattern(gfxFloat cx, gfxFloat cy, gfxFloat angle, gfxFloat startOffset, + gfxFloat endOffset); // conic + gfxPattern(mozilla::gfx::SourceSurface* aSurface, + const mozilla::gfx::Matrix& aPatternToUserSpace); + + void AddColorStop(gfxFloat offset, const mozilla::gfx::DeviceColor& c); + void SetColorStops(mozilla::gfx::GradientStops* aStops); + + // This should only be called on a cairo pattern that we want to use with + // Azure. We will read back the color stops from cairo and try to look + // them up in the cache. + void CacheColorStops(const mozilla::gfx::DrawTarget* aDT); + + void SetMatrix(const gfxMatrix& matrix); + gfxMatrix GetMatrix() const; + gfxMatrix GetInverseMatrix() const; + + /* Get an Azure Pattern for the current Cairo pattern. aPattern transform + * specifies the transform that was set on the DrawTarget when the pattern + * was set. When this is nullptr it is assumed the transform is identical + * to the current transform. + */ + mozilla::gfx::Pattern* GetPattern( + const mozilla::gfx::DrawTarget* aTarget, + const mozilla::gfx::Matrix* aOriginalUserToDevice = nullptr); + bool IsOpaque(); + + // clamp, repeat, reflect + void SetExtend(mozilla::gfx::ExtendMode aExtend); + + void SetSamplingFilter(mozilla::gfx::SamplingFilter aSamplingFilter); + mozilla::gfx::SamplingFilter SamplingFilter() const; + + /* returns TRUE if it succeeded */ + bool GetSolidColor(mozilla::gfx::DeviceColor& aColorOut); + + private: + // Private destructor, to discourage deletion outside of Release(): + ~gfxPattern() = default; + + mozilla::gfx::GeneralPattern mGfxPattern; + RefPtr<mozilla::gfx::SourceSurface> mSourceSurface; + mozilla::gfx::Matrix mPatternToUserSpace; + RefPtr<mozilla::gfx::GradientStops> mStops; + nsTArray<mozilla::gfx::GradientStop> mStopsList; + mozilla::gfx::ExtendMode mExtend; +}; + +#endif /* GFX_PATTERN_H */ diff --git a/gfx/thebes/gfxPlatform.cpp b/gfx/thebes/gfxPlatform.cpp new file mode 100644 index 0000000000..4009903e25 --- /dev/null +++ b/gfx/thebes/gfxPlatform.cpp @@ -0,0 +1,3518 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/RDDProcessManager.h" +#include "mozilla/image/ImageMemoryReporter.h" +#include "mozilla/layers/CompositorManagerChild.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/ImageBridgeChild.h" +#include "mozilla/layers/ISurfaceAllocator.h" // for GfxMemoryImageReporter +#include "mozilla/layers/CompositorBridgeChild.h" +#include "mozilla/layers/TiledContentClient.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "mozilla/webrender/webrender_ffi.h" +#include "mozilla/layers/PaintThread.h" +#include "mozilla/gfx/BuildConstants.h" +#include "mozilla/gfx/gfxConfigManager.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/gfx/GraphicsMessages.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/StaticPrefs_accessibility.h" +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/StaticPrefs_media.h" +#include "mozilla/StaticPrefs_webgl.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Unused.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Base64.h" + +#include "mozilla/Logging.h" +#include "mozilla/Services.h" +#include "nsAppRunner.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsCSSProps.h" + +#include "gfxCrashReporterUtils.h" +#include "gfxPlatform.h" + +#include "gfxBlur.h" +#include "gfxEnv.h" +#include "gfxTextRun.h" +#include "gfxUserFontSet.h" +#include "gfxConfig.h" +#include "GfxDriverInfo.h" +#include "VRProcessManager.h" +#include "VRThread.h" + +#ifdef XP_WIN +# include <process.h> +# define getpid _getpid +#else +# include <unistd.h> +#endif + +#include "nsXULAppAPI.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" + +#if defined(XP_WIN) +# include "gfxWindowsPlatform.h" +#elif defined(XP_MACOSX) +# include "gfxPlatformMac.h" +# include "gfxQuartzSurface.h" +# include "nsCocoaFeatures.h" +#elif defined(MOZ_WIDGET_GTK) +# include "gfxPlatformGtk.h" +#elif defined(ANDROID) +# include "gfxAndroidPlatform.h" +#endif +#if defined(MOZ_WIDGET_ANDROID) +# include "mozilla/jni/Utils.h" // for IsFennec +#endif + +#ifdef XP_WIN +# include "mozilla/WindowsVersion.h" +#endif + +#include "nsGkAtoms.h" +#include "gfxPlatformFontList.h" +#include "gfxContext.h" +#include "gfxImageSurface.h" +#include "nsUnicodeProperties.h" +#include "harfbuzz/hb.h" +#include "gfxGraphiteShaper.h" +#include "gfx2DGlue.h" +#include "gfxGradientCache.h" +#include "gfxUtils.h" // for NextPowerOfTwo +#include "gfxFontMissingGlyphs.h" + +#include "nsExceptionHandler.h" +#include "nsServiceManagerUtils.h" +#include "nsTArray.h" +#include "nsIObserverService.h" +#include "nsIScreenManager.h" +#include "MainThreadUtils.h" + +#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" + +#ifdef USE_SKIA +# ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wshadow" +# endif +# include "skia/include/core/SkGraphics.h" +# ifdef MOZ_ENABLE_FREETYPE +# include "skia/include/ports/SkTypeface_cairo.h" +# endif +# include "mozilla/gfx/SkMemoryReporter.h" +# ifdef __GNUC__ +# pragma GCC diagnostic pop // -Wshadow +# endif +static const uint32_t kDefaultGlyphCacheSize = -1; + +#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 "mozilla/dom/TouchEvent.h" +#include "gfxVR.h" +#include "VRManager.h" +#include "VRManagerChild.h" +#include "mozilla/gfx/GPUParent.h" +#include "mozilla/layers/MemoryReportingMLGPU.h" +#include "prsystem.h" + +using namespace mozilla; +using namespace mozilla::layers; +using namespace mozilla::gl; +using namespace mozilla::gfx; + +gfxPlatform* gPlatform = nullptr; +static bool gEverInitialized = false; + +static int32_t gLastUsedFrameRate = -1; + +const ContentDeviceData* gContentDeviceInitData = nullptr; + +static Mutex* gGfxPlatformPrefsLock = nullptr; + +// These two may point to the same profile +static qcms_profile* gCMSOutputProfile = nullptr; +static qcms_profile* gCMSsRGBProfile = nullptr; + +static bool gCMSRGBTransformFailed = false; +static qcms_transform* gCMSRGBTransform = nullptr; +static qcms_transform* gCMSInverseRGBTransform = nullptr; +static qcms_transform* gCMSRGBATransform = nullptr; +static qcms_transform* gCMSBGRATransform = nullptr; + +static bool gCMSInitialized = false; +static eCMSMode gCMSMode = eCMSMode_Off; + +static void ShutdownCMS(); + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/SourceSurfaceCairo.h" + +/* 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() = default; + + 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(CrashReporter::Annotation aKey); + void Log(const std::string& aString) override; + void CrashAction(LogReason aReason) override; + bool UpdateStringsVector(const std::string& aString) override; + + LoggingRecord LoggingRecordCopy() override; + + void SetCircularBufferSize(uint32_t aCapacity); + + private: + // Helper for the Log() + void UpdateCrashReport(); + + private: + LoggingRecord mBuffer; + CrashReporter::Annotation mCrashCriticalKey; + uint32_t mMaxCapacity; + int32_t mIndex; + Mutex mMutex; +}; + +CrashStatsLogForwarder::CrashStatsLogForwarder(CrashReporter::Annotation aKey) + : mBuffer(), + mCrashCriticalKey(aKey), + mMaxCapacity(0), + mIndex(-1), + mMutex("CrashStatsLogForwarder") {} + +void CrashStatsLogForwarder::SetCircularBufferSize(uint32_t aCapacity) { + MutexAutoLock lock(mMutex); + + mMaxCapacity = aCapacity; + mBuffer.reserve(static_cast<size_t>(aCapacity)); +} + +LoggingRecord CrashStatsLogForwarder::LoggingRecordCopy() { + MutexAutoLock lock(mMutex); + return mBuffer; +} + +bool CrashStatsLogForwarder::UpdateStringsVector(const std::string& aString) { + // We want at least the first one and the last one. Otherwise, no point. + if (mMaxCapacity < 2) { + return false; + } + + mIndex += 1; + MOZ_ASSERT(mIndex >= 0); + + // index will count 0, 1, 2, ..., max-1, 1, 2, ..., max-1, 1, 2, ... + int32_t index = mIndex ? (mIndex - 1) % (mMaxCapacity - 1) + 1 : 0; + MOZ_ASSERT(index >= 0 && index < (int32_t)mMaxCapacity); + MOZ_ASSERT(index <= mIndex && index <= (int32_t)mBuffer.size()); + + double tStamp = (TimeStamp::NowLoRes() - TimeStamp::ProcessCreation()) + .ToSecondsSigDigits(); + + // Checking for index >= mBuffer.size(), rather than index == mBuffer.size() + // just out of paranoia, but we know index <= mBuffer.size(). + LoggingRecordEntry newEntry(mIndex, aString, tStamp); + if (index >= static_cast<int32_t>(mBuffer.size())) { + mBuffer.push_back(newEntry); + } else { + mBuffer[index] = newEntry; + } + return true; +} + +void CrashStatsLogForwarder::UpdateCrashReport() { + std::stringstream message; + std::string logAnnotation; + + switch (XRE_GetProcessType()) { + case GeckoProcessType_Default: + logAnnotation = "|["; + break; + case GeckoProcessType_Content: + logAnnotation = "|[C"; + break; + case GeckoProcessType_GPU: + logAnnotation = "|[G"; + break; + default: + logAnnotation = "|[X"; + break; + } + + for (auto& it : mBuffer) { + message << logAnnotation << Get<0>(it) << "]" << Get<1>(it) + << " (t=" << Get<2>(it) << ") "; + } + + nsCString reportString(message.str().c_str()); + nsresult annotated = + CrashReporter::AnnotateCrashReport(mCrashCriticalKey, reportString); + + if (annotated != NS_OK) { + printf("Crash Annotation %s: %s", + CrashReporter::AnnotationToString(mCrashCriticalKey), + message.str().c_str()); + } +} + +class LogForwarderEvent : public Runnable { + virtual ~LogForwarderEvent() = default; + + public: + NS_INLINE_DECL_REFCOUNTING_INHERITED(LogForwarderEvent, Runnable) + + explicit LogForwarderEvent(const nsCString& aMessage) + : mozilla::Runnable("LogForwarderEvent"), mMessage(aMessage) {} + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread() && + (XRE_IsContentProcess() || XRE_IsGPUProcess())); + + if (XRE_IsContentProcess()) { + dom::ContentChild* cc = dom::ContentChild::GetSingleton(); + Unused << cc->SendGraphicsError(mMessage); + } else if (XRE_IsGPUProcess()) { + GPUParent* gp = GPUParent::GetSingleton(); + Unused << gp->SendGraphicsError(mMessage); + } + + return NS_OK; + } + + protected: + nsCString mMessage; +}; + +void CrashStatsLogForwarder::Log(const std::string& aString) { + MutexAutoLock lock(mMutex); + + if (UpdateStringsVector(aString)) { + UpdateCrashReport(); + } + + // Add it to the parent strings + if (!XRE_IsParentProcess()) { + nsCString stringToSend(aString.c_str()); + if (NS_IsMainThread()) { + if (XRE_IsContentProcess()) { + dom::ContentChild* cc = dom::ContentChild::GetSingleton(); + Unused << cc->SendGraphicsError(stringToSend); + } else if (XRE_IsGPUProcess()) { + GPUParent* gp = GPUParent::GetSingleton(); + Unused << gp->SendGraphicsError(stringToSend); + } + } else { + nsCOMPtr<nsIRunnable> r1 = new LogForwarderEvent(stringToSend); + NS_DispatchToMainThread(r1); + } + } +} + +class CrashTelemetryEvent : public Runnable { + virtual ~CrashTelemetryEvent() = default; + + public: + NS_INLINE_DECL_REFCOUNTING_INHERITED(CrashTelemetryEvent, Runnable) + + explicit CrashTelemetryEvent(uint32_t aReason) + : mozilla::Runnable("CrashTelemetryEvent"), mReason(aReason) {} + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread()); + Telemetry::Accumulate(Telemetry::GFX_CRASH, mReason); + return NS_OK; + } + + protected: + uint32_t mReason; +}; + +void CrashStatsLogForwarder::CrashAction(LogReason aReason) { +#ifndef RELEASE_OR_BETA + // Non-release builds crash by default, but will use telemetry + // if this environment variable is present. + static bool useTelemetry = gfxEnv::GfxDevCrashTelemetry(); +#else + // Release builds use telemetry by default, but will crash instead + // if this environment variable is present. + static bool useTelemetry = !gfxEnv::GfxDevCrashMozCrash(); +#endif + + if (useTelemetry) { + // The callers need to assure that aReason is in the range + // that the telemetry call below supports. + if (NS_IsMainThread()) { + Telemetry::Accumulate(Telemetry::GFX_CRASH, (uint32_t)aReason); + } else { + nsCOMPtr<nsIRunnable> r1 = new CrashTelemetryEvent((uint32_t)aReason); + NS_DispatchToMainThread(r1); + } + } else { + // ignoring aReason, we can get the information we need from the stack + MOZ_CRASH("GFX_CRASH"); + } +} + +NS_IMPL_ISUPPORTS(SRGBOverrideObserver, nsIObserver, nsISupportsWeakReference) + +#define GFX_DOWNLOADABLE_FONTS_ENABLED "gfx.downloadable_fonts.enabled" + +#define GFX_PREF_FALLBACK_USE_CMAPS \ + "gfx.font_rendering.fallback.always_use_cmaps" + +#define GFX_PREF_OPENTYPE_SVG "gfx.font_rendering.opentype_svg.enabled" + +#define GFX_PREF_WORD_CACHE_CHARLIMIT "gfx.font_rendering.wordcache.charlimit" +#define GFX_PREF_WORD_CACHE_MAXENTRIES "gfx.font_rendering.wordcache.maxentries" + +#define GFX_PREF_GRAPHITE_SHAPING "gfx.font_rendering.graphite.enabled" +#if defined(XP_MACOSX) +# define GFX_PREF_CORETEXT_SHAPING "gfx.font_rendering.coretext.enabled" +#endif + +#define BIDI_NUMERAL_PREF "bidi.numeral" + +#define GFX_PREF_CMS_FORCE_SRGB "gfx.color_management.force_srgb" + +#define FONT_VARIATIONS_PREF "layout.css.font-variations.enabled" + +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(); + // FIXME(aosmond): This is also racy for the transforms but the pref is only + // used for dev purposes. It can be made a static pref in a followup once the + // dependency on it is removed from the gtest suite (see bug 1620600). + gfxPlatform::GetCMSRGBTransform(); + gfxPlatform::GetCMSRGBATransform(); + gfxPlatform::GetCMSBGRATransform(); + return NS_OK; +} + +static const char* kObservedPrefs[] = {"gfx.downloadable_fonts.", + "gfx.font_rendering.", BIDI_NUMERAL_PREF, + nullptr}; + +static void FontPrefChanged(const char* aPref, void* aData) { + MOZ_ASSERT(aPref); + NS_ASSERTION(gfxPlatform::GetPlatform(), "the singleton instance has gone"); + gfxPlatform::GetPlatform()->FontsPrefsChanged(aPref); +} + +void gfxPlatform::OnMemoryPressure(layers::MemoryPressureReason aWhy) { + Factory::PurgeAllCaches(); + gfxGradientCache::PurgeAllCaches(); + gfxFontMissingGlyphs::Purge(); + PurgeSkiaFontCache(); + if (XRE_IsParentProcess()) { + layers::CompositorManagerChild* manager = + CompositorManagerChild::GetInstance(); + if (manager) { + manager->SendNotifyMemoryPressure(); + } + } +} + +gfxPlatform::gfxPlatform() + : mHasVariationFontSupport(false), + mTotalPhysicalMemory(~0), + mTotalVirtualMemory(~0), + mAzureCanvasBackendCollector(this, &gfxPlatform::GetAzureBackendInfo), + mApzSupportCollector(this, &gfxPlatform::GetApzSupportInfo), + mTilesInfoCollector(this, &gfxPlatform::GetTilesSupportInfo), + mFrameStatsCollector(this, &gfxPlatform::GetFrameStats), + mCMSInfoCollector(this, &gfxPlatform::GetCMSSupportInfo), + mDisplayInfoCollector(this, &gfxPlatform::GetDisplayInfo), + mCompositorBackend(layers::LayersBackend::LAYERS_NONE), + mScreenDepth(0), + mScreenPixels(0) { + mAllowDownloadableFonts = UNINITIALIZED_VALUE; + mFallbackUsesCmaps = UNINITIALIZED_VALUE; + + mWordCacheCharLimit = UNINITIALIZED_VALUE; + mWordCacheMaxEntries = UNINITIALIZED_VALUE; + mGraphiteShapingEnabled = UNINITIALIZED_VALUE; + mOpenTypeSVGEnabled = UNINITIALIZED_VALUE; + mBidiNumeralOption = UNINITIALIZED_VALUE; + + InitBackendPrefs(GetBackendPrefs()); + +#ifdef XP_WIN + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + if (GlobalMemoryStatusEx(&status)) { + mTotalPhysicalMemory = status.ullTotalPhys; + mTotalVirtualMemory = status.ullTotalVirtual; + } +#else + mTotalPhysicalMemory = PR_GetPhysicalMemorySize(); +#endif + + VRManager::ManagerInit(); +} + +gfxPlatform* gfxPlatform::GetPlatform() { + if (!gPlatform) { + MOZ_RELEASE_ASSERT(!XRE_IsContentProcess(), + "Content Process should have called InitChild() before " + "first GetPlatform()"); + Init(); + } + return gPlatform; +} + +bool gfxPlatform::Initialized() { return !!gPlatform; } + +/* static */ +void gfxPlatform::InitChild(const ContentDeviceData& aData) { + MOZ_ASSERT(XRE_IsContentProcess()); + MOZ_RELEASE_ASSERT(!gPlatform, + "InitChild() should be called before first GetPlatform()"); + // Make the provided initial ContentDeviceData available to the init + // routines, so they don't have to do a sync request from the parent. + gContentDeviceInitData = &aData; + Init(); + gContentDeviceInitData = nullptr; +} + +void RecordingPrefChanged(const char* aPrefName, void* aClosure) { + if (Preferences::GetBool("gfx.2d.recording", false)) { + nsAutoCString fileName; + nsAutoString prefFileName; + nsresult rv = Preferences::GetString("gfx.2d.recordingfile", prefFileName); + if (NS_SUCCEEDED(rv)) { + CopyUTF16toUTF8(prefFileName, fileName); + } else { + nsCOMPtr<nsIFile> tmpFile; + if (NS_FAILED(NS_GetSpecialDirectory(NS_OS_TEMP_DIR, + getter_AddRefs(tmpFile)))) { + return; + } + fileName.AppendPrintf("moz2drec_%i_%i.aer", XRE_GetProcessType(), + getpid()); + + nsresult rv = tmpFile->AppendNative(fileName); + if (NS_FAILED(rv)) return; + +#ifdef XP_WIN + rv = tmpFile->GetPath(prefFileName); + CopyUTF16toUTF8(prefFileName, fileName); +#else + rv = tmpFile->GetNativePath(fileName); +#endif + if (NS_FAILED(rv)) return; + } + +#ifdef XP_WIN + gPlatform->mRecorder = + Factory::CreateEventRecorderForFile(prefFileName.BeginReading()); +#else + gPlatform->mRecorder = + Factory::CreateEventRecorderForFile(fileName.BeginReading()); +#endif + printf_stderr("Recording to %s\n", fileName.get()); + Factory::SetGlobalEventRecorder(gPlatform->mRecorder); + } else { + Factory::SetGlobalEventRecorder(nullptr); + } +} + +#define WR_DEBUG_PREF "gfx.webrender.debug" + +static void WebRendeProfilerUIPrefChangeCallback(const char* aPrefName, void*) { + nsCString uiString; + if (NS_SUCCEEDED(Preferences::GetCString("gfx.webrender.debug.profiler-ui", + uiString))) { + gfxVars::SetWebRenderProfilerUI(uiString); + } +} + +static void WebRenderDebugPrefChangeCallback(const char* aPrefName, void*) { + wr::DebugFlags flags{0}; +#define GFX_WEBRENDER_DEBUG(suffix, bit) \ + if (Preferences::GetBool(WR_DEBUG_PREF suffix, false)) { \ + flags |= (bit); \ + } + + GFX_WEBRENDER_DEBUG(".profiler", wr::DebugFlags::PROFILER_DBG) + GFX_WEBRENDER_DEBUG(".render-targets", wr::DebugFlags::RENDER_TARGET_DBG) + GFX_WEBRENDER_DEBUG(".texture-cache", wr::DebugFlags::TEXTURE_CACHE_DBG) + GFX_WEBRENDER_DEBUG(".gpu-time-queries", wr::DebugFlags::GPU_TIME_QUERIES) + GFX_WEBRENDER_DEBUG(".gpu-sample-queries", wr::DebugFlags::GPU_SAMPLE_QUERIES) + GFX_WEBRENDER_DEBUG(".disable-batching", wr::DebugFlags::DISABLE_BATCHING) + GFX_WEBRENDER_DEBUG(".epochs", wr::DebugFlags::EPOCHS) + GFX_WEBRENDER_DEBUG(".smart-profiler", wr::DebugFlags::SMART_PROFILER) + GFX_WEBRENDER_DEBUG(".echo-driver-messages", + wr::DebugFlags::ECHO_DRIVER_MESSAGES) + GFX_WEBRENDER_DEBUG(".show-overdraw", wr::DebugFlags::SHOW_OVERDRAW) + GFX_WEBRENDER_DEBUG(".gpu-cache", wr::DebugFlags::GPU_CACHE_DBG) + GFX_WEBRENDER_DEBUG(".texture-cache.clear-evicted", + wr::DebugFlags::TEXTURE_CACHE_DBG_CLEAR_EVICTED) + GFX_WEBRENDER_DEBUG(".picture-caching", wr::DebugFlags::PICTURE_CACHING_DBG) + GFX_WEBRENDER_DEBUG(".force-picture-invalidation", + wr::DebugFlags::FORCE_PICTURE_INVALIDATION) + GFX_WEBRENDER_DEBUG(".tile-cache-logging", + wr::DebugFlags::TILE_CACHE_LOGGING_DBG) + GFX_WEBRENDER_DEBUG(".primitives", wr::DebugFlags::PRIMITIVE_DBG) + // Bit 18 is for the zoom display, which requires the mouse position and thus + // currently only works in wrench. + GFX_WEBRENDER_DEBUG(".small-screen", wr::DebugFlags::SMALL_SCREEN) + GFX_WEBRENDER_DEBUG(".disable-opaque-pass", + wr::DebugFlags::DISABLE_OPAQUE_PASS) + GFX_WEBRENDER_DEBUG(".disable-alpha-pass", wr::DebugFlags::DISABLE_ALPHA_PASS) + GFX_WEBRENDER_DEBUG(".disable-clip-masks", wr::DebugFlags::DISABLE_CLIP_MASKS) + GFX_WEBRENDER_DEBUG(".disable-text-prims", wr::DebugFlags::DISABLE_TEXT_PRIMS) + GFX_WEBRENDER_DEBUG(".disable-gradient-prims", + wr::DebugFlags::DISABLE_GRADIENT_PRIMS) + GFX_WEBRENDER_DEBUG(".obscure-images", wr::DebugFlags::OBSCURE_IMAGES) + GFX_WEBRENDER_DEBUG(".glyph-flashing", wr::DebugFlags::GLYPH_FLASHING) + GFX_WEBRENDER_DEBUG(".capture-profiler", wr::DebugFlags::PROFILER_CAPTURE) +#undef GFX_WEBRENDER_DEBUG + + gfx::gfxVars::SetWebRenderDebugFlags(flags.bits); +} + +static void WebRenderQualityPrefChangeCallback(const char* aPref, void*) { + gfxPlatform::GetPlatform()->UpdateForceSubpixelAAWherePossible(); +} + +static void WebRenderMultithreadingPrefChangeCallback(const char* aPrefName, + void*) { + bool enable = Preferences::GetBool( + StaticPrefs::GetPrefName_gfx_webrender_enable_multithreading(), true); + + gfx::gfxVars::SetUseWebRenderMultithreading(enable); +} + +static void WebRenderBatchingPrefChangeCallback(const char* aPrefName, void*) { + uint32_t count = Preferences::GetUint( + StaticPrefs::GetPrefName_gfx_webrender_batching_lookback(), 10); + + gfx::gfxVars::SetWebRenderBatchingLookback(count); +} + +#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 pref cache size on desktop due to talos perf tweaking. + // Chromium uses 20mb and skia default uses 2mb. + // We don't need to change the font cache count since we usually + // cache thrash due to asian character sets in talos. + // Only increase memory on the content process + uint32_t cacheSize = + StaticPrefs::gfx_content_skia_font_cache_size_AtStartup() * 1024 * 1024; + if (mozilla::BrowserTabsRemoteAutostart()) { + return XRE_IsContentProcess() ? cacheSize : kDefaultGlyphCacheSize; + } + + return cacheSize; +# else + return kDefaultGlyphCacheSize; +# endif // MOZ_WIDGET_ANDROID +} +#endif + +class WebRenderMemoryReporter final : public nsIMemoryReporter { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMEMORYREPORTER + + private: + ~WebRenderMemoryReporter() = default; +}; + +// Memory reporter for WebRender. +// +// The reporting within WebRender is manual and incomplete. We could do a much +// more thorough job by depending on the malloc_size_of crate, but integrating +// that into WebRender is tricky [1]. +// +// So the idea is to start with manual reporting for the large allocations +// detected by DMD, and see how much that can cover in practice (which may +// require a few rounds of iteration). If that approach turns out to be +// fundamentally insufficient, we can either duplicate more of the +// malloc_size_of functionality in WebRender, or deal with the complexity of a +// gecko-only crate dependency. +// +// [1] See https://bugzilla.mozilla.org/show_bug.cgi?id=1480293#c1 +struct WebRenderMemoryReporterHelper { + WebRenderMemoryReporterHelper(nsIHandleReportCallback* aCallback, + nsISupports* aData) + : mCallback(aCallback), mData(aData) {} + nsCOMPtr<nsIHandleReportCallback> mCallback; + nsCOMPtr<nsISupports> mData; + + void Report(size_t aBytes, const char* aName) const { + nsPrintfCString path("explicit/gfx/webrender/%s", aName); + nsCString desc("CPU heap memory used by WebRender"_ns); + ReportInternal(aBytes, path, desc, nsIMemoryReporter::KIND_HEAP); + } + + void ReportTexture(size_t aBytes, const char* aName) const { + nsPrintfCString path("gfx/webrender/textures/%s", aName); + nsCString desc("GPU texture memory used by WebRender"_ns); + ReportInternal(aBytes, path, desc, nsIMemoryReporter::KIND_OTHER); + } + + void ReportTotalGPUBytes(size_t aBytes) const { + nsCString path("gfx/webrender/total-gpu-bytes"_ns); + nsCString desc(nsLiteralCString( + "Total GPU bytes used by WebRender (should match textures/ sum)")); + ReportInternal(aBytes, path, desc, nsIMemoryReporter::KIND_OTHER); + } + + void ReportInternal(size_t aBytes, nsACString& aPath, nsACString& aDesc, + int32_t aKind) const { + // Generally, memory reporters pass the empty string as the process name to + // indicate "current process". However, if we're using a GPU process, the + // measurements will actually take place in that process, and it's easier to + // just note that here rather than trying to invoke the memory reporter in + // the GPU process. + nsAutoCString processName; + if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { + GPUParent::GetGPUProcessName(processName); + } + + mCallback->Callback(processName, aPath, aKind, + nsIMemoryReporter::UNITS_BYTES, aBytes, aDesc, mData); + } +}; + +static void FinishAsyncMemoryReport() { + nsCOMPtr<nsIMemoryReporterManager> imgr = + do_GetService("@mozilla.org/memory-reporter-manager;1"); + if (imgr) { + imgr->EndReport(); + } +} + +// clang-format off +// (For some reason, clang-format gets the second macro right, but totally mangles the first). +#define REPORT_INTERNER(id) \ + helper.Report(aReport.interning.interners.id, \ + "interning/" #id "/interners"); +// clang-format on + +#define REPORT_DATA_STORE(id) \ + helper.Report(aReport.interning.data_stores.id, \ + "interning/" #id "/data-stores"); + +NS_IMPL_ISUPPORTS(WebRenderMemoryReporter, nsIMemoryReporter) + +NS_IMETHODIMP +WebRenderMemoryReporter::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + layers::CompositorManagerChild* manager = + CompositorManagerChild::GetInstance(); + if (!manager) { + FinishAsyncMemoryReport(); + return NS_OK; + } + + WebRenderMemoryReporterHelper helper(aHandleReport, aData); + manager->SendReportMemory( + [=](wr::MemoryReport aReport) { + // CPU Memory. + helper.Report(aReport.clip_stores, "clip-stores"); + helper.Report(aReport.gpu_cache_metadata, "gpu-cache/metadata"); + helper.Report(aReport.gpu_cache_cpu_mirror, "gpu-cache/cpu-mirror"); + helper.Report(aReport.render_tasks, "render-tasks"); + helper.Report(aReport.hit_testers, "hit-testers"); + helper.Report(aReport.fonts, "resource-cache/fonts"); + helper.Report(aReport.weak_fonts, "resource-cache/weak-fonts"); + helper.Report(aReport.images, "resource-cache/images"); + helper.Report(aReport.rasterized_blobs, + "resource-cache/rasterized-blobs"); + helper.Report(aReport.texture_cache_structures, + "texture-cache/structures"); + helper.Report(aReport.shader_cache, "shader-cache"); + helper.Report(aReport.display_list, "display-list"); + + WEBRENDER_FOR_EACH_INTERNER(REPORT_INTERNER); + WEBRENDER_FOR_EACH_INTERNER(REPORT_DATA_STORE); + + // GPU Memory. + helper.ReportTexture(aReport.gpu_cache_textures, "gpu-cache"); + helper.ReportTexture(aReport.vertex_data_textures, "vertex-data"); + helper.ReportTexture(aReport.render_target_textures, "render-targets"); + helper.ReportTexture(aReport.texture_cache_textures, "texture-cache"); + helper.ReportTexture(aReport.depth_target_textures, "depth-targets"); + helper.ReportTexture(aReport.texture_upload_pbos, + "texture-upload-pbos"); + helper.ReportTexture(aReport.swap_chain, "swap-chains"); + helper.ReportTexture(aReport.render_texture_hosts, + "render-texture-hosts"); + + FinishAsyncMemoryReport(); + }, + [](mozilla::ipc::ResponseRejectReason&& aReason) { + FinishAsyncMemoryReport(); + }); + + return NS_OK; +} + +#undef REPORT_INTERNER +#undef REPORT_DATA_STORE + +static void FrameRatePrefChanged(const char* aPref, void*) { + int32_t newRate = gfxPlatform::ForceSoftwareVsync() + ? gfxPlatform::GetSoftwareVsyncRate() + : -1; + if (newRate != gLastUsedFrameRate) { + gLastUsedFrameRate = newRate; + gfxPlatform::ReInitFrameRate(); + } +} + +void gfxPlatform::Init() { + MOZ_RELEASE_ASSERT(!XRE_IsGPUProcess(), "GFX: Not allowed in GPU process."); + MOZ_RELEASE_ASSERT(!XRE_IsRDDProcess(), "GFX: Not allowed in RDD process."); + MOZ_RELEASE_ASSERT(NS_IsMainThread(), "GFX: Not in main thread."); + + if (gEverInitialized) { + MOZ_CRASH("Already started???"); + } + gEverInitialized = true; + + gfxVars::Initialize(); + + gfxConfig::Init(); + + if (XRE_IsParentProcess()) { + GPUProcessManager::Initialize(); + RDDProcessManager::Initialize(); + + nsCOMPtr<nsIFile> file; + nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(file)); + if (NS_FAILED(rv)) { + gfxVars::SetGREDirectory(nsString()); + } else { + nsAutoString path; + file->GetPath(path); + gfxVars::SetGREDirectory(nsString(path)); + } + } + + if (XRE_IsParentProcess()) { + nsCOMPtr<nsIFile> profDir; + nsresult rv = NS_GetSpecialDirectory(NS_APP_PROFILE_DIR_STARTUP, + getter_AddRefs(profDir)); + if (NS_FAILED(rv)) { + gfxVars::SetProfDirectory(nsString()); + } else { + nsAutoString path; + profDir->GetPath(path); + gfxVars::SetProfDirectory(nsString(path)); + } + + nsAutoCString path; + Preferences::GetCString("layers.windowrecording.path", path); + gfxVars::SetLayersWindowRecordingPath(path); + + if (gFxREmbedded) { + gfxVars::SetFxREmbedded(true); + } + } + + // Drop a note in the crash report if we end up forcing an option that could + // destabilize things. New items should be appended at the end (of an + // existing or in a new section), so that we don't have to know the version to + // interpret these cryptic strings. + { + nsAutoCString forcedPrefs; + // D2D prefs + forcedPrefs.AppendPrintf( + "FP(D%d%d", StaticPrefs::gfx_direct2d_disabled_AtStartup(), + StaticPrefs::gfx_direct2d_force_enabled_AtStartup()); + // Layers prefs + forcedPrefs.AppendPrintf( + "-L%d%d%d%d", + StaticPrefs::layers_amd_switchable_gfx_enabled_AtStartup(), + StaticPrefs::layers_acceleration_disabled_AtStartup_DoNotUseDirectly(), + StaticPrefs:: + layers_acceleration_force_enabled_AtStartup_DoNotUseDirectly(), + StaticPrefs::layers_d3d11_force_warp_AtStartup()); + // WebGL prefs + forcedPrefs.AppendPrintf( + "-W%d%d%d%d%d%d%d%d", StaticPrefs::webgl_angle_force_d3d11(), + StaticPrefs::webgl_angle_force_warp(), StaticPrefs::webgl_disabled(), + StaticPrefs::webgl_disable_angle(), StaticPrefs::webgl_dxgl_enabled(), + StaticPrefs::webgl_force_enabled(), + StaticPrefs::webgl_force_layers_readback(), + StaticPrefs::webgl_msaa_force()); + // Prefs that don't fit into any of the other sections + forcedPrefs.AppendPrintf("-T%d%d%d) ", + StaticPrefs::gfx_android_rgb16_force_AtStartup(), + 0, // SkiaGL canvas no longer supported + StaticPrefs::layers_force_shmem_tiles_AtStartup()); + ScopedGfxFeatureReporter::AppNote(forcedPrefs); + } + + InitMoz2DLogging(); + + gGfxPlatformPrefsLock = new Mutex("gfxPlatform::gGfxPlatformPrefsLock"); + + /* Initialize the GfxInfo service. + * Note: we can't call functions on GfxInfo that depend + * on gPlatform until after it has been initialized + * below. GfxInfo initialization annotates our + * crash reports so we want to do it before + * we try to load any drivers and do device detection + * incase that code crashes. See bug #591561. */ + nsCOMPtr<nsIGfxInfo> gfxInfo; + /* this currently will only succeed on Windows */ + gfxInfo = services::GetGfxInfo(); + + if (XRE_IsParentProcess()) { + // Some gfxVars must be initialized prior gPlatform for coherent results. + gfxVars::SetDXInterop2Blocked(IsDXInterop2Blocked()); + gfxVars::SetDXNV12Blocked(IsDXNV12Blocked()); + gfxVars::SetDXP010Blocked(IsDXP010Blocked()); + gfxVars::SetDXP016Blocked(IsDXP016Blocked()); + } + +#if defined(XP_WIN) + gPlatform = new gfxWindowsPlatform; +#elif defined(XP_MACOSX) + gPlatform = new gfxPlatformMac; +#elif defined(MOZ_WIDGET_GTK) + gPlatform = new gfxPlatformGtk; +#elif defined(ANDROID) + gPlatform = new gfxAndroidPlatform; +#else +# error "No gfxPlatform implementation available" +#endif + gPlatform->PopulateScreenInfo(); + gPlatform->InitAcceleration(); + gPlatform->InitWebRenderConfig(); + + gPlatform->InitWebGLConfig(); + gPlatform->InitWebGPUConfig(); + + // When using WebRender, we defer initialization of the D3D11 devices until + // the (rare) cases where they're used. Note that the GPU process where + // WebRender runs doesn't initialize gfxPlatform and performs explicit + // initialization of the bits it needs. + if (!UseWebRender() +#if defined(XP_WIN) + || (UseWebRender() && XRE_IsParentProcess() && + !gfxConfig::IsEnabled(Feature::GPU_PROCESS) && + StaticPrefs:: + gfx_webrender_enabled_no_gpu_process_with_angle_win_AtStartup()) +#endif + ) { + gPlatform->EnsureDevicesInitialized(); + } + gPlatform->InitOMTPConfig(); + + if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { + GPUProcessManager* gpu = GPUProcessManager::Get(); + gpu->LaunchGPUProcess(); + } + + gLastUsedFrameRate = ForceSoftwareVsync() ? GetSoftwareVsyncRate() : -1; + Preferences::RegisterCallback( + FrameRatePrefChanged, + nsDependentCString(StaticPrefs::GetPrefName_layout_frame_rate())); + // Set up the vsync source for the parent process. + ReInitFrameRate(); + +#ifdef USE_SKIA + SkGraphics::Init(); +# ifdef MOZ_ENABLE_FREETYPE + SkInitCairoFT(gPlatform->FontHintingEnabled()); +# endif +#endif + + InitLayersIPC(); + + gPlatform->ComputeTileSize(); + + gPlatform->mHasVariationFontSupport = gPlatform->CheckVariationFontSupport(); + + nsresult rv; + rv = gfxPlatformFontList::Init(); + if (NS_FAILED(rv)) { + MOZ_CRASH("Could not initialize gfxPlatformFontList"); + } + + gPlatform->mScreenReferenceSurface = gPlatform->CreateOffscreenSurface( + IntSize(1, 1), SurfaceFormat::A8R8G8B8_UINT32); + if (!gPlatform->mScreenReferenceSurface) { + MOZ_CRASH("Could not initialize mScreenReferenceSurface"); + } + + gPlatform->mScreenReferenceDrawTarget = + gPlatform->CreateOffscreenContentDrawTarget(IntSize(1, 1), + SurfaceFormat::B8G8R8A8); + if (!gPlatform->mScreenReferenceDrawTarget || + !gPlatform->mScreenReferenceDrawTarget->IsValid()) { + // If TDR is detected, create a draw target with software backend + // and it should be replaced later when the process gets the device + // reset notification. + if (!gPlatform->DidRenderingDeviceReset()) { + gfxCriticalError() << "Could not initialize mScreenReferenceDrawTarget"; + } + } + + rv = gfxFontCache::Init(); + if (NS_FAILED(rv)) { + MOZ_CRASH("Could not initialize gfxFontCache"); + } + + /* Create and register our CMS Override observer. */ + gPlatform->mSRGBOverrideObserver = new SRGBOverrideObserver(); + Preferences::AddWeakObserver(gPlatform->mSRGBOverrideObserver, + GFX_PREF_CMS_FORCE_SRGB); + + Preferences::RegisterPrefixCallbacks(FontPrefChanged, kObservedPrefs); + + GLContext::PlatformStartup(); + + Preferences::RegisterCallbackAndCall(RecordingPrefChanged, + "gfx.2d.recording"); + + CreateCMSOutputProfile(); + + // Create the sRGB to output display profile transforms. They can be accessed + // off the main thread so we want to avoid a race condition. + GetCMSRGBTransform(); + GetCMSRGBATransform(); + GetCMSBGRATransform(); + + // Listen to memory pressure event so we can purge DrawTarget caches + gPlatform->mMemoryPressureObserver = + layers::MemoryPressureObserver::Create(gPlatform); + + // Request the imgITools service, implicitly initializing ImageLib. + nsCOMPtr<imgITools> imgTools = do_GetService("@mozilla.org/image/tools;1"); + if (!imgTools) { + MOZ_CRASH("Could not initialize ImageLib"); + } + + RegisterStrongMemoryReporter(new GfxMemoryImageReporter()); + if (XRE_IsParentProcess() && UseWebRender()) { + RegisterStrongAsyncMemoryReporter(new WebRenderMemoryReporter()); + } + +#ifdef USE_SKIA + RegisterStrongMemoryReporter(new SkMemoryReporter()); +#endif + mlg::InitializeMemoryReporters(); + +#ifdef USE_SKIA + uint32_t skiaCacheSize = GetSkiaGlyphCacheSize(); + if (skiaCacheSize != kDefaultGlyphCacheSize) { + SkGraphics::SetFontCacheLimit(skiaCacheSize); + } +#endif + + InitNullMetadata(); + InitOpenGLConfig(); + + if (XRE_IsParentProcess()) { + Preferences::Unlock(FONT_VARIATIONS_PREF); + if (!gPlatform->HasVariationFontSupport()) { + // Ensure variation fonts are disabled and the pref is locked. + Preferences::SetBool(FONT_VARIATIONS_PREF, false, PrefValueKind::Default); + Preferences::SetBool(FONT_VARIATIONS_PREF, false); + Preferences::Lock(FONT_VARIATIONS_PREF); + } + } + + if (XRE_IsParentProcess()) { + ReportTelemetry(); + } + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "gfx-features-ready", nullptr); + } +} + +void gfxPlatform::ReportTelemetry() { + MOZ_RELEASE_ASSERT(XRE_IsParentProcess(), + "GFX: Only allowed to be called from parent process."); + + nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo(); + nsTArray<uint32_t> displayWidths; + nsTArray<uint32_t> displayHeights; + gfxInfo->GetDisplayWidth(displayWidths); + gfxInfo->GetDisplayHeight(displayHeights); + + uint32_t displayCount = displayWidths.Length(); + uint32_t displayWidth = displayWidths.Length() > 0 ? displayWidths[0] : 0; + uint32_t displayHeight = displayHeights.Length() > 0 ? displayHeights[0] : 0; + Telemetry::ScalarSet(Telemetry::ScalarID::GFX_DISPLAY_COUNT, displayCount); + Telemetry::ScalarSet(Telemetry::ScalarID::GFX_DISPLAY_PRIMARY_HEIGHT, + displayHeight); + Telemetry::ScalarSet(Telemetry::ScalarID::GFX_DISPLAY_PRIMARY_WIDTH, + displayWidth); + + nsString adapterDesc; + gfxInfo->GetAdapterDescription(adapterDesc); + Telemetry::ScalarSet(Telemetry::ScalarID::GFX_ADAPTER_DESCRIPTION, + adapterDesc); + + nsString adapterVendorId; + gfxInfo->GetAdapterVendorID(adapterVendorId); + Telemetry::ScalarSet(Telemetry::ScalarID::GFX_ADAPTER_VENDOR_ID, + adapterVendorId); + + nsString adapterDeviceId; + gfxInfo->GetAdapterDeviceID(adapterDeviceId); + Telemetry::ScalarSet(Telemetry::ScalarID::GFX_ADAPTER_DEVICE_ID, + adapterDeviceId); + + nsString adapterSubsystemId; + gfxInfo->GetAdapterSubsysID(adapterSubsystemId); + Telemetry::ScalarSet(Telemetry::ScalarID::GFX_ADAPTER_SUBSYSTEM_ID, + adapterSubsystemId); + + uint32_t adapterRam = 0; + gfxInfo->GetAdapterRAM(&adapterRam); + Telemetry::ScalarSet(Telemetry::ScalarID::GFX_ADAPTER_RAM, adapterRam); + + nsString adapterDriver; + gfxInfo->GetAdapterDriver(adapterDriver); + Telemetry::ScalarSet(Telemetry::ScalarID::GFX_ADAPTER_DRIVER_FILES, + adapterDriver); + + nsString adapterDriverVendor; + gfxInfo->GetAdapterDriverVendor(adapterDriverVendor); + Telemetry::ScalarSet(Telemetry::ScalarID::GFX_ADAPTER_DRIVER_VENDOR, + adapterDriverVendor); + + nsString adapterDriverVersion; + gfxInfo->GetAdapterDriverVersion(adapterDriverVersion); + Telemetry::ScalarSet(Telemetry::ScalarID::GFX_ADAPTER_DRIVER_VERSION, + adapterDriverVersion); + + nsString adapterDriverDate; + gfxInfo->GetAdapterDriverDate(adapterDriverDate); + Telemetry::ScalarSet(Telemetry::ScalarID::GFX_ADAPTER_DRIVER_DATE, + adapterDriverDate); + + Telemetry::ScalarSet(Telemetry::ScalarID::GFX_HEADLESS, IsHeadless()); +} + +static bool IsFeatureSupported(long aFeature, bool aDefault) { + nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo(); + nsCString blockId; + int32_t status; + if (!NS_SUCCEEDED(gfxInfo->GetFeatureStatus(aFeature, blockId, &status))) { + return aDefault; + } + return status == nsIGfxInfo::FEATURE_STATUS_OK; +} + +/* static*/ +bool gfxPlatform::IsDXInterop2Blocked() { + return !IsFeatureSupported(nsIGfxInfo::FEATURE_DX_INTEROP2, false); +} + +/* static*/ +bool gfxPlatform::IsDXNV12Blocked() { + return !IsFeatureSupported(nsIGfxInfo::FEATURE_DX_NV12, false); +} + +/* static*/ +bool gfxPlatform::IsDXP010Blocked() { + return !IsFeatureSupported(nsIGfxInfo::FEATURE_DX_P010, false); +} + +/* static*/ +bool gfxPlatform::IsDXP016Blocked() { + return !IsFeatureSupported(nsIGfxInfo::FEATURE_DX_P016, false); +} + +/* static */ +int32_t gfxPlatform::MaxTextureSize() { + // Make sure we don't completely break rendering because of a typo in the + // pref or whatnot. + const int32_t kMinSizePref = 2048; + return std::max( + kMinSizePref, + StaticPrefs::gfx_max_texture_size_AtStartup_DoNotUseDirectly()); +} + +/* static */ +int32_t gfxPlatform::MaxAllocSize() { + // Make sure we don't completely break rendering because of a typo in the + // pref or whatnot. + const int32_t kMinAllocPref = 10000000; + return std::max(kMinAllocPref, + StaticPrefs::gfx_max_alloc_size_AtStartup_DoNotUseDirectly()); +} + +/* static */ +void gfxPlatform::InitMoz2DLogging() { + auto fwd = new CrashStatsLogForwarder( + CrashReporter::Annotation::GraphicsCriticalError); + fwd->SetCircularBufferSize(StaticPrefs::gfx_logging_crash_length_AtStartup()); + + mozilla::gfx::Config cfg; + cfg.mLogForwarder = fwd; + cfg.mMaxTextureSize = gfxPlatform::MaxTextureSize(); + cfg.mMaxAllocSize = gfxPlatform::MaxAllocSize(); + + gfx::Factory::Init(cfg); +} + +/* static */ +bool gfxPlatform::IsHeadless() { + static bool initialized = false; + static bool headless = false; + if (!initialized) { + initialized = true; + headless = PR_GetEnv("MOZ_HEADLESS"); + } + return headless; +} + +/* static */ +bool gfxPlatform::UseWebRender() { return gfx::gfxVars::UseWebRender(); } + +/* static */ +bool gfxPlatform::CanMigrateMacGPUs() { + int32_t pMigration = StaticPrefs::gfx_compositor_gpu_migration(); + + bool forceDisable = pMigration == 0; + bool forceEnable = pMigration == 2; + + return forceEnable || !forceDisable; +} + +static bool sLayersIPCIsUp = false; + +/* static */ +void gfxPlatform::InitNullMetadata() { + ScrollMetadata::sNullMetadata = new ScrollMetadata(); + ClearOnShutdown(&ScrollMetadata::sNullMetadata); +} + +void gfxPlatform::Shutdown() { + // In some cases, gPlatform may not be created but Shutdown() called, + // e.g., during xpcshell tests. + if (!gPlatform) { + return; + } + + MOZ_ASSERT(!sLayersIPCIsUp); + + // These may be called before the corresponding subsystems have actually + // started up. That's OK, they can handle it. + gfxFontCache::Shutdown(); + gfxGradientCache::Shutdown(); + gfxAlphaBoxBlur::ShutdownBlurCache(); + gfxGraphiteShaper::Shutdown(); + gfxPlatformFontList::Shutdown(); + gfxFontMissingGlyphs::Shutdown(); + 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; + + Preferences::UnregisterPrefixCallbacks(FontPrefChanged, kObservedPrefs); + + NS_ASSERTION(gPlatform->mMemoryPressureObserver, + "mMemoryPressureObserver has already gone"); + if (gPlatform->mMemoryPressureObserver) { + gPlatform->mMemoryPressureObserver->Unregister(); + gPlatform->mMemoryPressureObserver = nullptr; + } + + if (XRE_IsParentProcess()) { + gPlatform->mVsyncSource->Shutdown(); + } + + gPlatform->mVsyncSource = nullptr; + + // Shut down the default GL context provider. + GLContextProvider::Shutdown(); + +#if defined(XP_WIN) + // The above shutdown calls operate on the available context providers on + // most platforms. Windows is a "special snowflake", though, and has three + // context providers available, so we have to shut all of them down. + // We should only support the default GL provider on Windows; then, this + // could go away. Unfortunately, we currently support WGL (the default) for + // WebGL on Optimus. + GLContextProviderEGL::Shutdown(); +#endif + + if (XRE_IsParentProcess()) { + GPUProcessManager::Shutdown(); + VRProcessManager::Shutdown(); + RDDProcessManager::Shutdown(); + } + + gfx::Factory::ShutDown(); + + delete gGfxPlatformPrefsLock; + + gfxVars::Shutdown(); + gfxFont::DestroySingletons(); + + gfxConfig::Shutdown(); + + gPlatform->WillShutdown(); + + delete gPlatform; + gPlatform = nullptr; +} + +/* static */ +void gfxPlatform::InitLayersIPC() { + if (sLayersIPCIsUp) { + return; + } + sLayersIPCIsUp = true; + + if (XRE_IsContentProcess()) { + if (gfxVars::UseOMTP()) { + layers::PaintThread::Start(); + } + } + + if (XRE_IsParentProcess()) { + if (!gfxConfig::IsEnabled(Feature::GPU_PROCESS) && UseWebRender()) { + wr::RenderThread::Start(); + image::ImageMemoryReporter::InitForWebRender(); + } + + layers::CompositorThreadHolder::Start(); + } +} + +/* static */ +void gfxPlatform::ShutdownLayersIPC() { + if (!sLayersIPCIsUp) { + return; + } + sLayersIPCIsUp = false; + + if (XRE_IsContentProcess()) { + gfx::VRManagerChild::ShutDown(); + // cf bug 1215265. + if (StaticPrefs::layers_child_process_shutdown()) { + layers::CompositorManagerChild::Shutdown(); + layers::ImageBridgeChild::ShutDown(); + } + + if (gfxVars::UseOMTP()) { + layers::PaintThread::Shutdown(); + } + } else if (XRE_IsParentProcess()) { + gfx::VRManagerChild::ShutDown(); + layers::CompositorManagerChild::Shutdown(); + layers::ImageBridgeChild::ShutDown(); + // This has to happen after shutting down the child protocols. + layers::CompositorThreadHolder::Shutdown(); + image::ImageMemoryReporter::ShutdownForWebRender(); + // There is a case that RenderThread exists when UseWebRender() is + // false. This could happen when WebRender was fallbacked to compositor. + if (wr::RenderThread::Get()) { + wr::RenderThread::ShutDown(); + + Preferences::UnregisterCallback(WebRenderDebugPrefChangeCallback, + WR_DEBUG_PREF); + Preferences::UnregisterCallback(WebRendeProfilerUIPrefChangeCallback, + "gfx.webrender.debug.profiler-ui"); + } + + } 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; + +#ifdef USE_SKIA + // Always clear out the Skia font cache here, in case it is referencing any + // SharedFTFaces that would otherwise outlive destruction of the FT_Library + // that owns them. + SkGraphics::PurgeFontCache(); +#endif + + // 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 +# if MOZ_TREE_CAIRO + cairo_debug_reset_static_data(); +# endif +#endif +} + +gfxPlatform::~gfxPlatform() = default; + +/* static */ +already_AddRefed<DrawTarget> gfxPlatform::CreateDrawTargetForSurface( + gfxASurface* aSurface, const IntSize& aSize) { + SurfaceFormat format = aSurface->GetSurfaceFormat(); + RefPtr<DrawTarget> drawTarget = Factory::CreateDrawTargetForCairoSurface( + aSurface->CairoSurface(), aSize, &format); + if (!drawTarget) { + gfxWarning() << "gfxPlatform::CreateDrawTargetForSurface failed in " + "CreateDrawTargetForCairoSurface"; + return nullptr; + } + return drawTarget.forget(); +} + +cairo_user_data_key_t kSourceSurface; + +/** + * Record the backend that was used to construct the SourceSurface. + * When getting the cached SourceSurface for a gfxASurface/DrawTarget pair, + * we check to make sure the DrawTarget's backend matches the backend + * for the cached SourceSurface, and only use it if they match. This + * can avoid expensive and unnecessary readbacks. + */ +struct SourceSurfaceUserData { + RefPtr<SourceSurface> mSrcSurface; + BackendType mBackendType; +}; + +static void SourceBufferDestroy(void* srcSurfUD) { + delete static_cast<SourceSurfaceUserData*>(srcSurfUD); +} + +UserDataKey kThebesSurface; + +struct DependentSourceSurfaceUserData { + RefPtr<gfxASurface> mSurface; +}; + +static void SourceSurfaceDestroyed(void* aData) { + delete static_cast<DependentSourceSurfaceUserData*>(aData); +} + +void gfxPlatform::ClearSourceSurfaceForSurface(gfxASurface* aSurface) { + aSurface->SetData(&kSourceSurface, nullptr, nullptr); +} + +/* static */ +already_AddRefed<SourceSurface> gfxPlatform::GetSourceSurfaceForSurface( + RefPtr<DrawTarget> aTarget, gfxASurface* aSurface, bool aIsPlugin) { + if (!aSurface->CairoSurface() || aSurface->CairoStatus()) { + return nullptr; + } + + if (!aTarget) { + aTarget = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + } + + void* userData = aSurface->GetData(&kSourceSurface); + + if (userData) { + SourceSurfaceUserData* surf = static_cast<SourceSurfaceUserData*>(userData); + + if (surf->mSrcSurface->IsValid() && + surf->mBackendType == aTarget->GetBackendType()) { + RefPtr<SourceSurface> srcSurface(surf->mSrcSurface); + return srcSurface.forget(); + } + // We can just continue here as when setting new user data the destroy + // function will be called for the old user data. + } + + SurfaceFormat format = aSurface->GetSurfaceFormat(); + + if (aTarget->GetBackendType() == BackendType::CAIRO) { + // If we're going to be used with a CAIRO DrawTarget, then just create a + // SourceSurfaceCairo since we don't know the underlying type of the CAIRO + // DrawTarget and can't pick a better surface type. Doing this also avoids + // readback of aSurface's surface into memory if, for example, aSurface + // wraps an xlib cairo surface (which can be important to avoid a major + // slowdown). + // + // We return here regardless of whether CreateSourceSurfaceFromNativeSurface + // succeeds or not since we don't expect to be able to do any better below + // if it fails. + // + // Note that the returned SourceSurfaceCairo holds a strong reference to + // the cairo_surface_t* that it wraps, which essencially means it holds a + // strong reference to aSurface since aSurface shares its + // cairo_surface_t*'s reference count variable. As a result we can't cache + // srcBuffer on aSurface (see below) since aSurface would then hold a + // strong reference back to srcBuffer, creating a reference loop and a + // memory leak. Not caching is fine since wrapping is cheap enough (no + // copying) so we can just wrap again next time we're called. + return Factory::CreateSourceSurfaceForCairoSurface( + aSurface->CairoSurface(), aSurface->GetSize(), format); + } + + RefPtr<SourceSurface> srcBuffer; + + // Currently no other DrawTarget types implement + // CreateSourceSurfaceFromNativeSurface + + if (!srcBuffer) { + // If aSurface wraps data, we can create a SourceSurfaceRawData that wraps + // the same data, then optimize it for aTarget: + RefPtr<DataSourceSurface> surf = GetWrappedDataSourceSurface(aSurface); + if (surf) { + srcBuffer = aIsPlugin + ? aTarget->OptimizeSourceSurfaceForUnknownAlpha(surf) + : aTarget->OptimizeSourceSurface(surf); + + if (srcBuffer == surf) { + // GetWrappedDataSourceSurface returns a SourceSurface that holds a + // strong reference to aSurface since it wraps aSurface's data and + // needs it to stay alive. As a result we can't cache srcBuffer on + // aSurface (below) since aSurface would then hold a strong reference + // back to srcBuffer, creating a reference loop and a memory leak. Not + // caching is fine since wrapping is cheap enough (no copying) so we + // can just wrap again next time we're called. + // + // Note that the check below doesn't catch this since srcBuffer will be + // a SourceSurfaceRawData object (even if aSurface is not a + // gfxImageSurface object), which is why we need this separate check. + return srcBuffer.forget(); + } + } + } + + if (!srcBuffer) { + MOZ_ASSERT(aTarget->GetBackendType() != BackendType::CAIRO, + "We already tried CreateSourceSurfaceFromNativeSurface with a " + "DrawTargetCairo above"); + // We've run out of performant options. We now try creating a SourceSurface + // using a temporary DrawTargetCairo and then optimizing it to aTarget's + // actual type. The CreateSourceSurfaceFromNativeSurface() call will + // likely create a DataSourceSurface (possibly involving copying and/or + // readback), and the OptimizeSourceSurface may well copy again and upload + // to the GPU. So, while this code path is rarely hit, hitting it may be + // very slow. + srcBuffer = Factory::CreateSourceSurfaceForCairoSurface( + aSurface->CairoSurface(), aSurface->GetSize(), format); + if (srcBuffer) { + srcBuffer = aTarget->OptimizeSourceSurface(srcBuffer); + } + } + + if (!srcBuffer) { + return nullptr; + } + + if ((srcBuffer->GetType() == SurfaceType::CAIRO && + static_cast<SourceSurfaceCairo*>(srcBuffer.get())->GetSurface() == + aSurface->CairoSurface()) || + (srcBuffer->GetType() == SurfaceType::CAIRO_IMAGE && + static_cast<DataSourceSurfaceCairo*>(srcBuffer.get())->GetSurface() == + aSurface->CairoSurface())) { + // See the "Note that the returned SourceSurfaceCairo..." comment above. + return srcBuffer.forget(); + } + + // Add user data to aSurface so we can cache lookups in the future. + auto* srcSurfUD = new SourceSurfaceUserData; + srcSurfUD->mBackendType = aTarget->GetBackendType(); + srcSurfUD->mSrcSurface = srcBuffer; + aSurface->SetData(&kSourceSurface, srcSurfUD, SourceBufferDestroy); + + return srcBuffer.forget(); +} + +already_AddRefed<DataSourceSurface> gfxPlatform::GetWrappedDataSourceSurface( + gfxASurface* aSurface) { + RefPtr<gfxImageSurface> image = aSurface->GetAsImageSurface(); + if (!image) { + return nullptr; + } + RefPtr<DataSourceSurface> result = Factory::CreateWrappingDataSourceSurface( + image->Data(), image->Stride(), image->GetSize(), + ImageFormatToSurfaceFormat(image->Format())); + + if (!result) { + return nullptr; + } + + // If we wrapped the underlying data of aSurface, then we need to add user + // data to make sure aSurface stays alive until we are done with the data. + auto* srcSurfUD = new DependentSourceSurfaceUserData; + srcSurfUD->mSurface = aSurface; + result->AddUserData(&kThebesSurface, srcSurfUD, SourceSurfaceDestroyed); + + return result.forget(); +} + +void gfxPlatform::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 = StaticPrefs::layers_tile_width_AtStartup(); + int32_t h = StaticPrefs::layers_tile_height_AtStartup(); + + if (StaticPrefs::layers_tiles_adjust_AtStartup()) { + gfx::IntSize screenSize = GetScreenSize(); + if (screenSize.width > 0) { + // Choose a size so that there are between 2 and 4 tiles per screen width. + // FIXME: we should probably make sure this is within the max texture + // size, but I think everything should at least support 1024 + w = h = clamped(int32_t(RoundUpPow2(screenSize.width)) / 4, 256, 1024); + } + } + + // Don't allow changing the tile size after we've set it. + // Right now the code assumes that the tile size doesn't change. + MOZ_ASSERT(gfxVars::TileSize().width == -1 && + gfxVars::TileSize().height == -1); + + gfxVars::SetTileSize(IntSize(w, h)); +} + +void gfxPlatform::PopulateScreenInfo() { + nsCOMPtr<nsIScreenManager> manager = + do_GetService("@mozilla.org/gfx/screenmanager;1"); + MOZ_ASSERT(manager, "failed to get nsIScreenManager"); + + manager->GetTotalScreenPixels(&mScreenPixels); + + nsCOMPtr<nsIScreen> screen; + manager->GetPrimaryScreen(getter_AddRefs(screen)); + if (!screen) { + // This can happen in xpcshell, for instance + return; + } + + screen->GetColorDepth(&mScreenDepth); + if (XRE_IsParentProcess()) { + gfxVars::SetScreenDepth(mScreenDepth); + } + + int left, top; + screen->GetRect(&left, &top, &mScreenSize.width, &mScreenSize.height); +} + +bool gfxPlatform::SupportsAzureContentForDrawTarget(DrawTarget* aTarget) { + if (!aTarget || !aTarget->IsValid()) { + return false; + } + + return SupportsAzureContentForType(aTarget->GetBackendType()); +} + +void gfxPlatform::PurgeSkiaFontCache() { +#ifdef USE_SKIA + if (gfxPlatform::GetPlatform()->GetDefaultContentBackend() == + BackendType::SKIA) { + SkGraphics::PurgeFontCache(); + } +#endif +} + +already_AddRefed<DrawTarget> gfxPlatform::CreateDrawTargetForBackend( + BackendType aBackend, const IntSize& aSize, SurfaceFormat aFormat) { + // There is a bunch of knowledge in the gfxPlatform heirarchy about how to + // create the best offscreen surface for the current system and situation. We + // can easily take advantage of this for the Cairo backend, so that's what we + // do. + // mozilla::gfx::Factory can get away without having all this knowledge for + // now, but this might need to change in the future (using + // CreateOffscreenSurface() and CreateDrawTargetForSurface() for all + // backends). + if (aBackend == BackendType::CAIRO) { + RefPtr<gfxASurface> surf = + CreateOffscreenSurface(aSize, SurfaceFormatToImageFormat(aFormat)); + if (!surf || surf->CairoStatus()) { + return nullptr; + } + return CreateDrawTargetForSurface(surf, aSize); + } + return Factory::CreateDrawTarget(aBackend, aSize, aFormat); +} + +already_AddRefed<DrawTarget> gfxPlatform::CreateOffscreenCanvasDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat) { + NS_ASSERTION(mPreferredCanvasBackend != BackendType::NONE, "No backend."); + RefPtr<DrawTarget> target = + CreateDrawTargetForBackend(mPreferredCanvasBackend, aSize, aFormat); + if (target || mFallbackCanvasBackend == BackendType::NONE) { + return target.forget(); + } + +#ifdef XP_WIN + // On Windows, the fallback backend (Cairo) should use its image backend. + return Factory::CreateDrawTarget(mFallbackCanvasBackend, aSize, aFormat); +#else + return CreateDrawTargetForBackend(mFallbackCanvasBackend, aSize, aFormat); +#endif +} + +already_AddRefed<DrawTarget> gfxPlatform::CreateOffscreenContentDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat, bool aFallback) { + BackendType backend = (aFallback) ? mSoftwareBackend : mContentBackend; + NS_ASSERTION(backend != BackendType::NONE, "No backend."); + RefPtr<DrawTarget> dt = CreateDrawTargetForBackend(backend, aSize, aFormat); + + if (!dt) { + return nullptr; + } + + // We'd prefer this to take proper care and return a CaptureDT, but for the + // moment since we can't and this means we're going to be drawing on the main + // thread force it's initialization. See bug 1526045 and bug 1521368. + dt->ClearRect(gfx::Rect()); + if (!dt->IsValid()) { + return nullptr; + } + return dt.forget(); +} + +already_AddRefed<DrawTarget> gfxPlatform::CreateSimilarSoftwareDrawTarget( + DrawTarget* aDT, const IntSize& aSize, SurfaceFormat aFormat) { + RefPtr<DrawTarget> dt; + + if (Factory::DoesBackendSupportDataDrawtarget(aDT->GetBackendType())) { + dt = aDT->CreateSimilarDrawTarget(aSize, aFormat); + } else { +#ifdef USE_SKIA + BackendType backendType = BackendType::SKIA; +#else + BackendType backendType = BackendType::CAIRO; +#endif + dt = Factory::CreateDrawTarget(backendType, aSize, aFormat); + } + + return dt.forget(); +} + +/* static */ +already_AddRefed<DrawTarget> gfxPlatform::CreateDrawTargetForData( + unsigned char* aData, const IntSize& aSize, int32_t aStride, + SurfaceFormat aFormat, bool aUninitialized) { + BackendType backendType = gfxVars::ContentBackend(); + NS_ASSERTION(backendType != BackendType::NONE, "No backend."); + + if (!Factory::DoesBackendSupportDataDrawtarget(backendType)) { +#ifdef USE_SKIA + backendType = BackendType::SKIA; +#else + backendType = BackendType::CAIRO; +#endif + } + + RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData( + backendType, aData, aSize, aStride, aFormat, aUninitialized); + + return dt.forget(); +} + +/* static */ +BackendType gfxPlatform::BackendTypeForName(const nsCString& aName) { + if (aName.EqualsLiteral("cairo")) return BackendType::CAIRO; + if (aName.EqualsLiteral("skia")) return BackendType::SKIA; + if (aName.EqualsLiteral("direct2d")) return BackendType::DIRECT2D; + if (aName.EqualsLiteral("direct2d1.1")) return BackendType::DIRECT2D1_1; + return BackendType::NONE; +} + +nsresult gfxPlatform::GetFontList(nsAtom* aLangGroup, + const nsACString& aGenericFamily, + nsTArray<nsString>& aListOfFonts) { + gfxPlatformFontList::PlatformFontList()->GetFontList( + aLangGroup, aGenericFamily, aListOfFonts); + return NS_OK; +} + +nsresult gfxPlatform::UpdateFontList(bool aFullRebuild) { + gfxPlatformFontList::PlatformFontList()->UpdateFontList(aFullRebuild); + return NS_OK; +} + +void gfxPlatform::GetStandardFamilyName(const nsCString& aFontName, + nsACString& aFamilyName) { + gfxPlatformFontList::PlatformFontList()->GetStandardFamilyName(aFontName, + aFamilyName); +} + +nsAutoCString gfxPlatform::GetDefaultFontName( + const nsACString& aLangGroup, const nsACString& aGenericFamily) { + // To benefit from Return Value Optimization, all paths here must return + // this one variable: + nsAutoCString result; + + auto* pfl = gfxPlatformFontList::PlatformFontList(); + FamilyAndGeneric fam = pfl->GetDefaultFontFamily(aLangGroup, aGenericFamily); + if (!pfl->GetLocalizedFamilyName(fam.mFamily, result)) { + NS_WARNING("missing default font-family name"); + } + + return result; +} + +bool gfxPlatform::DownloadableFontsEnabled() { + if (mAllowDownloadableFonts == UNINITIALIZED_VALUE) { + mAllowDownloadableFonts = + Preferences::GetBool(GFX_DOWNLOADABLE_FONTS_ENABLED, false); + } + + return mAllowDownloadableFonts; +} + +bool gfxPlatform::UseCmapsDuringSystemFallback() { + 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; +} + +bool gfxPlatform::IsFontFormatSupported(uint32_t aFormatFlags) { + // check for strange format flags + MOZ_ASSERT(!(aFormatFlags & gfxUserFontSet::FLAG_FORMAT_NOT_USED), + "strange font format hint set"); + + // accept "common" formats that we support on all platforms + 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* gfxPlatform::CreateFontGroup( + const FontFamilyList& aFontFamilyList, const gfxFontStyle* aStyle, + nsAtom* aLanguage, bool aExplicitLanguage, gfxTextPerfMetrics* aTextPerf, + FontMatchingStats* aFontMatchingStats, gfxUserFontSet* aUserFontSet, + gfxFloat aDevToCssSize) const { + return new gfxFontGroup(aFontFamilyList, aStyle, aLanguage, aExplicitLanguage, + aTextPerf, aFontMatchingStats, aUserFontSet, + aDevToCssSize); +} + +gfxFontEntry* gfxPlatform::LookupLocalFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) { + return gfxPlatformFontList::PlatformFontList()->LookupLocalFont( + aFontName, aWeightForEntry, aStretchForEntry, aStyleForEntry); +} + +gfxFontEntry* gfxPlatform::MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, + uint32_t aLength) { + return gfxPlatformFontList::PlatformFontList()->MakePlatformFont( + aFontName, aWeightForEntry, aStretchForEntry, aStyleForEntry, aFontData, + aLength); +} + +mozilla::layers::DiagnosticTypes gfxPlatform::GetLayerDiagnosticTypes() { + mozilla::layers::DiagnosticTypes type = DiagnosticTypes::NO_DIAGNOSTIC; + if (StaticPrefs::layers_draw_borders()) { + type |= mozilla::layers::DiagnosticTypes::LAYER_BORDERS; + } + if (StaticPrefs::layers_draw_tile_borders()) { + type |= mozilla::layers::DiagnosticTypes::TILE_BORDERS; + } + if (StaticPrefs::layers_draw_bigimage_borders()) { + type |= mozilla::layers::DiagnosticTypes::BIGIMAGE_BORDERS; + } + if (StaticPrefs::layers_flash_borders()) { + type |= mozilla::layers::DiagnosticTypes::FLASH_BORDERS; + } + return type; +} + +BackendPrefsData gfxPlatform::GetBackendPrefs() const { + BackendPrefsData data; + + data.mCanvasBitmask = BackendTypeBit(BackendType::SKIA); + data.mContentBitmask = BackendTypeBit(BackendType::SKIA); + +#ifdef MOZ_WIDGET_GTK + data.mCanvasBitmask |= BackendTypeBit(BackendType::CAIRO); + data.mContentBitmask |= BackendTypeBit(BackendType::CAIRO); +#endif + + data.mCanvasDefault = BackendType::SKIA; + data.mContentDefault = BackendType::SKIA; + + return data; +} + +void gfxPlatform::InitBackendPrefs(BackendPrefsData&& aPrefsData) { + mPreferredCanvasBackend = GetCanvasBackendPref(aPrefsData.mCanvasBitmask); + if (mPreferredCanvasBackend == BackendType::NONE) { + mPreferredCanvasBackend = aPrefsData.mCanvasDefault; + } + + if (mPreferredCanvasBackend == BackendType::DIRECT2D1_1) { + // Falling back to D2D 1.0 won't help us here. When D2D 1.1 DT creation + // fails it means the surface was too big or there's something wrong with + // the device. D2D 1.0 will encounter a similar situation. + mFallbackCanvasBackend = GetCanvasBackendPref( + aPrefsData.mCanvasBitmask & ~(BackendTypeBit(mPreferredCanvasBackend) | + BackendTypeBit(BackendType::DIRECT2D))); + } else { + mFallbackCanvasBackend = GetCanvasBackendPref( + aPrefsData.mCanvasBitmask & ~BackendTypeBit(mPreferredCanvasBackend)); + } + + mContentBackendBitmask = aPrefsData.mContentBitmask; + mContentBackend = GetContentBackendPref(mContentBackendBitmask); + if (mContentBackend == BackendType::NONE) { + mContentBackend = aPrefsData.mContentDefault; + // mContentBackendBitmask is our canonical reference for supported + // backends so we need to add the default if we are using it and + // overriding the prefs. + mContentBackendBitmask |= BackendTypeBit(aPrefsData.mContentDefault); + } + + uint32_t swBackendBits = BackendTypeBit(BackendType::SKIA); +#ifdef MOZ_WIDGET_GTK + swBackendBits |= BackendTypeBit(BackendType::CAIRO); +#endif + mSoftwareBackend = GetContentBackendPref(swBackendBits); + if (mSoftwareBackend == BackendType::NONE) { + mSoftwareBackend = BackendType::SKIA; + } + + if (XRE_IsParentProcess()) { + gfxVars::SetContentBackend(mContentBackend); + gfxVars::SetSoftwareBackend(mSoftwareBackend); + } +} + +/* static */ +BackendType gfxPlatform::GetCanvasBackendPref(uint32_t aBackendBitmask) { + return GetBackendPref("gfx.canvas.azure.backends", aBackendBitmask); +} + +/* static */ +BackendType gfxPlatform::GetContentBackendPref(uint32_t& aBackendBitmask) { + return GetBackendPref("gfx.content.azure.backends", aBackendBitmask); +} + +/* static */ +BackendType gfxPlatform::GetBackendPref(const char* aBackendPrefName, + uint32_t& aBackendBitmask) { + nsTArray<nsCString> backendList; + nsAutoCString prefString; + if (NS_SUCCEEDED(Preferences::GetCString(aBackendPrefName, prefString))) { + ParseString(prefString, ',', backendList); + } + + uint32_t allowedBackends = 0; + BackendType result = BackendType::NONE; + for (uint32_t i = 0; i < backendList.Length(); ++i) { + BackendType type = BackendTypeForName(backendList[i]); + if (BackendTypeBit(type) & aBackendBitmask) { + allowedBackends |= BackendTypeBit(type); + if (result == BackendType::NONE) { + result = type; + } + } + } + + aBackendBitmask = allowedBackends; + return result; +} + +bool gfxPlatform::InSafeMode() { + static bool sSafeModeInitialized = false; + static bool sInSafeMode = false; + + if (!sSafeModeInitialized) { + sSafeModeInitialized = true; + nsCOMPtr<nsIXULRuntime> xr = do_GetService("@mozilla.org/xre/runtime;1"); + if (xr) { + xr->GetInSafeMode(&sInSafeMode); + } + } + return sInSafeMode; +} + +bool gfxPlatform::OffMainThreadCompositingEnabled() { + return UsesOffMainThreadCompositing(); +} + +eCMSMode gfxPlatform::GetCMSMode() { + if (!gCMSInitialized) { + int32_t mode = StaticPrefs::gfx_color_management_mode(); + if (mode >= 0 && mode < eCMSMode_AllCount) { + gCMSMode = static_cast<eCMSMode>(mode); + } + + bool enableV4 = StaticPrefs::gfx_color_management_enablev4(); + if (enableV4) { + qcms_enable_iccv4(); + } + gCMSInitialized = true; + } + return gCMSMode; +} + +void gfxPlatform::SetCMSModeOverride(eCMSMode aMode) { + MOZ_ASSERT(gCMSInitialized); + gCMSMode = aMode; +} + +int gfxPlatform::GetRenderingIntent() { + // StaticPrefList.yaml is using 0 as the default for the rendering + // intent preference, based on that being the value for + // QCMS_INTENT_DEFAULT. Assert here to catch if that ever + // changes and we can then figure out what to do about it. + MOZ_ASSERT(QCMS_INTENT_DEFAULT == 0); + + /* Try to query the pref system for a rendering intent. */ + int32_t pIntent = StaticPrefs::gfx_color_management_rendering_intent(); + if ((pIntent < QCMS_INTENT_MIN) || (pIntent > QCMS_INTENT_MAX)) { + /* If the pref is out of range, use embedded profile. */ + pIntent = -1; + } + return pIntent; +} + +DeviceColor gfxPlatform::TransformPixel(const sRGBColor& in, + qcms_transform* transform) { + if (transform) { + /* we want the bytes in RGB order */ +#ifdef IS_LITTLE_ENDIAN + /* ABGR puts the bytes in |RGBA| order on little endian */ + uint32_t packed = in.ToABGR(); + qcms_transform_data(transform, (uint8_t*)&packed, (uint8_t*)&packed, 1); + auto out = DeviceColor::FromABGR(packed); +#else + /* ARGB puts the bytes in |ARGB| order on big endian */ + uint32_t packed = in.UnusualToARGB(); + /* add one to move past the alpha byte */ + qcms_transform_data(transform, (uint8_t*)&packed + 1, (uint8_t*)&packed + 1, + 1); + auto out = DeviceColor::UnusualFromARGB(packed); +#endif + out.a = in.a; + return out; + } + return DeviceColor(in.r, in.g, in.b, in.a); +} + +nsTArray<uint8_t> gfxPlatform::GetPlatformCMSOutputProfileData() { + return GetPrefCMSOutputProfileData(); +} + +nsTArray<uint8_t> gfxPlatform::GetPrefCMSOutputProfileData() { + nsAutoCString fname; + Preferences::GetCString("gfx.color_management.display_profile", fname); + + if (fname.IsEmpty()) { + return nsTArray<uint8_t>(); + } + + void* mem = nullptr; + size_t size = 0; + qcms_data_from_path(fname.get(), &mem, &size); + + nsTArray<uint8_t> result; + + if (mem) { + result.AppendElements(static_cast<uint8_t*>(mem), size); + free(mem); + } + + return result; +} + +const mozilla::gfx::ContentDeviceData* gfxPlatform::GetInitContentDeviceData() { + return gContentDeviceInitData; +} + +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) { + nsTArray<uint8_t> outputProfileData = + gfxPlatform::GetPlatform()->GetPlatformCMSOutputProfileData(); + if (!outputProfileData.IsEmpty()) { + gCMSOutputProfile = qcms_profile_from_memory( + outputProfileData.Elements(), outputProfileData.Length()); + } + } + + /* Determine if the profile looks bogus. If so, close the profile + * and use sRGB instead. See bug 460629, */ + if (gCMSOutputProfile && qcms_profile_is_bogus(gCMSOutputProfile)) { + NS_ASSERTION(gCMSOutputProfile != 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 && !gCMSRGBTransformFailed) { + 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); + if (!gCMSRGBTransform) { + gCMSRGBTransformFailed = true; + } + } + + 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; +} + +qcms_transform* gfxPlatform::GetCMSBGRATransform() { + if (!gCMSBGRATransform) { + qcms_profile *inProfile, *outProfile; + outProfile = GetCMSOutputProfile(); + inProfile = GetCMSsRGBProfile(); + + if (!inProfile || !outProfile) return nullptr; + + gCMSBGRATransform = + qcms_transform_create(inProfile, QCMS_DATA_BGRA_8, outProfile, + QCMS_DATA_BGRA_8, QCMS_INTENT_PERCEPTUAL); + } + + return gCMSBGRATransform; +} + +qcms_transform* gfxPlatform::GetCMSOSRGBATransform() { + switch (SurfaceFormat::OS_RGBA) { + case SurfaceFormat::B8G8R8A8: + return GetCMSBGRATransform(); + case SurfaceFormat::R8G8B8A8: + return GetCMSRGBATransform(); + default: + // We do not support color management with big endian. + return nullptr; + } +} + +qcms_data_type gfxPlatform::GetCMSOSRGBAType() { + switch (SurfaceFormat::OS_RGBA) { + case SurfaceFormat::B8G8R8A8: + return QCMS_DATA_BGRA_8; + case SurfaceFormat::R8G8B8A8: + return QCMS_DATA_RGBA_8; + default: + // We do not support color management with big endian. + return QCMS_DATA_RGBA_8; + } +} + +/* Shuts down various transforms and profiles for CMS. */ +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 (gCMSBGRATransform) { + qcms_transform_release(gCMSBGRATransform); + gCMSBGRATransform = nullptr; + } + if (gCMSOutputProfile) { + qcms_profile_release(gCMSOutputProfile); + + // handle the aliased case + if (gCMSsRGBProfile == gCMSOutputProfile) gCMSsRGBProfile = nullptr; + gCMSOutputProfile = nullptr; + } + if (gCMSsRGBProfile) { + qcms_profile_release(gCMSsRGBProfile); + gCMSsRGBProfile = nullptr; + } + + // Reset the state variables + gCMSMode = eCMSMode_Off; + gCMSInitialized = false; +} + +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->Flush(); + } + + gfxPlatform::PurgeSkiaFontCache(); +} + +/* static */ +void gfxPlatform::ForceGlobalReflow() { + MOZ_ASSERT(NS_IsMainThread()); + if (XRE_IsParentProcess()) { + // Modify a preference that will trigger reflow everywhere (in all + // content processes, as well as the parent). + static const char kPrefName[] = "font.internaluseonly.changed"; + bool fontInternalChange = Preferences::GetBool(kPrefName, false); + Preferences::SetBool(kPrefName, !fontInternalChange); + } else { + // Send a notification that will be observed by PresShells in this + // process only. + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "font-info-updated", nullptr); + } + } +} + +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 ( +#if defined(XP_MACOSX) + !strcmp(GFX_PREF_CORETEXT_SHAPING, aPref) || +#endif + !strcmp("gfx.font_rendering.ahem_antialias_none", aPref)) { + 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(); + gfxFontCache::GetCache()->NotifyGlyphsChanged(); + } +} + +mozilla::LogModule* gfxPlatform::GetLog(eGfxLog aWhichLog) { + // logs shared across gfx + static LazyLogModule sFontlistLog("fontlist"); + static LazyLogModule sFontInitLog("fontinit"); + static LazyLogModule sTextrunLog("textrun"); + static LazyLogModule sTextrunuiLog("textrunui"); + static LazyLogModule sCmapDataLog("cmapdata"); + static LazyLogModule sTextPerfLog("textperf"); + + switch (aWhichLog) { + case eGfxLog_fontlist: + return sFontlistLog; + case eGfxLog_fontinit: + return sFontInitLog; + case eGfxLog_textrun: + return sTextrunLog; + case eGfxLog_textrunui: + return sTextrunuiLog; + case eGfxLog_cmapdata: + return sCmapDataLog; + case eGfxLog_textperf: + return sTextPerfLog; + } + + MOZ_ASSERT_UNREACHABLE("Unexpected log type"); + return nullptr; +} + +RefPtr<mozilla::gfx::DrawTarget> gfxPlatform::ScreenReferenceDrawTarget() { + return (mScreenReferenceDrawTarget) + ? mScreenReferenceDrawTarget + : gPlatform->CreateOffscreenContentDrawTarget( + IntSize(1, 1), SurfaceFormat::B8G8R8A8, true); +} + +mozilla::gfx::SurfaceFormat gfxPlatform::Optimal2DFormatForContent( + gfxContentType aContent) { + switch (aContent) { + case gfxContentType::COLOR: + switch (GetOffscreenFormat()) { + case SurfaceFormat::A8R8G8B8_UINT32: + return mozilla::gfx::SurfaceFormat::B8G8R8A8; + case SurfaceFormat::X8R8G8B8_UINT32: + return mozilla::gfx::SurfaceFormat::B8G8R8X8; + case SurfaceFormat::R5G6B5_UINT16: + return mozilla::gfx::SurfaceFormat::R5G6B5_UINT16; + default: + MOZ_ASSERT_UNREACHABLE( + "unknown gfxImageFormat for " + "gfxContentType::COLOR"); + return mozilla::gfx::SurfaceFormat::B8G8R8A8; + } + case gfxContentType::ALPHA: + return mozilla::gfx::SurfaceFormat::A8; + case gfxContentType::COLOR_ALPHA: + return mozilla::gfx::SurfaceFormat::B8G8R8A8; + default: + MOZ_ASSERT_UNREACHABLE("unknown gfxContentType"); + return mozilla::gfx::SurfaceFormat::B8G8R8A8; + } +} + +gfxImageFormat gfxPlatform::OptimalFormatForContent(gfxContentType aContent) { + switch (aContent) { + case gfxContentType::COLOR: + return GetOffscreenFormat(); + case gfxContentType::ALPHA: + return SurfaceFormat::A8; + case gfxContentType::COLOR_ALPHA: + return SurfaceFormat::A8R8G8B8_UINT32; + default: + MOZ_ASSERT_UNREACHABLE("unknown gfxContentType"); + return SurfaceFormat::A8R8G8B8_UINT32; + } +} + +/** + * There are a number of layers acceleration (or layers in general) preferences + * that should be consistent for the lifetime of the application (bug 840967). + * As such, we will evaluate them all as soon as one of them is evaluated + * and remember the values. Changing these preferences during the run will + * not have any effect until we restart. + */ +static mozilla::Atomic<bool> sLayersSupportsHardwareVideoDecoding(false); +static bool sLayersHardwareVideoDecodingFailed = false; +static bool sBufferRotationCheckPref = true; + +static mozilla::Atomic<bool> sLayersAccelerationPrefsInitialized(false); + +static void VideoDecodingFailedChangedCallback(const char* aPref, void*) { + sLayersHardwareVideoDecodingFailed = Preferences::GetBool(aPref, false); + gfxPlatform::GetPlatform()->UpdateCanUseHardwareVideoDecoding(); +} + +void gfxPlatform::UpdateCanUseHardwareVideoDecoding() { + if (XRE_IsParentProcess()) { + gfxVars::SetCanUseHardwareVideoDecoding(CanUseHardwareVideoDecoding()); + } +} + +void gfxPlatform::UpdateForceSubpixelAAWherePossible() { + bool forceSubpixelAAWherePossible = + StaticPrefs::gfx_webrender_quality_force_subpixel_aa_where_possible(); + gfxVars::SetForceSubpixelAAWherePossible(forceSubpixelAAWherePossible); +} + +void gfxPlatform::InitAcceleration() { + if (sLayersAccelerationPrefsInitialized) { + return; + } + + InitCompositorAccelerationPrefs(); + + // If this is called for the first time on a non-main thread, we're screwed. + // At the moment there's no explicit guarantee that the main thread calls + // this before the compositor thread, but let's at least make the assumption + // explicit. + MOZ_ASSERT(NS_IsMainThread(), "can only initialize prefs on the main thread"); + + nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo(); + nsCString discardFailureId; + int32_t status; + + if (XRE_IsParentProcess()) { + gfxVars::SetBrowserTabsRemoteAutostart(BrowserTabsRemoteAutostart()); + gfxVars::SetOffscreenFormat(GetOffscreenFormat()); + gfxVars::SetRequiresAcceleratedGLContextForCompositorOGL( + RequiresAcceleratedGLContextForCompositorOGL()); +#ifdef XP_WIN + if (NS_SUCCEEDED( + gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_D3D11_KEYED_MUTEX, + discardFailureId, &status))) { + gfxVars::SetAllowD3D11KeyedMutex(status == nsIGfxInfo::FEATURE_STATUS_OK); + } else { + // If we couldn't properly evaluate the status, err on the side + // of caution and give this functionality to the user. + gfxCriticalNote << "Cannot evaluate keyed mutex feature status"; + gfxVars::SetAllowD3D11KeyedMutex(true); + } + if (StaticPrefs::gfx_direct3d11_use_double_buffering() && + IsWin10OrLater()) { + gfxVars::SetUseDoubleBufferingWithCompositor(true); + } +#endif + } + + if (Preferences::GetBool("media.hardware-video-decoding.enabled", false) && +#ifdef XP_WIN + Preferences::GetBool("media.wmf.dxva.enabled", true) && +#endif + NS_SUCCEEDED( + gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, + discardFailureId, &status))) { + if (status == nsIGfxInfo::FEATURE_STATUS_OK || + StaticPrefs::media_hardware_video_decoding_force_enabled_AtStartup()) { + sLayersSupportsHardwareVideoDecoding = true; + } + } + + sLayersAccelerationPrefsInitialized = true; + + if (XRE_IsParentProcess()) { + Preferences::RegisterCallbackAndCall( + VideoDecodingFailedChangedCallback, + "media.hardware-video-decoding.failed"); + InitGPUProcessPrefs(); + + gfxVars::SetRemoteCanvasEnabled(StaticPrefs::gfx_canvas_remote() && + gfxConfig::IsEnabled(Feature::GPU_PROCESS)); + } +} + +void gfxPlatform::InitGPUProcessPrefs() { + // We want to hide this from about:support, so only set a default if the + // pref is known to be true. + if (!StaticPrefs::layers_gpu_process_enabled_AtStartup() && + !StaticPrefs::layers_gpu_process_force_enabled_AtStartup()) { + return; + } + + FeatureState& gpuProc = gfxConfig::GetFeature(Feature::GPU_PROCESS); + + // We require E10S - otherwise, there is very little benefit to the GPU + // process, since the UI process must still use acceleration for + // performance. + if (!BrowserTabsRemoteAutostart()) { + gpuProc.DisableByDefault(FeatureStatus::Unavailable, + "Multi-process mode is not enabled", + "FEATURE_FAILURE_NO_E10S"_ns); + } else { + gpuProc.SetDefaultFromPref( + StaticPrefs::GetPrefName_layers_gpu_process_enabled(), true, + StaticPrefs::GetPrefDefault_layers_gpu_process_enabled()); + } + + if (StaticPrefs::layers_gpu_process_force_enabled_AtStartup()) { + gpuProc.UserForceEnable("User force-enabled via pref"); + } + + if (IsHeadless()) { + gpuProc.ForceDisable(FeatureStatus::Blocked, "Headless mode is enabled", + "FEATURE_FAILURE_HEADLESS_MODE"_ns); + return; + } + if (InSafeMode()) { + gpuProc.ForceDisable(FeatureStatus::Blocked, "Safe-mode is enabled", + "FEATURE_FAILURE_SAFE_MODE"_ns); + return; + } + if (StaticPrefs::gfx_layerscope_enabled()) { + gpuProc.ForceDisable(FeatureStatus::Blocked, + "LayerScope does not work in the GPU process", + "FEATURE_FAILURE_LAYERSCOPE"_ns); + return; + } + + InitPlatformGPUProcessPrefs(); +} + +void gfxPlatform::InitCompositorAccelerationPrefs() { + const char* acceleratedEnv = PR_GetEnv("MOZ_ACCELERATED"); + + FeatureState& feature = gfxConfig::GetFeature(Feature::HW_COMPOSITING); + + // Base value - does the platform allow acceleration? + if (feature.SetDefault(AccelerateLayersByDefault(), FeatureStatus::Blocked, + "Acceleration blocked by platform")) { + if (StaticPrefs:: + layers_acceleration_disabled_AtStartup_DoNotUseDirectly()) { + feature.UserDisable("Disabled by pref", "FEATURE_FAILURE_COMP_PREF"_ns); + } else if (acceleratedEnv && *acceleratedEnv == '0') { + feature.UserDisable("Disabled by envvar", "FEATURE_FAILURE_COMP_ENV"_ns); + } + } else { + if (acceleratedEnv && *acceleratedEnv == '1') { + feature.UserEnable("Enabled by envvar"); + } + } + + // This has specific meaning elsewhere, so we always record it. + if (StaticPrefs:: + layers_acceleration_force_enabled_AtStartup_DoNotUseDirectly()) { + feature.UserForceEnable("Force-enabled by pref"); + } + + // Safe, headless, and record/replay modes override everything. + if (InSafeMode()) { + feature.ForceDisable(FeatureStatus::Blocked, + "Acceleration blocked by safe-mode", + "FEATURE_FAILURE_COMP_SAFEMODE"_ns); + } + if (IsHeadless()) { + feature.ForceDisable(FeatureStatus::Blocked, + "Acceleration blocked by headless mode", + "FEATURE_FAILURE_COMP_HEADLESSMODE"_ns); + } +} + +/*static*/ +bool gfxPlatform::WebRenderPrefEnabled() { + return StaticPrefs::gfx_webrender_all_AtStartup() || + StaticPrefs::gfx_webrender_enabled_AtStartup_DoNotUseDirectly(); +} + +/*static*/ +bool gfxPlatform::WebRenderEnvvarEnabled() { + const char* env = PR_GetEnv("MOZ_WEBRENDER"); + return (env && *env == '1'); +} + +/*static*/ +bool gfxPlatform::WebRenderEnvvarDisabled() { + const char* env = PR_GetEnv("MOZ_WEBRENDER"); + return (env && *env == '0'); +} + +/* static */ const char* gfxPlatform::WebRenderResourcePathOverride() { + const char* resourcePath = PR_GetEnv("WR_RESOURCE_PATH"); + if (!resourcePath || resourcePath[0] == '\0') { + return nullptr; + } + return resourcePath; +} + +void gfxPlatform::InitWebRenderConfig() { + bool prefEnabled = WebRenderPrefEnabled(); + bool envvarEnabled = WebRenderEnvvarEnabled(); + + // This would ideally be in the nsCSSProps code + // but nsCSSProps is initialized before gfxPlatform + // so it has to be done here. + gfxVars::AddReceiver(&nsCSSProps::GfxVarReceiver()); + + // WR? WR+ => means WR was enabled via gfx.webrender.all.qualified on + // qualified hardware + // WR! WR+ => means WR was enabled via gfx.webrender.{all,enabled} or + // envvar, possibly on unqualified hardware + // In all cases WR- means WR was not enabled, for one of many possible + // reasons. Prior to bug 1523788 landing the gfx.webrender.{all,enabled} + // prefs only worked on Nightly so keep that in mind when looking at older + // crash reports. + ScopedGfxFeatureReporter reporter("WR", prefEnabled || envvarEnabled); + if (!XRE_IsParentProcess()) { + // The parent process runs through all the real decision-making code + // later in this function. For other processes we still want to report + // the state of the feature for crash reports. + if (gfxVars::UseWebRender()) { + // gfxVars doesn't notify receivers when initialized on content processes + // we need to explicitly recompute backdrop-filter's enabled state here. + nsCSSProps::RecomputeEnabledState("layout.css.backdrop-filter.enabled"); + reporter.SetSuccessful(); + } + return; + } + + // Update the gfxConfig feature states. + gfxConfigManager manager; + manager.Init(); + manager.ConfigureWebRender(); + + bool hasHardware = gfxConfig::IsEnabled(Feature::WEBRENDER); + bool hasSoftware = gfxConfig::IsEnabled(Feature::WEBRENDER_SOFTWARE); + bool hasWebRender = hasHardware || hasSoftware; + +#ifdef XP_WIN + if (gfxConfig::IsEnabled(Feature::WEBRENDER_ANGLE)) { + gfxVars::SetUseWebRenderANGLE(hasWebRender); + } +#endif + + if (Preferences::GetBool("gfx.webrender.program-binary-disk", false)) { + gfxVars::SetUseWebRenderProgramBinaryDisk(hasWebRender); + } + + if (StaticPrefs::gfx_webrender_use_optimized_shaders_AtStartup()) { + gfxVars::SetUseWebRenderOptimizedShaders(hasWebRender); + } + + gfxVars::SetUseSoftwareWebRender(!hasHardware && hasSoftware); + + // gfxFeature is not usable in the GPU process, so we use gfxVars to transmit + // this feature + if (hasWebRender) { + gfxVars::SetUseWebRender(true); + reporter.SetSuccessful(); + + Preferences::RegisterPrefixCallbackAndCall(WebRenderDebugPrefChangeCallback, + WR_DEBUG_PREF); + Preferences::RegisterPrefixCallbackAndCall( + WebRendeProfilerUIPrefChangeCallback, + "gfx.webrender.debug.profiler-ui"); + Preferences::RegisterCallback( + WebRenderQualityPrefChangeCallback, + nsDependentCString( + StaticPrefs:: + GetPrefName_gfx_webrender_quality_force_subpixel_aa_where_possible())); + Preferences::RegisterCallback( + WebRenderMultithreadingPrefChangeCallback, + nsDependentCString( + StaticPrefs::GetPrefName_gfx_webrender_enable_multithreading())); + + Preferences::RegisterCallback( + WebRenderBatchingPrefChangeCallback, + nsDependentCString( + StaticPrefs::GetPrefName_gfx_webrender_batching_lookback())); + + if (WebRenderResourcePathOverride()) { + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::IsWebRenderResourcePathOverridden, true); + } + + UpdateForceSubpixelAAWherePossible(); + } + +#ifdef XP_WIN + if (gfxConfig::IsEnabled(Feature::WEBRENDER_DCOMP_PRESENT)) { + gfxVars::SetUseWebRenderDCompWin(true); + } + if (Preferences::GetBool("gfx.webrender.dcomp-video-overlay-win", false)) { + if (IsWin10AnniversaryUpdateOrLater() && + gfxConfig::IsEnabled(Feature::WEBRENDER_COMPOSITOR)) { + MOZ_ASSERT(gfxConfig::IsEnabled(Feature::WEBRENDER_DCOMP_PRESENT)); + gfxVars::SetUseWebRenderDCompVideoOverlayWin(true); + } + } + if (Preferences::GetBool("gfx.webrender.flip-sequential", false)) { + if (UseWebRender() && gfxVars::UseWebRenderANGLE()) { + gfxVars::SetUseWebRenderFlipSequentialWin(true); + } + } + if (Preferences::GetBool("gfx.webrender.triple-buffering.enabled", false)) { + if (gfxVars::UseWebRenderDCompWin() || + gfxVars::UseWebRenderFlipSequentialWin()) { + gfxVars::SetUseWebRenderTripleBufferingWin(true); + } + } +#endif + + if (gfxConfig::IsEnabled(Feature::WEBRENDER_COMPOSITOR)) { + gfxVars::SetUseWebRenderCompositor(true); + } + + Telemetry::ScalarSet( + Telemetry::ScalarID::GFX_OS_COMPOSITOR, + gfx::gfxConfig::IsEnabled(gfx::Feature::WEBRENDER_COMPOSITOR)); + + if (gfxConfig::IsEnabled(Feature::WEBRENDER_PARTIAL)) { + gfxVars::SetWebRenderMaxPartialPresentRects( + StaticPrefs::gfx_webrender_max_partial_present_rects_AtStartup()); + } + + // Set features that affect WR's RendererOptions + gfxVars::SetUseGLSwizzle( + IsFeatureSupported(nsIGfxInfo::FEATURE_GL_SWIZZLE, true)); + gfxVars::SetUseWebRenderScissoredCacheClears(IsFeatureSupported( + nsIGfxInfo::FEATURE_WEBRENDER_SCISSORED_CACHE_CLEARS, true)); + + // The RemoveShaderCacheFromDiskIfNecessary() needs to be called after + // WebRenderConfig initialization. + gfxUtils::RemoveShaderCacheFromDiskIfNecessary(); +} + +void gfxPlatform::InitWebGLConfig() { + // Depends on InitWebRenderConfig() for UseWebRender(). + + if (!XRE_IsParentProcess()) return; + + const nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo(); + + const auto IsFeatureOk = [&](const int32_t feature) { + nsCString discardFailureId; + int32_t status; + MOZ_RELEASE_ASSERT(NS_SUCCEEDED( + gfxInfo->GetFeatureStatus(feature, discardFailureId, &status))); + return (status == nsIGfxInfo::FEATURE_STATUS_OK); + }; + + gfxVars::SetAllowWebgl2(IsFeatureOk(nsIGfxInfo::FEATURE_WEBGL2)); + gfxVars::SetWebglAllowWindowsNativeGl( + IsFeatureOk(nsIGfxInfo::FEATURE_WEBGL_OPENGL)); + gfxVars::SetAllowWebglAccelAngle( + IsFeatureOk(nsIGfxInfo::FEATURE_WEBGL_ANGLE)); + + if (kIsMacOS) { + // Avoid crash for Intel HD Graphics 3000 on OSX. (Bug 1413269) + nsString vendorID, deviceID; + gfxInfo->GetAdapterVendorID(vendorID); + gfxInfo->GetAdapterDeviceID(deviceID); + if (vendorID.EqualsLiteral("0x8086") && + (deviceID.EqualsLiteral("0x0116") || + deviceID.EqualsLiteral("0x0126"))) { + gfxVars::SetWebglAllowCoreProfile(false); + } + } + + { + bool allowWebGLOop = + IsFeatureOk(nsIGfxInfo::FEATURE_ALLOW_WEBGL_OUT_OF_PROCESS); + + const bool threadsafeGl = IsFeatureOk(nsIGfxInfo::FEATURE_THREADSAFE_GL); + if (gfxVars::UseWebRender() && !threadsafeGl) { + allowWebGLOop = false; + } + + gfxVars::SetAllowWebglOop(allowWebGLOop); + } + + if (kIsAndroid) { + // Don't enable robust buffer access on Adreno 630 devices. + // It causes the linking of some shaders to fail. See bug 1485441. + nsAutoString renderer; + gfxInfo->GetAdapterDeviceID(renderer); + if (renderer.Find("Adreno (TM) 630") != -1) { + gfxVars::SetAllowEglRbab(false); + } + } +} + +void gfxPlatform::InitWebGPUConfig() { + FeatureState& feature = gfxConfig::GetFeature(Feature::WEBGPU); + feature.SetDefaultFromPref("dom.webgpu.enabled", true, false); +#ifndef NIGHTLY_BUILD + feature.ForceDisable(FeatureStatus::Blocked, + "WebGPU can only be enabled in nightly", + "WEBGPU_DISABLE_NON_NIGHTLY"_ns); +#endif + if (!UseWebRender()) { + feature.ForceDisable(FeatureStatus::UnavailableNoWebRender, + "WebGPU can't present without WebRender", + "FEATURE_FAILURE_WEBGPU_NEED_WEBRENDER"_ns); + } +} + +void gfxPlatform::InitOMTPConfig() { + ScopedGfxFeatureReporter reporter("OMTP"); + + FeatureState& omtp = gfxConfig::GetFeature(Feature::OMTP); + int32_t paintWorkerCount = PaintThread::CalculatePaintWorkerCount(); + + if (!XRE_IsParentProcess()) { + // The parent process runs through all the real decision-making code + // later in this function. For other processes we still want to report + // the state of the feature for crash reports. + if (gfxVars::UseOMTP()) { + reporter.SetSuccessful(paintWorkerCount); + } + return; + } + + omtp.SetDefaultFromPref("layers.omtp.enabled", true, + Preferences::GetBool("layers.omtp.enabled", false, + PrefValueKind::Default)); + + int32_t cpuCores = PR_GetNumberOfProcessors(); + const uint64_t kMinSystemMemory = 2147483648; // 2 GB + if (cpuCores <= 2) { + omtp.ForceDisable(FeatureStatus::Broken, + "OMTP is not supported with <= 2 cores", + "FEATURE_FAILURE_OMTP_FEW_CORES"_ns); + } else if (mTotalPhysicalMemory < kMinSystemMemory) { + omtp.ForceDisable(FeatureStatus::Broken, + "OMTP is not supported with < 2 GB RAM", + "FEATURE_FAILURE_OMTP_LOW_PMEM"_ns); + } else if (mTotalVirtualMemory < kMinSystemMemory) { + omtp.ForceDisable(FeatureStatus::Broken, + "OMTP is not supported with < 2 GB VMEM", + "FEATURE_FAILURE_OMTP_LOW_VMEM"_ns); + } + + if (mContentBackend == BackendType::CAIRO) { + omtp.ForceDisable(FeatureStatus::Broken, + "OMTP is not supported when using cairo", + "FEATURE_FAILURE_COMP_PREF"_ns); + } + + if (InSafeMode()) { + omtp.ForceDisable(FeatureStatus::Blocked, "OMTP blocked by safe-mode", + "FEATURE_FAILURE_COMP_SAFEMODE"_ns); + } + + if (omtp.IsEnabled()) { + gfxVars::SetUseOMTP(true); + reporter.SetSuccessful(paintWorkerCount); + } +} + +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 && + StaticPrefs::layers_bufferrotation_enabled_AtStartup(); +} + +void gfxPlatform::DisableBufferRotation() { + MutexAutoLock autoLock(*gGfxPlatformPrefsLock); + + sBufferRotationCheckPref = false; +} + +/* static */ +bool gfxPlatform::UsesOffMainThreadCompositing() { + if (XRE_GetProcessType() == GeckoProcessType_GPU) { + return true; + } + + static bool firstTime = true; + static bool result = false; + + if (firstTime) { + MOZ_ASSERT(sLayersAccelerationPrefsInitialized); + result = gfxVars::BrowserTabsRemoteAutostart() || + !StaticPrefs:: + layers_offmainthreadcomposition_force_disabled_AtStartup(); +#if defined(MOZ_WIDGET_GTK) + // Linux users who chose OpenGL are being included in OMTC + result |= StaticPrefs:: + layers_acceleration_force_enabled_AtStartup_DoNotUseDirectly(); + +#endif + firstTime = false; + } + + return result; +} + +bool gfxPlatform::UsesTiling() const { + bool usesSkia = GetDefaultContentBackend() == BackendType::SKIA; + + // We can't just test whether the PaintThread is initialized here because + // this function is used when initializing the PaintThread. So instead we + // check the conditions that enable OMTP with parallel painting. + bool usesPOMTP = XRE_IsContentProcess() && gfxVars::UseOMTP() && + (StaticPrefs::layers_omtp_paint_workers_AtStartup() == -1 || + StaticPrefs::layers_omtp_paint_workers_AtStartup() > 1); + + return StaticPrefs::layers_enable_tiles_AtStartup() || + (StaticPrefs::layers_enable_tiles_if_skia_pomtp_AtStartup() && + usesSkia && usesPOMTP); +} + +bool gfxPlatform::ContentUsesTiling() const { + BackendPrefsData data = GetBackendPrefs(); + BackendType contentBackend = GetContentBackendPref(data.mContentBitmask); + if (contentBackend == BackendType::NONE) { + contentBackend = data.mContentDefault; + } + + bool contentUsesSkia = contentBackend == BackendType::SKIA; + bool contentUsesPOMTP = + gfxVars::UseOMTP() && + (StaticPrefs::layers_omtp_paint_workers_AtStartup() == -1 || + StaticPrefs::layers_omtp_paint_workers_AtStartup() > 1); + + return StaticPrefs::layers_enable_tiles_AtStartup() || + (StaticPrefs::layers_enable_tiles_if_skia_pomtp_AtStartup() && + contentUsesSkia && contentUsesPOMTP); +} + +/*** + * The preference "layout.frame_rate" has 3 meanings depending on the value: + * + * -1 = Auto (default), use hardware vsync or software vsync @ 60 hz if hw + * vsync fails. + * 0 = ASAP mode - used during talos testing. + * X = Software vsync at a rate of X times per second. + */ +already_AddRefed<mozilla::gfx::VsyncSource> +gfxPlatform::CreateHardwareVsyncSource() { + RefPtr<mozilla::gfx::VsyncSource> softwareVsync = new SoftwareVsyncSource(); + return softwareVsync.forget(); +} + +/* static */ +bool gfxPlatform::IsInLayoutAsapMode() { + // There are 2 modes of ASAP mode. + // 1 is that the refresh driver and compositor are in lock step + // the second is that the compositor goes ASAP and the refresh driver + // goes at whatever the configurated rate is. This only checks the version + // talos uses, which is the refresh driver and compositor are in lockstep. + return StaticPrefs::layout_frame_rate() == 0; +} + +/* static */ +bool gfxPlatform::ForceSoftwareVsync() { + return StaticPrefs::layout_frame_rate() > 0; +} + +/* static */ +int gfxPlatform::GetSoftwareVsyncRate() { + int preferenceRate = StaticPrefs::layout_frame_rate(); + if (preferenceRate <= 0) { + return gfxPlatform::GetDefaultFrameRate(); + } + return preferenceRate; +} + +/* static */ +int gfxPlatform::GetDefaultFrameRate() { return 60; } + +/* static */ +void gfxPlatform::ReInitFrameRate() { + if (XRE_IsParentProcess()) { + RefPtr<VsyncSource> oldSource = gPlatform->mVsyncSource; + + // Start a new one: + if (gfxPlatform::ForceSoftwareVsync()) { + gPlatform->mVsyncSource = + (gPlatform)->gfxPlatform::CreateHardwareVsyncSource(); + } else { + gPlatform->mVsyncSource = gPlatform->CreateHardwareVsyncSource(); + } + // Tidy up old vsync source. + if (oldSource) { + oldSource->MoveListenersToNewSource(gPlatform->mVsyncSource); + oldSource->Shutdown(); + } + } +} + +void gfxPlatform::GetAzureBackendInfo(mozilla::widget::InfoObject& aObj) { + if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { + aObj.DefineProperty("AzureCanvasBackend (UI Process)", + GetBackendName(mPreferredCanvasBackend)); + aObj.DefineProperty("AzureFallbackCanvasBackend (UI Process)", + GetBackendName(mFallbackCanvasBackend)); + aObj.DefineProperty("AzureContentBackend (UI Process)", + GetBackendName(mContentBackend)); + + // Assume content process' backend prefs. + BackendPrefsData data = GetBackendPrefs(); + BackendType canvasBackend = GetCanvasBackendPref(data.mCanvasBitmask); + if (canvasBackend == BackendType::NONE) { + canvasBackend = data.mCanvasDefault; + } + BackendType contentBackend = GetContentBackendPref(data.mContentBitmask); + if (contentBackend == BackendType::NONE) { + contentBackend = data.mContentDefault; + } + aObj.DefineProperty("AzureCanvasBackend", GetBackendName(canvasBackend)); + aObj.DefineProperty("AzureContentBackend", GetBackendName(contentBackend)); + } else { + aObj.DefineProperty("AzureCanvasBackend", + GetBackendName(mPreferredCanvasBackend)); + aObj.DefineProperty("AzureFallbackCanvasBackend", + GetBackendName(mFallbackCanvasBackend)); + aObj.DefineProperty("AzureContentBackend", GetBackendName(mContentBackend)); + } +} + +void gfxPlatform::GetApzSupportInfo(mozilla::widget::InfoObject& aObj) { + if (!gfxPlatform::AsyncPanZoomEnabled()) { + return; + } + + if (SupportsApzWheelInput()) { + aObj.DefineProperty("ApzWheelInput", 1); + } + + if (SupportsApzTouchInput()) { + aObj.DefineProperty("ApzTouchInput", 1); + } + + if (SupportsApzDragInput()) { + aObj.DefineProperty("ApzDragInput", 1); + } + + if (SupportsApzKeyboardInput() && + !StaticPrefs::accessibility_browsewithcaret()) { + aObj.DefineProperty("ApzKeyboardInput", 1); + } + + if (SupportsApzAutoscrolling()) { + aObj.DefineProperty("ApzAutoscrollInput", 1); + } + + if (SupportsApzZooming()) { + aObj.DefineProperty("ApzZoomingInput", 1); + } +} + +void gfxPlatform::GetTilesSupportInfo(mozilla::widget::InfoObject& aObj) { + if (!StaticPrefs::layers_enable_tiles_AtStartup()) { + return; + } + + IntSize tileSize = gfxVars::TileSize(); + aObj.DefineProperty("TileHeight", tileSize.height); + aObj.DefineProperty("TileWidth", tileSize.width); +} + +void gfxPlatform::GetFrameStats(mozilla::widget::InfoObject& aObj) { + uint32_t i = 0; + for (FrameStats& f : mFrameStats) { + nsPrintfCString name("Slow Frame #%02u", ++i); + + nsPrintfCString value( + "Frame %" PRIu64 + "(%s) CONTENT_FRAME_TIME %d - Transaction start %f, main-thread time " + "%f, full paint time %f, Skipped composites %u, Composite start %f, " + "Resource upload time %f, GPU cache upload time %f, Render time %f, " + "Composite time %f", + f.id().mId, f.url().get(), f.contentFrameTime(), + (f.transactionStart() - f.refreshStart()).ToMilliseconds(), + (f.fwdTime() - f.transactionStart()).ToMilliseconds(), + f.sceneBuiltTime() + ? (f.sceneBuiltTime() - f.transactionStart()).ToMilliseconds() + : 0.0, + f.skippedComposites(), + (f.compositeStart() - f.refreshStart()).ToMilliseconds(), + f.resourceUploadTime(), f.gpuCacheUploadTime(), + (f.compositeEnd() - f.renderStart()).ToMilliseconds(), + (f.compositeEnd() - f.compositeStart()).ToMilliseconds()); + aObj.DefineProperty(name.get(), value.get()); + } +} + +void gfxPlatform::GetCMSSupportInfo(mozilla::widget::InfoObject& aObj) { + nsTArray<uint8_t> outputProfileData = + gfxPlatform::GetPlatform()->GetPlatformCMSOutputProfileData(); + if (outputProfileData.IsEmpty()) { + nsPrintfCString msg("Empty profile data"); + aObj.DefineProperty("CMSOutputProfile", msg.get()); + return; + } + + // Some profiles can be quite large. We don't want to include giant profiles + // by default in about:support. For now, we only accept less than 8kiB. + const size_t kMaxProfileSize = 8192; + if (outputProfileData.Length() >= kMaxProfileSize) { + nsPrintfCString msg("%zu bytes, too large", outputProfileData.Length()); + aObj.DefineProperty("CMSOutputProfile", msg.get()); + return; + } + + nsString encodedProfile; + nsresult rv = + Base64Encode(reinterpret_cast<const char*>(outputProfileData.Elements()), + outputProfileData.Length(), encodedProfile); + if (!NS_SUCCEEDED(rv)) { + nsPrintfCString msg("base64 encode failed 0x%08x", + static_cast<uint32_t>(rv)); + aObj.DefineProperty("CMSOutputProfile", msg.get()); + return; + } + + aObj.DefineProperty("CMSOutputProfile", encodedProfile); +} + +void gfxPlatform::GetDisplayInfo(mozilla::widget::InfoObject& aObj) { + nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo(); + + nsTArray<nsString> displayInfo; + auto rv = gfxInfo->GetDisplayInfo(displayInfo); + if (NS_SUCCEEDED(rv)) { + size_t displayCount = displayInfo.Length(); + aObj.DefineProperty("DisplayCount", displayCount); + + for (size_t i = 0; i < displayCount; i++) { + nsPrintfCString name("Display%zu", i); + aObj.DefineProperty(name.get(), displayInfo[i]); + } + } + + GetPlatformDisplayInfo(aObj); +} + +class FrameStatsComparator { + public: + bool Equals(const FrameStats& aA, const FrameStats& aB) const { + return aA.contentFrameTime() == aB.contentFrameTime(); + } + // Reverse the condition here since we want the array sorted largest to + // smallest. + bool LessThan(const FrameStats& aA, const FrameStats& aB) const { + return aA.contentFrameTime() > aB.contentFrameTime(); + } +}; + +void gfxPlatform::NotifyFrameStats(nsTArray<FrameStats>&& aFrameStats) { + if (!StaticPrefs::gfx_logging_slow_frames_enabled_AtStartup()) { + return; + } + + FrameStatsComparator comp; + for (FrameStats& f : aFrameStats) { + mFrameStats.InsertElementSorted(f, comp); + } + if (mFrameStats.Length() > 10) { + mFrameStats.SetLength(10); + } +} + +/*static*/ +uint32_t gfxPlatform::TargetFrameRate() { + if (gPlatform && gPlatform->mVsyncSource) { + VsyncSource::Display& display = gPlatform->mVsyncSource->GetGlobalDisplay(); + return round(1000.0 / display.GetVsyncRate().ToMilliseconds()); + } + return 0; +} + +/* static */ +bool gfxPlatform::UseDesktopZoomingScrollbars() { + return StaticPrefs::apz_allow_zooming() && + !StaticPrefs::apz_force_disable_desktop_zooming_scrollbars(); +} + +/*static*/ +bool gfxPlatform::AsyncPanZoomEnabled() { +#if !defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_WIDGET_UIKIT) + // For XUL applications (everything but Firefox on Android) + // we only want to use APZ when E10S is enabled. If + // we ever get input events off the main thread we can consider relaxing + // this requirement. + if (!BrowserTabsRemoteAutostart()) { + return false; + } +#endif +#ifdef MOZ_WIDGET_ANDROID + return true; +#else + // If Fission is enabled, OOP iframes require APZ for hittest. So, we + // need to forcibly enable APZ in that case for avoiding users confused. + if (FissionAutostart()) { + return true; + } + return StaticPrefs:: + layers_async_pan_zoom_enabled_AtStartup_DoNotUseDirectly(); +#endif +} + +/*static*/ +bool gfxPlatform::PerfWarnings() { + return StaticPrefs::gfx_perf_warnings_enabled(); +} + +void gfxPlatform::GetAcceleratedCompositorBackends( + nsTArray<LayersBackend>& aBackends) { + if (gfxConfig::IsEnabled(Feature::OPENGL_COMPOSITING)) { + aBackends.AppendElement(LayersBackend::LAYERS_OPENGL); + } else { + static int tell_me_once = 0; + if (!tell_me_once) { + NS_WARNING("OpenGL-accelerated layers are not supported on this system"); + tell_me_once = 1; + } +#ifdef MOZ_WIDGET_ANDROID + MOZ_CRASH( + "OpenGL-accelerated layers are a hard requirement on this platform. " + "Cannot continue without support for them"); +#endif + } +} + +void gfxPlatform::GetCompositorBackends( + bool useAcceleration, nsTArray<mozilla::layers::LayersBackend>& aBackends) { + if (useAcceleration) { + GetAcceleratedCompositorBackends(aBackends); + } + aBackends.AppendElement(LayersBackend::LAYERS_BASIC); +} + +void gfxPlatform::NotifyCompositorCreated(LayersBackend aBackend) { + if (mCompositorBackend == aBackend) { + return; + } + + if (mCompositorBackend != LayersBackend::LAYERS_NONE) { + gfxCriticalNote << "Compositors might be mixed (" << int(mCompositorBackend) + << "," << int(aBackend) << ")"; + } + + // Set the backend before we notify so it's available immediately. + mCompositorBackend = aBackend; + + if (XRE_IsParentProcess()) { + Telemetry::ScalarSet( + Telemetry::ScalarID::GFX_COMPOSITOR, + NS_ConvertUTF8toUTF16(GetLayersBackendName(mCompositorBackend))); + + Telemetry::ScalarSet( + Telemetry::ScalarID::GFX_FEATURE_WEBRENDER, + NS_ConvertUTF8toUTF16(gfxConfig::GetFeature(gfx::Feature::WEBRENDER) + .GetStatusAndFailureIdString())); + } + + // Notify that we created a compositor, so telemetry can update. + NS_DispatchToMainThread( + NS_NewRunnableFunction("gfxPlatform::NotifyCompositorCreated", [] { + if (nsCOMPtr<nsIObserverService> obsvc = + services::GetObserverService()) { + obsvc->NotifyObservers(nullptr, "compositor:created", nullptr); + } + })); +} + +/* static */ +void gfxPlatform::DisableWebRender(FeatureStatus aStatus, const char* aMessage, + const nsACString& aFailureId) { + if (gfxConfig::IsEnabled(Feature::WEBRENDER)) { + gfxConfig::GetFeature(Feature::WEBRENDER) + .ForceDisable(aStatus, aMessage, aFailureId); + } + // TODO(aosmond): When WebRender Software replaces Basic, we must not disable + // it because of GPU process crashes, etc. + if (gfxConfig::IsEnabled(Feature::WEBRENDER_SOFTWARE)) { + gfxConfig::GetFeature(Feature::WEBRENDER_SOFTWARE) + .ForceDisable(aStatus, aMessage, aFailureId); + } + gfxVars::SetUseWebRender(false); + gfxVars::SetUseSoftwareWebRender(false); +} + +/* static */ +void gfxPlatform::NotifyGPUProcessDisabled() { + DisableWebRender(FeatureStatus::Unavailable, "GPU Process is disabled", + "FEATURE_FAILURE_GPU_PROCESS_DISABLED"_ns); + gfxVars::SetRemoteCanvasEnabled(false); +} + +void gfxPlatform::FetchAndImportContentDeviceData() { + MOZ_ASSERT(XRE_IsContentProcess()); + + if (gContentDeviceInitData) { + ImportContentDeviceData(*gContentDeviceInitData); + return; + } + + mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton(); + + mozilla::gfx::ContentDeviceData data; + cc->SendGetGraphicsDeviceInitData(&data); + + ImportContentDeviceData(data); +} + +void gfxPlatform::ImportContentDeviceData( + const mozilla::gfx::ContentDeviceData& aData) { + MOZ_ASSERT(XRE_IsContentProcess()); + + const DevicePrefs& prefs = aData.prefs(); + gfxConfig::Inherit(Feature::HW_COMPOSITING, prefs.hwCompositing()); + gfxConfig::Inherit(Feature::OPENGL_COMPOSITING, prefs.oglCompositing()); +} + +void gfxPlatform::BuildContentDeviceData( + mozilla::gfx::ContentDeviceData* aOut) { + MOZ_ASSERT(XRE_IsParentProcess()); + + // Make sure our settings are synchronized from the GPU process. + 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()); + gfxConfig::ImportChange(Feature::ADVANCED_LAYERS, aData.advancedLayers()); +} + +bool gfxPlatform::SupportsApzTouchInput() const { + return dom::TouchEvent::PrefEnabled(nullptr); +} + +bool gfxPlatform::SupportsApzDragInput() const { + return StaticPrefs::apz_drag_enabled(); +} + +bool gfxPlatform::SupportsApzKeyboardInput() const { + return StaticPrefs::apz_keyboard_enabled_AtStartup(); +} + +bool gfxPlatform::SupportsApzAutoscrolling() const { + return StaticPrefs::apz_autoscroll_enabled(); +} + +bool gfxPlatform::SupportsApzZooming() const { + return StaticPrefs::apz_allow_zooming(); +} + +void gfxPlatform::InitOpenGLConfig() { +#ifdef XP_WIN + // Don't enable by default on Windows, since it could show up in about:support + // even though it'll never get used. Only attempt if user enables the pref + if (!Preferences::GetBool("layers.prefer-opengl")) { + return; + } +#endif + + FeatureState& openGLFeature = + gfxConfig::GetFeature(Feature::OPENGL_COMPOSITING); + + // Check to see hw comp supported + if (!gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) { + openGLFeature.DisableByDefault(FeatureStatus::Unavailable, + "Hardware compositing is disabled", + "FEATURE_FAILURE_OPENGL_NEED_HWCOMP"_ns); + return; + } + +#ifdef XP_WIN + openGLFeature.SetDefaultFromPref( + StaticPrefs::GetPrefName_layers_prefer_opengl(), true, + StaticPrefs::GetPrefDefault_layers_prefer_opengl()); +#else + openGLFeature.EnableByDefault(); +#endif + + // When layers acceleration is force-enabled, enable it even for blocklisted + // devices. + if (StaticPrefs:: + layers_acceleration_force_enabled_AtStartup_DoNotUseDirectly()) { + openGLFeature.UserForceEnable("Force-enabled by pref"); + return; + } + + nsCString message; + nsCString failureId; + if (!IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_OPENGL_LAYERS, &message, + failureId)) { + openGLFeature.Disable(FeatureStatus::Blocklisted, message.get(), failureId); + } +} + +bool gfxPlatform::IsGfxInfoStatusOkay(int32_t aFeature, nsCString* aOutMessage, + nsCString& aFailureId) { + nsCOMPtr<nsIGfxInfo> gfxInfo = 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 0000000000..a76169797c --- /dev/null +++ b/gfx/thebes/gfxPlatform.h @@ -0,0 +1,1026 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_PLATFORM_H +#define GFX_PLATFORM_H + +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/gfx/Types.h" +#include "nsTArray.h" +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsUnicodeScriptCodes.h" + +#include "gfxTelemetry.h" +#include "gfxTypes.h" +#include "gfxSkipChars.h" + +#include "qcms.h" + +#include "mozilla/RefPtr.h" +#include "GfxInfoCollector.h" + +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/layers/MemoryPressureObserver.h" + +class gfxASurface; +class gfxFont; +class gfxFontGroup; +struct gfxFontStyle; +class gfxUserFontSet; +class gfxFontEntry; +class gfxPlatformFontList; +class gfxTextRun; +class nsIURI; +class nsAtom; +class nsIObserver; +class SRGBOverrideObserver; +class gfxTextPerfMetrics; +struct FontMatchingStats; +typedef struct FT_LibraryRec_* FT_Library; + +namespace mozilla { +class FontFamilyList; +class LogModule; +namespace layers { +class FrameStats; +} +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 dom { +class SystemFontListEntry; +} +} // 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 +}; + +// Used during font matching to express a preference, if any, for whether +// to use a font that will present a color or monochrome glyph. +enum class eFontPresentation : uint8_t { + // Character does not have the emoji property, so no special heuristics + // apply during font selection. + Any = 0, + // Character is potentially emoji, but Text-style presentation has been + // explicitly requested using VS15. + Text = 1, + // Character has Emoji-style presentation by default (but an author- + // provided webfont will be used even if it is not color). + EmojiDefault = 2, + // Character explicitly requires Emoji-style presentation due to VS16 or + // skin-tone codepoint. + EmojiExplicit = 3 +}; + +inline bool PrefersColor(eFontPresentation aPresentation) { + return aPresentation >= eFontPresentation::EmojiDefault; +} + +// when searching through pref langs, max number of pref langs +const uint32_t kMaxLenPrefLangList = 32; + +#define UNINITIALIZED_VALUE (-1) + +inline const char* GetBackendName(mozilla::gfx::BackendType aBackend) { + switch (aBackend) { + case mozilla::gfx::BackendType::DIRECT2D: + return "direct2d"; + case mozilla::gfx::BackendType::CAIRO: + return "cairo"; + case mozilla::gfx::BackendType::SKIA: + return "skia"; + case mozilla::gfx::BackendType::RECORDING: + return "recording"; + case mozilla::gfx::BackendType::DIRECT2D1_1: + return "direct2d 1.1"; + case mozilla::gfx::BackendType::WEBRENDER_TEXT: + return "webrender text"; + case mozilla::gfx::BackendType::CAPTURE: + return "capture"; + case mozilla::gfx::BackendType::NONE: + return "none"; + case mozilla::gfx::BackendType::BACKEND_LAST: + return "invalid"; + } + MOZ_CRASH("Incomplete switch"); +} + +enum class DeviceResetReason { + OK = 0, // No reset. + HUNG, // Windows specific, guilty device reset. + REMOVED, // Windows specific, device removed or driver upgraded. + RESET, // Guilty device reset. + DRIVER_ERROR, // Innocent device reset. + INVALID_CALL, // Windows specific, guilty device reset. + OUT_OF_MEMORY, + FORCED_RESET, // Simulated device reset. + OTHER, // Unrecognized reason for device reset. + D3D9_RESET, // Windows specific, not used. + NVIDIA_VIDEO, // Linux specific, NVIDIA video memory was reset. + UNKNOWN, // GL specific, unknown if guilty or innocent. +}; + +enum class ForcedDeviceResetReason { + OPENSHAREDHANDLE = 0, + COMPOSITOR_UPDATED, +}; + +struct BackendPrefsData { + uint32_t mCanvasBitmask = 0; + mozilla::gfx::BackendType mCanvasDefault = mozilla::gfx::BackendType::NONE; + uint32_t mContentBitmask = 0; + mozilla::gfx::BackendType mContentDefault = mozilla::gfx::BackendType::NONE; +}; + +class gfxPlatform : public mozilla::layers::MemoryPressureListener { + friend class SRGBOverrideObserver; + + public: + typedef mozilla::StretchRange StretchRange; + typedef mozilla::SlantStyleRange SlantStyleRange; + typedef mozilla::WeightRange WeightRange; + typedef mozilla::gfx::sRGBColor sRGBColor; + typedef mozilla::gfx::DeviceColor DeviceColor; + typedef mozilla::gfx::DataSourceSurface DataSourceSurface; + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::IntSize IntSize; + typedef mozilla::gfx::SourceSurface SourceSurface; + typedef mozilla::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(); + + /** + * Initialize gfxPlatform (if not already done) in a child process, with + * the provided ContentDeviceData. + */ + static void InitChild(const mozilla::gfx::ContentDeviceData& aData); + + static void InitLayersIPC(); + static void ShutdownLayersIPC(); + + /** + * Initialize ScrollMetadata statics. Does not depend on gfxPlatform. + */ + static void InitNullMetadata(); + + static int32_t MaxTextureSize(); + static int32_t MaxAllocSize(); + static void InitMoz2DLogging(); + + static bool IsHeadless(); + + static bool UseWebRender(); + + static bool CanMigrateMacGPUs(); + + /** + * Create an offscreen surface of the given dimensions + * and image format. + */ + virtual already_AddRefed<gfxASurface> CreateOffscreenSurface( + const IntSize& aSize, gfxImageFormat aFormat) = 0; + + /** + * Beware that this method may return DrawTargets which are not fully + * supported on the current platform and might fail silently in subtle ways. + * This is a massive potential footgun. You should only use these methods for + * canvas drawing really. Use extreme caution if you use them for content + * where you are not 100% sure we support the DrawTarget we get back. See + * SupportsAzureContentForDrawTarget. + */ + static already_AddRefed<DrawTarget> CreateDrawTargetForSurface( + gfxASurface* aSurface, const mozilla::gfx::IntSize& aSize); + + /* + * Creates a SourceSurface for a gfxASurface. This function does no caching, + * so the caller should cache the gfxASurface if it will be used frequently. + * The returned surface keeps a reference to aTarget, so it is OK to keep the + * surface, even if aTarget changes. + * aTarget should not keep a reference to the returned surface because that + * will cause a cycle. + * + * This function is static so that it can be accessed from + * PluginInstanceChild (where we can't call gfxPlatform::GetPlatform() + * because the prefs service can only be accessed from the main process). + * + * aIsPlugin is used to tell the backend that they can optimize this surface + * specifically because it's used for a plugin. This is mostly for Skia. + */ + static already_AddRefed<SourceSurface> GetSourceSurfaceForSurface( + RefPtr<mozilla::gfx::DrawTarget> aTarget, gfxASurface* aSurface, + bool aIsPlugin = false); + + static void ClearSourceSurfaceForSurface(gfxASurface* aSurface); + + static already_AddRefed<DataSourceSurface> GetWrappedDataSourceSurface( + gfxASurface* aSurface); + + already_AddRefed<DrawTarget> CreateOffscreenContentDrawTarget( + const mozilla::gfx::IntSize& aSize, mozilla::gfx::SurfaceFormat aFormat, + bool aFallback = false); + + already_AddRefed<DrawTarget> CreateOffscreenCanvasDrawTarget( + const mozilla::gfx::IntSize& aSize, mozilla::gfx::SurfaceFormat aFormat); + + already_AddRefed<DrawTarget> CreateSimilarSoftwareDrawTarget( + DrawTarget* aDT, const IntSize& aSize, + mozilla::gfx::SurfaceFormat aFormat); + + static already_AddRefed<DrawTarget> CreateDrawTargetForData( + unsigned char* aData, const mozilla::gfx::IntSize& aSize, int32_t aStride, + mozilla::gfx::SurfaceFormat aFormat, bool aUninitialized = false); + + /** + * Returns true if we should use Azure to render content with aTarget. For + * example, it is possible that we are using Direct2D for rendering and thus + * using Azure. But we want to render to a CairoDrawTarget, in which case + * SupportsAzureContent will return true but SupportsAzureContentForDrawTarget + * will return false. + */ + bool SupportsAzureContentForDrawTarget(mozilla::gfx::DrawTarget* aTarget); + + bool SupportsAzureContentForType(mozilla::gfx::BackendType aType) { + return BackendTypeBit(aType) & mContentBackendBitmask; + } + + static bool AsyncPanZoomEnabled(); + + virtual void GetAzureBackendInfo(mozilla::widget::InfoObject& aObj); + void GetApzSupportInfo(mozilla::widget::InfoObject& aObj); + void GetTilesSupportInfo(mozilla::widget::InfoObject& aObj); + void GetFrameStats(mozilla::widget::InfoObject& aObj); + void GetCMSSupportInfo(mozilla::widget::InfoObject& aObj); + void GetDisplayInfo(mozilla::widget::InfoObject& aObj); + + // Get the default content backend that will be used with the default + // compositor. If the compositor is known when calling this function, + // GetContentBackendFor() should be called instead. + mozilla::gfx::BackendType GetDefaultContentBackend() const { + return mContentBackend; + } + + /// Return the software backend to use by default. + mozilla::gfx::BackendType GetSoftwareBackend() { return mSoftwareBackend; } + + // Return the best content backend available that is compatible with the + // given layers backend. + virtual mozilla::gfx::BackendType GetContentBackendFor( + mozilla::layers::LayersBackend aLayers) { + return mContentBackend; + } + + virtual mozilla::gfx::BackendType GetPreferredCanvasBackend() { + return mPreferredCanvasBackend; + } + mozilla::gfx::BackendType GetFallbackCanvasBackend() { + return mFallbackCanvasBackend; + } + + /* + * Font bits + */ + + /** + * Fill aListOfFonts with the results of querying the list of font names + * that correspond to the given language group or generic font family + * (or both, or neither). + */ + virtual nsresult GetFontList(nsAtom* aLangGroup, + const nsACString& aGenericFamily, + nsTArray<nsString>& aListOfFonts); + + /** + * Fill aFontList with a list of SystemFontListEntry records for the + * available fonts on the platform; used to pass the list from chrome to + * content process. Currently implemented only on MacOSX and Linux. + */ + virtual void ReadSystemFontList( + nsTArray<mozilla::dom::SystemFontListEntry>* aFontList) {} + + /** + * Rebuilds the system font lists (if aFullRebuild is true), or just notifies + * content that the list has changed but existing memory mappings are still + * valid (aFullRebuild is false). + */ + nsresult UpdateFontList(bool aFullRebuild = true); + + /** + * Create the platform font-list object (gfxPlatformFontList concrete + * subclass). This function is responsible to create the appropriate subclass + * of gfxPlatformFontList *and* to call its InitFontList() method. + */ + virtual gfxPlatformFontList* CreatePlatformFontList() { + MOZ_ASSERT_UNREACHABLE( + "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. + */ + void GetStandardFamilyName(const nsCString& aFontName, + nsACString& aFamilyName); + + /** + * Returns default font name (localized family name) for aLangGroup and + * aGenericFamily. The result is typically the first font in + * font.name-list.<aGenericFamily>.<aLangGroup>. However, if it's not + * available in the system, this may return second or later font in the + * pref. If there are no available fonts in the pref, returns empty string. + */ + nsAutoCString GetDefaultFontName(const nsACString& aLangGroup, + const nsACString& aGenericFamily); + + /** + * Create a gfxFontGroup based on the given family list and style. + */ + gfxFontGroup* CreateFontGroup(const mozilla::FontFamilyList& aFontFamilyList, + const gfxFontStyle* aStyle, nsAtom* aLanguage, + bool aExplicitLanguage, + gfxTextPerfMetrics* aTextPerf, + FontMatchingStats* aFontMatchingStats, + gfxUserFontSet* aUserFontSet, + gfxFloat aDevToCssSize) const; + + /** + * Look up a local platform font using the full font face name. + * (Needed to support @font-face src local().) + * Ownership of the returned gfxFontEntry is passed to the caller, + * who must either AddRef() or delete. + */ + gfxFontEntry* LookupLocalFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry); + + /** + * Activate a platform font. (Needed to support @font-face src url().) + * aFontData is a NS_Malloc'ed block that must be freed by this function + * (or responsibility passed on) when it is no longer needed; the caller + * will NOT free it. + * Ownership of the returned gfxFontEntry is passed to the caller, + * who must either AddRef() or delete. + */ + gfxFontEntry* MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, uint32_t aLength); + + /** + * Whether to allow downloadable fonts via @font-face rules + */ + bool DownloadableFontsEnabled(); + + /** + * True when hinting should be enabled. This setting shouldn't + * change per gecko process, while the process is live. If so the + * results are not defined. + * + * NB: this bit is only honored by the FT2 backend, currently. + */ + virtual bool FontHintingEnabled() { return true; } + + /** + * True when zooming should not require reflow, so glyph metrics and + * positioning should not be adjusted for device pixels. + * If this is TRUE, then FontHintingEnabled() should be FALSE, + * but the converse is not necessarily required; + * + * Like FontHintingEnabled (above), this setting shouldn't + * change per gecko process, while the process is live. If so the + * results are not defined. + * + * NB: this bit is only honored by the FT2 backend, currently. + */ + virtual bool RequiresLinearZoom() { return false; } + + /** + * Whether the frame->StyleFont().mFont.smoothing field is respected by + * text rendering on this platform. + */ + virtual bool RespectsFontStyleSmoothing() const { return false; } + + /** + * Whether to check all font cmaps during system font fallback + */ + bool UseCmapsDuringSystemFallback(); + + /** + * Whether to render SVG glyphs within an OpenType font wrapper + */ + bool OpenTypeSVGEnabled(); + + /** + * Max character length of words in the word cache + */ + uint32_t WordCacheCharLimit(); + + /** + * Max number of entries in word cache + */ + uint32_t WordCacheMaxEntries(); + + /** + * Whether to use the SIL Graphite rendering engine + * (for fonts that include Graphite tables) + */ + bool UseGraphiteShaping(); + + // Check whether format is supported on a platform (if unclear, returns true). + // Default implementation checks for "common" formats that we support across + // all platforms, but individual platform implementations may override. + virtual bool IsFontFormatSupported(uint32_t aFormatFlags); + + virtual bool DidRenderingDeviceReset( + DeviceResetReason* aResetReason = nullptr) { + return false; + } + + // returns a list of commonly used fonts for a given character + // these are *possible* matches, no cmap-checking is done at this level + virtual void GetCommonFallbackFonts(uint32_t /*aCh*/, Script /*aRunScript*/, + eFontPresentation /*aPresentation*/, + nsTArray<const char*>& /*aFontList*/) { + // platform-specific override, by default do nothing + } + + // Are we in safe mode? + static bool InSafeMode(); + + static bool OffMainThreadCompositingEnabled(); + + void UpdateCanUseHardwareVideoDecoding(); + + // Returns a prioritized list of all available compositor backends. + void GetCompositorBackends( + bool useAcceleration, + nsTArray<mozilla::layers::LayersBackend>& aBackends); + + /** + * Is it possible to use buffer rotation. Note that these + * check the preference, but also allow for the override to + * disable it using DisableBufferRotation. + */ + static bool BufferRotationEnabled(); + static void DisableBufferRotation(); + + /** + * Are we going to try color management? + */ + static eCMSMode GetCMSMode(); + + /** + * Used only for testing. Override the pref setting. + */ + static void SetCMSModeOverride(eCMSMode aMode); + + /** + * Determines the rendering intent for color management. + * + * If the value in the pref gfx.color_management.rendering_intent is a + * valid rendering intent as defined in gfx/qcms/qcms.h, that + * value is returned. Otherwise, -1 is returned and the embedded intent + * should be used. + * + * See bug 444014 for details. + */ + static int GetRenderingIntent(); + + /** + * Convert a pixel using a cms transform in an endian-aware manner. + */ + static DeviceColor TransformPixel(const sRGBColor& in, + qcms_transform* transform); + + /** + * Return the output device ICC profile. + */ + static qcms_profile* GetCMSOutputProfile(); + + /** + * 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(); + + /** + * Return sBGRA -> output device transform. + */ + static qcms_transform* GetCMSBGRATransform(); + + /** + * Return OS RGBA -> output device transform. + */ + static qcms_transform* GetCMSOSRGBATransform(); + + /** + * Return OS RGBA QCMS type. + */ + static qcms_data_type GetCMSOSRGBAType(); + + virtual void FontsPrefsChanged(const char* aPref); + + int32_t GetBidiNumeralOption(); + + /** + * This is a bit ugly, but useful... force all presContexts to reflow, + * by toggling a preference that they observe. This is used when + * something about platform settings changes that might have an effect + * on layout, such as font rendering settings that influence metrics. + */ + static void ForceGlobalReflow(); + + 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. + */ + RefPtr<mozilla::gfx::DrawTarget> ScreenReferenceDrawTarget(); + + virtual mozilla::gfx::SurfaceFormat Optimal2DFormatForContent( + gfxContentType aContent); + + virtual gfxImageFormat OptimalFormatForContent(gfxContentType aContent); + + virtual gfxImageFormat GetOffscreenFormat() { + return mozilla::gfx::SurfaceFormat::X8R8G8B8_UINT32; + } + + /** + * Returns whether the current process should use tiling for layers. + */ + virtual bool UsesTiling() const; + + /** + * Returns whether the content process will use tiling for layers. This is + * only used by about:support. + */ + virtual bool ContentUsesTiling() const; + + /** + * 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 void PurgeSkiaFontCache(); + + static bool UsesOffMainThreadCompositing(); + + /** + * 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 whether or not a custom vsync rate is set. + */ + static bool ForceSoftwareVsync(); + + /** + * Returns the software vsync rate to use. + */ + static int GetSoftwareVsyncRate(); + + /** + * Returns the default frame rate for the refresh driver / software vsync. + */ + static int GetDefaultFrameRate(); + + /** + * Update the frame rate (called e.g. after pref changes). + */ + static void ReInitFrameRate(); + + /** + * Update force subpixel AA quality setting (called after pref + * changes). + */ + void UpdateForceSubpixelAAWherePossible(); + + /** + * Used to test which input types are handled via APZ. + */ + virtual bool SupportsApzWheelInput() const { return false; } + bool SupportsApzTouchInput() const; + bool SupportsApzDragInput() const; + bool SupportsApzKeyboardInput() const; + bool SupportsApzAutoscrolling() const; + bool SupportsApzZooming() const; + + virtual void FlushContentDrawing() {} + + // If a device reset has occurred, schedule any necessary paints in the + // widget. This should only be used within nsRefreshDriver. + virtual void SchedulePaintIfDeviceReset() {} + + /** + * Helper method, creates a draw target for a specific Azure backend. + * Used by CreateOffscreenDrawTarget. + */ + already_AddRefed<DrawTarget> CreateDrawTargetForBackend( + mozilla::gfx::BackendType aBackend, const mozilla::gfx::IntSize& aSize, + mozilla::gfx::SurfaceFormat aFormat); + + /** + * Wrapper around StaticPrefs::gfx_perf_warnings_enabled(). + * Extracted into a function to avoid including StaticPrefs_gfx.h from this + * file. + */ + static bool PerfWarnings(); + + static void NotifyGPUProcessDisabled(); + + void NotifyCompositorCreated(mozilla::layers::LayersBackend aBackend); + mozilla::layers::LayersBackend GetCompositorBackend() const { + return mCompositorBackend; + } + + virtual void CompositorUpdated() {} + + // Plugin async drawing support. + virtual bool SupportsPluginDirectBitmapDrawing() { return false; } + + // Some platforms don't support CompositorOGL in an unaccelerated OpenGL + // context. These platforms should return true here. + virtual bool RequiresAcceleratedGLContextForCompositorOGL() const { + return false; + } + + /** + * Check the blocklist for a feature. Returns false if the feature is blocked + * with an appropriate message and failure ID. + * */ + static bool IsGfxInfoStatusOkay(int32_t aFeature, nsCString* aOutMessage, + nsCString& aFailureId); + + const gfxSkipChars& EmptySkipChars() const { return kEmptySkipChars; } + + /** + * Returns a buffer containing the CMS output profile data. The way this + * is obtained is platform-specific. + */ + virtual nsTArray<uint8_t> GetPlatformCMSOutputProfileData(); + + /** + * Return 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); + + bool HasVariationFontSupport() const { return mHasVariationFontSupport; } + + bool HasNativeColrFontSupport() const { return mHasNativeColrFontSupport; } + + // you probably want to use gfxVars::UseWebRender() instead of this + static bool WebRenderPrefEnabled(); + // you probably want to use gfxVars::UseWebRender() instead of this + static bool WebRenderEnvvarEnabled(); + // you probably want to use gfxVars::UseWebRender() instead of this + static bool WebRenderEnvvarDisabled(); + + static const char* WebRenderResourcePathOverride(); + + static void DisableWebRender(mozilla::gfx::FeatureStatus aStatus, + const char* aMessage, + const nsACString& aFailureId); + + void NotifyFrameStats(nsTArray<mozilla::layers::FrameStats>&& aFrameStats); + + virtual void OnMemoryPressure( + mozilla::layers::MemoryPressureReason aWhy) override; + + virtual void EnsureDevicesInitialized(){}; + virtual bool DevicesInitialized() { return true; }; + + virtual bool UseDMABufWebGL() { return false; } + virtual bool IsWaylandDisplay() { return false; } + + static uint32_t TargetFrameRate(); + + static bool UseDesktopZoomingScrollbars(); + + protected: + gfxPlatform(); + virtual ~gfxPlatform(); + + virtual void InitAcceleration(); + virtual void InitWebRenderConfig(); + virtual void InitWebGLConfig(); + virtual void InitWebGPUConfig(); + + virtual void GetPlatformDisplayInfo(mozilla::widget::InfoObject& aObj) {} + + /** + * Called immediately before deleting the gfxPlatform object. + */ + virtual void WillShutdown(); + + /** + * Initialized hardware vsync based on each platform. + */ + virtual already_AddRefed<mozilla::gfx::VsyncSource> + CreateHardwareVsyncSource(); + + // Returns whether or not layers should be accelerated by default on this + // platform. + virtual bool AccelerateLayersByDefault(); + + // Returns a prioritized list of available compositor backends for + // acceleration. + virtual void GetAcceleratedCompositorBackends( + nsTArray<mozilla::layers::LayersBackend>& aBackends); + + // Returns preferences of canvas and content backends. + virtual BackendPrefsData GetBackendPrefs() const; + + /** + * Initialise the preferred and fallback canvas backends + * aBackendBitmask specifies the backends which are acceptable to the caller. + * The backend used is determined by aBackendBitmask and the order specified + * by the gfx.canvas.azure.backends pref. + */ + void InitBackendPrefs(BackendPrefsData&& aPrefsData); + + /** + * Content-process only. Requests device preferences from the parent process + * and updates any cached settings. + */ + void FetchAndImportContentDeviceData(); + virtual void ImportContentDeviceData( + const mozilla::gfx::ContentDeviceData& aData); + + /** + * Returns the contents of the file pointed to by the + * gfx.color_management.display_profile pref, if set. + * Returns an empty array if not set, or if an error occurs + */ + nsTArray<uint8_t> GetPrefCMSOutputProfileData(); + + /** + * If inside a child process and currently being initialized by the + * SetXPCOMProcessAttributes message, this can be used by subclasses to + * retrieve the ContentDeviceData passed by the message + * + * If not currently being initialized, will return nullptr. In this case, + * child should send a sync message to ask parent for color profile + */ + const mozilla::gfx::ContentDeviceData* GetInitContentDeviceData(); + + /** + * Increase the global device counter after a device has been removed/reset. + */ + void BumpDeviceCounter(); + + /** + * returns the first backend named in the pref gfx.canvas.azure.backends + * which is a component of aBackendBitmask, a bitmask of backend types + */ + static mozilla::gfx::BackendType GetCanvasBackendPref( + uint32_t aBackendBitmask); + + /** + * returns the first backend named in the pref gfx.content.azure.backend + * which is a component of aBackendBitmask, a bitmask of backend types + */ + static mozilla::gfx::BackendType GetContentBackendPref( + uint32_t& aBackendBitmask); + + /** + * Will return the first backend named in aBackendPrefName + * allowed by aBackendBitmask, a bitmask of backend types. + * It also modifies aBackendBitmask to only include backends that are + * allowed given the prefs. + */ + static mozilla::gfx::BackendType GetBackendPref(const char* aBackendPrefName, + uint32_t& aBackendBitmask); + /** + * Decode the backend enumberation from a string. + */ + static mozilla::gfx::BackendType BackendTypeForName(const nsCString& aName); + + virtual bool CanUseHardwareVideoDecoding(); + + virtual bool CheckVariationFontSupport() = 0; + + 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; + + // Whether the platform supports rendering OpenType font variations + bool mHasVariationFontSupport; + + // Whether the platform font APIs have native support for COLR fonts. + // Set to true during initialization on platforms that implement this. + bool mHasNativeColrFontSupport = false; + + // max character limit for words in word cache + int32_t mWordCacheCharLimit; + + // max number of entries in word cache + int32_t mWordCacheMaxEntries; + + uint64_t mTotalPhysicalMemory; + uint64_t mTotalVirtualMemory; + + // Hardware vsync source. Only valid on parent process + RefPtr<mozilla::gfx::VsyncSource> mVsyncSource; + + RefPtr<mozilla::gfx::DrawTarget> mScreenReferenceDrawTarget; + + private: + /** + * Start up Thebes. + */ + static void Init(); + + static void InitOpenGLConfig(); + static void CreateCMSOutputProfile(); + + friend void RecordingPrefChanged(const char* aPrefName, void* aClosure); + + /** + * 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(); + virtual void InitPlatformGPUProcessPrefs() {} + void InitOMTPConfig(); + + // Gather telemetry data about the Gfx Platform and send it + static void ReportTelemetry(); + + static bool IsDXInterop2Blocked(); + static bool IsDXNV12Blocked(); + static bool IsDXP010Blocked(); + static bool IsDXP016Blocked(); + + RefPtr<gfxASurface> mScreenReferenceSurface; + nsCOMPtr<nsIObserver> mSRGBOverrideObserver; + RefPtr<mozilla::layers::MemoryPressureObserver> mMemoryPressureObserver; + + // The preferred draw target backend to use for canvas + mozilla::gfx::BackendType mPreferredCanvasBackend; + // The fallback draw target backend to use for canvas, if the preferred + // backend fails + mozilla::gfx::BackendType mFallbackCanvasBackend; + // The backend to use for content + mozilla::gfx::BackendType mContentBackend; + // The backend to use when we need it not to be accelerated. + mozilla::gfx::BackendType mSoftwareBackend; + // Bitmask of backend types we can use to render content + uint32_t mContentBackendBitmask; + + mozilla::widget::GfxInfoCollector<gfxPlatform> mAzureCanvasBackendCollector; + mozilla::widget::GfxInfoCollector<gfxPlatform> mApzSupportCollector; + mozilla::widget::GfxInfoCollector<gfxPlatform> mTilesInfoCollector; + mozilla::widget::GfxInfoCollector<gfxPlatform> mFrameStatsCollector; + mozilla::widget::GfxInfoCollector<gfxPlatform> mCMSInfoCollector; + mozilla::widget::GfxInfoCollector<gfxPlatform> mDisplayInfoCollector; + + nsTArray<mozilla::layers::FrameStats> mFrameStats; + + RefPtr<mozilla::gfx::DrawEventRecorder> mRecorder; + + // 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; + + // Total number of screen pixels across all monitors. + int64_t mScreenPixels; + + // 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 0000000000..fa972fb9c7 --- /dev/null +++ b/gfx/thebes/gfxPlatformFontList.cpp @@ -0,0 +1,2753 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Logging.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/intl/LocaleService.h" +#include "mozilla/intl/MozLocale.h" +#include "mozilla/intl/OSPreferences.h" + +#include "gfxPlatformFontList.h" +#include "gfxTextRun.h" +#include "gfxUserFontSet.h" +#include "SharedFontList-impl.h" + +#include "nsCRT.h" +#include "nsGkAtoms.h" +#include "nsServiceManagerUtils.h" +#include "nsUnicharUtils.h" +#include "nsUnicodeProperties.h" +#include "nsXULAppAPI.h" + +#include "mozilla/Attributes.h" +#include "mozilla/Likely.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/dom/BlobImpl.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ContentProcessMessageManager.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/ipc/FileDescriptorUtils.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/TextUtils.h" +#include "mozilla/Unused.h" + +#include "base/eintr_wrapper.h" + +#include <locale.h> + +using namespace mozilla; +using mozilla::intl::Locale; +using mozilla::intl::LocaleService; +using mozilla::intl::OSPreferences; + +#define LOG_FONTLIST(args) \ + MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug, args) +#define LOG_FONTLIST_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug) +#define LOG_FONTINIT(args) \ + MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug, args) +#define LOG_FONTINIT_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug) + +gfxPlatformFontList* gfxPlatformFontList::sPlatformFontList = nullptr; + +// Character ranges that require complex-script shaping support in the font, +// and so should be masked out by ReadCMAP if the necessary layout tables +// are not present. +// Currently used by the Mac and FT2 implementations only, but probably should +// be supported on Windows as well. +const gfxFontEntry::ScriptRange gfxPlatformFontList::sComplexScriptRanges[] = { + // Actually, now that harfbuzz supports presentation-forms shaping for + // Arabic, we can render it without layout tables. So maybe we don't + // want to mask the basic Arabic block here? + // This affects the arabic-fallback-*.html reftests, which rely on + // loading a font that *doesn't* have any GSUB table. + {0x0600, 0x06FF, 1, {TRUETYPE_TAG('a', 'r', 'a', 'b'), 0, 0}}, + {0x0700, 0x074F, 1, {TRUETYPE_TAG('s', 'y', 'r', 'c'), 0, 0}}, + {0x0750, 0x077F, 1, {TRUETYPE_TAG('a', 'r', 'a', 'b'), 0, 0}}, + {0x08A0, 0x08FF, 1, {TRUETYPE_TAG('a', 'r', 'a', 'b'), 0, 0}}, + {0x0900, + 0x097F, + 2, + {TRUETYPE_TAG('d', 'e', 'v', '2'), TRUETYPE_TAG('d', 'e', 'v', 'a'), 0}}, + {0x0980, + 0x09FF, + 2, + {TRUETYPE_TAG('b', 'n', 'g', '2'), TRUETYPE_TAG('b', 'e', 'n', 'g'), 0}}, + {0x0A00, + 0x0A7F, + 2, + {TRUETYPE_TAG('g', 'u', 'r', '2'), TRUETYPE_TAG('g', 'u', 'r', 'u'), 0}}, + {0x0A80, + 0x0AFF, + 2, + {TRUETYPE_TAG('g', 'j', 'r', '2'), TRUETYPE_TAG('g', 'u', 'j', 'r'), 0}}, + {0x0B00, + 0x0B7F, + 2, + {TRUETYPE_TAG('o', 'r', 'y', '2'), TRUETYPE_TAG('o', 'r', 'y', 'a'), 0}}, + {0x0B80, + 0x0BFF, + 2, + {TRUETYPE_TAG('t', 'm', 'l', '2'), TRUETYPE_TAG('t', 'a', 'm', 'l'), 0}}, + {0x0C00, + 0x0C7F, + 2, + {TRUETYPE_TAG('t', 'e', 'l', '2'), TRUETYPE_TAG('t', 'e', 'l', 'u'), 0}}, + {0x0C80, + 0x0CFF, + 2, + {TRUETYPE_TAG('k', 'n', 'd', '2'), TRUETYPE_TAG('k', 'n', 'd', 'a'), 0}}, + {0x0D00, + 0x0D7F, + 2, + {TRUETYPE_TAG('m', 'l', 'm', '2'), TRUETYPE_TAG('m', 'l', 'y', 'm'), 0}}, + {0x0D80, 0x0DFF, 1, {TRUETYPE_TAG('s', 'i', 'n', 'h'), 0, 0}}, + {0x0E80, 0x0EFF, 1, {TRUETYPE_TAG('l', 'a', 'o', ' '), 0, 0}}, + {0x0F00, 0x0FFF, 1, {TRUETYPE_TAG('t', 'i', 'b', 't'), 0, 0}}, + {0x1000, + 0x109f, + 2, + {TRUETYPE_TAG('m', 'y', 'm', 'r'), TRUETYPE_TAG('m', 'y', 'm', '2'), 0}}, + {0x1780, 0x17ff, 1, {TRUETYPE_TAG('k', 'h', 'm', 'r'), 0, 0}}, + // Khmer Symbols (19e0..19ff) don't seem to need any special shaping + {0xaa60, + 0xaa7f, + 2, + {TRUETYPE_TAG('m', 'y', 'm', 'r'), TRUETYPE_TAG('m', 'y', 'm', '2'), 0}}, + // Thai seems to be "renderable" without AAT morphing tables + {0, 0, 0, {0, 0, 0}} // terminator +}; + +static const char* kObservedPrefs[] = {"font.", + "font.name-list.", + "intl.accept_languages", // hmmmm... + "layout.css.font-visibility.level", + "privacy.resistFingerprinting", + nullptr}; + +static const char kFontSystemWhitelistPref[] = "font.system.whitelist"; + +static const char kCJKFallbackOrderPref[] = "font.cjk_pref_fallback_order"; + +// xxx - this can probably be eliminated by reworking pref font handling code +static const char* gPrefLangNames[] = { +#define FONT_PREF_LANG(enum_id_, str_, atom_id_) str_ +#include "gfxFontPrefLangList.h" +#undef FONT_PREF_LANG +}; + +static_assert(MOZ_ARRAY_LENGTH(gPrefLangNames) == uint32_t(eFontPrefLang_Count), + "size of pref lang name array doesn't match pref lang enum size"); + +class gfxFontListPrefObserver final : public nsIObserver { + ~gfxFontListPrefObserver() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER +}; + +static void FontListPrefChanged(const char* aPref, void* aData = nullptr) { + // XXX this could be made to only clear out the cache for the prefs that were + // changed but it probably isn't that big a deal. + gfxPlatformFontList::PlatformFontList()->ClearLangGroupPrefFonts(); + gfxFontCache::GetCache()->AgeAllGenerations(); + if (aPref && (!strcmp(aPref, "layout.css.font-visibility.level") || + !strcmp(aPref, "privacy.resistFingerprinting"))) { + gfxPlatformFontList::PlatformFontList()->SetVisibilityLevel(); + if (XRE_IsParentProcess()) { + gfxPlatform::ForceGlobalReflow(); + } + } +} + +static gfxFontListPrefObserver* gFontListPrefObserver = nullptr; + +NS_IMPL_ISUPPORTS(gfxFontListPrefObserver, nsIObserver) + +#define LOCALES_CHANGED_TOPIC "intl:system-locales-changed" + +NS_IMETHODIMP +gfxFontListPrefObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + NS_ASSERTION(!strcmp(aTopic, LOCALES_CHANGED_TOPIC), "invalid topic"); + FontListPrefChanged(nullptr); + + if (XRE_IsParentProcess()) { + gfxPlatform::ForceGlobalReflow(); + } + return NS_OK; +} + +MOZ_DEFINE_MALLOC_SIZE_OF(FontListMallocSizeOf) + +NS_IMPL_ISUPPORTS(gfxPlatformFontList::MemoryReporter, nsIMemoryReporter) + +NS_IMETHODIMP +gfxPlatformFontList::MemoryReporter::CollectReports( + nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) { + FontListSizes sizes; + sizes.mFontListSize = 0; + sizes.mFontTableCacheSize = 0; + sizes.mCharMapsSize = 0; + sizes.mLoaderSize = 0; + sizes.mSharedSize = 0; + + gfxPlatformFontList::PlatformFontList()->AddSizeOfIncludingThis( + &FontListMallocSizeOf, &sizes); + + MOZ_COLLECT_REPORT( + "explicit/gfx/font-list", KIND_HEAP, UNITS_BYTES, sizes.mFontListSize, + "Memory used to manage the list of font families and faces."); + + MOZ_COLLECT_REPORT( + "explicit/gfx/font-charmaps", KIND_HEAP, UNITS_BYTES, sizes.mCharMapsSize, + "Memory used to record the character coverage of individual fonts."); + + if (sizes.mFontTableCacheSize) { + MOZ_COLLECT_REPORT( + "explicit/gfx/font-tables", KIND_HEAP, UNITS_BYTES, + sizes.mFontTableCacheSize, + "Memory used for cached font metrics and layout tables."); + } + + if (sizes.mLoaderSize) { + MOZ_COLLECT_REPORT("explicit/gfx/font-loader", KIND_HEAP, UNITS_BYTES, + sizes.mLoaderSize, + "Memory used for (platform-specific) font loader."); + } + + if (sizes.mSharedSize) { + MOZ_COLLECT_REPORT( + "font-list-shmem", KIND_NONHEAP, UNITS_BYTES, sizes.mSharedSize, + "Shared memory for system font list and character coverage data."); + } + + return NS_OK; +} + +gfxPlatformFontList::gfxPlatformFontList(bool aNeedFullnamePostscriptNames) + : mFontFamiliesMutex("gfxPlatformFontList::mFontFamiliesMutex"), + mFontFamilies(64), + mOtherFamilyNames(16), + mSharedCmaps(8), + mStartIndex(0), + mNumFamilies(0), + mFontlistInitCount(0), + mFontFamilyWhitelistActive(false) { + mOtherFamilyNamesInitialized = false; + + if (aNeedFullnamePostscriptNames) { + mExtraNames = MakeUnique<ExtraNames>(); + } + mFaceNameListsInitialized = false; + + mLangService = nsLanguageAtomService::GetService(); + + LoadBadUnderlineList(); + + // pref changes notification setup + NS_ASSERTION(!gFontListPrefObserver, + "There has been font list pref observer already"); + gFontListPrefObserver = new gfxFontListPrefObserver(); + NS_ADDREF(gFontListPrefObserver); + + Preferences::RegisterPrefixCallbacks(FontListPrefChanged, kObservedPrefs); + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->AddObserver(gFontListPrefObserver, LOCALES_CHANGED_TOPIC, false); + } + + // Only the parent process listens for whitelist changes; it will then + // notify its children to rebuild their font lists. + if (XRE_IsParentProcess()) { + Preferences::RegisterCallback(FontWhitelistPrefChanged, + kFontSystemWhitelistPref); + } + + RegisterStrongMemoryReporter(new MemoryReporter()); +} + +gfxPlatformFontList::~gfxPlatformFontList() { + mSharedCmaps.Clear(); + ClearLangGroupPrefFonts(); + NS_ASSERTION(gFontListPrefObserver, "There is no font list pref observer"); + + Preferences::UnregisterPrefixCallbacks(FontListPrefChanged, kObservedPrefs); + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->RemoveObserver(gFontListPrefObserver, LOCALES_CHANGED_TOPIC); + } + + if (XRE_IsParentProcess()) { + Preferences::UnregisterCallback(FontWhitelistPrefChanged, + kFontSystemWhitelistPref); + } + NS_RELEASE(gFontListPrefObserver); +} + +/* static */ +void gfxPlatformFontList::FontWhitelistPrefChanged(const char* aPref, + void* aClosure) { + MOZ_ASSERT(XRE_IsParentProcess()); + auto* pfl = gfxPlatformFontList::PlatformFontList(); + pfl->UpdateFontList(true); + dom::ContentParent::NotifyUpdatedFonts(true); +} + +// number of CSS generic font families +const uint32_t kNumGenerics = 5; + +void gfxPlatformFontList::ApplyWhitelist() { + nsTArray<nsCString> list; + gfxFontUtils::GetPrefsFontList(kFontSystemWhitelistPref, list); + uint32_t numFonts = list.Length(); + mFontFamilyWhitelistActive = (numFonts > 0); + if (!mFontFamilyWhitelistActive) { + return; + } + nsTHashtable<nsCStringHashKey> familyNamesWhitelist; + for (uint32_t i = 0; i < numFonts; i++) { + nsAutoCString key; + ToLowerCase(list[i], key); + familyNamesWhitelist.PutEntry(key); + } + AutoTArray<RefPtr<gfxFontFamily>, 128> accepted; + bool whitelistedFontFound = false; + for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { + if (iter.Data()->IsHidden()) { + // Hidden system fonts are exempt from whitelisting, but don't count + // towards determining whether we "kept" any (user-visible) fonts + accepted.AppendElement(iter.Data()); + continue; + } + nsAutoCString fontFamilyName(iter.Key()); + ToLowerCase(fontFamilyName); + if (familyNamesWhitelist.Contains(fontFamilyName)) { + accepted.AppendElement(iter.Data()); + whitelistedFontFound = true; + } + } + if (!whitelistedFontFound) { + // No whitelisted fonts found! Ignore the whitelist. + return; + } + // Replace the original full list with the accepted subset. + mFontFamilies.Clear(); + for (auto& f : accepted) { + nsAutoCString fontFamilyName(f->Name()); + ToLowerCase(fontFamilyName); + mFontFamilies.Put(fontFamilyName, std::move(f)); + } +} + +void gfxPlatformFontList::ApplyWhitelist( + nsTArray<fontlist::Family::InitData>& aFamilies) { + nsTArray<nsCString> list; + gfxFontUtils::GetPrefsFontList(kFontSystemWhitelistPref, list); + mFontFamilyWhitelistActive = !list.IsEmpty(); + if (!mFontFamilyWhitelistActive) { + return; + } + nsTHashtable<nsCStringHashKey> familyNamesWhitelist; + for (const auto& item : list) { + nsAutoCString key; + ToLowerCase(item, key); + familyNamesWhitelist.PutEntry(key); + } + AutoTArray<fontlist::Family::InitData, 128> accepted; + bool keptNonHidden = false; + for (auto& f : aFamilies) { + if (f.mVisibility == FontVisibility::Hidden || + familyNamesWhitelist.Contains(f.mKey)) { + accepted.AppendElement(f); + if (f.mVisibility != FontVisibility::Hidden) { + keptNonHidden = true; + } + } + } + if (!keptNonHidden) { + // No (visible) families were whitelisted: ignore the whitelist + // and just leave the fontlist unchanged. + return; + } + aFamilies = std::move(accepted); +} + +bool gfxPlatformFontList::FamilyInList(const nsACString& aName, + const char* aList[], size_t aCount) { + auto cmp = [&](const char* const aVal) -> int { + return nsCaseInsensitiveUTF8StringComparator(aName.BeginReading(), aVal, + aName.Length(), strlen(aVal)); + }; + size_t result; + return BinarySearchIf(aList, 0, aCount, cmp, &result); +} + +void gfxPlatformFontList::CheckFamilyList(const char* aList[], size_t aCount) { +#ifdef DEBUG + MOZ_ASSERT(aCount > 0, "empty font family list?"); + const char* a = aList[0]; + uint32_t aLen = strlen(a); + for (size_t i = 1; i < aCount; ++i) { + const char* b = aList[i]; + uint32_t bLen = strlen(b); + MOZ_ASSERT(nsCaseInsensitiveUTF8StringComparator(a, b, aLen, bLen) < 0, + "incorrectly sorted font family list!"); + a = b; + aLen = bLen; + } +#endif +} + +bool gfxPlatformFontList::AddWithLegacyFamilyName(const nsACString& aLegacyName, + gfxFontEntry* aFontEntry, + FontVisibility aVisibility) { + bool added = false; + nsAutoCString key; + ToLowerCase(aLegacyName, key); + gfxFontFamily* family = mOtherFamilyNames.GetWeak(key); + if (!family) { + family = CreateFontFamily(aLegacyName, aVisibility); + family->SetHasStyles(true); // we don't want the family to search for + // faces, we're adding them directly here + mOtherFamilyNames.Put(key, RefPtr{family}); + added = true; + } + family->AddFontEntry(aFontEntry->Clone()); + return added; +} + +nsresult gfxPlatformFontList::InitFontList() { + // This shouldn't be called from stylo threads! + MOZ_ASSERT(NS_IsMainThread()); + + 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->FlushShapedWordCaches(); + fontCache->Flush(); + } + + gfxPlatform::PurgeSkiaFontCache(); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + // Notify any current presContexts that fonts are being updated, so existing + // caches will no longer be valid. + obs->NotifyObservers(nullptr, "font-info-updated", nullptr); + } + + mAliasTable.Clear(); + mLocalNameTable.Clear(); + + CancelLoadCmapsTask(); + mStartedLoadingCmapsFrom = 0xffffffffu; + + CancelInitOtherFamilyNamesTask(); + MutexAutoLock lock(mFontFamiliesMutex); + mFontFamilies.Clear(); + mOtherFamilyNames.Clear(); + mOtherFamilyNamesInitialized = false; + + if (mExtraNames) { + mExtraNames->mFullnames.Clear(); + mExtraNames->mPostscriptNames.Clear(); + } + mFaceNameListsInitialized = false; + ClearLangGroupPrefFonts(); + CancelLoader(); + + // Ensure that SetVisibilityLevel will clear the mCodepointsWithNoFonts set. + mVisibilityLevel = FontVisibility::Unknown; + SetVisibilityLevel(); + + sPlatformFontList = this; + + // Try to initialize the cross-process shared font list if enabled by prefs, + // but not if we're running in Safe Mode. + if (StaticPrefs::gfx_e10s_font_list_shared_AtStartup() && + !gfxPlatform::InSafeMode()) { + for (auto i = mFontEntries.Iter(); !i.Done(); i.Next()) { + if (!i.Data()) { + continue; + } + i.Data()->mShmemCharacterMap = nullptr; + i.Data()->mShmemFace = nullptr; + i.Data()->mFamilyName.Truncate(); + } + mFontEntries.Clear(); + mShmemCharMaps.Clear(); + bool oldSharedList = mSharedFontList != nullptr; + mSharedFontList.reset(new fontlist::FontList(mFontlistInitCount)); + InitSharedFontListForPlatform(); + if (mSharedFontList && mSharedFontList->Initialized()) { + if (mLocalNameTable.Count()) { + SharedFontList()->SetLocalNames(mLocalNameTable); + mLocalNameTable.Clear(); + } + } else { + // something went wrong, fall back to in-process list + gfxCriticalNote << "Failed to initialize shared font list, " + "falling back to in-process list."; + mSharedFontList.reset(nullptr); + } + if (oldSharedList) { + if (XRE_IsParentProcess()) { + // notify all children of the change + dom::ContentParent::NotifyUpdatedFonts(true); + } + } + } + + if (!SharedFontList()) { + nsresult rv = InitFontListForPlatform(); + if (NS_FAILED(rv)) { + return rv; + } + ApplyWhitelist(); + } + + // Set up mDefaultFontEntry as a "last resort" default that we can use + // to avoid crashing if the font list is otherwise unusable. + gfxFontStyle defStyle; + FontFamily fam = GetDefaultFont(&defStyle); + if (fam.mIsShared) { + auto face = fam.mShared->FindFaceForStyle(SharedFontList(), defStyle); + if (!face) { + mDefaultFontEntry = nullptr; + } else { + mDefaultFontEntry = GetOrCreateFontEntry(face, fam.mShared); + } + } else { + mDefaultFontEntry = fam.mUnshared->FindFontForStyle(defStyle); + } + + return NS_OK; +} + +void gfxPlatformFontList::InitializeCodepointsWithNoFonts() { + mCodepointsWithNoFonts.reset(); + mCodepointsWithNoFonts.SetRange(0, 0x1f); // C0 controls + mCodepointsWithNoFonts.SetRange(0x7f, 0x9f); // C1 controls + mCodepointsWithNoFonts.SetRange(0xE000, 0xF8FF); // PUA + mCodepointsWithNoFonts.SetRange(0xF0000, 0x10FFFD); // Supplementary PUA + mCodepointsWithNoFonts.SetRange(0xfdd0, 0xfdef); // noncharacters + for (unsigned i = 0; i <= 0x100000; i += 0x10000) { + mCodepointsWithNoFonts.SetRange(i + 0xfffe, i + 0xffff); // noncharacters + } +} + +void gfxPlatformFontList::SetVisibilityLevel() { + FontVisibility newLevel; + if (StaticPrefs::privacy_resistFingerprinting()) { + newLevel = FontVisibility::Base; + } else { + newLevel = FontVisibility( + std::min(int32_t(FontVisibility::User), + std::max(int32_t(FontVisibility::Base), + StaticPrefs::layout_css_font_visibility_level()))); + } + if (newLevel != mVisibilityLevel) { + mVisibilityLevel = newLevel; + // (Re-)initialize ranges of characters for which system-wide font search + // should be skipped + InitializeCodepointsWithNoFonts(); + // Forget any font family we previously chose for U+FFFD. + mReplacementCharFallbackFamily = FontFamily(); + } +} + +void gfxPlatformFontList::FontListChanged() { + MOZ_ASSERT(!XRE_IsParentProcess()); + InitializeCodepointsWithNoFonts(); + if (SharedFontList()) { + // If we're using a shared local face-name list, this may have changed + // such that existing font entries held by user font sets are no longer + // safe to use: ensure they all get flushed. + RebuildLocalFonts(/*aForgetLocalFaces*/ true); + } + ForceGlobalReflow(); +} + +void gfxPlatformFontList::GenerateFontListKey(const nsACString& aKeyName, + nsACString& aResult) { + aResult = aKeyName; + ToLowerCase(aResult); +} + +// Used if a stylo thread wants to trigger InitOtherFamilyNames in the main +// process: we can't do IPC from the stylo thread so we post this to the main +// thread instead. +class InitOtherFamilyNamesForStylo : public mozilla::Runnable { + public: + explicit InitOtherFamilyNamesForStylo(bool aDeferOtherFamilyNamesLoading) + : Runnable("gfxPlatformFontList::InitOtherFamilyNamesForStylo"), + mDefer(aDeferOtherFamilyNamesLoading) {} + + NS_IMETHOD Run() override { + auto pfl = gfxPlatformFontList::PlatformFontList(); + auto list = pfl->SharedFontList(); + if (!list) { + return NS_OK; + } + dom::ContentChild::GetSingleton()->SendInitOtherFamilyNames( + list->GetGeneration(), mDefer, &pfl->mOtherFamilyNamesInitialized); + return NS_OK; + } + + private: + bool mDefer; +}; + +#define OTHERNAMES_TIMEOUT 200 + +bool gfxPlatformFontList::InitOtherFamilyNames( + bool aDeferOtherFamilyNamesLoading) { + if (mOtherFamilyNamesInitialized) { + return true; + } + + if (SharedFontList() && !XRE_IsParentProcess()) { + if (NS_IsMainThread()) { + dom::ContentChild::GetSingleton()->SendInitOtherFamilyNames( + SharedFontList()->GetGeneration(), aDeferOtherFamilyNamesLoading, + &mOtherFamilyNamesInitialized); + } else { + NS_DispatchToMainThread( + new InitOtherFamilyNamesForStylo(aDeferOtherFamilyNamesLoading)); + } + return mOtherFamilyNamesInitialized; + } + + // If the font loader delay has been set to zero, we don't defer loading + // additional family names (regardless of the aDefer... parameter), as we + // take this to mean availability of font info is to be prioritized over + // potential startup perf or main-thread jank. + // (This is used so we can reliably run reftests that depend on localized + // font-family names being available.) + if (aDeferOtherFamilyNamesLoading && + StaticPrefs::gfx_font_loader_delay_AtStartup() > 0) { + if (!mPendingOtherFamilyNameTask) { + RefPtr<mozilla::CancelableRunnable> task = + new InitOtherFamilyNamesRunnable(); + mPendingOtherFamilyNameTask = task; + NS_DispatchToMainThreadQueue(task.forget(), EventQueuePriority::Idle); + } + } else { + InitOtherFamilyNamesInternal(false); + } + return mOtherFamilyNamesInitialized; +} + +// time limit for loading facename lists (ms) +#define NAMELIST_TIMEOUT 200 + +gfxFontEntry* gfxPlatformFontList::SearchFamiliesForFaceName( + const nsACString& aFaceName) { + TimeStamp start = TimeStamp::Now(); + bool timedOut = false; + // if mFirstChar is not 0, only load facenames for families + // that start with this character + char16_t firstChar = 0; + gfxFontEntry* lookup = nullptr; + + // iterate over familes starting with the same letter + firstChar = ToLowerCase(aFaceName.CharAt(0)); + + for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { + nsCStringHashKey::KeyType key = iter.Key(); + RefPtr<gfxFontFamily>& family = iter.Data(); + + // when filtering, skip names that don't start with the filter character + if (firstChar && ToLowerCase(key.CharAt(0)) != firstChar) { + continue; + } + + family->ReadFaceNames(this, NeedFullnamePostscriptNames()); + + TimeDuration elapsed = TimeStamp::Now() - start; + if (elapsed.ToMilliseconds() > NAMELIST_TIMEOUT) { + timedOut = true; + break; + } + } + + lookup = FindFaceName(aFaceName); + + TimeStamp end = TimeStamp::Now(); + Telemetry::AccumulateTimeDelta(Telemetry::FONTLIST_INITFACENAMELISTS, start, + end); + if (LOG_FONTINIT_ENABLED()) { + TimeDuration elapsed = end - start; + LOG_FONTINIT(("(fontinit) SearchFamiliesForFaceName took %8.2f ms %s %s", + elapsed.ToMilliseconds(), (lookup ? "found name" : ""), + (timedOut ? "timeout" : ""))); + } + + return lookup; +} + +gfxFontEntry* gfxPlatformFontList::FindFaceName(const nsACString& aFaceName) { + gfxFontEntry* lookup; + + // lookup in name lookup tables, return null if not found + if (mExtraNames && + ((lookup = mExtraNames->mPostscriptNames.GetWeak(aFaceName)) || + (lookup = mExtraNames->mFullnames.GetWeak(aFaceName)))) { + return lookup; + } + + return nullptr; +} + +gfxFontEntry* gfxPlatformFontList::LookupInFaceNameLists( + const nsACString& aFaceName) { + gfxFontEntry* lookup = nullptr; + + // initialize facename lookup tables if needed + // note: this can terminate early or time out, in which case + // mFaceNameListsInitialized remains false + if (!mFaceNameListsInitialized) { + lookup = SearchFamiliesForFaceName(aFaceName); + if (lookup) { + return lookup; + } + } + + // lookup in name lookup tables, return null if not found + if (!(lookup = FindFaceName(aFaceName))) { + // names not completely initialized, so keep track of lookup misses + if (!mFaceNameListsInitialized) { + if (!mFaceNamesMissed) { + mFaceNamesMissed = MakeUnique<nsTHashtable<nsCStringHashKey>>(2); + } + mFaceNamesMissed->PutEntry(aFaceName); + } + } + + return lookup; +} + +gfxFontEntry* gfxPlatformFontList::LookupInSharedFaceNameList( + const nsACString& aFaceName, WeightRange aWeightForEntry, + StretchRange aStretchForEntry, SlantStyleRange aStyleForEntry) { + nsAutoCString keyName(aFaceName); + ToLowerCase(keyName); + fontlist::FontList* list = SharedFontList(); + fontlist::Family* family = nullptr; + fontlist::Face* face = nullptr; + if (list->NumLocalFaces()) { + fontlist::LocalFaceRec* rec = list->FindLocalFace(keyName); + if (rec) { + auto* families = list->Families(); + if (families) { + family = &families[rec->mFamilyIndex]; + face = static_cast<fontlist::Face*>( + family->Faces(list)[rec->mFaceIndex].ToPtr(list)); + } + } + } else { + list->SearchForLocalFace(keyName, &family, &face); + } + if (!face || !family) { + return nullptr; + } + gfxFontEntry* fe = CreateFontEntry(face, family); + if (fe) { + fe->mIsLocalUserFont = true; + fe->mWeightRange = aWeightForEntry; + fe->mStretchRange = aStretchForEntry; + fe->mStyleRange = aStyleForEntry; + } + return fe; +} + +void gfxPlatformFontList::PreloadNamesList() { + AutoTArray<nsCString, 10> preloadFonts; + gfxFontUtils::GetPrefsFontList("font.preload-names-list", preloadFonts); + + uint32_t numFonts = preloadFonts.Length(); + for (uint32_t i = 0; i < numFonts; i++) { + nsAutoCString key; + GenerateFontListKey(preloadFonts[i], key); + + // only search canonical names! + gfxFontFamily* familyEntry = mFontFamilies.GetWeak(key); + if (familyEntry) { + familyEntry->ReadOtherFamilyNames(this); + } + } +} + +void gfxPlatformFontList::LoadBadUnderlineList() { + gfxFontUtils::GetPrefsFontList("font.blacklist.underline_offset", + mBadUnderlineFamilyNames); + for (auto& fam : mBadUnderlineFamilyNames) { + ToLowerCase(fam); + } + mBadUnderlineFamilyNames.Compact(); + mBadUnderlineFamilyNames.Sort(); +} + +void gfxPlatformFontList::UpdateFontList(bool aFullRebuild) { + MOZ_ASSERT(NS_IsMainThread()); + if (aFullRebuild) { + InitFontList(); + RebuildLocalFonts(); + } else { + InitializeCodepointsWithNoFonts(); + mStartedLoadingCmapsFrom = 0xffffffffu; + gfxPlatform::ForceGlobalReflow(); + } +} + +bool gfxPlatformFontList::IsVisibleToCSS(const gfxFontFamily& aFamily) const { + return aFamily.Visibility() <= mVisibilityLevel || + IsFontFamilyWhitelistActive(); +} + +bool gfxPlatformFontList::IsVisibleToCSS( + const fontlist::Family& aFamily) const { + return aFamily.Visibility() <= mVisibilityLevel || + IsFontFamilyWhitelistActive(); +} + +void gfxPlatformFontList::GetFontList(nsAtom* aLangGroup, + const nsACString& aGenericFamily, + nsTArray<nsString>& aListOfFonts) { + if (SharedFontList()) { + fontlist::FontList* list = SharedFontList(); + const fontlist::Family* families = list->Families(); + if (families) { + for (uint32_t i = 0; i < list->NumFamilies(); i++) { + auto& f = families[i]; + if (!IsVisibleToCSS(f) || f.IsAltLocaleFamily()) { + continue; + } + // XXX TODO: filter families for aGenericFamily, if supported by + // platform + aListOfFonts.AppendElement( + NS_ConvertUTF8toUTF16(list->LocalizedFamilyName(&f))); + } + } + return; + } + + MutexAutoLock lock(mFontFamiliesMutex); + for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { + RefPtr<gfxFontFamily>& family = iter.Data(); + if (!IsVisibleToCSS(*family)) { + continue; + } + if (family->FilterForFontList(aLangGroup, aGenericFamily)) { + nsAutoCString localizedFamilyName; + family->LocalizedName(localizedFamilyName); + aListOfFonts.AppendElement(NS_ConvertUTF8toUTF16(localizedFamilyName)); + } + } + + aListOfFonts.Sort(); + aListOfFonts.Compact(); +} + +void gfxPlatformFontList::GetFontFamilyList( + nsTArray<RefPtr<gfxFontFamily>>& aFamilyArray) { + for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { + RefPtr<gfxFontFamily>& family = iter.Data(); + aFamilyArray.AppendElement(family); + } +} + +gfxFont* gfxPlatformFontList::SystemFindFontForChar( + uint32_t aCh, uint32_t aNextCh, Script aRunScript, + eFontPresentation aPresentation, const gfxFontStyle* aStyle, + FontVisibility* aVisibility, FontMatchingStats* aFontMatchingStats) { + MOZ_ASSERT(!mCodepointsWithNoFonts.test(aCh), + "don't call for codepoints already known to be unsupported"); + + // Try to short-circuit font fallback for U+FFFD, used to represent + // encoding errors: just use cached family from last time U+FFFD was seen. + // This helps speed up pages with lots of encoding errors, binary-as-text, + // etc. + if (aCh == 0xFFFD) { + gfxFontEntry* fontEntry = nullptr; + if (mReplacementCharFallbackFamily.mIsShared && + mReplacementCharFallbackFamily.mShared) { + fontlist::Face* face = + mReplacementCharFallbackFamily.mShared->FindFaceForStyle( + SharedFontList(), *aStyle); + if (face) { + fontEntry = + GetOrCreateFontEntry(face, mReplacementCharFallbackFamily.mShared); + *aVisibility = mReplacementCharFallbackFamily.mShared->Visibility(); + } + } else if (!mReplacementCharFallbackFamily.mIsShared && + mReplacementCharFallbackFamily.mUnshared) { + fontEntry = + mReplacementCharFallbackFamily.mUnshared->FindFontForStyle(*aStyle); + *aVisibility = mReplacementCharFallbackFamily.mUnshared->Visibility(); + } + + // this should never fail, as we must have found U+FFFD in order to set + // mReplacementCharFallbackFamily at all, but better play it safe + if (fontEntry && fontEntry->HasCharacter(aCh)) { + return fontEntry->FindOrMakeFont(aStyle); + } + } + + TimeStamp start = TimeStamp::Now(); + + // search commonly available fonts + bool common = true; + FontFamily fallbackFamily; + gfxFont* candidate = CommonFontFallback( + aCh, aNextCh, aRunScript, aPresentation, aStyle, fallbackFamily); + gfxFont* font = nullptr; + if (candidate) { + if (aPresentation == eFontPresentation::Any) { + font = candidate; + } else { + bool hasColorGlyph = candidate->HasColorGlyphFor(aCh, aNextCh); + if (hasColorGlyph == PrefersColor(aPresentation)) { + font = candidate; + } + } + } + + // If we didn't find a common font, or it was not the preferred type (color + // or monochrome), do system-wide fallback (except for specials). + uint32_t cmapCount = 0; + if (!font) { + common = false; + font = GlobalFontFallback(aCh, aNextCh, aRunScript, aPresentation, aStyle, + cmapCount, fallbackFamily, aFontMatchingStats); + // If the font we found doesn't match the requested type, and we also found + // a candidate above, prefer that one. + if (font && aPresentation != eFontPresentation::Any && candidate) { + bool hasColorGlyph = font->HasColorGlyphFor(aCh, aNextCh); + if (hasColorGlyph != PrefersColor(aPresentation)) { + // We're discarding `font` and using `candidate` instead, so ensure + // `font` is known to the global cache expiration tracker. + RefPtr<gfxFont> autoRefDeref(font); + font = candidate; + } + } + } + TimeDuration elapsed = TimeStamp::Now() - start; + + LogModule* log = gfxPlatform::GetLog(eGfxLog_textrun); + + if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Warning))) { + Script script = mozilla::unicode::GetScriptCode(aCh); + MOZ_LOG(log, LogLevel::Warning, + ("(textrun-systemfallback-%s) char: u+%6.6x " + "script: %d match: [%s]" + " time: %dus cmaps: %d\n", + (common ? "common" : "global"), aCh, static_cast<int>(script), + (font ? font->GetFontEntry()->Name().get() : "<none>"), + int32_t(elapsed.ToMicroseconds()), cmapCount)); + } + + // no match? add to set of non-matching codepoints + if (!font) { + mCodepointsWithNoFonts.set(aCh); + } else { + *aVisibility = fallbackFamily.mIsShared + ? fallbackFamily.mShared->Visibility() + : fallbackFamily.mUnshared->Visibility(); + if (aCh == 0xFFFD) { + 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 font; +} + +#define NUM_FALLBACK_FONTS 8 + +gfxFont* gfxPlatformFontList::CommonFontFallback( + uint32_t aCh, uint32_t aNextCh, Script aRunScript, + eFontPresentation aPresentation, const gfxFontStyle* aMatchStyle, + FontFamily& aMatchedFamily) { + AutoTArray<const char*, NUM_FALLBACK_FONTS> defaultFallbacks; + gfxPlatform::GetPlatform()->GetCommonFallbackFonts( + aCh, aRunScript, aPresentation, defaultFallbacks); + GlobalFontMatch data(aCh, aNextCh, *aMatchStyle, aPresentation); + if (SharedFontList()) { + for (const auto name : defaultFallbacks) { + fontlist::Family* family = FindSharedFamily(nsDependentCString(name)); + if (!family || !IsVisibleToCSS(*family)) { + continue; + } + // XXX(jfkthame) Should we fire the async cmap-loader here, or let it + // always do a potential sync initialization of the family? + family->SearchAllFontsForChar(SharedFontList(), &data); + if (data.mBestMatch) { + aMatchedFamily = FontFamily(family); + return data.mBestMatch->FindOrMakeFont(aMatchStyle); + } + } + } else { + for (const auto name : defaultFallbacks) { + gfxFontFamily* fallback = + FindFamilyByCanonicalName(nsDependentCString(name)); + if (!fallback || !IsVisibleToCSS(*fallback)) { + continue; + } + fallback->FindFontForChar(&data); + if (data.mBestMatch) { + aMatchedFamily = FontFamily(fallback); + return data.mBestMatch->FindOrMakeFont(aMatchStyle); + } + } + } + return nullptr; +} + +gfxFont* gfxPlatformFontList::GlobalFontFallback( + uint32_t aCh, uint32_t aNextCh, Script aRunScript, + eFontPresentation aPresentation, const gfxFontStyle* aMatchStyle, + uint32_t& aCmapCount, FontFamily& aMatchedFamily, + FontMatchingStats* aFontMatchingStats) { + bool useCmaps = IsFontFamilyWhitelistActive() || + gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback(); + FontVisibility rejectedFallbackVisibility = FontVisibility::Unknown; + if (!useCmaps) { + // Allow platform-specific fallback code to try and find a usable font + gfxFontEntry* fe = PlatformGlobalFontFallback(aCh, aRunScript, aMatchStyle, + aMatchedFamily); + if (fe) { + if (aMatchedFamily.mIsShared) { + if (IsVisibleToCSS(*aMatchedFamily.mShared)) { + gfxFont* font = fe->FindOrMakeFont(aMatchStyle); + if (font) { + if (aPresentation == eFontPresentation::Any) { + return font; + } + bool hasColorGlyph = font->HasColorGlyphFor(aCh, aNextCh); + if (hasColorGlyph == PrefersColor(aPresentation)) { + return font; + } + } + } + rejectedFallbackVisibility = aMatchedFamily.mShared->Visibility(); + } else { + if (IsVisibleToCSS(*aMatchedFamily.mUnshared)) { + gfxFont* font = fe->FindOrMakeFont(aMatchStyle); + if (font) { + if (aPresentation == eFontPresentation::Any) { + return font; + } + bool hasColorGlyph = font->HasColorGlyphFor(aCh, aNextCh); + if (hasColorGlyph == PrefersColor(aPresentation)) { + return font; + } + } + } + rejectedFallbackVisibility = aMatchedFamily.mUnshared->Visibility(); + } + } + } + + // otherwise, try to find it among local fonts + GlobalFontMatch data(aCh, aNextCh, *aMatchStyle, aPresentation); + if (SharedFontList()) { + fontlist::Family* families = SharedFontList()->Families(); + if (families) { + for (uint32_t i = 0; i < SharedFontList()->NumFamilies(); i++) { + fontlist::Family& family = families[i]; + if (!IsVisibleToCSS(family)) { + continue; + } + if (!family.IsFullyInitialized() && + StaticPrefs::gfx_font_rendering_fallback_async()) { + // Start loading all the missing charmaps; but this is async, + // so for now we just continue, ignoring this family. + StartCmapLoadingFromFamily(i); + } else { + family.SearchAllFontsForChar(SharedFontList(), &data); + if (data.mMatchDistance == 0.0) { + // no better style match is possible, so stop searching + break; + } + } + } + if (data.mBestMatch) { + aMatchedFamily = FontFamily(data.mMatchedSharedFamily); + return data.mBestMatch->FindOrMakeFont(aMatchStyle); + } + } + } else { + // iterate over all font families to find a font that support the + // character + for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { + RefPtr<gfxFontFamily>& family = iter.Data(); + if (!IsVisibleToCSS(*family)) { + continue; + } + // evaluate all fonts in this family for a match + family->FindFontForChar(&data); + if (data.mMatchDistance == 0.0) { + // no better style match is possible, so stop searching + break; + } + } + + aCmapCount = data.mCmapsTested; + if (data.mBestMatch) { + aMatchedFamily = FontFamily(data.mMatchedFamily); + return data.mBestMatch->FindOrMakeFont(aMatchStyle); + } + } + + if (aFontMatchingStats) { + if (rejectedFallbackVisibility == FontVisibility::LangPack) { + aFontMatchingStats->mFallbacks |= FallbackTypes::MissingFontLangPack; + } else if (rejectedFallbackVisibility == FontVisibility::User) { + aFontMatchingStats->mFallbacks |= FallbackTypes::MissingFontUser; + } + } + + return nullptr; +} + +class StartCmapLoadingRunnable : public mozilla::Runnable { + public: + explicit StartCmapLoadingRunnable(uint32_t aStartIndex) + : Runnable("gfxPlatformFontList::StartCmapLoadingRunnable"), + mStartIndex(aStartIndex) {} + + NS_IMETHOD Run() override { + auto* pfl = gfxPlatformFontList::PlatformFontList(); + auto* list = pfl->SharedFontList(); + if (!list) { + return NS_OK; + } + if (mStartIndex >= list->NumFamilies()) { + return NS_OK; + } + if (XRE_IsParentProcess()) { + pfl->StartCmapLoading(list->GetGeneration(), mStartIndex); + } else { + dom::ContentChild::GetSingleton()->SendStartCmapLoading( + list->GetGeneration(), mStartIndex); + } + return NS_OK; + } + + private: + uint32_t mStartIndex; +}; + +void gfxPlatformFontList::StartCmapLoadingFromFamily(uint32_t aStartIndex) { + if (aStartIndex > mStartedLoadingCmapsFrom) { + // We already initiated cmap-loading from somewhere earlier in the list; + // no need to do it again here. + return; + } + mStartedLoadingCmapsFrom = aStartIndex; + + // If we're already on the main thread, don't bother dispatching a runnable + // here to kick off the loading process, just do it directly. + if (NS_IsMainThread()) { + auto* list = SharedFontList(); + if (XRE_IsParentProcess()) { + StartCmapLoading(list->GetGeneration(), aStartIndex); + } else { + dom::ContentChild::GetSingleton()->SendStartCmapLoading( + list->GetGeneration(), aStartIndex); + } + } else { + NS_DispatchToMainThread(new StartCmapLoadingRunnable(aStartIndex)); + } +} + +class LoadCmapsRunnable : public CancelableRunnable { + public: + explicit LoadCmapsRunnable(uint32_t aGeneration, uint32_t aFamilyIndex) + : CancelableRunnable("gfxPlatformFontList::LoadCmapsRunnable"), + mGeneration(aGeneration), + mStartIndex(aFamilyIndex), + mIndex(aFamilyIndex) {} + + // Reset the current family index, if the value passed is earlier than our + // original starting position. We don't "reset" if it would move the current + // position forward, or back into the already-scanned range. + // We could optimize further by remembering the current position reached, + // and then skipping ahead from the original start, but it doesn't seem worth + // extra complexity for a task that usually only happens once, and already- + // processed families will be skipped pretty quickly in Run() anyhow. + void MaybeResetIndex(uint32_t aFamilyIndex) { + if (aFamilyIndex < mStartIndex) { + mStartIndex = aFamilyIndex; + mIndex = aFamilyIndex; + } + } + + nsresult Cancel() override { + mIsCanceled = true; + return NS_OK; + } + + NS_IMETHOD Run() override { + if (mIsCanceled) { + return NS_OK; + } + auto* pfl = gfxPlatformFontList::PlatformFontList(); + auto* list = pfl->SharedFontList(); + MOZ_ASSERT(list); + if (!list) { + return NS_OK; + } + if (mGeneration != list->GetGeneration()) { + return NS_OK; + } + uint32_t numFamilies = list->NumFamilies(); + if (mIndex >= numFamilies) { + return NS_OK; + } + auto* families = list->Families(); + // Skip any families that are already initialized. + while (mIndex < numFamilies && families[mIndex].IsFullyInitialized()) { + ++mIndex; + } + // Fully process one family, and advance index. + if (mIndex < numFamilies) { + Unused << pfl->InitializeFamily(&families[mIndex], true); + ++mIndex; + } + // If there are more families to initialize, post ourselves back to the + // idle queue to handle the next one; otherwise we're finished and we need + // to notify content processes to update their rendering. + if (mIndex < numFamilies) { + RefPtr<CancelableRunnable> task = this; + NS_DispatchToMainThreadQueue(task.forget(), EventQueuePriority::Idle); + } else { + pfl->CancelLoadCmapsTask(); + pfl->InitializeCodepointsWithNoFonts(); + dom::ContentParent::NotifyUpdatedFonts(false); + } + return NS_OK; + } + + private: + uint32_t mGeneration; + uint32_t mStartIndex; + uint32_t mIndex; + bool mIsCanceled = false; +}; + +void gfxPlatformFontList::StartCmapLoading(uint32_t aGeneration, + uint32_t aStartIndex) { + MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); + if (aGeneration != SharedFontList()->GetGeneration()) { + return; + } + if (mLoadCmapsRunnable) { + // We already have a runnable; just make sure it covers the full range of + // families needed. + static_cast<LoadCmapsRunnable*>(mLoadCmapsRunnable.get()) + ->MaybeResetIndex(aStartIndex); + return; + } + mLoadCmapsRunnable = new LoadCmapsRunnable(aGeneration, aStartIndex); + RefPtr<CancelableRunnable> task = mLoadCmapsRunnable; + NS_DispatchToMainThreadQueue(task.forget(), EventQueuePriority::Idle); +} + +gfxFontFamily* gfxPlatformFontList::CheckFamily(gfxFontFamily* aFamily) { + if (aFamily && !aFamily->HasStyles()) { + aFamily->FindStyleVariations(); + } + + if (aFamily && aFamily->GetFontList().Length() == 0) { + // failed to load any faces for this family, so discard it + nsAutoCString key; + GenerateFontListKey(aFamily->Name(), key); + mFontFamilies.Remove(key); + return nullptr; + } + + return aFamily; +} + +bool gfxPlatformFontList::FindAndAddFamilies( + StyleGenericFontFamily aGeneric, const nsACString& aFamily, + nsTArray<FamilyAndGeneric>* aOutput, FindFamiliesFlags aFlags, + gfxFontStyle* aStyle, nsAtom* aLanguage, gfxFloat aDevToCssSize) { + nsAutoCString key; + GenerateFontListKey(aFamily, key); + + bool allowHidden = bool(aFlags & FindFamiliesFlags::eSearchHiddenFamilies); + if (SharedFontList()) { + fontlist::Family* family = SharedFontList()->FindFamily(key); + // If not found, and other family names have not yet been initialized, + // initialize the rest of the list and try again. This is done lazily + // since reading name table entries is expensive. + // Although ASCII localized family names are possible they don't occur + // in practice, so avoid pulling in names at startup. + if (!family && !mOtherFamilyNamesInitialized) { + bool triggerLoading = true; + bool mayDefer = + !(aFlags & FindFamiliesFlags::eForceOtherFamilyNamesLoading); + if (IsAscii(key)) { + // If `key` is an ASCII name, only trigger loading if it includes a + // space, and the "base" name (up to the last space) exists as a known + // family, so that this might be a legacy styled-family name. + const char* data = key.BeginReading(); + int32_t index = key.Length(); + while (--index > 0) { + if (data[index] == ' ') { + break; + } + } + if (index <= 0 || + !SharedFontList()->FindFamily(nsAutoCString(key.get(), index))) { + triggerLoading = false; + } + } + if (triggerLoading) { + if (InitOtherFamilyNames(mayDefer)) { + family = SharedFontList()->FindFamily(key); + } + } + if (!family && !mOtherFamilyNamesInitialized && + !(aFlags & FindFamiliesFlags::eNoAddToNamesMissedWhenSearching)) { + // localized family names load timed out, add name to list of + // names to check after localized names are loaded + if (!mOtherNamesMissed) { + mOtherNamesMissed = MakeUnique<nsTHashtable<nsCStringHashKey>>(2); + } + mOtherNamesMissed->PutEntry(key); + } + } + // Check whether the family we found is actually allowed to be looked up, + // according to current font-visibility prefs. + if (family && + (IsVisibleToCSS(*family) || (allowHidden && family->IsHidden()))) { + aOutput->AppendElement(FamilyAndGeneric(family, aGeneric)); + return true; + } + return false; + } + + 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 (familyEntry && !IsVisibleToCSS(*familyEntry) && + !(allowHidden && familyEntry->IsHidden())) { + return false; + } + + // if not found, lookup in other family names list (mostly localized names) + if (!familyEntry) { + familyEntry = mOtherFamilyNames.GetWeak(key); + } + if (familyEntry && !IsVisibleToCSS(*familyEntry) && + !(allowHidden && familyEntry->IsHidden())) { + return false; + } + + // if still not found and other family names not yet fully initialized, + // initialize the rest of the list and try again. this is done lazily + // since reading name table entries is expensive. + // although ASCII localized family names are possible they don't occur + // in practice so avoid pulling in names at startup + if (!familyEntry && !mOtherFamilyNamesInitialized && !IsAscii(aFamily)) { + InitOtherFamilyNames( + !(aFlags & FindFamiliesFlags::eForceOtherFamilyNamesLoading)); + familyEntry = mOtherFamilyNames.GetWeak(key); + if (!familyEntry && !mOtherFamilyNamesInitialized && + !(aFlags & FindFamiliesFlags::eNoAddToNamesMissedWhenSearching)) { + // localized family names load timed out, add name to list of + // names to check after localized names are loaded + if (!mOtherNamesMissed) { + mOtherNamesMissed = MakeUnique<nsTHashtable<nsCStringHashKey>>(2); + } + mOtherNamesMissed->PutEntry(key); + } + if (familyEntry && !IsVisibleToCSS(*familyEntry) && + !(allowHidden && familyEntry->IsHidden())) { + return false; + } + } + + familyEntry = CheckFamily(familyEntry); + + // If we failed to find the requested family, check for a space in the + // name; if found, and if the "base" name (up to the last space) exists + // as a family, then this might be a legacy GDI-style family name for + // an additional weight/width. Try searching the faces of the base family + // and create any corresponding legacy families. + if (!familyEntry && + !(aFlags & FindFamiliesFlags::eNoSearchForLegacyFamilyNames)) { + // We don't have nsAString::RFindChar, so look for a space manually + const char* data = aFamily.BeginReading(); + int32_t index = aFamily.Length(); + while (--index > 0) { + if (data[index] == ' ') { + break; + } + } + if (index > 0) { + gfxFontFamily* base = + FindUnsharedFamily(Substring(aFamily, 0, index), + FindFamiliesFlags::eNoSearchForLegacyFamilyNames); + // If we found the "base" family name, and if it has members with + // legacy names, this will add corresponding font-family entries to + // the mOtherFamilyNames list; then retry the legacy-family search. + if (base && base->CheckForLegacyFamilyNames(this)) { + familyEntry = mOtherFamilyNames.GetWeak(key); + } + if (familyEntry && !IsVisibleToCSS(*familyEntry) && + !(allowHidden && familyEntry->IsHidden())) { + return false; + } + } + } + + if (familyEntry) { + aOutput->AppendElement(FamilyAndGeneric(familyEntry, aGeneric)); + return true; + } + + return false; +} + +fontlist::Family* gfxPlatformFontList::FindSharedFamily( + const nsACString& aFamily, FindFamiliesFlags aFlags, gfxFontStyle* aStyle, + nsAtom* aLanguage, gfxFloat aDevToCss) { + if (!SharedFontList()) { + return nullptr; + } + AutoTArray<FamilyAndGeneric, 1> families; + if (!FindAndAddFamilies(StyleGenericFontFamily::None, aFamily, &families, + aFlags, aStyle, aLanguage, aDevToCss) || + !families[0].mFamily.mIsShared) { + return nullptr; + } + fontlist::Family* family = families[0].mFamily.mShared; + if (!family->IsInitialized()) { + if (!InitializeFamily(family)) { + return nullptr; + } + } + return family; +} + +class InitializeFamilyRunnable : public mozilla::Runnable { + public: + explicit InitializeFamilyRunnable(uint32_t aFamilyIndex, bool aLoadCmaps) + : Runnable("gfxPlatformFontList::InitializeFamilyRunnable"), + mIndex(aFamilyIndex), + mLoadCmaps(aLoadCmaps) {} + + NS_IMETHOD Run() override { + auto list = gfxPlatformFontList::PlatformFontList()->SharedFontList(); + if (!list) { + return NS_OK; + } + if (mIndex >= list->NumFamilies()) { + // Out of range? Maybe the list got reinitialized since this request + // was posted - just ignore it. + return NS_OK; + } + dom::ContentChild::GetSingleton()->SendInitializeFamily( + list->GetGeneration(), mIndex, mLoadCmaps); + return NS_OK; + } + + private: + uint32_t mIndex; + bool mLoadCmaps; +}; + +bool gfxPlatformFontList::InitializeFamily(fontlist::Family* aFamily, + bool aLoadCmaps) { + MOZ_ASSERT(SharedFontList()); + auto list = SharedFontList(); + if (!XRE_IsParentProcess()) { + auto* families = list->Families(); + if (!families) { + return false; + } + uint32_t index = aFamily - families; + if (index >= list->NumFamilies()) { + return false; + } + if (NS_IsMainThread()) { + dom::ContentChild::GetSingleton()->SendInitializeFamily( + list->GetGeneration(), index, aLoadCmaps); + } else { + NS_DispatchToMainThread(new InitializeFamilyRunnable(index, aLoadCmaps)); + } + return aFamily->IsInitialized(); + } + + if (!aFamily->IsInitialized()) { + // The usual case: we're being asked to populate the face list. + AutoTArray<fontlist::Face::InitData, 16> faceList; + GetFacesInitDataForFamily(aFamily, faceList, aLoadCmaps); + aFamily->AddFaces(list, faceList); + } else { + // The family's face list was already initialized, but if aLoadCmaps is + // true we also want to eagerly load character maps. This is used when a + // child process is doing SearchAllFontsForChar, to have the parent load + // all the cmaps at once and reduce IPC traffic (and content-process file + // access overhead, which is crippling for DirectWrite on Windows). + if (aLoadCmaps) { + auto* faces = aFamily->Faces(list); + if (faces) { + for (size_t i = 0; i < aFamily->NumFaces(); i++) { + auto* face = static_cast<fontlist::Face*>(faces[i].ToPtr(list)); + if (face && face->mCharacterMap.IsNull()) { + // We don't want to cache this font entry, as the parent will most + // likely never use it again; it's just to populate the charmap for + // the benefit of the child process. + RefPtr<gfxFontEntry> fe = CreateFontEntry(face, aFamily); + if (fe) { + fe->ReadCMAP(); + } + } + } + } + } + } + + if (aLoadCmaps && aFamily->IsInitialized()) { + aFamily->SetupFamilyCharMap(list); + } + + return aFamily->IsInitialized(); +} + +gfxFontEntry* gfxPlatformFontList::FindFontForFamily( + const nsACString& aFamily, const gfxFontStyle* aStyle) { + nsAutoCString key; + GenerateFontListKey(aFamily, key); + FontFamily family = FindFamily(key); + if (family.IsNull()) { + return nullptr; + } + if (family.mIsShared) { + auto face = family.mShared->FindFaceForStyle(SharedFontList(), *aStyle); + if (!face) { + return nullptr; + } + return GetOrCreateFontEntry(face, family.mShared); + } + return family.mUnshared->FindFontForStyle(*aStyle); +} + +gfxFontEntry* gfxPlatformFontList::GetOrCreateFontEntry( + fontlist::Face* aFace, const fontlist::Family* aFamily) { + return mFontEntries.LookupForAdd(aFace).OrInsert( + [=]() { return CreateFontEntry(aFace, aFamily); }); +} + +void gfxPlatformFontList::AddOtherFamilyName( + gfxFontFamily* aFamilyEntry, const nsCString& aOtherFamilyName) { + nsAutoCString key; + GenerateFontListKey(aOtherFamilyName, key); + + if (!mOtherFamilyNames.GetWeak(key)) { + mOtherFamilyNames.Put(key, RefPtr{aFamilyEntry}); + LOG_FONTLIST( + ("(fontlist-otherfamily) canonical family: %s, " + "other family: %s\n", + aFamilyEntry->Name().get(), aOtherFamilyName.get())); + if (mBadUnderlineFamilyNames.ContainsSorted(key)) { + aFamilyEntry->SetBadUnderlineFamily(); + } + } +} + +void gfxPlatformFontList::AddFullname(gfxFontEntry* aFontEntry, + const nsCString& aFullname) { + if (!mExtraNames->mFullnames.GetWeak(aFullname)) { + mExtraNames->mFullnames.Put(aFullname, RefPtr{aFontEntry}); + LOG_FONTLIST(("(fontlist-fullname) name: %s, fullname: %s\n", + aFontEntry->Name().get(), aFullname.get())); + } +} + +void gfxPlatformFontList::AddPostscriptName(gfxFontEntry* aFontEntry, + const nsCString& aPostscriptName) { + if (!mExtraNames->mPostscriptNames.GetWeak(aPostscriptName)) { + mExtraNames->mPostscriptNames.Put(aPostscriptName, RefPtr{aFontEntry}); + LOG_FONTLIST(("(fontlist-postscript) name: %s, psname: %s\n", + aFontEntry->Name().get(), aPostscriptName.get())); + } +} + +bool gfxPlatformFontList::GetStandardFamilyName(const nsCString& aFontName, + nsACString& aFamilyName) { + FontFamily family = FindFamily(aFontName); + if (family.IsNull()) { + return false; + } + return GetLocalizedFamilyName(FindFamily(aFontName), aFamilyName); +} + +bool gfxPlatformFontList::GetLocalizedFamilyName(const FontFamily& aFamily, + nsACString& aFamilyName) { + if (aFamily.mIsShared) { + if (aFamily.mShared) { + aFamilyName = SharedFontList()->LocalizedFamilyName(aFamily.mShared); + return true; + } + } else if (aFamily.mUnshared) { + aFamily.mUnshared->LocalizedName(aFamilyName); + return true; + } + return false; // leaving the aFamilyName outparam untouched +} + +FamilyAndGeneric gfxPlatformFontList::GetDefaultFontFamily( + const nsACString& aLangGroup, const nsACString& aGenericFamily) { + if (NS_WARN_IF(aLangGroup.IsEmpty()) || + NS_WARN_IF(aGenericFamily.IsEmpty())) { + return FamilyAndGeneric(); + } + + AutoTArray<nsCString, 4> names; + gfxFontUtils::AppendPrefsFontList( + NameListPref(aGenericFamily, aLangGroup).get(), names); + + for (const nsCString& name : names) { + FontFamily family = FindFamily(name); + if (!family.IsNull()) { + return FamilyAndGeneric(family); + } + } + + return FamilyAndGeneric(); +} + +ShmemCharMapHashEntry::ShmemCharMapHashEntry(const gfxSparseBitSet* aCharMap) + : mList(gfxPlatformFontList::PlatformFontList()->SharedFontList()), + mCharMap(), + mHash(aCharMap->GetChecksum()) { + size_t len = SharedBitSet::RequiredSize(*aCharMap); + mCharMap = mList->Alloc(len); + SharedBitSet::Create(mCharMap.ToPtr(mList), len, *aCharMap); +} + +fontlist::Pointer gfxPlatformFontList::GetShmemCharMap( + const gfxSparseBitSet* aCmap) { + auto* entry = mShmemCharMaps.GetEntry(aCmap); + if (!entry) { + entry = mShmemCharMaps.PutEntry(aCmap); + } + return entry->GetCharMap(); +} + +gfxCharacterMap* gfxPlatformFontList::FindCharMap(gfxCharacterMap* aCmap) { + aCmap->CalcHash(); + gfxCharacterMap* cmap = AddCmap(aCmap); + cmap->mShared = true; + return cmap; +} + +// add a cmap to the shared cmap set +gfxCharacterMap* gfxPlatformFontList::AddCmap(const gfxCharacterMap* aCharMap) { + CharMapHashKey* found = + mSharedCmaps.PutEntry(const_cast<gfxCharacterMap*>(aCharMap)); + return found->GetKey(); +} + +// remove the cmap from the shared cmap set +void gfxPlatformFontList::RemoveCmap(const gfxCharacterMap* aCharMap) { + // skip lookups during teardown + if (mSharedCmaps.Count() == 0) { + return; + } + + // cmap needs to match the entry *and* be the same ptr before removing + CharMapHashKey* found = + mSharedCmaps.GetEntry(const_cast<gfxCharacterMap*>(aCharMap)); + if (found && found->GetKey() == aCharMap) { + mSharedCmaps.RemoveEntry(found); + } +} + +void gfxPlatformFontList::ResolveGenericFontNames( + StyleGenericFontFamily aGenericType, eFontPrefLang aPrefLang, + PrefFontList* aGenericFamilies) { + const char* langGroupStr = GetPrefLangName(aPrefLang); + const char* generic = GetGenericName(aGenericType); + + if (!generic) { + return; + } + + AutoTArray<nsCString, 4> genericFamilies; + + // load family for "font.name.generic.lang" + gfxFontUtils::AppendPrefsFontList(NamePref(generic, langGroupStr).get(), + genericFamilies); + + // load fonts for "font.name-list.generic.lang" + gfxFontUtils::AppendPrefsFontList(NameListPref(generic, langGroupStr).get(), + genericFamilies); + + nsAtom* langGroup = GetLangGroupForPrefLang(aPrefLang); + NS_ASSERTION(langGroup, "null lang group for pref lang"); + + GetFontFamiliesFromGenericFamilies(aGenericType, genericFamilies, langGroup, + aGenericFamilies); + +#if 0 // dump out generic mappings + printf("%s ===> ", NamePref(generic, langGroupStr).get()); + for (uint32_t k = 0; k < aGenericFamilies->Length(); k++) { + if (k > 0) printf(", "); + printf("%s", (*aGenericFamilies)[k].mIsShared + ? (*aGenericFamilies)[k].mShared->DisplayName().AsString(SharedFontList()).get() + : (*aGenericFamilies)[k].mUnshared->Name().get()); + } + printf("\n"); +#endif +} + +void gfxPlatformFontList::ResolveEmojiFontNames( + PrefFontList* aGenericFamilies) { + // emoji preference has no lang name + AutoTArray<nsCString, 4> genericFamilies; + + nsAutoCString prefFontListName("font.name-list.emoji"); + gfxFontUtils::AppendPrefsFontList(prefFontListName.get(), genericFamilies); + + GetFontFamiliesFromGenericFamilies(StyleGenericFontFamily::MozEmoji, + genericFamilies, nullptr, + aGenericFamilies); +} + +void gfxPlatformFontList::GetFontFamiliesFromGenericFamilies( + StyleGenericFontFamily aGenericType, + nsTArray<nsCString>& aGenericNameFamilies, nsAtom* aLangGroup, + PrefFontList* aGenericFamilies) { + // lookup and add platform fonts uniquely + for (const nsCString& genericFamily : aGenericNameFamilies) { + AutoTArray<FamilyAndGeneric, 10> families; + FindAndAddFamilies(aGenericType, genericFamily, &families, + FindFamiliesFlags(0), nullptr, aLangGroup); + for (const FamilyAndGeneric& f : families) { + if (!aGenericFamilies->Contains(f.mFamily)) { + aGenericFamilies->AppendElement(f.mFamily); + } + } + } +} + +gfxPlatformFontList::PrefFontList* gfxPlatformFontList::GetPrefFontsLangGroup( + StyleGenericFontFamily aGenericType, eFontPrefLang aPrefLang) { + if (aGenericType == StyleGenericFontFamily::MozEmoji) { + // Emoji font has no lang + PrefFontList* prefFonts = mEmojiPrefFont.get(); + if (MOZ_UNLIKELY(!prefFonts)) { + prefFonts = new PrefFontList; + ResolveEmojiFontNames(prefFonts); + mEmojiPrefFont.reset(prefFonts); + } + return prefFonts; + } + + auto index = static_cast<size_t>(aGenericType); + PrefFontList* prefFonts = mLangGroupPrefFonts[aPrefLang][index].get(); + if (MOZ_UNLIKELY(!prefFonts)) { + prefFonts = new PrefFontList; + ResolveGenericFontNames(aGenericType, aPrefLang, prefFonts); + mLangGroupPrefFonts[aPrefLang][index].reset(prefFonts); + } + return prefFonts; +} + +void gfxPlatformFontList::AddGenericFonts( + StyleGenericFontFamily aGenericType, nsAtom* aLanguage, + nsTArray<FamilyAndGeneric>& aFamilyList) { + // map lang ==> langGroup + nsAtom* langGroup = GetLangGroup(aLanguage); + + // langGroup ==> prefLang + eFontPrefLang prefLang = GetFontPrefLangFor(langGroup); + + // lookup pref fonts + PrefFontList* prefFonts = GetPrefFontsLangGroup(aGenericType, prefLang); + + if (!prefFonts->IsEmpty()) { + aFamilyList.SetCapacity(aFamilyList.Length() + prefFonts->Length()); + for (auto& f : *prefFonts) { + aFamilyList.AppendElement(FamilyAndGeneric(f, aGenericType)); + } + } +} + +static nsAtom* PrefLangToLangGroups(uint32_t aIndex) { + // static array here avoids static constructor + static nsAtom* gPrefLangToLangGroups[] = { +#define FONT_PREF_LANG(enum_id_, str_, atom_id_) nsGkAtoms::atom_id_ +#include "gfxFontPrefLangList.h" +#undef FONT_PREF_LANG + }; + + return aIndex < ArrayLength(gPrefLangToLangGroups) + ? gPrefLangToLangGroups[aIndex] + : nsGkAtoms::Unicode; +} + +eFontPrefLang gfxPlatformFontList::GetFontPrefLangFor(const char* aLang) { + if (!aLang || !aLang[0]) { + return eFontPrefLang_Others; + } + for (uint32_t i = 0; i < ArrayLength(gPrefLangNames); ++i) { + if (!PL_strcasecmp(gPrefLangNames[i], aLang)) { + return eFontPrefLang(i); + } + } + return eFontPrefLang_Others; +} + +eFontPrefLang gfxPlatformFontList::GetFontPrefLangFor(nsAtom* aLang) { + if (!aLang) return eFontPrefLang_Others; + nsAutoCString lang; + aLang->ToUTF8String(lang); + return GetFontPrefLangFor(lang.get()); +} + +nsAtom* gfxPlatformFontList::GetLangGroupForPrefLang(eFontPrefLang aLang) { + // the special CJK set pref lang should be resolved into separate + // calls to individual CJK pref langs before getting here + NS_ASSERTION(aLang != eFontPrefLang_CJKSet, "unresolved CJK set pref lang"); + + return PrefLangToLangGroups(uint32_t(aLang)); +} + +const char* gfxPlatformFontList::GetPrefLangName(eFontPrefLang aLang) { + if (uint32_t(aLang) < ArrayLength(gPrefLangNames)) { + return gPrefLangNames[uint32_t(aLang)]; + } + return nullptr; +} + +eFontPrefLang gfxPlatformFontList::GetFontPrefLangFor(uint32_t aCh) { + switch (ublock_getCode(aCh)) { + case UBLOCK_BASIC_LATIN: + case UBLOCK_LATIN_1_SUPPLEMENT: + case UBLOCK_LATIN_EXTENDED_A: + case UBLOCK_LATIN_EXTENDED_B: + case UBLOCK_IPA_EXTENSIONS: + case UBLOCK_SPACING_MODIFIER_LETTERS: + case UBLOCK_LATIN_EXTENDED_ADDITIONAL: + case UBLOCK_LATIN_EXTENDED_C: + case UBLOCK_LATIN_EXTENDED_D: + case UBLOCK_LATIN_EXTENDED_E: + case UBLOCK_PHONETIC_EXTENSIONS: + return eFontPrefLang_Western; + case UBLOCK_GREEK: + case UBLOCK_GREEK_EXTENDED: + return eFontPrefLang_Greek; + case UBLOCK_CYRILLIC: + case UBLOCK_CYRILLIC_SUPPLEMENT: + case UBLOCK_CYRILLIC_EXTENDED_A: + case UBLOCK_CYRILLIC_EXTENDED_B: + case UBLOCK_CYRILLIC_EXTENDED_C: + return eFontPrefLang_Cyrillic; + case UBLOCK_ARMENIAN: + return eFontPrefLang_Armenian; + case UBLOCK_HEBREW: + return eFontPrefLang_Hebrew; + case UBLOCK_ARABIC: + case UBLOCK_ARABIC_PRESENTATION_FORMS_A: + case UBLOCK_ARABIC_PRESENTATION_FORMS_B: + case UBLOCK_ARABIC_SUPPLEMENT: + case UBLOCK_ARABIC_EXTENDED_A: + case UBLOCK_ARABIC_MATHEMATICAL_ALPHABETIC_SYMBOLS: + return eFontPrefLang_Arabic; + case UBLOCK_DEVANAGARI: + case UBLOCK_DEVANAGARI_EXTENDED: + return eFontPrefLang_Devanagari; + case UBLOCK_BENGALI: + return eFontPrefLang_Bengali; + case UBLOCK_GURMUKHI: + return eFontPrefLang_Gurmukhi; + case UBLOCK_GUJARATI: + return eFontPrefLang_Gujarati; + case UBLOCK_ORIYA: + return eFontPrefLang_Oriya; + case UBLOCK_TAMIL: + return eFontPrefLang_Tamil; + case UBLOCK_TELUGU: + return eFontPrefLang_Telugu; + case UBLOCK_KANNADA: + return eFontPrefLang_Kannada; + case UBLOCK_MALAYALAM: + return eFontPrefLang_Malayalam; + case UBLOCK_SINHALA: + case UBLOCK_SINHALA_ARCHAIC_NUMBERS: + return eFontPrefLang_Sinhala; + case UBLOCK_THAI: + return eFontPrefLang_Thai; + case UBLOCK_TIBETAN: + return eFontPrefLang_Tibetan; + case UBLOCK_GEORGIAN: + case UBLOCK_GEORGIAN_SUPPLEMENT: + case UBLOCK_GEORGIAN_EXTENDED: + return eFontPrefLang_Georgian; + case UBLOCK_HANGUL_JAMO: + case UBLOCK_HANGUL_COMPATIBILITY_JAMO: + case UBLOCK_HANGUL_SYLLABLES: + case UBLOCK_HANGUL_JAMO_EXTENDED_A: + case UBLOCK_HANGUL_JAMO_EXTENDED_B: + return eFontPrefLang_Korean; + case UBLOCK_ETHIOPIC: + case UBLOCK_ETHIOPIC_EXTENDED: + case UBLOCK_ETHIOPIC_SUPPLEMENT: + case UBLOCK_ETHIOPIC_EXTENDED_A: + return eFontPrefLang_Ethiopic; + case UBLOCK_UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS: + case UBLOCK_UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_EXTENDED: + return eFontPrefLang_Canadian; + case UBLOCK_KHMER: + case UBLOCK_KHMER_SYMBOLS: + return eFontPrefLang_Khmer; + case UBLOCK_CJK_RADICALS_SUPPLEMENT: + case UBLOCK_KANGXI_RADICALS: + case UBLOCK_IDEOGRAPHIC_DESCRIPTION_CHARACTERS: + case UBLOCK_CJK_SYMBOLS_AND_PUNCTUATION: + case UBLOCK_HIRAGANA: + case UBLOCK_KATAKANA: + case UBLOCK_BOPOMOFO: + case UBLOCK_KANBUN: + case UBLOCK_BOPOMOFO_EXTENDED: + case UBLOCK_ENCLOSED_CJK_LETTERS_AND_MONTHS: + case UBLOCK_CJK_COMPATIBILITY: + case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A: + case UBLOCK_CJK_UNIFIED_IDEOGRAPHS: + case UBLOCK_CJK_COMPATIBILITY_IDEOGRAPHS: + case UBLOCK_CJK_COMPATIBILITY_FORMS: + case UBLOCK_SMALL_FORM_VARIANTS: + case UBLOCK_HALFWIDTH_AND_FULLWIDTH_FORMS: + case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B: + case UBLOCK_CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT: + case UBLOCK_KATAKANA_PHONETIC_EXTENSIONS: + case UBLOCK_CJK_STROKES: + case UBLOCK_VERTICAL_FORMS: + case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_C: + case UBLOCK_KANA_SUPPLEMENT: + case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_D: + case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_E: + case UBLOCK_IDEOGRAPHIC_SYMBOLS_AND_PUNCTUATION: + case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_F: + case UBLOCK_KANA_EXTENDED_A: + return eFontPrefLang_CJKSet; + case UBLOCK_MATHEMATICAL_OPERATORS: + case UBLOCK_MATHEMATICAL_ALPHANUMERIC_SYMBOLS: + case UBLOCK_MISCELLANEOUS_MATHEMATICAL_SYMBOLS_A: + case UBLOCK_MISCELLANEOUS_MATHEMATICAL_SYMBOLS_B: + case UBLOCK_SUPPLEMENTAL_MATHEMATICAL_OPERATORS: + return eFontPrefLang_Mathematics; + default: + return eFontPrefLang_Others; + } +} + +bool gfxPlatformFontList::IsLangCJK(eFontPrefLang aLang) { + switch (aLang) { + case eFontPrefLang_Japanese: + case eFontPrefLang_ChineseTW: + case eFontPrefLang_ChineseCN: + case eFontPrefLang_ChineseHK: + case eFontPrefLang_Korean: + case eFontPrefLang_CJKSet: + return true; + default: + return false; + } +} + +void gfxPlatformFontList::GetLangPrefs(eFontPrefLang aPrefLangs[], + uint32_t& aLen, eFontPrefLang aCharLang, + eFontPrefLang aPageLang) { + if (IsLangCJK(aCharLang)) { + AppendCJKPrefLangs(aPrefLangs, aLen, aCharLang, aPageLang); + } else { + AppendPrefLang(aPrefLangs, aLen, aCharLang); + } + + AppendPrefLang(aPrefLangs, aLen, eFontPrefLang_Others); +} + +void gfxPlatformFontList::AppendCJKPrefLangs(eFontPrefLang aPrefLangs[], + uint32_t& aLen, + eFontPrefLang aCharLang, + eFontPrefLang aPageLang) { + // prefer the lang specified by the page *if* CJK + if (IsLangCJK(aPageLang)) { + AppendPrefLang(aPrefLangs, aLen, aPageLang); + } + + // if not set up, set up the default CJK order, based on accept lang + // settings and locale + if (mCJKPrefLangs.Length() == 0) { + // temp array + eFontPrefLang tempPrefLangs[kMaxLenPrefLangList]; + uint32_t tempLen = 0; + + // Add the CJK pref fonts from accept languages, the order should be same + // order. We use gfxFontUtils::GetPrefsFontList to read the list even + // though it's not actually a list of fonts but of lang codes; the format + // is the same. + AutoTArray<nsCString, 5> list; + gfxFontUtils::GetPrefsFontList("intl.accept_languages", list, true); + for (const auto& lang : list) { + eFontPrefLang fpl = GetFontPrefLangFor(lang.get()); + switch (fpl) { + case eFontPrefLang_Japanese: + case eFontPrefLang_Korean: + case eFontPrefLang_ChineseCN: + case eFontPrefLang_ChineseHK: + case eFontPrefLang_ChineseTW: + AppendPrefLang(tempPrefLangs, tempLen, fpl); + break; + default: + break; + } + } + + // Try using app's locale + nsAutoCString localeStr; + LocaleService::GetInstance()->GetAppLocaleAsBCP47(localeStr); + + { + Locale locale(localeStr); + if (locale.GetLanguage().Equals("ja")) { + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Japanese); + } else if (locale.GetLanguage().Equals("zh")) { + if (locale.GetRegion().Equals("CN")) { + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseCN); + } else if (locale.GetRegion().Equals("TW")) { + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseTW); + } else if (locale.GetRegion().Equals("HK")) { + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseHK); + } + } else if (locale.GetLanguage().Equals("ko")) { + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Korean); + } + } + + // Then add the known CJK prefs in order of system preferred locales + AutoTArray<nsCString, 5> prefLocales; + prefLocales.AppendElement("ja"_ns); + prefLocales.AppendElement("zh-CN"_ns); + prefLocales.AppendElement("zh-TW"_ns); + prefLocales.AppendElement("zh-HK"_ns); + prefLocales.AppendElement("ko"_ns); + + AutoTArray<nsCString, 16> sysLocales; + AutoTArray<nsCString, 16> negLocales; + if (NS_SUCCEEDED( + OSPreferences::GetInstance()->GetSystemLocales(sysLocales))) { + LocaleService::GetInstance()->NegotiateLanguages( + sysLocales, prefLocales, ""_ns, + LocaleService::kLangNegStrategyFiltering, negLocales); + for (const auto& localeStr : negLocales) { + Locale locale(localeStr); + + if (locale.GetLanguage().Equals("ja")) { + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Japanese); + } else if (locale.GetLanguage().Equals("zh")) { + if (locale.GetRegion().Equals("CN")) { + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseCN); + } else if (locale.GetRegion().Equals("TW")) { + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseTW); + } else if (locale.GetRegion().Equals("HK")) { + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseHK); + } + } else if (locale.GetLanguage().Equals("ko")) { + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Korean); + } + } + } + + // Last resort... set up CJK font prefs in the order listed by the user- + // configurable ordering pref. + gfxFontUtils::GetPrefsFontList(kCJKFallbackOrderPref, list); + for (const auto& item : list) { + eFontPrefLang fpl = GetFontPrefLangFor(item.get()); + switch (fpl) { + case eFontPrefLang_Japanese: + case eFontPrefLang_Korean: + case eFontPrefLang_ChineseCN: + case eFontPrefLang_ChineseHK: + case eFontPrefLang_ChineseTW: + AppendPrefLang(tempPrefLangs, tempLen, fpl); + break; + default: + break; + } + } + + // Truly-last resort... try Chinese font prefs before Japanese because + // they tend to have more complete character coverage, and therefore less + // risk of "ransom-note" effects. + // (If the kCJKFallbackOrderPref was fully populated, as it is by default, + // this will do nothing as all these values are already present.) + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseCN); + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseHK); + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseTW); + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Japanese); + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Korean); + + // copy into the cached array + for (const auto lang : Span<eFontPrefLang>(tempPrefLangs, tempLen)) { + mCJKPrefLangs.AppendElement(lang); + } + } + + // append in cached CJK langs + for (const auto lang : mCJKPrefLangs) { + AppendPrefLang(aPrefLangs, aLen, eFontPrefLang(lang)); + } +} + +void gfxPlatformFontList::AppendPrefLang(eFontPrefLang aPrefLangs[], + uint32_t& aLen, + eFontPrefLang aAddLang) { + if (aLen >= kMaxLenPrefLangList) { + return; + } + + // If the lang is already present, just ignore the addition. + for (const auto lang : Span<eFontPrefLang>(aPrefLangs, aLen)) { + if (lang == aAddLang) { + return; + } + } + + aPrefLangs[aLen++] = aAddLang; +} + +StyleGenericFontFamily gfxPlatformFontList::GetDefaultGeneric( + eFontPrefLang aLang) { + if (aLang == eFontPrefLang_Emoji) { + return StyleGenericFontFamily::MozEmoji; + } + + // 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))); + nsAutoCString serifOrSans; + Preferences::GetCString(prefDefaultFontType.get(), serifOrSans); + if (serifOrSans.EqualsLiteral("sans-serif")) { + mDefaultGenericsLangGroup[i] = StyleGenericFontFamily::SansSerif; + } else { + mDefaultGenericsLangGroup[i] = StyleGenericFontFamily::Serif; + } + } + } + + if (uint32_t(aLang) < ArrayLength(gPrefLangNames)) { + return mDefaultGenericsLangGroup[uint32_t(aLang)]; + } + return StyleGenericFontFamily::Serif; +} + +FontFamily gfxPlatformFontList::GetDefaultFont(const gfxFontStyle* aStyle) { + FontFamily family = GetDefaultFontForPlatform(aStyle); + if (!family.IsNull()) { + return family; + } + // Something has gone wrong and we were unable to retrieve a default font + // from the platform. (Likely the whitelist has blocked all potential + // default fonts.) As a last resort, we return the first font in our list. + if (SharedFontList()) { + MOZ_RELEASE_ASSERT(SharedFontList()->NumFamilies() > 0); + return FontFamily(SharedFontList()->Families()); + } + MOZ_RELEASE_ASSERT(mFontFamilies.Count() > 0); + return FontFamily(mFontFamilies.Iter().Data()); +} + +void gfxPlatformFontList::GetFontFamilyNames( + nsTArray<nsCString>& aFontFamilyNames) { + if (SharedFontList()) { + fontlist::FontList* list = SharedFontList(); + const fontlist::Family* families = list->Families(); + if (families) { + for (uint32_t i = 0, n = list->NumFamilies(); i < n; i++) { + const fontlist::Family& family = families[i]; + if (!family.IsHidden()) { + aFontFamilyNames.AppendElement(family.DisplayName().AsString(list)); + } + } + } + } else { + for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { + RefPtr<gfxFontFamily>& family = iter.Data(); + if (!family->IsHidden()) { + aFontFamilyNames.AppendElement(family->Name()); + } + } + } +} + +nsAtom* gfxPlatformFontList::GetLangGroup(nsAtom* aLanguage) { + // map lang ==> langGroup + nsAtom* langGroup = nullptr; + if (aLanguage) { + langGroup = mLangService->GetLanguageGroup(aLanguage); + } + if (!langGroup) { + langGroup = nsGkAtoms::Unicode; + } + return langGroup; +} + +/* static */ const char* gfxPlatformFontList::GetGenericName( + StyleGenericFontFamily aGenericType) { + // type should be standard generic type at this point + // map generic type to string + switch (aGenericType) { + case StyleGenericFontFamily::Serif: + return "serif"; + case StyleGenericFontFamily::SansSerif: + return "sans-serif"; + case StyleGenericFontFamily::Monospace: + return "monospace"; + case StyleGenericFontFamily::Cursive: + return "cursive"; + case StyleGenericFontFamily::Fantasy: + return "fantasy"; + case StyleGenericFontFamily::MozEmoji: + return "-moz-emoji"; + case StyleGenericFontFamily::None: + break; + } + MOZ_ASSERT_UNREACHABLE("Unknown generic"); + return nullptr; +} + +void gfxPlatformFontList::InitLoader() { + GetFontFamilyNames(mFontInfo->mFontFamiliesToLoad); + mStartIndex = 0; + mNumFamilies = mFontInfo->mFontFamiliesToLoad.Length(); + memset(&(mFontInfo->mLoadStats), 0, sizeof(mFontInfo->mLoadStats)); +} + +#define FONT_LOADER_MAX_TIMESLICE \ + 20 // max time for one pass through RunLoader = 20ms + +bool gfxPlatformFontList::LoadFontInfo() { + TimeStamp start = TimeStamp::Now(); + uint32_t i, endIndex = mNumFamilies; + fontlist::FontList* list = SharedFontList(); + bool loadCmaps = + !list && (!UsesSystemFallback() || + gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback()); + + // for each font family, load in various font info + for (i = mStartIndex; i < endIndex; i++) { + nsAutoCString key; + GenerateFontListKey(mFontInfo->mFontFamiliesToLoad[i], key); + + if (list) { + fontlist::Family* family = list->FindFamily(key); + if (!family) { + continue; + } + ReadFaceNamesForFamily(family, NeedFullnamePostscriptNames()); + } else { + // lookup in canonical (i.e. English) family name list + gfxFontFamily* familyEntry = mFontFamilies.GetWeak(key); + if (!familyEntry) { + continue; + } + + // read in face names + familyEntry->ReadFaceNames(this, NeedFullnamePostscriptNames(), + mFontInfo); + + // load the cmaps if needed + if (loadCmaps) { + familyEntry->ReadAllCMAPs(mFontInfo); + } + } + + // Limit the time spent reading fonts in one pass, unless the font-loader + // delay was set to zero, in which case we run to completion even if it + // causes some jank. + if (StaticPrefs::gfx_font_loader_delay_AtStartup() > 0) { + TimeDuration elapsed = TimeStamp::Now() - start; + if (elapsed.ToMilliseconds() > FONT_LOADER_MAX_TIMESLICE && + i + 1 != endIndex) { + endIndex = i + 1; + break; + } + } + } + + mStartIndex = endIndex; + bool done = mStartIndex >= mNumFamilies; + + if (LOG_FONTINIT_ENABLED()) { + TimeDuration elapsed = TimeStamp::Now() - start; + LOG_FONTINIT(("(fontinit) fontloader load pass %8.2f ms done %s\n", + elapsed.ToMilliseconds(), (done ? "true" : "false"))); + } + + if (done) { + mOtherFamilyNamesInitialized = true; + CancelInitOtherFamilyNamesTask(); + mFaceNameListsInitialized = true; + } + + return done; +} + +void gfxPlatformFontList::CleanupLoader() { + 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 (FindUnsharedFamily( + it.Get()->GetKey(), + (FindFamiliesFlags::eForceOtherFamilyNamesLoading | + FindFamiliesFlags::eNoAddToNamesMissedWhenSearching))) { + 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() { + uint32_t delay = std::max(1u, StaticPrefs::gfx_font_loader_delay_AtStartup()); + StartLoader(delay); +} + +void gfxPlatformFontList::ForceGlobalReflow() { + gfxPlatform::ForceGlobalReflow(); +} + +void gfxPlatformFontList::RebuildLocalFonts(bool aForgetLocalFaces) { + for (auto it = mUserFontSetList.Iter(); !it.Done(); it.Next()) { + auto* fontset = it.Get()->GetKey(); + if (aForgetLocalFaces) { + fontset->ForgetLocalFaces(); + } + fontset->RebuildLocalRules(); + } +} + +void gfxPlatformFontList::ClearLangGroupPrefFonts() { + for (uint32_t i = eFontPrefLang_First; + i < eFontPrefLang_First + eFontPrefLang_Count; i++) { + auto& prefFontsLangGroup = mLangGroupPrefFonts[i]; + for (auto& pref : prefFontsLangGroup) { + pref = nullptr; + } + } + mCJKPrefLangs.Clear(); + mEmojiPrefFont = nullptr; +} + +// 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 (const UniquePtr<PrefFontList>& pf : prefFontsLangGroup) { + if (pf) { + aSizes->mFontListSize += pf->ShallowSizeOfExcludingThis(aMallocSizeOf); + } + } + } + + aSizes->mFontListSize += + mCodepointsWithNoFonts.SizeOfExcludingThis(aMallocSizeOf); + aSizes->mFontListSize += + mFontFamiliesToLoad.ShallowSizeOfExcludingThis(aMallocSizeOf); + + aSizes->mFontListSize += + mBadUnderlineFamilyNames.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& i : mBadUnderlineFamilyNames) { + aSizes->mFontListSize += i.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + + aSizes->mFontListSize += + mSharedCmaps.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = mSharedCmaps.ConstIter(); !iter.Done(); iter.Next()) { + aSizes->mCharMapsSize += + iter.Get()->GetKey()->SizeOfIncludingThis(aMallocSizeOf); + } + + aSizes->mFontListSize += + mFontEntries.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = mFontEntries.ConstIter(); !iter.Done(); iter.Next()) { + if (iter.Data()) { + iter.Data()->AddSizeOfIncludingThis(aMallocSizeOf, aSizes); + } + } + + if (SharedFontList()) { + aSizes->mFontListSize += + SharedFontList()->SizeOfIncludingThis(aMallocSizeOf); + if (XRE_IsParentProcess()) { + aSizes->mSharedSize += SharedFontList()->AllocatedShmemSize(); + } + } +} + +void gfxPlatformFontList::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +void gfxPlatformFontList::InitOtherFamilyNamesInternal( + bool aDeferOtherFamilyNamesLoading) { + if (mOtherFamilyNamesInitialized) { + return; + } + + if (aDeferOtherFamilyNamesLoading) { + TimeStamp start = TimeStamp::Now(); + bool timedOut = false; + + auto list = SharedFontList(); + if (list) { + // If the gfxFontInfoLoader task is not yet running, kick it off now so + // that it will load remaining names etc as soon as idle time permits. + if (mState == stateInitial || mState == stateTimerOnDelay) { + StartLoader(0); + timedOut = true; + } + } else { + for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { + RefPtr<gfxFontFamily>& family = iter.Data(); + family->ReadOtherFamilyNames(this); + TimeDuration elapsed = TimeStamp::Now() - start; + if (elapsed.ToMilliseconds() > OTHERNAMES_TIMEOUT) { + timedOut = true; + break; + } + } + } + + if (!timedOut) { + mOtherFamilyNamesInitialized = true; + CancelInitOtherFamilyNamesTask(); + } + TimeStamp end = TimeStamp::Now(); + Telemetry::AccumulateTimeDelta(Telemetry::FONTLIST_INITOTHERFAMILYNAMES, + start, end); + + if (LOG_FONTINIT_ENABLED()) { + TimeDuration elapsed = end - start; + LOG_FONTINIT(("(fontinit) InitOtherFamilyNames took %8.2f ms %s", + elapsed.ToMilliseconds(), (timedOut ? "timeout" : ""))); + } + } else { + TimeStamp start = TimeStamp::Now(); + + auto list = SharedFontList(); + if (list) { + for (auto& f : mozilla::Range<fontlist::Family>(list->Families(), + list->NumFamilies())) { + ReadFaceNamesForFamily(&f, false); + } + } else { + for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { + RefPtr<gfxFontFamily>& family = iter.Data(); + family->ReadOtherFamilyNames(this); + } + } + + mOtherFamilyNamesInitialized = true; + CancelInitOtherFamilyNamesTask(); + + TimeStamp end = TimeStamp::Now(); + Telemetry::AccumulateTimeDelta( + Telemetry::FONTLIST_INITOTHERFAMILYNAMES_NO_DEFERRING, start, end); + + if (LOG_FONTINIT_ENABLED()) { + TimeDuration elapsed = end - start; + LOG_FONTINIT( + ("(fontinit) InitOtherFamilyNames without deferring took %8.2f ms", + elapsed.ToMilliseconds())); + } + } +} + +void gfxPlatformFontList::CancelInitOtherFamilyNamesTask() { + if (mPendingOtherFamilyNameTask) { + mPendingOtherFamilyNameTask->Cancel(); + mPendingOtherFamilyNameTask = nullptr; + } + auto list = SharedFontList(); + if (list && XRE_IsParentProcess()) { + bool forceReflow = false; + if (!mAliasTable.IsEmpty()) { + list->SetAliases(mAliasTable); + mAliasTable.Clear(); + forceReflow = true; + } + if (mLocalNameTable.Count()) { + list->SetLocalNames(mLocalNameTable); + mLocalNameTable.Clear(); + forceReflow = true; + } + if (forceReflow) { + dom::ContentParent::BroadcastFontListChanged(); + } + } +} + +void gfxPlatformFontList::ShareFontListShmBlockToProcess( + uint32_t aGeneration, uint32_t aIndex, base::ProcessId aPid, + base::SharedMemoryHandle* aOut) { + auto list = SharedFontList(); + if (!list) { + return; + } + if (!aGeneration || list->GetGeneration() == aGeneration) { + list->ShareShmBlockToProcess(aIndex, aPid, aOut); + } else { + *aOut = base::SharedMemory::NULLHandle(); + } +} + +void gfxPlatformFontList::ShareFontListToProcess( + nsTArray<base::SharedMemoryHandle>* aBlocks, base::ProcessId aPid) { + auto list = SharedFontList(); + if (list) { + list->ShareBlocksToProcess(aBlocks, aPid); + } +} + +void gfxPlatformFontList::InitializeFamily(uint32_t aGeneration, + uint32_t aFamilyIndex, + bool aLoadCmaps) { + auto list = SharedFontList(); + MOZ_ASSERT(list); + if (!list) { + return; + } + if (list->GetGeneration() != aGeneration) { + return; + } + if (aFamilyIndex >= list->NumFamilies()) { + return; + } + fontlist::Family* family = list->Families() + aFamilyIndex; + if (!family->IsInitialized() || aLoadCmaps) { + Unused << InitializeFamily(family, aLoadCmaps); + } +} + +void gfxPlatformFontList::SetCharacterMap(uint32_t aGeneration, + const fontlist::Pointer& aFacePtr, + const gfxSparseBitSet& aMap) { + MOZ_ASSERT(XRE_IsParentProcess()); + auto list = SharedFontList(); + MOZ_ASSERT(list); + if (!list) { + return; + } + if (list->GetGeneration() != aGeneration) { + return; + } + fontlist::Face* face = static_cast<fontlist::Face*>(aFacePtr.ToPtr(list)); + if (face) { + face->mCharacterMap = GetShmemCharMap(&aMap); + } +} + +void gfxPlatformFontList::SetupFamilyCharMap( + uint32_t aGeneration, const fontlist::Pointer& aFamilyPtr) { + MOZ_ASSERT(XRE_IsParentProcess()); + auto list = SharedFontList(); + MOZ_ASSERT(list); + if (!list) { + return; + } + if (list->GetGeneration() != aGeneration) { + return; + } + + // aFamilyPtr was passed from a content process which may not be trusted, + // so we cannot assume it is valid or safe to use. If the Pointer value is + // bad, we must not crash or do anything bad, just bail out. + // (In general, if the child process was trying to use an invalid pointer it + // should have hit the MOZ_DIAGNOSTIC_ASSERT in FontList::ToSharedPointer + // rather than passing a null or bad pointer to the parent.) + + auto* family = static_cast<fontlist::Family*>(aFamilyPtr.ToPtr(list)); + if (!family) { + // Unable to resolve to a native pointer (or it was null). + NS_WARNING("unexpected null Family pointer"); + return; + } + + // Validate the pointer before trying to use it: check that it points to a + // correctly-aligned offset within the Families() or AliasFamilies() array. + // We just assert (in debug builds only) on failure, and return safely. + // A misaligned pointer here would indicate a buggy (or compromised) child + // process, but crashing the parent would be unnecessary and does not yield + // any useful insight. + if (family >= list->Families() && + family < list->Families() + list->NumFamilies()) { + size_t offset = (char*)family - (char*)list->Families(); + if (offset % sizeof(fontlist::Family) != 0) { + MOZ_ASSERT(false, "misaligned Family pointer"); + return; + } + } else if (family >= list->AliasFamilies() && + family < list->AliasFamilies() + list->NumAliases()) { + size_t offset = (char*)family - (char*)list->AliasFamilies(); + if (offset % sizeof(fontlist::Family) != 0) { + MOZ_ASSERT(false, "misaligned Family pointer"); + return; + } + } else { + MOZ_ASSERT(false, "not a valid Family or AliasFamily pointer"); + return; + } + + family->SetupFamilyCharMap(list); +} + +bool gfxPlatformFontList::InitOtherFamilyNames(uint32_t aGeneration, + bool aDefer) { + auto list = SharedFontList(); + MOZ_ASSERT(list); + if (!list) { + return false; + } + if (list->GetGeneration() != aGeneration) { + return false; + } + return InitOtherFamilyNames(aDefer); +} + +uint32_t gfxPlatformFontList::GetGeneration() const { + return SharedFontList() ? SharedFontList()->GetGeneration() : 0; +} + +#undef LOG +#undef LOG_ENABLED diff --git a/gfx/thebes/gfxPlatformFontList.h b/gfx/thebes/gfxPlatformFontList.h new file mode 100644 index 0000000000..79b8772703 --- /dev/null +++ b/gfx/thebes/gfxPlatformFontList.h @@ -0,0 +1,895 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFXPLATFORMFONTLIST_H_ +#define GFXPLATFORMFONTLIST_H_ + +#include "nsClassHashtable.h" +#include "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 "SharedFontList.h" + +#include "nsIMemoryReporter.h" +#include "mozilla/Attributes.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/RangedArray.h" +#include "nsLanguageAtomService.h" + +#include "base/shared_memory.h" + +namespace mozilla { +namespace fontlist { +struct AliasData; +} +} // namespace mozilla + +class CharMapHashKey : public PLDHashEntryHdr { + public: + typedef gfxCharacterMap* KeyType; + typedef const gfxCharacterMap* KeyTypePointer; + + explicit CharMapHashKey(const gfxCharacterMap* aCharMap) + : mCharMap(const_cast<gfxCharacterMap*>(aCharMap)) { + MOZ_COUNT_CTOR(CharMapHashKey); + } + CharMapHashKey(const CharMapHashKey& toCopy) : mCharMap(toCopy.mCharMap) { + MOZ_COUNT_CTOR(CharMapHashKey); + } + MOZ_COUNTED_DTOR(CharMapHashKey) + + gfxCharacterMap* GetKey() const { return mCharMap; } + + 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: + // charMaps are not owned by the shared cmap cache, but it will be notified + // by gfxCharacterMap::Release() when an entry is about to be deleted + gfxCharacterMap* MOZ_NON_OWNING_REF mCharMap; +}; + +/** + * A helper class used to create a SharedBitSet instance in a FontList's shared + * memory, while ensuring that we avoid bloating memory by avoiding creating + * duplicate instances. + */ +class ShmemCharMapHashEntry final : public PLDHashEntryHdr { + public: + typedef const gfxSparseBitSet* KeyType; + typedef const gfxSparseBitSet* KeyTypePointer; + + /** + * Creation from a gfxSparseBitSet creates not only the ShmemCharMapHashEntry + * itself, but also a SharedBitSet in shared memory. + * Only the parent process creates and manages these entries. + */ + explicit ShmemCharMapHashEntry(const gfxSparseBitSet* aCharMap); + + ShmemCharMapHashEntry(ShmemCharMapHashEntry&&) = default; + ShmemCharMapHashEntry& operator=(ShmemCharMapHashEntry&&) = default; + + /** + * Return a shared-memory Pointer that refers to the wrapped SharedBitSet. + * This can be passed to content processes to give them access to the same + * SharedBitSet as the parent stored. + */ + mozilla::fontlist::Pointer GetCharMap() const { return mCharMap; } + + bool KeyEquals(KeyType aCharMap) const { + // mHash is a 32-bit Adler checksum of the bitset; if it doesn't match we + // can immediately reject it as non-matching, but if it is equal we still + // need to do a full equality check below. + if (mHash != aCharMap->GetChecksum()) { + return false; + } + + return static_cast<const SharedBitSet*>(mCharMap.ToPtr(mList)) + ->Equals(aCharMap); + } + + static KeyTypePointer KeyToPointer(KeyType aCharMap) { return aCharMap; } + static PLDHashNumber HashKey(KeyType aCharMap) { + return aCharMap->GetChecksum(); + } + + enum { ALLOW_MEMMOVE = true }; + + private: + // charMaps are stored in the shared memory that FontList objects point to, + // and are never deleted until the FontList (all referencing font lists, + // actually) have gone away. + mozilla::fontlist::FontList* mList; + mozilla::fontlist::Pointer mCharMap; + uint32_t mHash; +}; + +// gfxPlatformFontList is an abstract class for the global font list on the +// system; concrete subclasses for each platform implement the actual interface +// to the system fonts. This class exists because we cannot rely on the platform +// font-finding APIs to behave in sensible/similar ways, particularly with rich, +// complex OpenType families, so we do our own font family/style management here +// instead. + +// Much of this is based on the old gfxQuartzFontCache, but adapted for use on +// all platforms. + +struct FontListSizes { + uint32_t mFontListSize; // size of the font list and dependent objects + // (font family and face names, etc), but NOT + // including the font table cache and the cmaps + uint32_t + mFontTableCacheSize; // memory used for the gfxFontEntry table caches + uint32_t mCharMapsSize; // memory used for cmap coverage info + uint32_t mLoaderSize; // memory used for (platform-specific) loader + uint32_t mSharedSize; // shared-memory use (reported by parent only) +}; + +class gfxUserFontSet; + +class gfxPlatformFontList : public gfxFontInfoLoader { + friend class InitOtherFamilyNamesRunnable; + + public: + typedef mozilla::StretchRange StretchRange; + typedef mozilla::SlantStyleRange SlantStyleRange; + typedef mozilla::WeightRange WeightRange; + typedef mozilla::unicode::Script Script; + + // For font family lists loaded from user preferences (prefs such as + // font.name-list.<generic>.<langGroup>) that map CSS generics to + // platform-specific font families. + typedef nsTArray<FontFamily> PrefFontList; + + static gfxPlatformFontList* PlatformFontList() { 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(); + + void FontListChanged(); + + /** + * Gathers (from a platform's underlying font system) the information needed + * to initialize a fontlist::Family with its Face members. + */ + virtual void GetFacesInitDataForFamily( + const mozilla::fontlist::Family* aFamily, + nsTArray<mozilla::fontlist::Face::InitData>& aFaces, + bool aLoadCmaps) const {} + + virtual void GetFontList(nsAtom* aLangGroup, const nsACString& aGenericFamily, + nsTArray<nsString>& aListOfFonts); + + // Pass false to notify content that the shared font list has been modified + // but not completely invalidated. + void UpdateFontList(bool aFullRebuild = true); + + virtual void ClearLangGroupPrefFonts(); + + void GetFontFamilyList(nsTArray<RefPtr<gfxFontFamily>>& aFamilyArray); + + gfxFont* SystemFindFontForChar(uint32_t aCh, uint32_t aNextCh, + Script aRunScript, + eFontPresentation aPresentation, + const gfxFontStyle* aStyle, + FontVisibility* aVisibility, + FontMatchingStats* aFontMatchingStats); + + // Flags to control optional behaviors in FindAndAddFamilies. The sense + // of the bit flags have been chosen such that the default parameter of + // FindFamiliesFlags(0) in FindFamily will give the most commonly-desired + // behavior, and only a few callsites need to explicitly pass other values. + enum class FindFamiliesFlags { + // If set, "other" (e.g. localized) family names should be loaded + // immediately; if clear, InitOtherFamilyNames is allowed to defer + // loading to avoid blocking. + eForceOtherFamilyNamesLoading = 1 << 0, + + // If set, FindAndAddFamilies should not check for legacy "styled + // family" names to add to the font list. This is used to avoid + // a recursive search when using FindFamily to find a potential base + // family name for a styled variant. + eNoSearchForLegacyFamilyNames = 1 << 1, + + // If set, FindAndAddFamilies will not add a missing entry to + // mOtherNamesMissed + eNoAddToNamesMissedWhenSearching = 1 << 2, + + // If set, the family name was quoted and so must not be treated as a CSS + // generic. + eQuotedFamilyName = 1 << 3, + + // If set, "hidden" font families (like ".SF NS Text" on macOS) are + // searched in addition to standard user-visible families. + eSearchHiddenFamilies = 1 << 4, + }; + + // Find family(ies) matching aFamily and append to the aOutput array + // (there may be multiple results in the case of fontconfig aliases, etc). + // Return true if any match was found and appended, false if none. + virtual bool FindAndAddFamilies(mozilla::StyleGenericFontFamily aGeneric, + const nsACString& aFamily, + nsTArray<FamilyAndGeneric>* aOutput, + FindFamiliesFlags aFlags, + gfxFontStyle* aStyle = nullptr, + nsAtom* aLanguage = nullptr, + gfxFloat aDevToCssSize = 1.0); + + gfxFontEntry* FindFontForFamily(const nsACString& aFamily, + const gfxFontStyle* aStyle); + + mozilla::fontlist::FontList* SharedFontList() const { + return mSharedFontList.get(); + } + + // Create a handle for a single shmem block (identified by index) ready to + // be shared to the given processId. + void ShareFontListShmBlockToProcess(uint32_t aGeneration, uint32_t aIndex, + base::ProcessId aPid, + base::SharedMemoryHandle* aOut); + + // Populate the array aBlocks with the complete list of shmem handles ready + // to be shared to the given processId. + void ShareFontListToProcess(nsTArray<base::SharedMemoryHandle>* aBlocks, + base::ProcessId aPid); + + void SetCharacterMap(uint32_t aGeneration, + const mozilla::fontlist::Pointer& aFacePtr, + const gfxSparseBitSet& aMap); + + void SetupFamilyCharMap(uint32_t aGeneration, + const mozilla::fontlist::Pointer& aFamilyPtr); + + // Start the async cmap loading process, if not already under way, from the + // given family index. (For use in any process that needs font lookups.) + void StartCmapLoadingFromFamily(uint32_t aStartIndex); + + // [Parent] Handle request from content process to start cmap loading. + void StartCmapLoading(uint32_t aGeneration, uint32_t aStartIndex); + + void CancelLoadCmapsTask() { + if (mLoadCmapsRunnable) { + mLoadCmapsRunnable->Cancel(); + mLoadCmapsRunnable = nullptr; + } + } + + // Populate aFamily with face records, and if aLoadCmaps is true, also load + // their character maps (rather than leaving this to be done lazily). + // Note that even when aFamily->IsInitialized() is true, it can make sense + // to call InitializeFamily again if passing aLoadCmaps=true, in order to + // ensure cmaps are loaded. + [[nodiscard]] bool InitializeFamily(mozilla::fontlist::Family* aFamily, + bool aLoadCmaps = false); + void InitializeFamily(uint32_t aGeneration, uint32_t aFamilyIndex, + bool aLoadCmaps); + + // name lookup table methods + + void AddOtherFamilyName(gfxFontFamily* aFamilyEntry, + const nsCString& aOtherFamilyName); + + void AddFullname(gfxFontEntry* aFontEntry, const nsCString& aFullname); + + void AddPostscriptName(gfxFontEntry* aFontEntry, + const nsCString& aPostscriptName); + + bool NeedFullnamePostscriptNames() { return mExtraNames != nullptr; } + + /** + * Read PSName and FullName of the given face, for src:local lookup, + * returning true if actually implemented and succeeded. + */ + virtual bool ReadFaceNames(mozilla::fontlist::Family* aFamily, + mozilla::fontlist::Face* aFace, nsCString& aPSName, + nsCString& aFullName) { + return false; + } + + // initialize localized family names + bool InitOtherFamilyNames(bool aDeferOtherFamilyNamesLoading); + bool InitOtherFamilyNames(uint32_t aGeneration, bool aDefer); + + // pure virtual functions, to be provided by concrete subclasses + + // get the system default font family + FontFamily GetDefaultFont(const gfxFontStyle* aStyle); + + // get the "ultimate" default font, for use if the font list is otherwise + // unusable (e.g. in the middle of being updated) + gfxFontEntry* GetDefaultFontEntry() { return mDefaultFontEntry.get(); } + + /** + * Look up a font by name on the host platform. + * + * Note that the style attributes (weight, stretch, style) are NOT used in + * selecting the platform font, which is looked up by name only; these are + * values to be recorded in the new font entry. + */ + virtual gfxFontEntry* LookupLocalFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) = 0; + + /** + * Create a new platform font from downloaded data (@font-face). + * + * Note that the style attributes (weight, stretch, style) are NOT related + * (necessarily) to any values within the font resource itself; these are + * values to be recorded in the new font entry and used for face selection, + * in place of whatever inherent style attributes the resource may have. + * + * This method takes ownership of the data block passed in as aFontData, + * and must ensure it is free()'d when no longer required. + */ + virtual gfxFontEntry* MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, + uint32_t aLength) = 0; + + // get the standard family name on the platform for a given font name + // (platforms may override, eg Mac) + virtual bool GetStandardFamilyName(const nsCString& aFontName, + nsACString& aFamilyName); + + // Get the localized family name for a given font family. + bool GetLocalizedFamilyName(const FontFamily& aFamily, + nsACString& aFamilyName); + + // get the default font name which is available on the system from + // font.name-list.*. if there are no available fonts in the pref, + // returns an empty FamilyAndGeneric record. + FamilyAndGeneric GetDefaultFontFamily(const nsACString& aLangGroup, + const nsACString& aGenericFamily); + + virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const; + virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const; + + mozilla::fontlist::Pointer GetShmemCharMap(const gfxSparseBitSet* aCmap); + + // 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::StyleGenericFontFamily aGenericType, + nsAtom* aLanguage, + nsTArray<FamilyAndGeneric>& aFamilyList); + + /** + * Given a Face from the shared font list, return a gfxFontEntry usable + * by the current process. This returns a cached entry if available, + * otherwise it calls the (platform-specific) CreateFontEntry method to + * make one, and adds it to the cache. + */ + gfxFontEntry* GetOrCreateFontEntry(mozilla::fontlist::Face* aFace, + const mozilla::fontlist::Family* aFamily); + + PrefFontList* GetPrefFontsLangGroup( + mozilla::StyleGenericFontFamily 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(nsAtom* aLang); + + // convert an enum constant to a lang group atom + static nsAtom* GetLangGroupForPrefLang(eFontPrefLang aLang); + + // convert a enum constant to lang group string (i.e. eFontPrefLang_ChineseTW + // ==> "zh-TW") + static const char* GetPrefLangName(eFontPrefLang aLang); + + // map a char code to a font language for Preferences + static eFontPrefLang GetFontPrefLangFor(uint32_t aCh); + + // returns true if a pref lang is CJK + static bool IsLangCJK(eFontPrefLang aLang); + + // helper method to add a pref lang to an array, if not already in array + static void AppendPrefLang(eFontPrefLang aPrefLangs[], uint32_t& aLen, + eFontPrefLang aAddLang); + + // default serif/sans-serif choice based on font.default.xxx prefs + mozilla::StyleGenericFontFamily GetDefaultGeneric(eFontPrefLang aLang); + + // Returns true if the font family whitelist is not empty. In this case we + // ignore the "CSS visibility level"; only the given fonts are present in + // the browser's font list. + bool IsFontFamilyWhitelistActive() const { + return mFontFamilyWhitelistActive; + }; + + static void FontWhitelistPrefChanged(const char* aPref, void* aClosure); + + bool AddWithLegacyFamilyName(const nsACString& aLegacyName, + gfxFontEntry* aFontEntry, + FontVisibility aVisibility); + + static const char* GetGenericName( + mozilla::StyleGenericFontFamily aGenericType); + + bool SkipFontFallbackForChar(uint32_t aCh) const { + return mCodepointsWithNoFonts.test(aCh); + } + + // Return whether the given font-family record should be visible to CSS, + // given the current font visibility preferences. + bool IsVisibleToCSS(const gfxFontFamily& aFamily) const; + bool IsVisibleToCSS(const mozilla::fontlist::Family& aFamily) const; + + // Initialize the current visibility level from user prefs. + void SetVisibilityLevel(); + + // (Re-)initialize the set of codepoints that we know cannot be rendered. + void InitializeCodepointsWithNoFonts(); + + // If using the shared font list, returns a generation count that is + // incremented if/when the platform list is reinitialized (e.g. because + // fonts are installed/removed while the browser is running), such that + // existing references to shared font family or face objects and character + // maps will no longer be valid. + // (The legacy (non-shared) list just returns 0 here.) + uint32_t GetGeneration() const; + + protected: + friend class mozilla::fontlist::FontList; + friend class InitOtherFamilyNamesForStylo; + + static bool FamilyInList(const nsACString& aName, const char* aList[], + size_t aCount); + // Check list is correctly sorted (in debug build only; no-op on release). + static void CheckFamilyList(const char* aList[], size_t aCount); + + class InitOtherFamilyNamesRunnable : public mozilla::CancelableRunnable { + public: + InitOtherFamilyNamesRunnable() + : CancelableRunnable( + "gfxPlatformFontList::InitOtherFamilyNamesRunnable"), + mIsCanceled(false) {} + + NS_IMETHOD Run() override { + if (mIsCanceled) { + return NS_OK; + } + + gfxPlatformFontList* fontList = gfxPlatformFontList::PlatformFontList(); + if (!fontList) { + return NS_OK; + } + + fontList->InitOtherFamilyNamesInternal(true); + + return NS_OK; + } + + nsresult Cancel() override { + mIsCanceled = true; + + return NS_OK; + } + + private: + bool mIsCanceled; + }; + + class MemoryReporter final : public nsIMemoryReporter { + ~MemoryReporter() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMEMORYREPORTER + }; + + template <bool ForNameList> + class PrefNameMaker final : public nsAutoCString { + void Init(const nsACString& aGeneric, const nsACString& aLangGroup) { + if (ForNameList) { + AssignLiteral("font.name-list."); + } else { + AssignLiteral("font.name."); + } + Append(aGeneric); + if (!aLangGroup.IsEmpty()) { + Append('.'); + Append(aLangGroup); + } + } + + public: + PrefNameMaker(const nsACString& aGeneric, const nsACString& aLangGroup) { + Init(aGeneric, aLangGroup); + } + + PrefNameMaker(const char* aGeneric, const char* aLangGroup) { + Init(nsDependentCString(aGeneric), nsDependentCString(aLangGroup)); + } + + PrefNameMaker(const char* aGeneric, nsAtom* aLangGroup) { + if (aLangGroup) { + Init(nsDependentCString(aGeneric), nsAtomCString(aLangGroup)); + } else { + Init(nsDependentCString(aGeneric), nsAutoCString()); + } + } + }; + + typedef PrefNameMaker<false> NamePref; + typedef PrefNameMaker<true> NameListPref; + + explicit gfxPlatformFontList(bool aNeedFullnamePostscriptNames = true); + + static gfxPlatformFontList* sPlatformFontList; + + /** + * Convenience method to return the first matching family (if any) as found + * by FindAndAddFamilies(). The family will be initialized (synchronously) + * if this has not already been done, so the returned pointer, if non-null, + * is ready for use. + */ + mozilla::fontlist::Family* FindSharedFamily( + const nsACString& aFamily, + FindFamiliesFlags aFlags = FindFamiliesFlags(0), + gfxFontStyle* aStyle = nullptr, nsAtom* aLanguage = nullptr, + gfxFloat aDevToCssSize = 1.0); + + gfxFontFamily* FindUnsharedFamily( + const nsACString& aFamily, + FindFamiliesFlags aFlags = FindFamiliesFlags(0), + gfxFontStyle* aStyle = nullptr, nsAtom* aLanguage = nullptr, + gfxFloat aDevToCssSize = 1.0) { + if (SharedFontList()) { + return nullptr; + } + AutoTArray<FamilyAndGeneric, 1> families; + if (FindAndAddFamilies(mozilla::StyleGenericFontFamily::None, aFamily, + &families, aFlags, aStyle, aLanguage, + aDevToCssSize)) { + return families[0].mFamily.mUnshared; + } + return nullptr; + } + + FontFamily FindFamily(const nsACString& aFamily, + FindFamiliesFlags aFlags = FindFamiliesFlags(0), + gfxFontStyle* aStyle = nullptr, + nsAtom* aLanguage = nullptr, + gfxFloat aDevToCssSize = 1.0) { + if (SharedFontList()) { + return FontFamily( + FindSharedFamily(aFamily, aFlags, aStyle, aLanguage, aDevToCssSize)); + } + return FontFamily( + FindUnsharedFamily(aFamily, aFlags, aStyle, aLanguage, aDevToCssSize)); + } + + // Lookup family name in global family list without substitutions or + // localized family name lookup. Used for common font fallback families. + gfxFontFamily* FindFamilyByCanonicalName(const nsACString& aFamily) { + nsAutoCString key; + gfxFontFamily* familyEntry; + GenerateFontListKey(aFamily, key); + if ((familyEntry = mFontFamilies.GetWeak(key))) { + return CheckFamily(familyEntry); + } + return nullptr; + } + + // returns default font for a given character, null otherwise + gfxFont* CommonFontFallback(uint32_t aCh, uint32_t aNextCh, Script aRunScript, + eFontPresentation aPresentation, + const gfxFontStyle* aMatchStyle, + FontFamily& aMatchedFamily); + + // Search fonts system-wide for a given character, null if not found. + gfxFont* GlobalFontFallback(uint32_t aCh, uint32_t aNextCh, Script aRunScript, + eFontPresentation aPresentation, + const gfxFontStyle* aMatchStyle, + uint32_t& aCmapCount, FontFamily& aMatchedFamily, + FontMatchingStats* aFontMatchingStats); + + // 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, + FontFamily& aMatchedFamily) { + return nullptr; + } + + // whether system-based font fallback is used or not + // if system fallback is used, no need to load all cmaps + virtual bool UsesSystemFallback() { return false; } + + void AppendCJKPrefLangs(eFontPrefLang aPrefLangs[], uint32_t& aLen, + eFontPrefLang aCharLang, eFontPrefLang aPageLang); + + // verifies that a family contains a non-zero font count + gfxFontFamily* CheckFamily(gfxFontFamily* aFamily); + + // initialize localized family names + void InitOtherFamilyNamesInternal(bool aDeferOtherFamilyNamesLoading); + void CancelInitOtherFamilyNamesTask(); + + // search through font families, looking for a given name, initializing + // facename lists along the way. first checks all families with names + // close to face name, then searchs all families if not found. + gfxFontEntry* SearchFamiliesForFaceName(const nsACString& aFaceName); + + // helper method for finding fullname/postscript names in facename lists + gfxFontEntry* FindFaceName(const nsACString& aFaceName); + + // look up a font by name, for cases where platform font list + // maintains explicit mappings of fullname/psname ==> font + virtual gfxFontEntry* LookupInFaceNameLists(const nsACString& aFontName); + + gfxFontEntry* LookupInSharedFaceNameList(const nsACString& aFaceName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry); + + // commonly used fonts for which the name table should be loaded at startup + virtual void PreloadNamesList(); + + // load the bad underline blocklist from pref. + void LoadBadUnderlineList(); + + void GenerateFontListKey(const nsACString& aKeyName, nsACString& aResult); + + virtual void GetFontFamilyNames(nsTArray<nsCString>& aFontFamilyNames); + + // helper function to map lang to lang group + nsAtom* GetLangGroup(nsAtom* aLanguage); + + // gfxFontInfoLoader overrides, used to load in font cmaps + void InitLoader() override; + bool LoadFontInfo() override; + void CleanupLoader() override; + + // read the loader initialization prefs, and start it + void GetPrefsAndStartLoader(); + + // for font list changes that affect all documents + void ForceGlobalReflow(); + + // If aForgetLocalFaces is true, all gfxFontEntries for src:local fonts must + // be discarded (not potentially reused to satisfy the rebuilt rules), + // because they may no longer be valid. + void RebuildLocalFonts(bool aForgetLocalFaces = false); + + void ResolveGenericFontNames(mozilla::StyleGenericFontFamily aGenericType, + eFontPrefLang aPrefLang, + PrefFontList* aGenericFamilies); + + void ResolveEmojiFontNames(PrefFontList* aGenericFamilies); + + void GetFontFamiliesFromGenericFamilies( + mozilla::StyleGenericFontFamily aGenericType, + nsTArray<nsCString>& aGenericNameFamilies, nsAtom* aLangGroup, + PrefFontList* aFontFamilies); + + virtual nsresult InitFontListForPlatform() = 0; + virtual void InitSharedFontListForPlatform() {} + + virtual gfxFontEntry* CreateFontEntry( + mozilla::fontlist::Face* aFace, + const mozilla::fontlist::Family* aFamily) { + return nullptr; + } + + /** + * Methods to apply the font.system.whitelist anti-fingerprinting pref, + * by filtering the list of installed fonts so that only whitelisted families + * are exposed. + * There are separate implementations of this for the per-process font list + * and for the shared-memory font list. + */ + void ApplyWhitelist(); + void ApplyWhitelist(nsTArray<mozilla::fontlist::Family::InitData>& aFamilies); + + // Create a new gfxFontFamily of the appropriate subclass for the platform, + // used when AddWithLegacyFamilyName needs to create a new family. + virtual gfxFontFamily* CreateFontFamily(const nsACString& aName, + FontVisibility aVisibility) const = 0; + + /** + * For the post-startup font info loader task. + * Perform platform-specific work to read alternate names (if any) for a + * font family, recording them in mAliasTable. Once alternate names have been + * loaded for all families, the accumulated records are stored in the shared + * font list's mAliases list. + * Some platforms (currently Linux/fontconfig) may load alternate names as + * part of initially populating the font list with family records, in which + * case this method is unused. + */ + virtual void ReadFaceNamesForFamily(mozilla::fontlist::Family* aFamily, + bool aNeedFullnamePostscriptNames) {} + + typedef nsRefPtrHashtable<nsCStringHashKey, gfxFontFamily> FontFamilyTable; + typedef nsRefPtrHashtable<nsCStringHashKey, gfxFontEntry> FontEntryTable; + + // used by memory reporter to accumulate sizes of family names in the table + static size_t SizeOfFontFamilyTableExcludingThis( + const FontFamilyTable& aTable, mozilla::MallocSizeOf aMallocSizeOf); + static size_t SizeOfFontEntryTableExcludingThis( + const FontEntryTable& aTable, mozilla::MallocSizeOf aMallocSizeOf); + + // Platform-specific helper for GetDefaultFont(...). + virtual FontFamily GetDefaultFontForPlatform(const gfxFontStyle* aStyle, + nsAtom* aLanguage = nullptr) = 0; + + // Protects mFontFamilies. + mozilla::Mutex mFontFamiliesMutex; + + // 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; + + // The pending InitOtherFamilyNames() task. + RefPtr<mozilla::CancelableRunnable> mPendingOtherFamilyNameTask; + + // flag set after fullname and Postcript name lists are populated + bool mFaceNameListsInitialized; + + struct ExtraNames { + ExtraNames() = default; + + // fullname ==> font entry (unique, one name per font entry) + FontEntryTable mFullnames{64}; + // Postscript name ==> font entry (unique, one name per font entry) + FontEntryTable mPostscriptNames{64}; + }; + mozilla::UniquePtr<ExtraNames> mExtraNames; + + // face names missed when face name loading takes a long time + mozilla::UniquePtr<nsTHashtable<nsCStringHashKey>> mFaceNamesMissed; + + // localized family names missed when face name loading takes a long time + mozilla::UniquePtr<nsTHashtable<nsCStringHashKey>> mOtherNamesMissed; + + typedef mozilla::RangedArray<mozilla::UniquePtr<PrefFontList>, + size_t(mozilla::StyleGenericFontFamily::None), + size_t( + mozilla::StyleGenericFontFamily::MozEmoji)> + PrefFontsForLangGroup; + mozilla::RangedArray<PrefFontsForLangGroup, eFontPrefLang_First, + eFontPrefLang_Count> + mLangGroupPrefFonts; + mozilla::UniquePtr<PrefFontList> mEmojiPrefFont; + + // 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 + FontFamily mReplacementCharFallbackFamily; + + // Sorted array of lowercased family names; use ContainsSorted to test + nsTArray<nsCString> mBadUnderlineFamilyNames; + + // character map data shared across families + // contains weak ptrs to cmaps shared by font entry objects + nsTHashtable<CharMapHashKey> mSharedCmaps; + + nsTHashtable<ShmemCharMapHashEntry> mShmemCharMaps; + + // data used as part of the font cmap loading process + nsTArray<RefPtr<gfxFontFamily>> mFontFamiliesToLoad; + uint32_t mStartIndex; + uint32_t mNumFamilies; + + // xxx - info for diagnosing no default font aborts + // see bugs 636957, 1070983, 1189129 + uint32_t mFontlistInitCount; // num times InitFontList called + + nsTHashtable<nsPtrHashKey<gfxUserFontSet>> mUserFontSetList; + + nsLanguageAtomService* mLangService; + + nsTArray<uint32_t> mCJKPrefLangs; + nsTArray<mozilla::StyleGenericFontFamily> mDefaultGenericsLangGroup; + + mozilla::UniquePtr<mozilla::fontlist::FontList> mSharedFontList; + + nsClassHashtable<nsCStringHashKey, mozilla::fontlist::AliasData> mAliasTable; + nsDataHashtable<nsCStringHashKey, mozilla::fontlist::LocalFaceRec::InitData> + mLocalNameTable; + + nsRefPtrHashtable<nsPtrHashKey<mozilla::fontlist::Face>, gfxFontEntry> + mFontEntries; + + RefPtr<gfxFontEntry> mDefaultFontEntry; + + RefPtr<mozilla::CancelableRunnable> mLoadCmapsRunnable; + uint32_t mStartedLoadingCmapsFrom = 0xffffffffu; + + FontVisibility mVisibilityLevel = FontVisibility::Unknown; + + bool mFontFamilyWhitelistActive; +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(gfxPlatformFontList::FindFamiliesFlags) + +#endif /* GFXPLATFORMFONTLIST_H_ */ diff --git a/gfx/thebes/gfxPlatformGtk.cpp b/gfx/thebes/gfxPlatformGtk.cpp new file mode 100644 index 0000000000..05125b999a --- /dev/null +++ b/gfx/thebes/gfxPlatformGtk.cpp @@ -0,0 +1,738 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#define PANGO_ENABLE_BACKEND +#define PANGO_ENABLE_ENGINE + +#include "gfxPlatformGtk.h" + +#include <gtk/gtk.h> +#include <fontconfig/fontconfig.h> + +#include "base/task.h" +#include "base/thread.h" +#include "base/message_loop.h" +#include "cairo.h" +#include "gfx2DGlue.h" +#include "gfxFcPlatformFontList.h" +#include "gfxConfig.h" +#include "gfxContext.h" +#include "gfxImageSurface.h" +#include "gfxUserFontSet.h" +#include "gfxUtils.h" +#include "gfxFT2FontBase.h" +#include "gfxTextRun.h" +#include "GLContextProvider.h" +#include "mozilla/Atomics.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/Monitor.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_layers.h" +#include "nsAppRunner.h" +#include "nsIGfxInfo.h" +#include "nsMathUtils.h" +#include "nsUnicharUtils.h" +#include "nsUnicodeProperties.h" +#include "VsyncSource.h" + +#ifdef MOZ_X11 +# include <gdk/gdkx.h> +# include "cairo-xlib.h" +# include "gfxXlibSurface.h" +# include "GLContextGLX.h" +# include "GLXLibrary.h" +# include "mozilla/X11Util.h" + +/* Undefine the Status from Xlib since it will conflict with system headers on + * OSX */ +# if defined(__APPLE__) && defined(Status) +# undef Status +# endif +#endif /* MOZ_X11 */ + +#ifdef MOZ_WAYLAND +# include <gdk/gdkwayland.h> +# include "mozilla/widget/nsWaylandDisplay.h" +# include "mozilla/widget/DMABufLibWrapper.h" +# include "mozilla/StaticPrefs_media.h" +#endif + +#define GDK_PIXMAP_SIZE_MAX 32767 + +#define GFX_PREF_MAX_GENERIC_SUBSTITUTIONS \ + "gfx.font_rendering.fontconfig.max_generic_substitutions" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::unicode; +using namespace mozilla::widget; +using mozilla::dom::SystemFontListEntry; + +static FT_Library gPlatformFTLibrary = nullptr; +static int32_t sDPI; + +static void screen_resolution_changed(GdkScreen* aScreen, GParamSpec* aPspec, + gpointer aClosure) { + sDPI = 0; +} + +gfxPlatformGtk::gfxPlatformGtk() { + if (!gfxPlatform::IsHeadless()) { + gtk_init(nullptr, nullptr); + } + + mMaxGenericSubstitutions = UNINITIALIZED_VALUE; + mIsX11Display = gfxPlatform::IsHeadless() + ? false + : GDK_IS_X11_DISPLAY(gdk_display_get_default()); + if (XRE_IsParentProcess()) { +#ifdef MOZ_X11 + if (mIsX11Display && mozilla::Preferences::GetBool("gfx.xrender.enabled")) { + gfxVars::SetUseXRender(true); + } +#endif + + bool useEGLOnX11 = false; +#ifdef MOZ_X11 + useEGLOnX11 = IsX11EGLEnabled(); +#endif + if (IsWaylandDisplay() || useEGLOnX11) { + gfxVars::SetUseEGL(true); + + nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo(); + nsAutoCString drmRenderDevice; + gfxInfo->GetDrmRenderDevice(drmRenderDevice); + gfxVars::SetDrmRenderDevice(drmRenderDevice); + } + } + + InitBackendPrefs(GetBackendPrefs()); + +#ifdef MOZ_WAYLAND + mUseWebGLDmabufBackend = + gfxVars::UseEGL() && GetDMABufDevice()->IsDMABufWebGLEnabled(); +#endif + + gPlatformFTLibrary = Factory::NewFTLibrary(); + MOZ_RELEASE_ASSERT(gPlatformFTLibrary); + Factory::SetFTLibrary(gPlatformFTLibrary); + + GdkScreen* gdkScreen = gdk_screen_get_default(); + if (gdkScreen) { + g_signal_connect(gdkScreen, "notify::resolution", + G_CALLBACK(screen_resolution_changed), nullptr); + } +} + +gfxPlatformGtk::~gfxPlatformGtk() { + Factory::ReleaseFTLibrary(gPlatformFTLibrary); + gPlatformFTLibrary = nullptr; +} + +void gfxPlatformGtk::FlushContentDrawing() { + if (gfxVars::UseXRender()) { + XFlush(DefaultXDisplay()); + } +} + +void gfxPlatformGtk::InitPlatformGPUProcessPrefs() { +#ifdef MOZ_WAYLAND + if (IsWaylandDisplay()) { + FeatureState& gpuProc = gfxConfig::GetFeature(Feature::GPU_PROCESS); + gpuProc.ForceDisable(FeatureStatus::Blocked, + "Wayland does not work in the GPU process", + "FEATURE_FAILURE_WAYLAND"_ns); + } +#endif +} + +already_AddRefed<gfxASurface> gfxPlatformGtk::CreateOffscreenSurface( + const IntSize& aSize, gfxImageFormat aFormat) { + if (!Factory::AllowedSurfaceSize(aSize)) { + return nullptr; + } + + RefPtr<gfxASurface> newSurface; + bool needsClear = true; +#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(nsAtom* aLangGroup, + const nsACString& aGenericFamily, + nsTArray<nsString>& aListOfFonts) { + gfxPlatformFontList::PlatformFontList()->GetFontList( + aLangGroup, aGenericFamily, aListOfFonts); + return NS_OK; +} + +// xxx - this is ubuntu centric, need to go through other distros and flesh +// out a more general list +static const char kFontDejaVuSans[] = "DejaVu Sans"; +static const char kFontDejaVuSerif[] = "DejaVu Serif"; +static const char kFontFreeSans[] = "FreeSans"; +static const char kFontFreeSerif[] = "FreeSerif"; +static const char kFontTakaoPGothic[] = "TakaoPGothic"; +static const char kFontTwemojiMozilla[] = "Twemoji Mozilla"; +static const char kFontDroidSansFallback[] = "Droid Sans Fallback"; +static const char kFontWenQuanYiMicroHei[] = "WenQuanYi Micro Hei"; +static const char kFontNanumGothic[] = "NanumGothic"; +static const char kFontSymbola[] = "Symbola"; + +void gfxPlatformGtk::GetCommonFallbackFonts(uint32_t aCh, Script aRunScript, + eFontPresentation aPresentation, + nsTArray<const char*>& aFontList) { + if (PrefersColor(aPresentation)) { + aFontList.AppendElement(kFontTwemojiMozilla); + } + + aFontList.AppendElement(kFontDejaVuSerif); + aFontList.AppendElement(kFontFreeSerif); + aFontList.AppendElement(kFontDejaVuSans); + aFontList.AppendElement(kFontFreeSans); + aFontList.AppendElement(kFontSymbola); + + // add fonts for CJK ranges + // xxx - this isn't really correct, should use the same CJK font ordering + // as the pref font code + if (aCh >= 0x3000 && ((aCh < 0xe000) || (aCh >= 0xf900 && aCh < 0xfff0) || + ((aCh >> 16) == 2))) { + aFontList.AppendElement(kFontTakaoPGothic); + aFontList.AppendElement(kFontDroidSansFallback); + aFontList.AppendElement(kFontWenQuanYiMicroHei); + aFontList.AppendElement(kFontNanumGothic); + } +} + +void gfxPlatformGtk::ReadSystemFontList( + nsTArray<SystemFontListEntry>* retValue) { + gfxFcPlatformFontList::PlatformFontList()->ReadSystemFontList(retValue); +} + +gfxPlatformFontList* gfxPlatformGtk::CreatePlatformFontList() { + gfxPlatformFontList* list = new gfxFcPlatformFontList(); + if (NS_SUCCEEDED(list->InitFontList())) { + return list; + } + gfxPlatformFontList::Shutdown(); + return nullptr; +} + +int32_t gfxPlatformGtk::GetFontScaleDPI() { + if (MOZ_LIKELY(sDPI != 0)) { + return sDPI; + } + GdkScreen* screen = gdk_screen_get_default(); + // Ensure settings in config files are processed. + gtk_settings_get_for_screen(screen); + int32_t dpi = int32_t(round(gdk_screen_get_resolution(screen))); + if (dpi <= 0) { + // Fall back to something sane + dpi = 96; + } + sDPI = dpi; + return dpi; +} + +double gfxPlatformGtk::GetFontScaleFactor() { + // Integer scale factors work well with GTK window scaling, image scaling, and + // pixel alignment, but there is a range where 1 is too small and 2 is too + // big. + // + // An additional step of 1.5 is added because this is common scale on WINNT + // and at this ratio the advantages of larger rendering outweigh the + // disadvantages from scaling and pixel mis-alignment. + // + // A similar step for 1.25 is added as well, because this is the scale that + // "Large text" settings use in gnome, and it seems worth to allow, especially + // on already-hidpi environments. + int32_t dpi = GetFontScaleDPI(); + if (dpi < 120) { + return 1.0; + } + if (dpi < 132) { + return 1.25; + } + if (dpi < 168) { + return 1.5; + } + return round(dpi / 96.0); +} + +bool gfxPlatformGtk::UseImageOffscreenSurfaces() { + return GetDefaultContentBackend() != mozilla::gfx::BackendType::CAIRO || + StaticPrefs::layers_use_image_offscreen_surfaces_AtStartup(); +} + +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; + 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); +} + +bool gfxPlatformGtk::AccelerateLayersByDefault() { return true; } + +#if defined(MOZ_X11) + +static nsTArray<uint8_t> GetDisplayICCProfile(Display* dpy, Window& root) { + const char kIccProfileAtomName[] = "_ICC_PROFILE"; + Atom iccAtom = XInternAtom(dpy, kIccProfileAtomName, TRUE); + if (!iccAtom) { + return nsTArray<uint8_t>(); + } + + Atom retAtom; + int retFormat; + unsigned long retLength, retAfter; + unsigned char* retProperty; + + if (XGetWindowProperty(dpy, root, iccAtom, 0, INT_MAX /* length */, X11False, + AnyPropertyType, &retAtom, &retFormat, &retLength, + &retAfter, &retProperty) != Success) { + return nsTArray<uint8_t>(); + } + + nsTArray<uint8_t> result; + + if (retLength > 0) { + result.AppendElements(static_cast<uint8_t*>(retProperty), retLength); + } + + XFree(retProperty); + + return result; +} + +nsTArray<uint8_t> gfxPlatformGtk::GetPlatformCMSOutputProfileData() { + nsTArray<uint8_t> prefProfileData = GetPrefCMSOutputProfileData(); + if (!prefProfileData.IsEmpty()) { + return prefProfileData; + } + + if (!mIsX11Display) { + return nsTArray<uint8_t>(); + } + + GdkDisplay* display = gdk_display_get_default(); + Display* dpy = GDK_DISPLAY_XDISPLAY(display); + // In xpcshell tests, we never initialize X and hence don't have a Display. + // In this case, there's no output colour management to be done, so we just + // return with nullptr. + if (!dpy) { + return nsTArray<uint8_t>(); + } + + Window root = gdk_x11_get_default_root_xwindow(); + + // First try ICC Profile + nsTArray<uint8_t> iccResult = GetDisplayICCProfile(dpy, root); + if (!iccResult.IsEmpty()) { + return iccResult; + } + + // If ICC doesn't work, then try EDID + const char kEdid1AtomName[] = "XFree86_DDC_EDID1_RAWDATA"; + Atom edidAtom = XInternAtom(dpy, kEdid1AtomName, TRUE); + if (!edidAtom) { + return nsTArray<uint8_t>(); + } + + Atom retAtom; + int retFormat; + unsigned long retLength, retAfter; + unsigned char* retProperty; + + if (XGetWindowProperty(dpy, root, edidAtom, 0, 32, X11False, AnyPropertyType, + &retAtom, &retFormat, &retLength, &retAfter, + &retProperty) != Success) { + return nsTArray<uint8_t>(); + } + + if (retLength != 128) { + return nsTArray<uint8_t>(); + } + + // Format documented in "VESA E-EDID Implementation Guide" + float gamma = (100 + retProperty[0x17]) / 100.0f; + + qcms_CIE_xyY whitePoint; + whitePoint.x = + ((retProperty[0x21] << 2) | (retProperty[0x1a] >> 2 & 3)) / 1024.0; + whitePoint.y = + ((retProperty[0x22] << 2) | (retProperty[0x1a] >> 0 & 3)) / 1024.0; + whitePoint.Y = 1.0; + + qcms_CIE_xyYTRIPLE primaries; + primaries.red.x = + ((retProperty[0x1b] << 2) | (retProperty[0x19] >> 6 & 3)) / 1024.0; + primaries.red.y = + ((retProperty[0x1c] << 2) | (retProperty[0x19] >> 4 & 3)) / 1024.0; + primaries.red.Y = 1.0; + + primaries.green.x = + ((retProperty[0x1d] << 2) | (retProperty[0x19] >> 2 & 3)) / 1024.0; + primaries.green.y = + ((retProperty[0x1e] << 2) | (retProperty[0x19] >> 0 & 3)) / 1024.0; + primaries.green.Y = 1.0; + + primaries.blue.x = + ((retProperty[0x1f] << 2) | (retProperty[0x1a] >> 6 & 3)) / 1024.0; + primaries.blue.y = + ((retProperty[0x20] << 2) | (retProperty[0x1a] >> 4 & 3)) / 1024.0; + primaries.blue.Y = 1.0; + + XFree(retProperty); + + void* mem = nullptr; + size_t size = 0; + qcms_data_create_rgb_with_gamma(whitePoint, primaries, gamma, &mem, &size); + if (!mem) { + return nsTArray<uint8_t>(); + } + + nsTArray<uint8_t> result; + result.AppendElements(static_cast<uint8_t*>(mem), size); + free(mem); + + return result; +} + +#else // defined(MOZ_X11) + +nsTArray<uint8_t> gfxPlatformGtk::GetPlatformCMSOutputProfileData() { + return nsTArray<uint8_t>(); +} + +#endif + +bool gfxPlatformGtk::CheckVariationFontSupport() { + // Although there was some variation/multiple-master support in FreeType + // in older versions, it seems too incomplete/unstable for us to use + // until at least 2.7.1. + FT_Int major, minor, patch; + FT_Library_Version(Factory::GetFTLibrary(), &major, &minor, &patch); + return major * 1000000 + minor * 1000 + patch >= 2007001; +} + +#ifdef MOZ_X11 + +class GtkVsyncSource final : public VsyncSource { + public: + GtkVsyncSource() { + MOZ_ASSERT(NS_IsMainThread()); + mGlobalDisplay = new GLXDisplay(); + } + + virtual ~GtkVsyncSource() { MOZ_ASSERT(NS_IsMainThread()); } + + virtual Display& GetGlobalDisplay() override { return *mGlobalDisplay; } + + class GLXDisplay final : public VsyncSource::Display { + public: + GLXDisplay() + : mGLContext(nullptr), + mXDisplay(nullptr), + mSetupLock("GLXVsyncSetupLock"), + mVsyncThread("GLXVsyncThread"), + mVsyncTask(nullptr), + mVsyncEnabledLock("GLXVsyncEnabledLock"), + mVsyncEnabled(false) {} + + // Sets up the display's GL context on a worker thread. + // Required as GLContexts may only be used by the creating thread. + // Returns true if setup was a success. + bool Setup() { + MonitorAutoLock lock(mSetupLock); + MOZ_ASSERT(NS_IsMainThread()); + if (!mVsyncThread.Start()) return false; + + RefPtr<Runnable> vsyncSetup = + NewRunnableMethod("GtkVsyncSource::GLXDisplay::SetupGLContext", this, + &GLXDisplay::SetupGLContext); + mVsyncThread.message_loop()->PostTask(vsyncSetup.forget()); + // Wait until the setup has completed. + lock.Wait(); + return mGLContext != nullptr; + } + + // Called on the Vsync thread to setup the GL context. + void SetupGLContext() { + MonitorAutoLock lock(mSetupLock); + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(!mGLContext, "GLContext already setup!"); + + // Create video sync timer on a separate Display to prevent locking the + // main thread X display. + mXDisplay = XOpenDisplay(nullptr); + if (!mXDisplay) { + lock.NotifyAll(); + return; + } + + // Most compositors wait for vsync events on the root window. + Window root = DefaultRootWindow(mXDisplay); + int screen = DefaultScreen(mXDisplay); + + ScopedXFree<GLXFBConfig> cfgs; + GLXFBConfig config; + int visid; + bool forWebRender = false; + if (!gl::GLContextGLX::FindFBConfigForWindow( + mXDisplay, screen, root, &cfgs, &config, &visid, forWebRender)) { + lock.NotifyAll(); + return; + } + + mGLContext = gl::GLContextGLX::CreateGLContext({}, mXDisplay, root, + config, false, nullptr); + + if (!mGLContext) { + lock.NotifyAll(); + return; + } + + mGLContext->MakeCurrent(); + + // Test that SGI_video_sync lets us get the counter. + unsigned int syncCounter = 0; + if (gl::sGLXLibrary.fGetVideoSync(&syncCounter) != 0) { + mGLContext = nullptr; + } + + lock.NotifyAll(); + } + + virtual void EnableVsync() override { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mGLContext, "GLContext not setup!"); + + MonitorAutoLock lock(mVsyncEnabledLock); + if (mVsyncEnabled) { + return; + } + mVsyncEnabled = true; + + // If the task has not nulled itself out, it hasn't yet realized + // that vsync was disabled earlier, so continue its execution. + if (!mVsyncTask) { + mVsyncTask = NewRunnableMethod("GtkVsyncSource::GLXDisplay::RunVsync", + this, &GLXDisplay::RunVsync); + RefPtr<Runnable> addrefedTask = mVsyncTask; + mVsyncThread.message_loop()->PostTask(addrefedTask.forget()); + } + } + + virtual void DisableVsync() override { + MonitorAutoLock lock(mVsyncEnabledLock); + mVsyncEnabled = false; + } + + virtual bool IsVsyncEnabled() override { + MonitorAutoLock lock(mVsyncEnabledLock); + return mVsyncEnabled; + } + + virtual void Shutdown() override { + MOZ_ASSERT(NS_IsMainThread()); + DisableVsync(); + + // Cleanup thread-specific resources before shutting down. + RefPtr<Runnable> shutdownTask = NewRunnableMethod( + "GtkVsyncSource::GLXDisplay::Cleanup", this, &GLXDisplay::Cleanup); + mVsyncThread.message_loop()->PostTask(shutdownTask.forget()); + + // Stop, waiting for the cleanup task to finish execution. + mVsyncThread.Stop(); + } + + private: + virtual ~GLXDisplay() = default; + + void RunVsync() { + MOZ_ASSERT(!NS_IsMainThread()); + + mGLContext->MakeCurrent(); + + unsigned int syncCounter = 0; + gl::sGLXLibrary.fGetVideoSync(&syncCounter); + for (;;) { + { + MonitorAutoLock lock(mVsyncEnabledLock); + if (!mVsyncEnabled) { + mVsyncTask = nullptr; + return; + } + } + + TimeStamp lastVsync = TimeStamp::Now(); + bool useSoftware = false; + + // Wait until the video sync counter reaches the next value by waiting + // until the parity of the counter value changes. + unsigned int nextSync = syncCounter + 1; + int status; + if ((status = gl::sGLXLibrary.fWaitVideoSync(2, 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(); + TimeStamp outputTime = lastVsync + GetVsyncRate(); + NotifyVsync(lastVsync, outputTime); + } + } + + void Cleanup() { + MOZ_ASSERT(!NS_IsMainThread()); + + mGLContext = nullptr; + if (mXDisplay) XCloseDisplay(mXDisplay); + } + + // Owned by the vsync thread. + RefPtr<gl::GLContextGLX> mGLContext; + _XDisplay* mXDisplay; + Monitor mSetupLock; + base::Thread mVsyncThread; + RefPtr<Runnable> mVsyncTask; + Monitor mVsyncEnabledLock; + bool mVsyncEnabled; + }; + + private: + // We need a refcounted VsyncSource::Display to use chromium IPC runnables. + RefPtr<GLXDisplay> mGlobalDisplay; +}; + +already_AddRefed<gfx::VsyncSource> gfxPlatformGtk::CreateHardwareVsyncSource() { +# ifdef MOZ_WAYLAND + if (IsWaylandDisplay()) { + // For wayland, we simply return the standard software vsync for now. + // This powers refresh drivers and the likes. + return gfxPlatform::CreateHardwareVsyncSource(); + } +# endif + + // Only use GLX vsync when the OpenGL compositor / WebRedner is being used. + // The extra cost of initializing a GLX context while blocking the main + // thread is not worth it when using basic composition. + // + // Don't call gl::sGLXLibrary.SupportsVideoSync() when EGL is used. + // NVIDIA drivers refuse to use EGL GL context when GLX was initialized first + // and fail silently. + if (gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) { + bool useGlxVsync = false; + + nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo(); + nsString adapterDriverVendor; + gfxInfo->GetAdapterDriverVendor(adapterDriverVendor); + + // Nvidia doesn't support GLX at the same time as EGL but Mesa does. + if (!gfxVars::UseEGL() || (adapterDriverVendor.Find("mesa") != -1)) { + useGlxVsync = gl::sGLXLibrary.SupportsVideoSync(); + } + if (useGlxVsync) { + RefPtr<VsyncSource> vsyncSource = new GtkVsyncSource(); + VsyncSource::Display& display = vsyncSource->GetGlobalDisplay(); + if (!static_cast<GtkVsyncSource::GLXDisplay&>(display).Setup()) { + NS_WARNING( + "Failed to setup GLContext, falling back to software vsync."); + return gfxPlatform::CreateHardwareVsyncSource(); + } + return vsyncSource.forget(); + } + NS_WARNING("SGI_video_sync unsupported. Falling back to software vsync."); + } + return gfxPlatform::CreateHardwareVsyncSource(); +} + +#endif diff --git a/gfx/thebes/gfxPlatformGtk.h b/gfx/thebes/gfxPlatformGtk.h new file mode 100644 index 0000000000..50747ea303 --- /dev/null +++ b/gfx/thebes/gfxPlatformGtk.h @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_PLATFORM_GTK_H +#define GFX_PLATFORM_GTK_H + +#include "gfxPlatform.h" +#include "nsAutoRef.h" +#include "nsTArray.h" +#include "mozilla/gfx/gfxVars.h" + +#ifdef MOZ_X11 +struct _XDisplay; +typedef struct _XDisplay Display; +#endif // MOZ_X11 + +namespace mozilla { +namespace dom { +class SystemFontListEntry; +}; +}; // namespace mozilla + +class gfxPlatformGtk final : public gfxPlatform { + public: + gfxPlatformGtk(); + virtual ~gfxPlatformGtk(); + + static gfxPlatformGtk* GetPlatform() { + return (gfxPlatformGtk*)gfxPlatform::GetPlatform(); + } + + void ReadSystemFontList( + nsTArray<mozilla::dom::SystemFontListEntry>* retValue) override; + + already_AddRefed<gfxASurface> CreateOffscreenSurface( + const IntSize& aSize, gfxImageFormat aFormat) override; + + nsresult GetFontList(nsAtom* aLangGroup, const nsACString& aGenericFamily, + nsTArray<nsString>& aListOfFonts) override; + + void GetCommonFallbackFonts(uint32_t aCh, Script aRunScript, + eFontPresentation aPresentation, + nsTArray<const char*>& aFontList) override; + + gfxPlatformFontList* CreatePlatformFontList() override; + + /** + * Calls XFlush if xrender is enabled. + */ + void FlushContentDrawing() override; + + static int32_t GetFontScaleDPI(); + static double GetFontScaleFactor(); + +#ifdef MOZ_X11 + void GetAzureBackendInfo(mozilla::widget::InfoObject& aObj) override { + gfxPlatform::GetAzureBackendInfo(aObj); + aObj.DefineProperty("CairoUseXRender", mozilla::gfx::gfxVars::UseXRender()); + } +#endif + + bool UseImageOffscreenSurfaces(); + + gfxImageFormat GetOffscreenFormat() override; + + bool SupportsApzWheelInput() const override { return true; } + + 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 MOZ_X11 + already_AddRefed<mozilla::gfx::VsyncSource> CreateHardwareVsyncSource() + override; +#endif + +#ifdef MOZ_WAYLAND + bool UseDMABufWebGL() override { return mUseWebGLDmabufBackend; } + void DisableDMABufWebGL() { mUseWebGLDmabufBackend = false; } +#endif + + bool IsX11Display() { return mIsX11Display; } + bool IsWaylandDisplay() override { + return !mIsX11Display && !gfxPlatform::IsHeadless(); + } + + protected: + void InitPlatformGPUProcessPrefs() override; + bool CheckVariationFontSupport() override; + + int8_t mMaxGenericSubstitutions; + + private: + nsTArray<uint8_t> GetPlatformCMSOutputProfileData() override; + + bool mIsX11Display; +#ifdef MOZ_WAYLAND + bool mUseWebGLDmabufBackend; +#endif +}; + +#endif /* GFX_PLATFORM_GTK_H */ diff --git a/gfx/thebes/gfxPlatformMac.cpp b/gfx/thebes/gfxPlatformMac.cpp new file mode 100644 index 0000000000..4d68e58e42 --- /dev/null +++ b/gfx/thebes/gfxPlatformMac.cpp @@ -0,0 +1,661 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxPlatformMac.h" + +#include "gfxQuartzSurface.h" +#include "mozilla/DataMutex.h" +#include "mozilla/gfx/2D.h" + +#include "gfxMacPlatformFontList.h" +#include "gfxMacFont.h" +#include "gfxCoreTextShaper.h" +#include "gfxTextRun.h" +#include "gfxUserFontSet.h" +#include "gfxConfig.h" + +#include "nsTArray.h" +#include "mozilla/Preferences.h" +#include "mozilla/VsyncDispatcher.h" +#include "nsCocoaFeatures.h" +#include "nsUnicodeProperties.h" +#include "qcms.h" +#include "gfx2DGlue.h" + +#include <dlfcn.h> +#include <CoreVideo/CoreVideo.h> + +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/SurfacePool.h" +#include "VsyncSource.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::unicode; + +using mozilla::dom::SystemFontListEntry; + +// 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(); + + InitBackendPrefs(GetBackendPrefs()); + + if (nsCocoaFeatures::OnHighSierraOrLater()) { + mHasNativeColrFontSupport = true; + } +} + +gfxPlatformMac::~gfxPlatformMac() { gfxCoreTextShaper::Shutdown(); } + +BackendPrefsData gfxPlatformMac::GetBackendPrefs() const { + BackendPrefsData data; + + data.mCanvasBitmask = BackendTypeBit(BackendType::SKIA); + data.mContentBitmask = BackendTypeBit(BackendType::SKIA); + data.mCanvasDefault = BackendType::SKIA; + data.mContentDefault = BackendType::SKIA; + + return data; +} + +bool gfxPlatformMac::UsesTiling() const { + // The non-tiling ContentClient requires CrossProcessSemaphore which + // isn't implemented for OSX. + return true; +} + +bool gfxPlatformMac::ContentUsesTiling() const { return UsesTiling(); } + +gfxPlatformFontList* gfxPlatformMac::CreatePlatformFontList() { + gfxPlatformFontList* list = new gfxMacPlatformFontList(); + if (NS_SUCCEEDED(list->InitFontList())) { + return list; + } + gfxPlatformFontList::Shutdown(); + return nullptr; +} + +void gfxPlatformMac::ReadSystemFontList( + nsTArray<SystemFontListEntry>* aFontList) { + gfxMacPlatformFontList::PlatformFontList()->ReadSystemFontList(aFontList); +} + +already_AddRefed<gfxASurface> gfxPlatformMac::CreateOffscreenSurface( + const IntSize& aSize, gfxImageFormat aFormat) { + if (!Factory::AllowedSurfaceSize(aSize)) { + return nullptr; + } + + RefPtr<gfxASurface> newSurface = new gfxQuartzSurface(aSize, aFormat); + return newSurface.forget(); +} + +bool gfxPlatformMac::IsFontFormatSupported(uint32_t aFormatFlags) { + if (gfxPlatform::IsFontFormatSupported(aFormatFlags)) { + return true; + } + + // If the generic method rejected the format hint, then check for any + // platform-specific format we know about. + if (aFormatFlags & gfxUserFontSet::FLAG_FORMAT_TRUETYPE_AAT) { + return true; + } + + return false; +} + +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 kFontHelvetica[] = "Helvetica"; +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 kFontNotoSansMongolian[] = "Noto Sans Mongolian"; +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"; +static const char kFontZapfDingbats[] = "Zapf Dingbats"; + +void gfxPlatformMac::GetCommonFallbackFonts(uint32_t aCh, Script aRunScript, + eFontPresentation aPresentation, + nsTArray<const char*>& aFontList) { + if (PrefersColor(aPresentation)) { + aFontList.AppendElement(kFontAppleColorEmoji); + } + + aFontList.AppendElement(kFontLucidaGrande); + + if (!IS_IN_BMP(aCh)) { + uint32_t p = aCh >> 16; + if (p == 1) { + 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(kFontHelvetica); + 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(kFontNotoSansMongolian); + aFontList.AppendElement(kFontEuphemiaUCAS); + break; + case 0x19: // Khmer + aFontList.AppendElement(kFontKhmerMN); + aFontList.AppendElement(kFontMicrosoftTaiLe); + break; + case 0x1d: + case 0x1e: + aFontList.AppendElement(kFontGeneva); + break; + case 0x27: // For Dingbats block 2700-27BF, prefer Zapf Dingbats + aFontList.AppendElement(kFontZapfDingbats); + [[fallthrough]]; + case 0x20: // Symbol ranges + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + 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, nsACString& aSystemFontName, + gfxFontStyle& aFontStyle) { + gfxMacPlatformFontList* pfl = gfxMacPlatformFontList::PlatformFontList(); + return pfl->LookupSystemFont(aSystemFontID, aSystemFontName, aFontStyle); +} + +uint32_t gfxPlatformMac::ReadAntiAliasingThreshold() { + uint32_t threshold = 0; // default == no threshold + + // first read prefs flag to determine whether to use the setting or not + bool useAntiAliasingThreshold = + Preferences::GetBool("gfx.use_text_smoothing_setting", false); + + // if the pref setting is disabled, return 0 which effectively disables this + // feature + if (!useAntiAliasingThreshold) return threshold; + + // value set via Appearance pref panel, "Turn off text smoothing for font + // sizes xxx and smaller" + CFNumberRef prefValue = (CFNumberRef)CFPreferencesCopyAppValue( + CFSTR("AppleAntiAliasingThreshold"), kCFPreferencesCurrentApplication); + + if (prefValue) { + if (!CFNumberGetValue(prefValue, kCFNumberIntType, &threshold)) { + threshold = 0; + } + CFRelease(prefValue); + } + + return threshold; +} + +bool gfxPlatformMac::AccelerateLayersByDefault() { return true; } + +// This is the renderer output callback function, called on the vsync thread +static CVReturn VsyncCallback(CVDisplayLinkRef aDisplayLink, + const CVTimeStamp* aNow, + const CVTimeStamp* aOutputTime, + CVOptionFlags aFlagsIn, CVOptionFlags* aFlagsOut, + void* aDisplayLinkContext); + +class OSXVsyncSource final : public VsyncSource { + public: + OSXVsyncSource() : mGlobalDisplay(new OSXDisplay()) {} + + Display& GetGlobalDisplay() override { return *mGlobalDisplay; } + + class OSXDisplay final : public VsyncSource::Display { + public: + OSXDisplay() + : mDisplayLink(nullptr, "OSXVsyncSource::OSXDisplay::mDisplayLink") { + MOZ_ASSERT(NS_IsMainThread()); + mTimer = NS_NewTimer(); + CGDisplayRegisterReconfigurationCallback(DisplayReconfigurationCallback, + this); + } + + virtual ~OSXDisplay() { + MOZ_ASSERT(NS_IsMainThread()); + CGDisplayRemoveReconfigurationCallback(DisplayReconfigurationCallback, + this); + } + + static void RetryEnableVsync(nsITimer* aTimer, void* aOsxDisplay) { + MOZ_ASSERT(NS_IsMainThread()); + OSXDisplay* osxDisplay = static_cast<OSXDisplay*>(aOsxDisplay); + MOZ_ASSERT(osxDisplay); + osxDisplay->EnableVsync(); + } + + void EnableVsync() override { + MOZ_ASSERT(NS_IsMainThread()); + if (IsVsyncEnabled()) { + return; + } + + auto displayLink = mDisplayLink.Lock(); + + // Create a display link capable of being used with all active displays + // TODO: See if we need to create an active DisplayLink for each monitor + // in multi-monitor situations. According to the docs, it is compatible + // with all displays running on the computer But if we have different + // monitors at different display rates, we may hit issues. + CVReturn retval = CVDisplayLinkCreateWithActiveCGDisplays(&*displayLink); + + // Workaround for bug 1201401: CVDisplayLinkCreateWithCGDisplays() + // (called by CVDisplayLinkCreateWithActiveCGDisplays()) sometimes + // creates a CVDisplayLinkRef with an uninitialized (nulled) internal + // pointer. If we continue to use this CVDisplayLinkRef, we will + // eventually crash in CVCGDisplayLink::getDisplayTimes(), where the + // internal pointer is dereferenced. Fortunately, when this happens + // another internal variable is also left uninitialized (zeroed), + // which is accessible via CVDisplayLinkGetCurrentCGDisplay(). In + // normal conditions the current display is never zero. + if ((retval == kCVReturnSuccess) && + (CVDisplayLinkGetCurrentCGDisplay(*displayLink) == 0)) { + retval = kCVReturnInvalidDisplay; + } + + if (retval != kCVReturnSuccess) { + NS_WARNING( + "Could not create a display link with all active displays. " + "Retrying"); + CVDisplayLinkRelease(*displayLink); + *displayLink = nullptr; + + // bug 1142708 - When coming back from sleep, + // or when changing displays, active displays may not be ready yet, + // even if listening for the kIOMessageSystemHasPoweredOn event + // from OS X sleep notifications. + // Active displays are those that are drawable. + // bug 1144638 - When changing display configurations and getting + // notifications from CGDisplayReconfigurationCallBack, the + // callback gets called twice for each active display + // so it's difficult to know when all displays are active. + // Instead, try again soon. The delay is arbitrary. 100ms chosen + // because on a late 2013 15" retina, it takes about that + // long to come back up from sleep. + uint32_t delay = 100; + mTimer->InitWithNamedFuncCallback(RetryEnableVsync, this, delay, + nsITimer::TYPE_ONE_SHOT, + "RetryEnableVsync"); + return; + } + + if (CVDisplayLinkSetOutputCallback(*displayLink, &VsyncCallback, this) != + kCVReturnSuccess) { + NS_WARNING("Could not set displaylink output callback"); + CVDisplayLinkRelease(*displayLink); + *displayLink = nullptr; + return; + } + + mPreviousTimestamp = TimeStamp::Now(); + if (CVDisplayLinkStart(*displayLink) != kCVReturnSuccess) { + NS_WARNING("Could not activate the display link"); + CVDisplayLinkRelease(*displayLink); + *displayLink = nullptr; + } + + CVTime vsyncRate = + CVDisplayLinkGetNominalOutputVideoRefreshPeriod(*displayLink); + if (vsyncRate.flags & kCVTimeIsIndefinite) { + NS_WARNING("Could not get vsync rate, setting to 60."); + mVsyncRate = TimeDuration::FromMilliseconds(1000.0 / 60.0); + } else { + int64_t timeValue = vsyncRate.timeValue; + int64_t timeScale = vsyncRate.timeScale; + const int milliseconds = 1000; + float rateInMs = ((double)timeValue / (double)timeScale) * milliseconds; + mVsyncRate = TimeDuration::FromMilliseconds(rateInMs); + } + } + + void DisableVsync() override { + MOZ_ASSERT(NS_IsMainThread()); + if (!IsVsyncEnabled()) { + return; + } + + // Release the display link + auto displayLink = mDisplayLink.Lock(); + if (*displayLink) { + CVDisplayLinkRelease(*displayLink); + *displayLink = nullptr; + } + } + + bool IsVsyncEnabled() override { + MOZ_ASSERT(NS_IsMainThread()); + auto displayLink = mDisplayLink.Lock(); + return *displayLink != nullptr; + } + + TimeDuration GetVsyncRate() override { return mVsyncRate; } + + void Shutdown() override { + MOZ_ASSERT(NS_IsMainThread()); + mTimer->Cancel(); + mTimer = nullptr; + DisableVsync(); + } + + // The vsync timestamps given by the CVDisplayLinkCallback are + // in the future for the NEXT frame. Large parts of Gecko, such + // as animations assume a timestamp at either now or in the past. + // Normalize the timestamps given to the VsyncDispatchers to the vsync + // that just occured, not the vsync that is upcoming. + TimeStamp mPreviousTimestamp; + + private: + static void DisplayReconfigurationCallback( + CGDirectDisplayID aDisplay, CGDisplayChangeSummaryFlags aFlags, + void* aUserInfo) { + static_cast<OSXDisplay*>(aUserInfo)->OnDisplayReconfiguration(aDisplay, + aFlags); + } + + void OnDisplayReconfiguration(CGDirectDisplayID aDisplay, + CGDisplayChangeSummaryFlags aFlags) { + // Display reconfiguration notifications are fired in two phases: Before + // the reconfiguration and after the reconfiguration. + // All displays are notified before (with a "BeginConfiguration" flag), + // and the reconfigured displays are notified again after the + // configuration. + if (aFlags & kCGDisplayBeginConfigurationFlag) { + // We're only interested in the "after" notification, for the display + // link's current display. + return; + } + + if (!NS_IsMainThread()) { + return; + } + + bool didReconfigureCurrentDisplayLinkDisplay = false; + { // scope for lock + auto displayLink = mDisplayLink.Lock(); + didReconfigureCurrentDisplayLinkDisplay = + *displayLink && + CVDisplayLinkGetCurrentCGDisplay(*displayLink) == aDisplay; + } + + if (didReconfigureCurrentDisplayLinkDisplay) { + // The link's current display has been reconfigured. + // Recreate the display link, because otherwise it may be stuck with a + // "removed" display forever and never notify us again. + DisableVsync(); + EnableVsync(); + } + } + + // Accessed from main thread and from display reconfiguration callback + // thread... which also happens to be the main thread. + DataMutex<CVDisplayLinkRef> mDisplayLink; + + // Accessed only from the main thread. + RefPtr<nsITimer> mTimer; + TimeDuration mVsyncRate; + }; // OSXDisplay + + private: + virtual ~OSXVsyncSource() = default; + + RefPtr<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; + + mozilla::TimeStamp outputTime = + mozilla::TimeStamp::FromSystemTime(aOutputTime->hostTime); + mozilla::TimeStamp nextVsync = outputTime; + 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, outputTime); + return kCVReturnSuccess; +} + +already_AddRefed<mozilla::gfx::VsyncSource> +gfxPlatformMac::CreateHardwareVsyncSource() { + RefPtr<VsyncSource> osxVsyncSource = new OSXVsyncSource(); + VsyncSource::Display& primaryDisplay = osxVsyncSource->GetGlobalDisplay(); + primaryDisplay.EnableVsync(); + if (!primaryDisplay.IsVsyncEnabled()) { + NS_WARNING( + "OS X Vsync source not enabled. Falling back to software vsync."); + return gfxPlatform::CreateHardwareVsyncSource(); + } + + primaryDisplay.DisableVsync(); + return osxVsyncSource.forget(); +} + +nsTArray<uint8_t> gfxPlatformMac::GetPlatformCMSOutputProfileData() { + nsTArray<uint8_t> prefProfileData = GetPrefCMSOutputProfileData(); + if (!prefProfileData.IsEmpty()) { + return prefProfileData; + } + + CGColorSpaceRef cspace = ::CGDisplayCopyColorSpace(::CGMainDisplayID()); + if (!cspace) { + cspace = ::CGColorSpaceCreateDeviceRGB(); + } + if (!cspace) { + return nsTArray<uint8_t>(); + } + + CFDataRef iccp = ::CGColorSpaceCopyICCProfile(cspace); + + ::CFRelease(cspace); + + if (!iccp) { + return nsTArray<uint8_t>(); + } + + // copy to external buffer + size_t size = static_cast<size_t>(::CFDataGetLength(iccp)); + + nsTArray<uint8_t> result; + + if (size > 0) { + result.AppendElements(::CFDataGetBytePtr(iccp), size); + } + + ::CFRelease(iccp); + + return result; +} + +bool gfxPlatformMac::CheckVariationFontSupport() { + // We don't allow variation fonts to be enabled before 10.13, + // as although the Core Text APIs existed, they are known to be + // fairly buggy. + // (Note that Safari also requires 10.13 for variation-font support.) + return nsCocoaFeatures::OnHighSierraOrLater(); +} + +void gfxPlatformMac::InitPlatformGPUProcessPrefs() { + FeatureState& gpuProc = gfxConfig::GetFeature(Feature::GPU_PROCESS); + gpuProc.ForceDisable(FeatureStatus::Blocked, + "GPU process does not work on Mac", + "FEATURE_FAILURE_MAC_GPU_PROC"_ns); +} diff --git a/gfx/thebes/gfxPlatformMac.h b/gfx/thebes/gfxPlatformMac.h new file mode 100644 index 0000000000..f11608c03c --- /dev/null +++ b/gfx/thebes/gfxPlatformMac.h @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_PLATFORM_MAC_H +#define GFX_PLATFORM_MAC_H + +#include "nsTArrayForwardDeclare.h" +#include "gfxPlatform.h" +#include "mozilla/LookAndFeel.h" + +namespace mozilla { +namespace gfx { +class DrawTarget; +class VsyncSource; +} // namespace gfx +} // namespace mozilla + +class gfxPlatformMac : public gfxPlatform { + public: + gfxPlatformMac(); + virtual ~gfxPlatformMac(); + + static gfxPlatformMac* GetPlatform() { + return (gfxPlatformMac*)gfxPlatform::GetPlatform(); + } + + bool UsesTiling() const override; + bool ContentUsesTiling() const override; + + already_AddRefed<gfxASurface> CreateOffscreenSurface( + const IntSize& aSize, gfxImageFormat aFormat) override; + + gfxPlatformFontList* CreatePlatformFontList() override; + + void ReadSystemFontList( + nsTArray<mozilla::dom::SystemFontListEntry>* aFontList) override; + + bool IsFontFormatSupported(uint32_t aFormatFlags) override; + + void GetCommonFallbackFonts(uint32_t aCh, Script aRunScript, + eFontPresentation aPresentation, + nsTArray<const char*>& aFontList) override; + + // lookup the system font for a particular system font type and set + // the name and style characteristics + static void LookupSystemFont(mozilla::LookAndFeel::FontID aSystemFontID, + nsACString& aSystemFontName, + gfxFontStyle& aFontStyle); + + bool SupportsApzWheelInput() const override { return true; } + + bool RespectsFontStyleSmoothing() const override { + // gfxMacFont respects the font smoothing hint. + return true; + } + + bool RequiresAcceleratedGLContextForCompositorOGL() const override { + // On OS X in a VM, unaccelerated CompositorOGL shows black flashes, so we + // require accelerated GL for CompositorOGL but allow unaccelerated GL for + // BasicCompositor. + return true; + } + + already_AddRefed<mozilla::gfx::VsyncSource> CreateHardwareVsyncSource() + override; + + // lower threshold on font anti-aliasing + uint32_t GetAntiAliasingThreshold() { return mFontAntiAliasingThreshold; } + + protected: + bool AccelerateLayersByDefault() override; + + BackendPrefsData GetBackendPrefs() const override; + + bool CheckVariationFontSupport() override; + + void InitPlatformGPUProcessPrefs() override; + + private: + nsTArray<uint8_t> GetPlatformCMSOutputProfileData() override; + + // read in the pref value for the lower threshold on font anti-aliasing + static uint32_t ReadAntiAliasingThreshold(); + + 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 0000000000..624a06aaad --- /dev/null +++ b/gfx/thebes/gfxPoint.h @@ -0,0 +1,14 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_POINT_H +#define GFX_POINT_H + +#include "mozilla/gfx/Point.h" + +typedef mozilla::gfx::SizeDouble gfxSize; +typedef mozilla::gfx::PointDouble gfxPoint; + +#endif /* GFX_POINT_H */ diff --git a/gfx/thebes/gfxQuad.h b/gfx/thebes/gfxQuad.h new file mode 100644 index 0000000000..76c6717e39 --- /dev/null +++ b/gfx/thebes/gfxQuad.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_QUAD_H +#define GFX_QUAD_H + +#include "gfxTypes.h" +#include "gfxRect.h" +#include "gfxLineSegment.h" +#include <algorithm> + +struct gfxQuad { + gfxQuad(const gfxPoint& aOne, const gfxPoint& aTwo, const gfxPoint& aThree, + const gfxPoint& aFour) { + mPoints[0] = aOne; + mPoints[1] = aTwo; + mPoints[2] = aThree; + mPoints[3] = aFour; + } + + bool Contains(const gfxPoint& aPoint) { + return (gfxLineSegment(mPoints[0], mPoints[1]) + .PointsOnSameSide(aPoint, mPoints[2]) && + gfxLineSegment(mPoints[1], mPoints[2]) + .PointsOnSameSide(aPoint, mPoints[3]) && + gfxLineSegment(mPoints[2], mPoints[3]) + .PointsOnSameSide(aPoint, mPoints[0]) && + gfxLineSegment(mPoints[3], mPoints[0]) + .PointsOnSameSide(aPoint, mPoints[1])); + } + + gfxRect GetBounds() { + gfxFloat min_x, max_x; + gfxFloat min_y, max_y; + + min_x = max_x = mPoints[0].x; + min_y = max_y = mPoints[0].y; + + for (int i = 1; i < 4; i++) { + min_x = std::min(mPoints[i].x, 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 0000000000..0505e372ba --- /dev/null +++ b/gfx/thebes/gfxQuartzNativeDrawing.cpp @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxQuartzNativeDrawing.h" +#include "gfxPlatform.h" +#include "mozilla/gfx/Helpers.h" + +using namespace mozilla::gfx; +using namespace mozilla; + +gfxQuartzNativeDrawing::gfxQuartzNativeDrawing(DrawTarget& aDrawTarget, + const Rect& nativeRect) + : mDrawTarget(&aDrawTarget), mNativeRect(nativeRect), mCGContext(nullptr) {} + +CGContextRef gfxQuartzNativeDrawing::BeginNativeDrawing() { + NS_ASSERTION(!mCGContext, + "BeginNativeDrawing called when drawing already in progress"); + + DrawTarget* dt = mDrawTarget; + if (dt->IsDualDrawTarget() || dt->IsTiledDrawTarget() || dt->IsCaptureDT() || + dt->GetBackendType() != BackendType::SKIA || dt->IsRecording()) { + // We need a DrawTarget that we can get a CGContextRef from: + Matrix transform = dt->GetTransform(); + + mNativeRect = transform.TransformBounds(mNativeRect); + mNativeRect.RoundOut(); + if (mNativeRect.IsEmpty()) { + return nullptr; + } + + mTempDrawTarget = Factory::CreateDrawTarget( + BackendType::SKIA, + IntSize::Truncate(mNativeRect.width, mNativeRect.height), + SurfaceFormat::B8G8R8A8); + if (!mTempDrawTarget) { + return nullptr; + } + + transform.PostTranslate(-mNativeRect.x, -mNativeRect.y); + mTempDrawTarget->SetTransform(transform); + + dt = mTempDrawTarget; + } else { + // Clip the DT in case BorrowedCGContext needs to create a new layer. + // This prevents it from creating a new layer the size of the window. + // But make sure that this clip is device pixel aligned. + Matrix transform = dt->GetTransform(); + + Rect deviceRect = transform.TransformBounds(mNativeRect); + deviceRect.RoundOut(); + mNativeRect = transform.Inverse().TransformBounds(deviceRect); + mDrawTarget->PushClipRect(mNativeRect); + } + + MOZ_ASSERT(dt->GetBackendType() == BackendType::SKIA); + mCGContext = mBorrowedContext.Init(dt); + + if (NS_WARN_IF(!mCGContext)) { + // Failed borrowing CG context, so we need to clean up. + if (!mTempDrawTarget) { + mDrawTarget->PopClip(); + } + return nullptr; + } + + return mCGContext; +} + +void gfxQuartzNativeDrawing::EndNativeDrawing() { + NS_ASSERTION(mCGContext, + "EndNativeDrawing called without BeginNativeDrawing"); + + mBorrowedContext.Finish(); + if (mTempDrawTarget) { + RefPtr<SourceSurface> source = mTempDrawTarget->Snapshot(); + + AutoRestoreTransform autoRestore(mDrawTarget); + mDrawTarget->SetTransform(Matrix()); + mDrawTarget->DrawSurface(source, mNativeRect, + Rect(0, 0, mNativeRect.width, mNativeRect.height)); + } else { + mDrawTarget->PopClip(); + } +} diff --git a/gfx/thebes/gfxQuartzNativeDrawing.h b/gfx/thebes/gfxQuartzNativeDrawing.h new file mode 100644 index 0000000000..9e8b0bede6 --- /dev/null +++ b/gfx/thebes/gfxQuartzNativeDrawing.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _GFXQUARTZNATIVEDRAWING_H_ +#define _GFXQUARTZNATIVEDRAWING_H_ + +#include "mozilla/Attributes.h" + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/BorrowedContext.h" +#include "mozilla/RefPtr.h" + +class gfxQuartzNativeDrawing { + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::Rect Rect; + + public: + /* Create native Quartz drawing for a rectangle bounded by + * nativeRect. + * + * Typical usage looks like: + * + * gfxQuartzNativeDrawing nativeDraw(ctx, nativeRect); + * CGContextRef cgContext = nativeDraw.BeginNativeDrawing(); + * if (!cgContext) + * return NS_ERROR_FAILURE; + * + * ... call Quartz operations on CGContextRef to draw to nativeRect ... + * + * nativeDraw.EndNativeDrawing(); + * + * aNativeRect is the size of the surface (in Quartz/Cocoa points) that + * will be created _if_ the gfxQuartzNativeDrawing decides to create a new + * surface and CGContext for its drawing operations, which it then + * composites into the target DrawTarget. + * + * (Note that aNativeRect will be ignored if the gfxQuartzNativeDrawing + * uses the target DrawTarget directly.) + * + * The optional aBackingScale parameter is a scaling factor that will be + * applied when creating and rendering into such a temporary surface. + */ + gfxQuartzNativeDrawing(DrawTarget& aDrawTarget, const Rect& aNativeRect); + + /* Returns a CGContextRef which may be used for native drawing. This + * CGContextRef is valid until EndNativeDrawing is called; if it is used + * for drawing after that time, the result is undefined. */ + CGContextRef BeginNativeDrawing(); + + /* Marks the end of native drawing */ + void EndNativeDrawing(); + + private: + // don't allow copying via construction or assignment + gfxQuartzNativeDrawing(const gfxQuartzNativeDrawing&) = delete; + const gfxQuartzNativeDrawing& operator=(const gfxQuartzNativeDrawing&) = + delete; + + // Final destination context + RefPtr<DrawTarget> mDrawTarget; + RefPtr<DrawTarget> mTempDrawTarget; + mozilla::gfx::BorrowedCGContext mBorrowedContext; + mozilla::gfx::Rect mNativeRect; + + // saved state + CGContextRef mCGContext; +}; + +#endif diff --git a/gfx/thebes/gfxQuartzSurface.cpp b/gfx/thebes/gfxQuartzSurface.cpp new file mode 100644 index 0000000000..ff5711569f --- /dev/null +++ b/gfx/thebes/gfxQuartzSurface.cpp @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxQuartzSurface.h" +#include "gfxContext.h" +#include "gfxImageSurface.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/HelpersCairo.h" + +#include "cairo-quartz.h" + +void gfxQuartzSurface::MakeInvalid() { mSize = mozilla::gfx::IntSize(-1, -1); } + +gfxQuartzSurface::gfxQuartzSurface(const mozilla::gfx::IntSize& desiredSize, + gfxImageFormat format) + : mCGContext(nullptr), mSize(desiredSize) { + if (!mozilla::gfx::Factory::CheckSurfaceSize(desiredSize)) MakeInvalid(); + + unsigned int width = static_cast<unsigned int>(mSize.width); + unsigned int height = static_cast<unsigned int>(mSize.height); + + cairo_format_t cformat = GfxFormatToCairoFormat(format); + cairo_surface_t* surf = cairo_quartz_surface_create(cformat, width, height); + + mCGContext = cairo_quartz_surface_get_cg_context(surf); + + CGContextRetain(mCGContext); + + Init(surf); + if (mSurfaceValid) { + RecordMemoryUsed(mSize.height * 4 + sizeof(gfxQuartzSurface)); + } +} + +gfxQuartzSurface::gfxQuartzSurface(CGContextRef context, + const mozilla::gfx::IntSize& size) + : mCGContext(context), mSize(size) { + if (!mozilla::gfx::Factory::CheckSurfaceSize(size)) MakeInvalid(); + + unsigned int width = static_cast<unsigned int>(mSize.width); + unsigned int height = static_cast<unsigned int>(mSize.height); + + cairo_surface_t* surf = + cairo_quartz_surface_create_for_cg_context(context, width, height); + + CGContextRetain(mCGContext); + + Init(surf); + if (mSurfaceValid) { + RecordMemoryUsed(mSize.height * 4 + sizeof(gfxQuartzSurface)); + } +} + +gfxQuartzSurface::gfxQuartzSurface(cairo_surface_t* csurf, + const mozilla::gfx::IntSize& aSize) + : mSize(aSize) { + mCGContext = cairo_quartz_surface_get_cg_context(csurf); + CGContextRetain(mCGContext); + + Init(csurf, true); +} + +gfxQuartzSurface::gfxQuartzSurface(unsigned char* data, + const mozilla::gfx::IntSize& aSize, + long stride, gfxImageFormat format) + : mCGContext(nullptr), mSize(aSize.width, aSize.height) { + if (!mozilla::gfx::Factory::CheckSurfaceSize(aSize)) MakeInvalid(); + + cairo_format_t cformat = GfxFormatToCairoFormat(format); + cairo_surface_t* surf = cairo_quartz_surface_create_for_data( + data, cformat, aSize.width, aSize.height, stride); + + mCGContext = cairo_quartz_surface_get_cg_context(surf); + + CGContextRetain(mCGContext); + + Init(surf); + if (mSurfaceValid) { + RecordMemoryUsed(mSize.height * stride + sizeof(gfxQuartzSurface)); + } +} + +already_AddRefed<gfxASurface> gfxQuartzSurface::CreateSimilarSurface( + gfxContentType aType, const mozilla::gfx::IntSize& aSize) { + cairo_surface_t* surface = cairo_quartz_surface_create_cg_layer( + mSurface, (cairo_content_t)aType, aSize.width, aSize.height); + if (cairo_surface_status(surface)) { + cairo_surface_destroy(surface); + return nullptr; + } + + RefPtr<gfxASurface> result = Wrap(surface, aSize); + cairo_surface_destroy(surface); + return result.forget(); +} + +already_AddRefed<gfxImageSurface> gfxQuartzSurface::GetAsImageSurface() { + cairo_surface_t* surface = cairo_quartz_surface_get_image(mSurface); + if (!surface || cairo_surface_status(surface)) return nullptr; + + RefPtr<gfxASurface> img = Wrap(surface); + + // cairo_quartz_surface_get_image returns a referenced image, and thebes + // shares the refcounts of Cairo surfaces. However, Wrap also adds a + // reference to the image. We need to remove one of these references + // explicitly so we don't leak. + img.get()->Release(); + + img->SetOpaqueRect(GetOpaqueRect()); + + return img.forget().downcast<gfxImageSurface>(); +} + +gfxQuartzSurface::~gfxQuartzSurface() { CGContextRelease(mCGContext); } diff --git a/gfx/thebes/gfxQuartzSurface.h b/gfx/thebes/gfxQuartzSurface.h new file mode 100644 index 0000000000..8c3b9c62d1 --- /dev/null +++ b/gfx/thebes/gfxQuartzSurface.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_QUARTZSURFACE_H +#define GFX_QUARTZSURFACE_H + +#include "gfxASurface.h" +#include "nsSize.h" +#include "gfxPoint.h" + +#include <Carbon/Carbon.h> + +class gfxContext; +class gfxImageSurface; + +class gfxQuartzSurface : public gfxASurface { + public: + gfxQuartzSurface(const mozilla::gfx::IntSize&, gfxImageFormat format); + gfxQuartzSurface(CGContextRef context, const mozilla::gfx::IntSize& size); + gfxQuartzSurface(cairo_surface_t* csurf, const mozilla::gfx::IntSize& aSize); + gfxQuartzSurface(unsigned char* data, const mozilla::gfx::IntSize& size, + long stride, gfxImageFormat format); + + virtual ~gfxQuartzSurface(); + + virtual already_AddRefed<gfxASurface> CreateSimilarSurface( + gfxContentType aType, const mozilla::gfx::IntSize& aSize); + + virtual const mozilla::gfx::IntSize GetSize() const { + return mozilla::gfx::IntSize(mSize.width, mSize.height); + } + + CGContextRef GetCGContext() { return mCGContext; } + + already_AddRefed<gfxImageSurface> GetAsImageSurface(); + + protected: + void MakeInvalid(); + + CGContextRef mCGContext; + mozilla::gfx::IntSize mSize; +}; + +#endif /* GFX_QUARTZSURFACE_H */ diff --git a/gfx/thebes/gfxQuaternion.h b/gfx/thebes/gfxQuaternion.h new file mode 100644 index 0000000000..4180f7a7b4 --- /dev/null +++ b/gfx/thebes/gfxQuaternion.h @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_QUATERNION_H +#define GFX_QUATERNION_H + +#include "mozilla/gfx/BasePoint4D.h" +#include "mozilla/gfx/Matrix.h" +#include "nsAlgorithm.h" +#include <algorithm> + +struct gfxQuaternion + : public mozilla::gfx::BasePoint4D<gfxFloat, gfxQuaternion> { + typedef mozilla::gfx::BasePoint4D<gfxFloat, gfxQuaternion> Super; + + gfxQuaternion() : Super() {} + gfxQuaternion(gfxFloat aX, gfxFloat aY, gfxFloat aZ, gfxFloat aW) + : Super(aX, aY, aZ, aW) {} + + explicit gfxQuaternion(const mozilla::gfx::Matrix4x4& aMatrix) { + w = 0.5 * + sqrt(std::max(1 + aMatrix[0][0] + aMatrix[1][1] + aMatrix[2][2], 0.0f)); + x = 0.5 * + sqrt(std::max(1 + aMatrix[0][0] - aMatrix[1][1] - aMatrix[2][2], 0.0f)); + y = 0.5 * + sqrt(std::max(1 - aMatrix[0][0] + aMatrix[1][1] - aMatrix[2][2], 0.0f)); + z = 0.5 * + sqrt(std::max(1 - aMatrix[0][0] - aMatrix[1][1] + aMatrix[2][2], 0.0f)); + + if (aMatrix[2][1] > aMatrix[1][2]) x = -x; + if (aMatrix[0][2] > aMatrix[2][0]) y = -y; + if (aMatrix[1][0] > aMatrix[0][1]) z = -z; + } + + // Convert from |direction axis, angle| pair to gfxQuaternion. + // + // Reference: + // https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation + // + // if the direction axis is (x, y, z) = xi + yj + zk, + // and the angle is |theta|, this formula can be done using + // an extension of Euler's formula: + // q = cos(theta/2) + (xi + yj + zk)(sin(theta/2)) + // = cos(theta/2) + + // x*sin(theta/2)i + y*sin(theta/2)j + z*sin(theta/2)k + // Note: aDirection should be an unit vector and + // the unit of aAngle should be Radian. + gfxQuaternion(const mozilla::gfx::Point3D& aDirection, gfxFloat aAngle) { + MOZ_ASSERT(mozilla::gfx::FuzzyEqual(aDirection.Length(), 1.0f), + "aDirection should be an unit vector"); + x = aDirection.x * sin(aAngle / 2.0); + y = aDirection.y * sin(aAngle / 2.0); + z = aDirection.z * sin(aAngle / 2.0); + w = cos(aAngle / 2.0); + } + + gfxQuaternion Slerp(const gfxQuaternion& aOther, gfxFloat aCoeff) const { + gfxFloat dot = mozilla::clamped(DotProduct(aOther), -1.0, 1.0); + if (dot == 1.0) { + return *this; + } + + gfxFloat theta = acos(dot); + gfxFloat rsintheta = 1 / sqrt(1 - dot * dot); + gfxFloat rightWeight = sin(aCoeff * theta) * rsintheta; + + gfxQuaternion left = *this; + gfxQuaternion right = aOther; + + left *= cos(aCoeff * theta) - dot * rightWeight; + right *= rightWeight; + + return left + right; + } + + using Super::operator*=; + + // Quaternion multiplication + // Reference: + // https://en.wikipedia.org/wiki/Quaternion#Ordered_list_form + // + // (w1, x1, y1, z1)(w2, x2, y2, z2) = (w1w2 - x1x2 - y1y2 - z1z2, + // w1x2 + x1w2 + y1z2 - z1y2, + // w1y2 - x1z2 + y1w2 + z1x2, + // w1z2 + x1y2 - y1x2 + z1w2) + gfxQuaternion operator*(const gfxQuaternion& aOther) const { + return gfxQuaternion( + w * aOther.x + x * aOther.w + y * aOther.z - z * aOther.y, + w * aOther.y - x * aOther.z + y * aOther.w + z * aOther.x, + w * aOther.z + x * aOther.y - y * aOther.x + z * aOther.w, + w * aOther.w - x * aOther.x - y * aOther.y - z * aOther.z); + } + gfxQuaternion& operator*=(const gfxQuaternion& aOther) { + *this = *this * aOther; + return *this; + } + + mozilla::gfx::Matrix4x4 ToMatrix() const { + mozilla::gfx::Matrix4x4 temp; + + temp[0][0] = 1 - 2 * (y * y + z * z); + temp[0][1] = 2 * (x * y + w * z); + temp[0][2] = 2 * (x * z - w * y); + temp[1][0] = 2 * (x * y - w * z); + temp[1][1] = 1 - 2 * (x * x + z * z); + temp[1][2] = 2 * (y * z + w * x); + temp[2][0] = 2 * (x * z + w * y); + temp[2][1] = 2 * (y * z - w * x); + temp[2][2] = 1 - 2 * (x * x + y * y); + + return temp; + } +}; + +#endif /* GFX_QUATERNION_H */ diff --git a/gfx/thebes/gfxRect.h b/gfx/thebes/gfxRect.h new file mode 100644 index 0000000000..f93fc77ca4 --- /dev/null +++ b/gfx/thebes/gfxRect.h @@ -0,0 +1,14 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_RECT_H +#define GFX_RECT_H + +#include "mozilla/gfx/Rect.h" + +typedef mozilla::gfx::MarginDouble gfxMargin; +typedef mozilla::gfx::RectDouble gfxRect; + +#endif /* GFX_RECT_H */ diff --git a/gfx/thebes/gfxSVGGlyphs.cpp b/gfx/thebes/gfxSVGGlyphs.cpp new file mode 100644 index 0000000000..aa973e4782 --- /dev/null +++ b/gfx/thebes/gfxSVGGlyphs.cpp @@ -0,0 +1,459 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxSVGGlyphs.h" + +#include "mozilla/BasePrincipal.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/PresShell.h" +#include "mozilla/SMILAnimationController.h" +#include "mozilla/SVGContextPaint.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/FontTableURIProtocolHandler.h" +#include "mozilla/dom/ImageTracker.h" +#include "mozilla/dom/SVGDocument.h" +#include "nsError.h" +#include "nsString.h" +#include "nsICategoryManager.h" +#include "nsIDocumentLoaderFactory.h" +#include "nsIContentViewer.h" +#include "nsIStreamListener.h" +#include "nsServiceManagerUtils.h" +#include "nsNetUtil.h" +#include "nsIInputStream.h" +#include "nsStringStream.h" +#include "nsStreamUtils.h" +#include "nsIPrincipal.h" +#include "nsContentUtils.h" +#include "gfxFont.h" +#include "gfxContext.h" +#include "harfbuzz/hb.h" +#include "zlib.h" + +#define SVG_CONTENT_TYPE "image/svg+xml"_ns +#define UTF8_CHARSET "utf-8"_ns + +using namespace mozilla; +using mozilla::dom::Document; +using mozilla::dom::Element; + +/* static */ +const mozilla::gfx::DeviceColor SimpleTextContextPaint::sZero; + +gfxSVGGlyphs::gfxSVGGlyphs(hb_blob_t* aSVGTable, gfxFontEntry* aFontEntry) + : mSVGData(aSVGTable), mFontEntry(aFontEntry) { + unsigned int length; + const char* svgData = hb_blob_get_data(mSVGData, &length); + mHeader = reinterpret_cast<const Header*>(svgData); + mDocIndex = nullptr; + + if (sizeof(Header) <= length && uint16_t(mHeader->mVersion) == 0 && + uint64_t(mHeader->mDocIndexOffset) + 2 <= length) { + const DocIndex* docIndex = + reinterpret_cast<const DocIndex*>(svgData + mHeader->mDocIndexOffset); + // Limit the number of documents to avoid overflow + if (uint64_t(mHeader->mDocIndexOffset) + 2 + + uint16_t(docIndex->mNumEntries) * sizeof(IndexEntry) <= + length) { + mDocIndex = docIndex; + } + } +} + +gfxSVGGlyphs::~gfxSVGGlyphs() { hb_blob_destroy(mSVGData); } + +void gfxSVGGlyphs::DidRefresh() { mFontEntry->NotifyGlyphsChanged(); } + +/* + * Comparison operator for finding a range containing a given glyph ID. Simply + * checks whether |key| is less (greater) than every element of |range|, in + * which case return |key| < |range| (|key| > |range|). Otherwise |key| is in + * |range|, in which case return equality. + * The total ordering here is guaranteed by + * (1) the index ranges being disjoint; and + * (2) the (sole) key always being a singleton, so intersection => containment + * (note that this is wrong if we have more than one intersection or two + * sets intersecting of size > 1 -- so... don't do that) + */ +/* static */ +int gfxSVGGlyphs::CompareIndexEntries(const void* aKey, const void* aEntry) { + const uint32_t key = *(uint32_t*)aKey; + const IndexEntry* entry = (const IndexEntry*)aEntry; + + if (key < uint16_t(entry->mStartGlyph)) { + return -1; + } + if (key > uint16_t(entry->mEndGlyph)) { + return 1; + } + return 0; +} + +gfxSVGGlyphsDocument* gfxSVGGlyphs::FindOrCreateGlyphsDocument( + uint32_t aGlyphId) { + if (!mDocIndex) { + // Invalid table + return nullptr; + } + + IndexEntry* entry = (IndexEntry*)bsearch( + &aGlyphId, mDocIndex->mEntries, uint16_t(mDocIndex->mNumEntries), + sizeof(IndexEntry), CompareIndexEntries); + if (!entry) { + return nullptr; + } + + gfxSVGGlyphsDocument* result = mGlyphDocs.Get(entry->mDocOffset); + + if (!result) { + unsigned int length; + const uint8_t* data = (const uint8_t*)hb_blob_get_data(mSVGData, &length); + if (entry->mDocOffset > 0 && uint64_t(mHeader->mDocIndexOffset) + + entry->mDocOffset + + entry->mDocLength <= + length) { + result = new gfxSVGGlyphsDocument( + data + mHeader->mDocIndexOffset + entry->mDocOffset, + entry->mDocLength, this); + mGlyphDocs.Put(entry->mDocOffset, result); + } + } + + return result; +} + +nsresult gfxSVGGlyphsDocument::SetupPresentation() { + nsCOMPtr<nsICategoryManager> catMan = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID); + nsCString contractId; + nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", + "image/svg+xml", contractId); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory = + do_GetService(contractId.get()); + NS_ASSERTION(docLoaderFactory, "Couldn't get DocumentLoaderFactory"); + + nsCOMPtr<nsIContentViewer> viewer; + rv = docLoaderFactory->CreateInstanceForDocument(nullptr, mDocument, nullptr, + getter_AddRefs(viewer)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = viewer->Init(nullptr, gfx::IntRect(0, 0, 1000, 1000), nullptr); + if (NS_SUCCEEDED(rv)) { + rv = viewer->Open(nullptr, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + } + + RefPtr<PresShell> presShell = viewer->GetPresShell(); + if (!presShell->DidInitialize()) { + rv = presShell->Initialize(); + NS_ENSURE_SUCCESS(rv, rv); + } + + mDocument->FlushPendingNotifications(FlushType::Layout); + + if (mDocument->HasAnimationController()) { + mDocument->GetAnimationController()->Resume(SMILTimeContainer::PAUSE_IMAGE); + } + mDocument->ImageTracker()->SetAnimatingState(true); + + mViewer = viewer; + mPresShell = presShell; + mPresShell->AddPostRefreshObserver(this); + + return NS_OK; +} + +void gfxSVGGlyphsDocument::DidRefresh() { mOwner->DidRefresh(); } + +/** + * Walk the DOM tree to find all glyph elements and insert them into the lookup + * table + * @param aElem The element to search from + */ +void gfxSVGGlyphsDocument::FindGlyphElements(Element* aElem) { + for (nsIContent* child = aElem->GetLastChild(); child; + child = child->GetPreviousSibling()) { + if (!child->IsElement()) { + continue; + } + FindGlyphElements(child->AsElement()); + } + + InsertGlyphId(aElem); +} + +/** + * If there exists an SVG glyph with the specified glyph id, render it and + * return true If no such glyph exists, or in the case of an error return false + * @param aContext The thebes aContext to draw to + * @param aGlyphId The glyph id + * @return true iff rendering succeeded + */ +void gfxSVGGlyphs::RenderGlyph(gfxContext* aContext, uint32_t aGlyphId, + SVGContextPaint* aContextPaint) { + gfxContextAutoSaveRestore aContextRestorer(aContext); + + Element* glyph = mGlyphIdMap.Get(aGlyphId); + MOZ_ASSERT(glyph, "No glyph element. Should check with HasSVGGlyph() first!"); + + AutoSetRestoreSVGContextPaint autoSetRestore( + *aContextPaint, *glyph->OwnerDoc()->AsSVGDocument()); + + SVGUtils::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 SVGUtils::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) { + if (aBufLen >= 14 && aBuffer[0] == 31 && aBuffer[1] == 139) { + // It's a gzip-compressed document; decompress it before parsing. + // The original length (modulo 2^32) is found in the last 4 bytes + // of the data, stored in little-endian format. We read it as + // individual bytes to avoid possible alignment issues. + // (Note that if the original length was >2^32, then origLen here + // will be incorrect; but then the inflate() call will not return + // Z_STREAM_END and we'll bail out safely.) + size_t origLen = (size_t(aBuffer[aBufLen - 1]) << 24) + + (size_t(aBuffer[aBufLen - 2]) << 16) + + (size_t(aBuffer[aBufLen - 3]) << 8) + + size_t(aBuffer[aBufLen - 4]); + AutoTArray<uint8_t, 4096> outBuf; + if (outBuf.SetLength(origLen, mozilla::fallible)) { + z_stream s = {0}; + s.next_in = const_cast<Byte*>(aBuffer); + s.avail_in = aBufLen; + s.next_out = outBuf.Elements(); + s.avail_out = outBuf.Length(); + // The magic number 16 here is the zlib flag to expect gzip format, + // see http://www.zlib.net/manual.html#Advanced + if (Z_OK == inflateInit2(&s, 16 + MAX_WBITS)) { + int result = inflate(&s, Z_FINISH); + if (Z_STREAM_END == result) { + MOZ_ASSERT(size_t(s.next_out - outBuf.Elements()) == origLen); + ParseDocument(outBuf.Elements(), outBuf.Length()); + } else { + NS_WARNING("Failed to decompress SVG glyphs document"); + } + inflateEnd(&s); + } + } else { + NS_WARNING("Failed to allocate memory for SVG glyphs document"); + } + } else { + ParseDocument(aBuffer, aBufLen); + } + + if (!mDocument) { + NS_WARNING("Could not parse SVG glyphs document"); + return; + } + + Element* root = mDocument->GetRootElement(); + if (!root) { + NS_WARNING("Could not parse SVG glyphs document"); + return; + } + + nsresult rv = SetupPresentation(); + if (NS_FAILED(rv)) { + NS_WARNING("Couldn't setup presentation for SVG glyphs document"); + return; + } + + FindGlyphElements(root); +} + +gfxSVGGlyphsDocument::~gfxSVGGlyphsDocument() { + if (mDocument) { + mDocument->OnPageHide(false, nullptr); + } + if (mPresShell) { + mPresShell->RemovePostRefreshObserver(this); + } + if (mViewer) { + mViewer->Close(nullptr); + mViewer->Destroy(); + } +} + +static nsresult CreateBufferedStream(const uint8_t* aBuffer, uint32_t aBufLen, + nsCOMPtr<nsIInputStream>& aResult) { + nsCOMPtr<nsIInputStream> stream; + nsresult rv = NS_NewByteInputStream( + getter_AddRefs(stream), + Span(reinterpret_cast<const char*>(aBuffer), aBufLen), + NS_ASSIGNMENT_DEPEND); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIInputStream> aBufferedStream; + if (!NS_InputStreamIsBuffered(stream)) { + rv = NS_NewBufferedInputStream(getter_AddRefs(aBufferedStream), + stream.forget(), 4096); + NS_ENSURE_SUCCESS(rv, rv); + stream = aBufferedStream; + } + + aResult = stream; + + return NS_OK; +} + +nsresult gfxSVGGlyphsDocument::ParseDocument(const uint8_t* aBuffer, + uint32_t aBufLen) { + // Mostly pulled from nsDOMParser::ParseFromStream + + nsCOMPtr<nsIInputStream> stream; + nsresult rv = CreateBufferedStream(aBuffer, aBufLen, stream); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURI> uri; + mozilla::dom::FontTableURIProtocolHandler::GenerateURIString( + mSVGGlyphsDocumentURI); + + rv = NS_NewURI(getter_AddRefs(uri), mSVGGlyphsDocumentURI); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrincipal> principal = + NullPrincipal::CreateWithoutOriginAttributes(); + + RefPtr<Document> document; + rv = NS_NewDOMDocument(getter_AddRefs(document), + u""_ns, // aNamespaceURI + u""_ns, // aQualifiedName + nullptr, // aDoctype + uri, uri, principal, + false, // aLoadedAsData + nullptr, // aEventObject + DocumentFlavorSVG); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIChannel> channel; + rv = NS_NewInputStreamChannel( + getter_AddRefs(channel), uri, + nullptr, // aStream + principal, nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL, + nsIContentPolicy::TYPE_OTHER, SVG_CONTENT_TYPE, UTF8_CHARSET); + NS_ENSURE_SUCCESS(rv, rv); + + // Set this early because various decisions during page-load depend on it. + document->SetIsBeingUsedAsImage(); + document->SetIsSVGGlyphsDocument(); + document->SetReadyStateInternal(Document::READYSTATE_UNINITIALIZED); + + nsCOMPtr<nsIStreamListener> listener; + rv = document->StartDocumentLoad("external-resource", channel, + nullptr, // aLoadGroup + nullptr, // aContainer + getter_AddRefs(listener), true /* aReset */); + if (NS_FAILED(rv) || !listener) { + return NS_ERROR_FAILURE; + } + + rv = listener->OnStartRequest(channel); + if (NS_FAILED(rv)) { + channel->Cancel(rv); + } + + nsresult status; + channel->GetStatus(&status); + if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(status)) { + rv = listener->OnDataAvailable(channel, stream, 0, aBufLen); + if (NS_FAILED(rv)) { + channel->Cancel(rv); + } + channel->GetStatus(&status); + } + + rv = listener->OnStopRequest(channel, status); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + document.swap(mDocument); + + return NS_OK; +} + +void gfxSVGGlyphsDocument::InsertGlyphId(Element* aGlyphElement) { + nsAutoString glyphIdStr; + static const uint32_t glyphPrefixLength = 5; + // The maximum glyph ID is 65535 so the maximum length of the numeric part + // is 5. + if (!aGlyphElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, glyphIdStr) || + !StringBeginsWith(glyphIdStr, u"glyph"_ns) || + glyphIdStr.Length() > glyphPrefixLength + 5) { + return; + } + + uint32_t id = 0; + for (uint32_t i = glyphPrefixLength; i < glyphIdStr.Length(); ++i) { + char16_t ch = glyphIdStr.CharAt(i); + if (ch < '0' || ch > '9') { + return; + } + if (ch == '0' && i == glyphPrefixLength) { + return; + } + id = id * 10 + (ch - '0'); + } + + mGlyphIdMap.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 0000000000..8ce2e5aa6c --- /dev/null +++ b/gfx/thebes/gfxSVGGlyphs.h @@ -0,0 +1,239 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_SVG_GLYPHS_WRAPPER_H +#define GFX_SVG_GLYPHS_WRAPPER_H + +#include "gfxFontUtils.h" +#include "mozilla/gfx/2D.h" +#include "nsString.h" +#include "nsClassHashtable.h" +#include "nsBaseHashtable.h" +#include "nsHashKeys.h" +#include "gfxPattern.h" +#include "mozilla/gfx/UserData.h" +#include "mozilla/SVGContextPaint.h" +#include "nsRefreshObservers.h" + +class nsIContentViewer; +class gfxSVGGlyphs; + +namespace mozilla { +class PresShell; +class SVGContextPaint; +namespace dom { +class Document; +class Element; +} // namespace dom +} // namespace mozilla + +/** + * Wraps an SVG document contained in the SVG table of an OpenType font. + * There may be multiple SVG documents in an SVG table which we lazily parse + * so we have an instance of this class for every document in the SVG table + * which contains a glyph ID which has been used + * Finds and looks up elements contained in the SVG document which have glyph + * mappings to be drawn by gfxSVGGlyphs + */ +class gfxSVGGlyphsDocument final : public nsAPostRefreshObserver { + typedef mozilla::dom::Element Element; + + public: + gfxSVGGlyphsDocument(const uint8_t* aBuffer, uint32_t aBufLen, + gfxSVGGlyphs* aSVGGlyphs); + + Element* GetGlyphElement(uint32_t aGlyphId); + + ~gfxSVGGlyphsDocument(); + + void DidRefresh() override; + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + private: + nsresult ParseDocument(const uint8_t* aBuffer, uint32_t aBufLen); + + nsresult SetupPresentation(); + + void FindGlyphElements(Element* aElement); + + void InsertGlyphId(Element* aGlyphElement); + + // Weak so as not to create a cycle. mOwner owns us so this can't dangle. + gfxSVGGlyphs* mOwner; + RefPtr<mozilla::dom::Document> mDocument; + nsCOMPtr<nsIContentViewer> mViewer; + RefPtr<mozilla::PresShell> mPresShell; + + nsBaseHashtable<nsUint32HashKey, Element*, Element*> mGlyphIdMap; + + 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|. + */ + void RenderGlyph(gfxContext* aContext, uint32_t aGlyphId, + mozilla::SVGContextPaint* aContextPaint); + + /** + * Get the extents for the SVG glyph associated with |aGlyphId| + * @param aSVGToAppSpace The matrix mapping the SVG glyph space to the + * target context space + */ + bool GetGlyphExtents(uint32_t aGlyphId, const gfxMatrix& aSVGToAppSpace, + gfxRect* aResult); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + private: + Element* GetGlyphElement(uint32_t aGlyphId); + + nsClassHashtable<nsUint32HashKey, gfxSVGGlyphsDocument> mGlyphDocs; + nsBaseHashtable<nsUint32HashKey, Element*, Element*> mGlyphIdMap; + + hb_blob_t* mSVGData; + + // pointer to the font entry that owns this gfxSVGGlyphs object + gfxFontEntry* MOZ_NON_OWNING_REF mFontEntry; + + const struct Header { + mozilla::AutoSwap_PRUint16 mVersion; + mozilla::AutoSwap_PRUint32 mDocIndexOffset; + mozilla::AutoSwap_PRUint32 mColorPalettesOffset; + } * mHeader; + + struct IndexEntry { + mozilla::AutoSwap_PRUint16 mStartGlyph; + mozilla::AutoSwap_PRUint16 mEndGlyph; + mozilla::AutoSwap_PRUint32 mDocOffset; + mozilla::AutoSwap_PRUint32 mDocLength; + }; + + const struct DocIndex { + mozilla::AutoSwap_PRUint16 mNumEntries; + IndexEntry mEntries[1]; /* actual length = mNumEntries */ + } * mDocIndex; + + static int CompareIndexEntries(const void* _a, const void* _b); +}; + +/** + * XXX This is a complete hack and should die (see bug 1291494). + * + * This class is used when code fails to pass through an SVGContextPaint from + * the context in which we are painting. In that case we create one of these + * as a fallback and have it wrap the gfxContext's current gfxPattern and + * pretend that that is the paint context's fill pattern. In some contexts + * that will be the case, in others it will not. As we convert more code to + * Moz2D the less likely it is that this hack will work. It will also make + * converting to Moz2D harder. + */ +class SimpleTextContextPaint : public mozilla::SVGContextPaint { + private: + static const mozilla::gfx::DeviceColor sZero; + + static gfxMatrix SetupDeviceToPatternMatrix(gfxPattern* aPattern, + const gfxMatrix& aCTM) { + if (!aPattern) { + return gfxMatrix(); + } + gfxMatrix deviceToUser = aCTM; + if (!deviceToUser.Invert()) { + return gfxMatrix(0, 0, 0, 0, 0, 0); // singular + } + return deviceToUser * aPattern->GetMatrix(); + } + + public: + SimpleTextContextPaint(gfxPattern* aFillPattern, gfxPattern* aStrokePattern, + const gfxMatrix& aCTM) + : mFillPattern(aFillPattern ? aFillPattern : new gfxPattern(sZero)), + mStrokePattern(aStrokePattern ? aStrokePattern + : new gfxPattern(sZero)) { + mFillMatrix = SetupDeviceToPatternMatrix(aFillPattern, aCTM); + mStrokeMatrix = SetupDeviceToPatternMatrix(aStrokePattern, aCTM); + } + + already_AddRefed<gfxPattern> GetFillPattern( + const DrawTarget* aDrawTarget, float aOpacity, const gfxMatrix& aCTM, + imgDrawingParams& aImgParams) override { + if (mFillPattern) { + mFillPattern->SetMatrix(aCTM * mFillMatrix); + } + RefPtr<gfxPattern> fillPattern = mFillPattern; + return fillPattern.forget(); + } + + already_AddRefed<gfxPattern> GetStrokePattern( + const DrawTarget* aDrawTarget, float aOpacity, const gfxMatrix& aCTM, + imgDrawingParams& aImgParams) override { + if (mStrokePattern) { + mStrokePattern->SetMatrix(aCTM * mStrokeMatrix); + } + RefPtr<gfxPattern> strokePattern = mStrokePattern; + return strokePattern.forget(); + } + + float GetFillOpacity() const override { return mFillPattern ? 1.0f : 0.0f; } + + float GetStrokeOpacity() const override { + return mStrokePattern ? 1.0f : 0.0f; + } + + private: + RefPtr<gfxPattern> mFillPattern; + RefPtr<gfxPattern> mStrokePattern; + + // Device space to pattern space transforms + gfxMatrix mFillMatrix; + gfxMatrix mStrokeMatrix; +}; + +#endif diff --git a/gfx/thebes/gfxScriptItemizer.cpp b/gfx/thebes/gfxScriptItemizer.cpp new file mode 100644 index 0000000000..d4d20da384 --- /dev/null +++ b/gfx/thebes/gfxScriptItemizer.cpp @@ -0,0 +1,245 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This file is based on usc_impl.c from ICU 4.2.0.1, slightly adapted + * for use within Mozilla Gecko, separate from a standard ICU build. + * + * The original ICU license of the code follows: + * + * ICU License - ICU 1.8.1 and later + * + * COPYRIGHT AND PERMISSION NOTICE + * + * Copyright (c) 1995-2009 International Business Machines Corporation and + * others + * + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, provided that the above copyright notice(s) and this + * permission notice appear in all copies of the Software and that both the + * above copyright notice(s) and this permission notice appear in supporting + * documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE + * BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, + * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, + * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + * + * Except as contained in this notice, the name of a copyright holder shall + * not be used in advertising or otherwise to promote the sale, use or other + * dealings in this Software without prior written authorization of the + * copyright holder. + * + * All trademarks and registered trademarks mentioned herein are the property + * of their respective owners. + */ + +#include "gfxScriptItemizer.h" +#include "nsUnicodeProperties.h" +#include "nsCharTraits.h" +#include "harfbuzz/hb.h" +#include "unicode/uscript.h" + +using namespace mozilla::unicode; + +#define MOD(sp) ((sp) % PAREN_STACK_DEPTH) +#define LIMIT_INC(sp) \ + (((sp) < PAREN_STACK_DEPTH) ? (sp) + 1 : PAREN_STACK_DEPTH) +#define INC(sp, count) (MOD((sp) + (count))) +#define INC1(sp) (INC(sp, 1)) +#define DEC(sp, count) (MOD((sp) + PAREN_STACK_DEPTH - (count))) +#define DEC1(sp) (DEC(sp, 1)) +#define STACK_IS_EMPTY() (pushCount <= 0) +#define STACK_IS_NOT_EMPTY() (!STACK_IS_EMPTY()) +#define TOP() (parenStack[parenSP]) +#define SYNC_FIXUP() (fixupCount = 0) + +void gfxScriptItemizer::push(uint32_t endPairChar, Script newScriptCode) { + pushCount = LIMIT_INC(pushCount); + fixupCount = LIMIT_INC(fixupCount); + + parenSP = INC1(parenSP); + parenStack[parenSP].endPairChar = endPairChar; + parenStack[parenSP].scriptCode = newScriptCode; +} + +void gfxScriptItemizer::pop() { + if (STACK_IS_EMPTY()) { + return; + } + + if (fixupCount > 0) { + fixupCount -= 1; + } + + pushCount -= 1; + parenSP = DEC1(parenSP); + + /* If the stack is now empty, reset the stack + pointers to their initial values. + */ + if (STACK_IS_EMPTY()) { + parenSP = -1; + } +} + +void gfxScriptItemizer::fixup(Script newScriptCode) { + int32_t fixupSP = DEC(parenSP, fixupCount); + + while (fixupCount-- > 0) { + fixupSP = INC1(fixupSP); + parenStack[fixupSP].scriptCode = newScriptCode; + } +} + +static inline bool CanMergeWithContext(Script aScript) { + return aScript <= Script::INHERITED || aScript == Script::UNKNOWN; +} + +// We regard the current char as having the same script as the in-progress run +// if either script is Common/Inherited/Unknown, or if the run script appears +// in the character's ScriptExtensions, or if the char is a cluster extender. +static inline bool SameScript(Script runScript, Script currCharScript, + uint32_t aCurrCh) { + return CanMergeWithContext(runScript) || + CanMergeWithContext(currCharScript) || currCharScript == runScript || + IsClusterExtender(aCurrCh) || HasScript(aCurrCh, runScript); +} + +gfxScriptItemizer::gfxScriptItemizer(const char16_t* src, uint32_t length) + : textPtr(src), textLength(length) { + reset(); +} + +void gfxScriptItemizer::SetText(const char16_t* src, uint32_t length) { + textPtr = src; + textLength = length; + + reset(); +} + +bool gfxScriptItemizer::Next(uint32_t& aRunStart, uint32_t& aRunLimit, + Script& aRunScript) { + /* if we've fallen off the end of the text, we're done */ + if (scriptLimit >= textLength) { + return false; + } + + SYNC_FIXUP(); + scriptCode = Script::COMMON; + Script fallbackScript = Script::UNKNOWN; + + for (scriptStart = scriptLimit; scriptLimit < textLength; scriptLimit += 1) { + uint32_t ch; + Script sc; + uint32_t startOfChar = scriptLimit; + + ch = textPtr[scriptLimit]; + + /* decode UTF-16 (may be surrogate pair) */ + if (NS_IS_HIGH_SURROGATE(ch) && scriptLimit < textLength - 1) { + uint32_t low = textPtr[scriptLimit + 1]; + if (NS_IS_LOW_SURROGATE(low)) { + ch = SURROGATE_TO_UCS4(ch, low); + scriptLimit += 1; + } + } + + // Initialize gc to UNASSIGNED; we'll only set it to the true GC + // if the character has script=COMMON, otherwise we don't care. + uint8_t gc = HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED; + + sc = 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, ch)) { + if (scriptCode == Script::COMMON) { + // If we have not yet resolved a specific scriptCode for the run, + // check whether this character provides it. + if (!CanMergeWithContext(sc)) { + // Use this character's script. + scriptCode = sc; + fixup(scriptCode); + } else if (fallbackScript == Script::UNKNOWN) { + // See if the character has a ScriptExtensions property we can + // store for use in the event the run remains unresolved. + UErrorCode error = U_ZERO_ERROR; + UScriptCode extension; + int32_t n = uscript_getScriptExtensions(ch, &extension, 1, &error); + if (error == U_BUFFER_OVERFLOW_ERROR && n > 0) { + fallbackScript = Script(extension); + } + } + } + + /* + * 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; + + if (scriptCode == Script::COMMON && fallbackScript != Script::UNKNOWN) { + aRunScript = fallbackScript; + } else { + aRunScript = scriptCode; + } + + return true; +} diff --git a/gfx/thebes/gfxScriptItemizer.h b/gfx/thebes/gfxScriptItemizer.h new file mode 100644 index 0000000000..b218f5c8d2 --- /dev/null +++ b/gfx/thebes/gfxScriptItemizer.h @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This file is based on usc_impl.c from ICU 4.2.0.1, slightly adapted + * for use within Mozilla Gecko, separate from a standard ICU build. + * + * The original ICU license of the code follows: + * + * ICU License - ICU 1.8.1 and later + * + * COPYRIGHT AND PERMISSION NOTICE + * + * Copyright (c) 1995-2009 International Business Machines Corporation and + * others + * + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, provided that the above copyright notice(s) and this + * permission notice appear in all copies of the Software and that both the + * above copyright notice(s) and this permission notice appear in supporting + * documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE + * BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, + * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, + * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + * + * Except as contained in this notice, the name of a copyright holder shall + * not be used in advertising or otherwise to promote the sale, use or other + * dealings in this Software without prior written authorization of the + * copyright holder. + * + * All trademarks and registered trademarks mentioned herein are the property + * of their respective owners. + */ + +#ifndef GFX_SCRIPTITEMIZER_H +#define GFX_SCRIPTITEMIZER_H + +#include <stdint.h> +#include "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 0000000000..b8bf461ed1 --- /dev/null +++ b/gfx/thebes/gfxSharedImageSurface.h @@ -0,0 +1,27 @@ +// vim:set ts=4 sts=2 sw=2 et cin: +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_SHARED_IMAGESURFACE_H +#define GFX_SHARED_IMAGESURFACE_H + +#include "gfxBaseSharedMemorySurface.h" + +class gfxSharedImageSurface + : public gfxBaseSharedMemorySurface<gfxImageSurface, + gfxSharedImageSurface> { + typedef gfxBaseSharedMemorySurface<gfxImageSurface, gfxSharedImageSurface> + Super; + friend class gfxBaseSharedMemorySurface<gfxImageSurface, + gfxSharedImageSurface>; + + private: + gfxSharedImageSurface(const mozilla::gfx::IntSize& aSize, long aStride, + gfxImageFormat aFormat, + const mozilla::ipc::Shmem& aShmem) + : Super(aSize, aStride, aFormat, aShmem) {} +}; + +#endif /* GFX_SHARED_IMAGESURFACE_H */ diff --git a/gfx/thebes/gfxSkipChars.cpp b/gfx/thebes/gfxSkipChars.cpp new file mode 100644 index 0000000000..cd230efe74 --- /dev/null +++ b/gfx/thebes/gfxSkipChars.cpp @@ -0,0 +1,146 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxSkipChars.h" +#include "mozilla/BinarySearch.h" +#include "mozilla/gfx/Logging.h" + +struct SkippedRangeStartComparator { + const uint32_t mOffset; + explicit SkippedRangeStartComparator(const uint32_t aOffset) + : mOffset(aOffset) {} + int operator()(const gfxSkipChars::SkippedRange& aRange) const { + return (mOffset < aRange.Start()) ? -1 : 1; + } +}; + +void gfxSkipCharsIterator::SetOriginalOffset(int32_t aOffset) { + aOffset += mOriginalStringToSkipCharsOffset; + if (MOZ_UNLIKELY(uint32_t(aOffset) > mSkipChars->mCharCount)) { + gfxCriticalError() << "invalid offset " << aOffset + << " for gfxSkipChars length " << mSkipChars->mCharCount; + aOffset = mSkipChars->mCharCount; + } + + mOriginalStringOffset = aOffset; + + const uint32_t rangeCount = mSkipChars->mRanges.Length(); + if (rangeCount == 0) { + mSkippedStringOffset = aOffset; + return; + } + + // at start of string? + if (aOffset == 0) { + mSkippedStringOffset = 0; + mCurrentRangeIndex = + rangeCount && mSkipChars->mRanges[0].Start() == 0 ? 0 : -1; + return; + } + + // find the range that includes or precedes aOffset + const nsTArray<gfxSkipChars::SkippedRange>& ranges = mSkipChars->mRanges; + size_t idx; + mozilla::BinarySearchIf(ranges, 0, rangeCount, + SkippedRangeStartComparator(aOffset), &idx); + + if (idx == rangeCount) { + mCurrentRangeIndex = rangeCount - 1; + } else if (uint32_t(aOffset) < ranges[idx].Start()) { + mCurrentRangeIndex = idx - 1; + if (mCurrentRangeIndex == -1) { + mSkippedStringOffset = aOffset; + return; + } + } else { + mCurrentRangeIndex = idx; + } + + const gfxSkipChars::SkippedRange& r = ranges[mCurrentRangeIndex]; + if (uint32_t(aOffset) < r.End()) { + mSkippedStringOffset = r.SkippedOffset(); + return; + } + + mSkippedStringOffset = aOffset - r.NextDelta(); +} + +struct SkippedRangeOffsetComparator { + const uint32_t mOffset; + explicit SkippedRangeOffsetComparator(const uint32_t aOffset) + : mOffset(aOffset) {} + int operator()(const gfxSkipChars::SkippedRange& aRange) const { + return (mOffset < aRange.SkippedOffset()) ? -1 : 1; + } +}; + +void gfxSkipCharsIterator::SetSkippedOffset(uint32_t aOffset) { + NS_ASSERTION( + (mSkipChars->mRanges.IsEmpty() && aOffset <= mSkipChars->mCharCount) || + (aOffset <= mSkipChars->LastRange().SkippedOffset() + + mSkipChars->mCharCount - + mSkipChars->LastRange().End()), + "Invalid skipped offset"); + mSkippedStringOffset = aOffset; + + uint32_t rangeCount = mSkipChars->mRanges.Length(); + if (rangeCount == 0) { + mOriginalStringOffset = aOffset; + return; + } + + const nsTArray<gfxSkipChars::SkippedRange>& ranges = mSkipChars->mRanges; + size_t idx; + mozilla::BinarySearchIf(ranges, 0, rangeCount, + SkippedRangeOffsetComparator(aOffset), &idx); + + if (idx == rangeCount) { + mCurrentRangeIndex = rangeCount - 1; + } else if (aOffset < ranges[idx].SkippedOffset()) { + mCurrentRangeIndex = idx - 1; + if (mCurrentRangeIndex == -1) { + mOriginalStringOffset = aOffset; + return; + } + } else { + mCurrentRangeIndex = idx; + } + + const gfxSkipChars::SkippedRange& r = ranges[mCurrentRangeIndex]; + mOriginalStringOffset = r.End() + aOffset - r.SkippedOffset(); +} + +bool gfxSkipCharsIterator::IsOriginalCharSkipped(int32_t* aRunLength) const { + if (mCurrentRangeIndex == -1) { + // we're before the first skipped range (if any) + if (aRunLength) { + uint32_t end = mSkipChars->mRanges.IsEmpty() + ? mSkipChars->mCharCount + : mSkipChars->mRanges[0].Start(); + *aRunLength = end - mOriginalStringOffset; + } + return mSkipChars->mCharCount == uint32_t(mOriginalStringOffset); + } + + const gfxSkipChars::SkippedRange& range = + mSkipChars->mRanges[mCurrentRangeIndex]; + + if (uint32_t(mOriginalStringOffset) < range.End()) { + if (aRunLength) { + *aRunLength = range.End() - mOriginalStringOffset; + } + return true; + } + + if (aRunLength) { + uint32_t end = + uint32_t(mCurrentRangeIndex) + 1 < mSkipChars->mRanges.Length() + ? mSkipChars->mRanges[mCurrentRangeIndex + 1].Start() + : mSkipChars->mCharCount; + *aRunLength = end - mOriginalStringOffset; + } + + return mSkipChars->mCharCount == uint32_t(mOriginalStringOffset); +} diff --git a/gfx/thebes/gfxSkipChars.h b/gfx/thebes/gfxSkipChars.h new file mode 100644 index 0000000000..4b63442d3a --- /dev/null +++ b/gfx/thebes/gfxSkipChars.h @@ -0,0 +1,252 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_SKIP_CHARS_H +#define GFX_SKIP_CHARS_H + +#include "nsTArray.h" + +/* + * gfxSkipChars is a data structure representing a list of characters that + * have been skipped. The initial string is called the "original string" + * and after skipping some characters, the result is called the "skipped + * string". gfxSkipChars provides efficient ways to translate between offsets in + * the original string and the skipped string. It is used by textrun code to + * keep track of offsets before and after text transformations such as + * whitespace compression and control code deletion. + */ + +/** + * The gfxSkipChars is represented as a sorted array of skipped ranges. + * + * A freshly-created gfxSkipChars means "all chars kept". + */ +class gfxSkipChars { + friend struct SkippedRangeStartComparator; + friend struct SkippedRangeOffsetComparator; + + private: + class SkippedRange { + public: + SkippedRange(uint32_t aOffset, uint32_t aLength, uint32_t aDelta) + : mOffset(aOffset), mLength(aLength), mDelta(aDelta) {} + + uint32_t Start() const { return mOffset; } + + uint32_t End() const { return mOffset + mLength; } + + uint32_t Length() const { return mLength; } + + uint32_t SkippedOffset() const { return mOffset - mDelta; } + + uint32_t Delta() const { return mDelta; } + + uint32_t NextDelta() const { return mDelta + mLength; } + + void Extend(uint32_t aChars) { mLength += aChars; } + + private: + uint32_t mOffset; // original-string offset at which we want to skip + uint32_t mLength; // number of skipped chars at this offset + uint32_t mDelta; // sum of lengths of preceding skipped-ranges + }; + + public: + gfxSkipChars() : mCharCount(0) {} + + void SkipChars(uint32_t aChars) { + NS_ASSERTION(mCharCount + aChars > mCharCount, "Character count overflow"); + uint32_t rangeCount = mRanges.Length(); + uint32_t delta = 0; + if (rangeCount > 0) { + SkippedRange& lastRange = mRanges[rangeCount - 1]; + if (lastRange.End() == mCharCount) { + lastRange.Extend(aChars); + mCharCount += aChars; + return; + } + delta = lastRange.NextDelta(); + } + mRanges.AppendElement(SkippedRange(mCharCount, aChars, delta)); + mCharCount += aChars; + } + + void KeepChars(uint32_t aChars) { + NS_ASSERTION(mCharCount + aChars > mCharCount, "Character count overflow"); + mCharCount += aChars; + } + + void SkipChar() { SkipChars(1); } + + void KeepChar() { KeepChars(1); } + + void TakeFrom(gfxSkipChars* aSkipChars) { + mRanges = std::move(aSkipChars->mRanges); + mCharCount = aSkipChars->mCharCount; + aSkipChars->mCharCount = 0; + } + + int32_t GetOriginalCharCount() const { return mCharCount; } + + const SkippedRange& LastRange() const { + // this is only valid if mRanges is non-empty; no assertion here + // because nsTArray will already assert if we abuse it + return mRanges[mRanges.Length() - 1]; + } + + friend class gfxSkipCharsIterator; + + private: + nsTArray<SkippedRange> mRanges; + uint32_t mCharCount; +}; + +/** + * A gfxSkipCharsIterator represents a position in the original string. It lets + * you map efficiently to and from positions in the string after skipped + * characters have been removed. You can also specify an offset that is added to + * all incoming original string offsets and subtracted from all outgoing + * original string offsets --- useful when the gfxSkipChars corresponds to + * something offset from the original DOM coordinates, which it often does for + * gfxTextRuns. + * + * The current positions (in both the original and skipped strings) are + * always constrained to be >= 0 and <= the string length. When the position + * is equal to the string length, it is at the end of the string. The current + * positions do not include any aOriginalStringToSkipCharsOffset. + * + * When the position in the original string corresponds to a skipped character, + * the skipped-characters offset is the offset of the next unskipped character, + * or the skipped-characters string length if there is no next unskipped + * character. + */ +class gfxSkipCharsIterator { + public: + /** + * @param aOriginalStringToSkipCharsOffset add this to all incoming and + * outgoing original string offsets + */ + gfxSkipCharsIterator(const gfxSkipChars& aSkipChars, + int32_t aOriginalStringToSkipCharsOffset, + int32_t aOriginalStringOffset) + : mSkipChars(&aSkipChars), + mOriginalStringOffset(0), + mSkippedStringOffset(0), + mCurrentRangeIndex(-1), + mOriginalStringToSkipCharsOffset(aOriginalStringToSkipCharsOffset) { + SetOriginalOffset(aOriginalStringOffset); + } + + explicit gfxSkipCharsIterator(const gfxSkipChars& aSkipChars, + int32_t aOriginalStringToSkipCharsOffset = 0) + : mSkipChars(&aSkipChars), + mOriginalStringOffset(0), + mSkippedStringOffset(0), + mOriginalStringToSkipCharsOffset(aOriginalStringToSkipCharsOffset) { + mCurrentRangeIndex = + mSkipChars->mRanges.IsEmpty() || mSkipChars->mRanges[0].Start() > 0 ? -1 + : 0; + } + + gfxSkipCharsIterator(const gfxSkipCharsIterator& aIterator) = default; + + /** + * The empty constructor creates an object that is useless until it is + * assigned. + */ + gfxSkipCharsIterator() + : mSkipChars(nullptr), + mOriginalStringOffset(0), + mSkippedStringOffset(0), + mCurrentRangeIndex(0), + mOriginalStringToSkipCharsOffset(0) {} + + /** + * Return true if this iterator is properly initialized and usable. + */ + bool IsInitialized() const { return mSkipChars != nullptr; } + + /** + * Set the iterator to aOriginalStringOffset in the original string. + * This can efficiently move forward or backward from the current position. + * aOriginalStringOffset is clamped to [0,originalStringLength]. + */ + void SetOriginalOffset(int32_t aOriginalStringOffset); + + /** + * Set the iterator to aSkippedStringOffset in the skipped string. + * This can efficiently move forward or backward from the current position. + * aSkippedStringOffset is clamped to [0,skippedStringLength]. + */ + void SetSkippedOffset(uint32_t aSkippedStringOffset); + + uint32_t ConvertOriginalToSkipped(int32_t aOriginalStringOffset) { + SetOriginalOffset(aOriginalStringOffset); + return GetSkippedOffset(); + } + + int32_t ConvertSkippedToOriginal(uint32_t aSkippedStringOffset) { + SetSkippedOffset(aSkippedStringOffset); + return GetOriginalOffset(); + } + + /** + * Test if the character at the current position in the original string + * is skipped or not. If aRunLength is non-null, then *aRunLength is set + * to a number of characters all of which are either skipped or not, starting + * at this character. When the current position is at the end of the original + * string, we return true and *aRunLength is set to zero. + */ + bool IsOriginalCharSkipped(int32_t* aRunLength = nullptr) const; + + void AdvanceOriginal(int32_t aDelta) { + SetOriginalOffset(GetOriginalOffset() + aDelta); + } + + void AdvanceSkipped(int32_t aDelta) { + SetSkippedOffset(GetSkippedOffset() + aDelta); + } + + /** + * @return the offset within the original string + */ + int32_t GetOriginalOffset() const { + return mOriginalStringOffset - mOriginalStringToSkipCharsOffset; + } + + /** + * @return the offset within the skipped string corresponding to the + * current position in the original string. If the current position + * in the original string is a character that is skipped, then we return + * the position corresponding to the first non-skipped character in the + * original string after the current position, or the length of the skipped + * string if there is no such character. + */ + uint32_t GetSkippedOffset() const { return mSkippedStringOffset; } + + int32_t GetOriginalEnd() const { + return mSkipChars->GetOriginalCharCount() - + mOriginalStringToSkipCharsOffset; + } + + private: + const gfxSkipChars* mSkipChars; + + // Current position + int32_t mOriginalStringOffset; + uint32_t mSkippedStringOffset; + + // Index of the last skippedRange that precedes or contains the current + // position in the original string. + // If index == -1 then we are before the first skipped char. + int32_t mCurrentRangeIndex; + + // This offset is added to map from "skipped+unskipped characters in + // the original DOM string" character space to "skipped+unskipped + // characters in the textrun's gfxSkipChars" character space + int32_t mOriginalStringToSkipCharsOffset; +}; + +#endif /*GFX_SKIP_CHARS_H*/ diff --git a/gfx/thebes/gfxTextRun.cpp b/gfx/thebes/gfxTextRun.cpp new file mode 100644 index 0000000000..6ce24bea4d --- /dev/null +++ b/gfx/thebes/gfxTextRun.cpp @@ -0,0 +1,3829 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=4 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxTextRun.h" +#include "gfxGlyphExtents.h" +#include "gfxHarfBuzzShaper.h" +#include "gfxPlatformFontList.h" +#include "gfxUserFontSet.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/Sprintf.h" + +#include "gfxContext.h" +#include "gfxFontConstants.h" +#include "gfxFontMissingGlyphs.h" +#include "gfxScriptItemizer.h" +#include "nsUnicodeProperties.h" +#include "nsStyleConsts.h" +#include "nsStyleUtil.h" +#include "mozilla/Likely.h" +#include "gfx2DGlue.h" +#include "mozilla/gfx/Logging.h" // for gfxCriticalError +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" +#include "SharedFontList-impl.h" +#include "TextDrawTarget.h" + +#include <unicode/unorm2.h> + +#ifdef XP_WIN +# include "gfxWindowsPlatform.h" +#endif + +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() { + int32_t glyphRunCount; + if (mTextRun->mHasGlyphRunArray) { + glyphRunCount = mTextRun->mGlyphRunArray.Length(); + if (mNextIndex >= glyphRunCount || mNextIndex < 0) { + return false; + } + mGlyphRun = &mTextRun->mGlyphRunArray[mNextIndex]; + } else { + if (mNextIndex != 0 || !mTextRun->mSingleGlyphRun.mFont) { + return false; + } + glyphRunCount = 1; + mGlyphRun = &mTextRun->mSingleGlyphRun; + } + + if (mGlyphRun->mCharacterOffset >= mEndOffset) { + return false; + } + + uint32_t glyphRunEndOffset = + mNextIndex + 1 < (int32_t)glyphRunCount + ? mTextRun->mGlyphRunArray[mNextIndex + 1].mCharacterOffset + : mTextRun->GetLength(); + + if (glyphRunEndOffset <= mStartOffset) { + return false; + } + + mStringEnd = std::min(mEndOffset, glyphRunEndOffset); + mStringStart = std::max(mStartOffset, mGlyphRun->mCharacterOffset); + mNextIndex += mDirection; + 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() & gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX) + return true; + uint32_t numRuns; + const gfxTextRun::GlyphRun* glyphRuns = aTextRun->GetGlyphRuns(&numRuns); + for (uint32_t i = 0; i < numRuns; ++i) { + if (glyphRuns[i].mFont->GetFontEntry()->IsUserFont()) return true; + } + return false; +} + +// Helper for textRun creation to preallocate storage for glyph records; +// this function returns a pointer to the newly-allocated glyph storage. +// Returns nullptr if allocation fails. +void* gfxTextRun::AllocateStorageForTextRun(size_t aSize, uint32_t aLength) { + // Allocate the storage we need, returning nullptr on failure rather than + // throwing an exception (because web content can create huge runs). + void* storage = malloc(aSize + aLength * sizeof(CompressedGlyph)); + if (!storage) { + NS_WARNING("failed to allocate storage for text run!"); + return nullptr; + } + + // Initialize the glyph storage (beyond aSize) to zero + memset(reinterpret_cast<char*>(storage) + aSize, 0, + aLength * sizeof(CompressedGlyph)); + + return storage; +} + +already_AddRefed<gfxTextRun> gfxTextRun::Create( + const gfxTextRunFactory::Parameters* aParams, uint32_t aLength, + gfxFontGroup* aFontGroup, gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2) { + void* storage = AllocateStorageForTextRun(sizeof(gfxTextRun), aLength); + if (!storage) { + return nullptr; + } + + RefPtr<gfxTextRun> result = + new (storage) gfxTextRun(aParams, aLength, aFontGroup, aFlags, aFlags2); + return result.forget(); +} + +gfxTextRun::gfxTextRun(const gfxTextRunFactory::Parameters* aParams, + uint32_t aLength, gfxFontGroup* aFontGroup, + gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2) + : gfxShapedText(aLength, aFlags, aParams->mAppUnitsPerDevUnit), + mSingleGlyphRun(), + mUserData(aParams->mUserData), + mFontGroup(aFontGroup), + mFlags2(aFlags2), + mReleasedFontGroup(false), + mReleasedFontGroupSkippedDrawing(false), + mHasGlyphRunArray(false), + mShapingState(eShapingState_Normal) { + NS_ASSERTION(mAppUnitsPerDevUnit > 0, "Invalid app unit scale"); + NS_ADDREF(mFontGroup); + +#ifndef RELEASE_OR_BETA + gfxTextPerfMetrics* tp = aFontGroup->GetTextPerfMetrics(); + if (tp) { + tp->current.textrunConst++; + } +#endif + + mCharacterGlyphs = reinterpret_cast<CompressedGlyph*>(this + 1); + + if (aParams->mSkipChars) { + mSkipChars.TakeFrom(aParams->mSkipChars); + } + +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + AccountStorageForTextRun(this, 1); +#endif + + mDontSkipDrawing = + !!(aFlags2 & nsTextFrameUtils::Flags::DontSkipDrawingForPendingUserFonts); +} + +gfxTextRun::~gfxTextRun() { +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + AccountStorageForTextRun(this, -1); +#endif +#ifdef DEBUG + // Make it easy to detect a dead text run + mFlags = ~gfx::ShapedTextFlags(); + mFlags2 = ~nsTextFrameUtils::Flags(); +#endif + + if (mHasGlyphRunArray) { + mGlyphRunArray.~nsTArray<GlyphRun>(); + } else { + mSingleGlyphRun.mFont = nullptr; + } + + // The cached ellipsis textrun (if any) in a fontgroup will have already + // been told to release its reference to the group, so we mustn't do that + // again here. + if (!mReleasedFontGroup) { +#ifndef RELEASE_OR_BETA + gfxTextPerfMetrics* tp = mFontGroup->GetTextPerfMetrics(); + if (tp) { + tp->current.textrunDestr++; + } +#endif + NS_RELEASE(mFontGroup); + } +} + +void gfxTextRun::ReleaseFontGroup() { + NS_ASSERTION(!mReleasedFontGroup, "doubly released!"); + + // After dropping our reference to the font group, we'll no longer be able + // to get up-to-date results for ShouldSkipDrawing(). Store the current + // value in mReleasedFontGroupSkippedDrawing. + // + // (It doesn't actually matter that we can't get up-to-date results for + // ShouldSkipDrawing(), since the only text runs that we call + // ReleaseFontGroup() for are ellipsis text runs, and we ask the font + // group for a new ellipsis text run each time we want to draw one, + // and ensure that the cached one is cleared in ClearCachedData() when + // font loading status changes.) + mReleasedFontGroupSkippedDrawing = mFontGroup->ShouldSkipDrawing(); + + NS_RELEASE(mFontGroup); + mReleasedFontGroup = true; +} + +bool gfxTextRun::SetPotentialLineBreaks(Range aRange, + const uint8_t* aBreakBefore) { + NS_ASSERTION(aRange.end <= GetLength(), "Overflow"); + + uint32_t changed = 0; + CompressedGlyph* cg = mCharacterGlyphs + aRange.start; + const CompressedGlyph* const end = cg + aRange.Length(); + while (cg < end) { + uint8_t canBreak = *aBreakBefore++; + if (canBreak && !cg->IsClusterStart()) { + // XXX If we replace the line-breaker with one based more closely + // on UAX#14 (e.g. using ICU), this may not be needed any more. + // Avoid possible breaks inside a cluster, EXCEPT when the previous + // character was a space (compare UAX#14 rules LB9, LB10). + if (cg == mCharacterGlyphs || !(cg - 1)->CharIsSpace()) { + canBreak = CompressedGlyph::FLAG_BREAK_TYPE_NONE; + } + } + changed |= cg->SetCanBreakBefore(canBreak); + ++cg; + } + return changed != 0; +} + +gfxTextRun::LigatureData gfxTextRun::ComputeLigatureData( + Range aPartRange, PropertyProvider* aProvider) const { + NS_ASSERTION(aPartRange.start < aPartRange.end, + "Computing ligature data for empty range"); + NS_ASSERTION(aPartRange.end <= GetLength(), "Character length overflow"); + + LigatureData result; + const CompressedGlyph* charGlyphs = mCharacterGlyphs; + + uint32_t i; + for (i = aPartRange.start; !charGlyphs[i].IsLigatureGroupStart(); --i) { + NS_ASSERTION(i > 0, "Ligature at the start of the run??"); + } + result.mRange.start = i; + for (i = aPartRange.start + 1; + i < GetLength() && !charGlyphs[i].IsLigatureGroupStart(); ++i) { + } + result.mRange.end = i; + + int32_t ligatureWidth = GetAdvanceForGlyphs(result.mRange); + // Count the number of started clusters we have seen + uint32_t totalClusterCount = 0; + uint32_t partClusterIndex = 0; + uint32_t partClusterCount = 0; + for (i = result.mRange.start; i < result.mRange.end; ++i) { + // Treat the first character of the ligature as the start of a + // cluster for our purposes of allocating ligature width to its + // characters. + if (i == result.mRange.start || charGlyphs[i].IsClusterStart()) { + ++totalClusterCount; + if (i < aPartRange.start) { + ++partClusterIndex; + } else if (i < aPartRange.end) { + ++partClusterCount; + } + } + } + NS_ASSERTION(totalClusterCount > 0, "Ligature involving no clusters??"); + result.mPartAdvance = partClusterIndex * (ligatureWidth / totalClusterCount); + result.mPartWidth = partClusterCount * (ligatureWidth / totalClusterCount); + + // Any rounding errors are apportioned to the final part of the ligature, + // so that measuring all parts of a ligature and summing them is equal to + // the ligature width. + if (aPartRange.end == result.mRange.end) { + gfxFloat allParts = totalClusterCount * (ligatureWidth / totalClusterCount); + result.mPartWidth += ligatureWidth - allParts; + } + + if (partClusterCount == 0) { + // nothing to draw + result.mClipBeforePart = result.mClipAfterPart = true; + } else { + // Determine whether we should clip before or after this part when + // drawing its slice of the ligature. + // We need to clip before the part if any cluster is drawn before + // this part. + result.mClipBeforePart = partClusterIndex > 0; + // We need to clip after the part if any cluster is drawn after + // this part. + result.mClipAfterPart = + partClusterIndex + partClusterCount < totalClusterCount; + } + + if (aProvider && (mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING)) { + gfxFont::Spacing spacing; + if (aPartRange.start == result.mRange.start) { + aProvider->GetSpacing(Range(aPartRange.start, aPartRange.start + 1), + &spacing); + result.mPartWidth += spacing.mBefore; + } + if (aPartRange.end == result.mRange.end) { + aProvider->GetSpacing(Range(aPartRange.end - 1, aPartRange.end), + &spacing); + result.mPartWidth += spacing.mAfter; + } + } + + return result; +} + +gfxFloat gfxTextRun::ComputePartialLigatureWidth( + Range aPartRange, PropertyProvider* aProvider) const { + if (aPartRange.start >= aPartRange.end) return 0; + LigatureData data = ComputeLigatureData(aPartRange, aProvider); + return data.mPartWidth; +} + +int32_t gfxTextRun::GetAdvanceForGlyphs(Range aRange) const { + int32_t advance = 0; + for (auto i = aRange.start; i < aRange.end; ++i) { + advance += GetAdvanceForGlyph(i); + } + return advance; +} + +static void GetAdjustedSpacing( + const gfxTextRun* aTextRun, gfxTextRun::Range aRange, + gfxTextRun::PropertyProvider* aProvider, + gfxTextRun::PropertyProvider::Spacing* aSpacing) { + if (aRange.start >= aRange.end) return; + + aProvider->GetSpacing(aRange, aSpacing); + +#ifdef DEBUG + // Check to see if we have spacing inside ligatures + + const gfxTextRun::CompressedGlyph* charGlyphs = + aTextRun->GetCharacterGlyphs(); + uint32_t i; + + for (i = aRange.start; i < aRange.end; ++i) { + if (!charGlyphs[i].IsLigatureGroupStart()) { + NS_ASSERTION(i == aRange.start || aSpacing[i - aRange.start].mBefore == 0, + "Before-spacing inside a ligature!"); + NS_ASSERTION( + i - 1 <= aRange.start || aSpacing[i - 1 - aRange.start].mAfter == 0, + "After-spacing inside a ligature!"); + } + } +#endif +} + +bool gfxTextRun::GetAdjustedSpacingArray( + Range aRange, PropertyProvider* aProvider, Range aSpacingRange, + nsTArray<PropertyProvider::Spacing>* aSpacing) const { + if (!aProvider || !(mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING)) { + return false; + } + if (!aSpacing->AppendElements(aRange.Length(), fallible)) { + return false; + } + auto spacingOffset = aSpacingRange.start - aRange.start; + memset(aSpacing->Elements(), 0, sizeof(gfxFont::Spacing) * spacingOffset); + GetAdjustedSpacing(this, aSpacingRange, aProvider, + aSpacing->Elements() + spacingOffset); + memset(aSpacing->Elements() + 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, gfx::Point* aPt, + PropertyProvider* aProvider, Range aSpacingRange, + TextRunDrawParams& aParams, + gfx::ShapedTextFlags aOrientation) const { + AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer; + bool haveSpacing = + GetAdjustedSpacingArray(aRange, aProvider, aSpacingRange, &spacingBuffer); + aParams.spacing = haveSpacing ? spacingBuffer.Elements() : nullptr; + aFont->Draw(this, aRange.start, aRange.end, aPt, aParams, aOrientation); +} + +static void ClipPartialLigature(const gfxTextRun* aTextRun, gfxFloat* aStart, + gfxFloat* aEnd, gfxFloat aOrigin, + gfxTextRun::LigatureData* aLigature) { + if (aLigature->mClipBeforePart) { + if (aTextRun->IsRightToLeft()) { + *aEnd = std::min(*aEnd, aOrigin); + } else { + *aStart = std::max(*aStart, aOrigin); + } + } + if (aLigature->mClipAfterPart) { + gfxFloat endEdge = + aOrigin + aTextRun->GetDirection() * aLigature->mPartWidth; + if (aTextRun->IsRightToLeft()) { + *aStart = std::max(*aStart, endEdge); + } else { + *aEnd = std::min(*aEnd, endEdge); + } + } +} + +void gfxTextRun::DrawPartialLigature(gfxFont* aFont, Range aRange, + gfx::Point* aPt, + PropertyProvider* aProvider, + TextRunDrawParams& aParams, + gfx::ShapedTextFlags aOrientation) const { + if (aRange.start >= aRange.end) { + return; + } + + // Draw partial ligature. We hack this by clipping the ligature. + LigatureData data = ComputeLigatureData(aRange, aProvider); + gfxRect clipExtents = aParams.context->GetClipExtents(); + gfxFloat start, end; + if (aParams.isVerticalRun) { + start = clipExtents.Y() * mAppUnitsPerDevUnit; + end = clipExtents.YMost() * mAppUnitsPerDevUnit; + ClipPartialLigature(this, &start, &end, aPt->y, &data); + } else { + start = clipExtents.X() * mAppUnitsPerDevUnit; + end = clipExtents.XMost() * mAppUnitsPerDevUnit; + ClipPartialLigature(this, &start, &end, aPt->x, &data); + } + + { + // 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->Clip(clipRect); + } + + gfx::Point pt; + if (aParams.isVerticalRun) { + pt = Point(aPt->x, aPt->y - aParams.direction * data.mPartAdvance); + } else { + pt = Point(aPt->x - aParams.direction * data.mPartAdvance, aPt->y); + } + + DrawGlyphs(aFont, data.mRange, &pt, aProvider, aRange, aParams, aOrientation); + aParams.context->PopClip(); + + if (aParams.isVerticalRun) { + aPt->y += aParams.direction * data.mPartWidth; + } else { + aPt->x += aParams.direction * data.mPartWidth; + } +} + +// Returns true if the font has synthetic bolding enabled, +// or is a color font (COLR/SVG/sbix/CBDT), false otherwise. This is used to +// check whether the text run needs to be explicitly composited in order to +// support opacity. +static bool HasSyntheticBoldOrColor(gfxFont* aFont) { + if (aFont->IsSyntheticBold()) { + return true; + } + gfxFontEntry* fe = aFont->GetFontEntry(); + if (fe->TryGetSVGData(aFont) || fe->TryGetColorGlyphs()) { + return true; + } +#if defined(XP_MACOSX) // sbix fonts only supported via Core Text + if (fe->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'))) { + return true; + } +#endif + return false; +} + +// helper class for double-buffering drawing with non-opaque color +struct MOZ_STACK_CLASS BufferAlphaColor { + explicit BufferAlphaColor(gfxContext* aContext) : mContext(aContext) {} + + ~BufferAlphaColor() = default; + + void PushSolidColor(const gfxRect& aBounds, const DeviceColor& aAlphaColor, + uint32_t appsPerDevUnit) { + mContext->Save(); + mContext->SnappedClip(gfxRect( + aBounds.X() / appsPerDevUnit, aBounds.Y() / appsPerDevUnit, + aBounds.Width() / appsPerDevUnit, aBounds.Height() / appsPerDevUnit)); + mContext->SetDeviceColor( + DeviceColor(aAlphaColor.r, aAlphaColor.g, aAlphaColor.b)); + mContext->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, aAlphaColor.a); + } + + void PopAlpha() { + // pop the text, using the color alpha as the opacity + mContext->PopGroupAndBlend(); + mContext->Restore(); + } + + gfxContext* mContext; +}; + +void gfxTextRun::Draw(const Range aRange, const gfx::Point aPt, + const DrawParams& aParams) const { + NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range"); + NS_ASSERTION(aParams.drawMode == DrawMode::GLYPH_PATH || + !(aParams.drawMode & DrawMode::GLYPH_PATH), + "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or " + "GLYPH_STROKE_UNDERNEATH"); + NS_ASSERTION(aParams.drawMode == DrawMode::GLYPH_PATH || !aParams.callbacks, + "callback must not be specified unless using GLYPH_PATH"); + + bool skipDrawing = + !mDontSkipDrawing && (mFontGroup ? mFontGroup->ShouldSkipDrawing() + : mReleasedFontGroupSkippedDrawing); + if (aParams.drawMode & DrawMode::GLYPH_FILL) { + DeviceColor currentColor; + if (aParams.context->GetDeviceColor(currentColor) && currentColor.a == 0 && + !aParams.context->GetTextDrawer()) { + skipDrawing = true; + } + } + + gfxFloat direction = GetDirection(); + + if (skipDrawing) { + // We don't need to draw anything; + // but if the caller wants advance width, we need to compute it here + if (aParams.advanceWidth) { + gfxTextRun::Metrics metrics = + MeasureText(aRange, gfxFont::LOOSE_INK_EXTENTS, + aParams.context->GetDrawTarget(), aParams.provider); + *aParams.advanceWidth = metrics.mAdvanceWidth * direction; + } + + // return without drawing + return; + } + + // synthetic bolding draws glyphs twice ==> colors with opacity won't draw + // correctly unless first drawn without alpha + BufferAlphaColor syntheticBoldBuffer(aParams.context); + DeviceColor currentColor; + bool mayNeedBuffering = + aParams.drawMode & DrawMode::GLYPH_FILL && + aParams.context->HasNonOpaqueNonTransparentColor(currentColor) && + !aParams.context->GetTextDrawer(); + + // If we need to double-buffer, we'll need to measure the text first to + // get the bounds of the area of interest. Ideally we'd do that just for + // the specific glyph run(s) that need buffering, but because of bug + // 1612610 we currently use the extent of the entire range even when + // just buffering a subrange. So we'll measure the full range once and + // keep the metrics on hand for any subsequent subranges. + gfxTextRun::Metrics metrics; + bool gotMetrics = false; + + // Set up parameters that will be constant across all glyph runs we need + // to draw, regardless of the font used. + TextRunDrawParams params; + params.context = aParams.context; + params.devPerApp = 1.0 / double(GetAppUnitsPerDevUnit()); + params.isVerticalRun = IsVertical(); + params.isRTL = IsRightToLeft(); + params.direction = direction; + params.strokeOpts = aParams.strokeOpts; + params.textStrokeColor = aParams.textStrokeColor; + params.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(); + + GlyphRunIterator iter(this, aRange); + gfxFloat advance = 0.0; + gfx::Point pt = aPt; + + while (iter.NextRun()) { + gfxFont* font = iter.GetGlyphRun()->mFont; + Range runRange(iter.GetStringStart(), iter.GetStringEnd()); + + bool needToRestore = false; + if (mayNeedBuffering && HasSyntheticBoldOrColor(font)) { + needToRestore = true; + if (!gotMetrics) { + // Measure text; use the bounding box to determine the area we need + // to buffer. We measure the entire range, rather than just the glyph + // run that we're actually handling, because of bug 1612610: if the + // bounding box passed to PushSolidColor does not intersect the + // drawTarget's current clip, the skia backend fails to clip properly. + // This means we may use a larger buffer than actually needed, but is + // otherwise harmless. + metrics = + MeasureText(aRange, gfxFont::LOOSE_INK_EXTENTS, + aParams.context->GetDrawTarget(), aParams.provider); + if (IsRightToLeft()) { + metrics.mBoundingBox.MoveBy( + gfxPoint(aPt.x - metrics.mAdvanceWidth, aPt.y)); + } else { + metrics.mBoundingBox.MoveBy(gfxPoint(aPt.x, aPt.y)); + } + gotMetrics = true; + } + syntheticBoldBuffer.PushSolidColor(metrics.mBoundingBox, currentColor, + GetAppUnitsPerDevUnit()); + } + + Range ligatureRange(runRange); + ShrinkToLigatureBoundaries(&ligatureRange); + + bool drawPartial = + (aParams.drawMode & (DrawMode::GLYPH_FILL | DrawMode::GLYPH_STROKE)) || + (aParams.drawMode == DrawMode::GLYPH_PATH && aParams.callbacks); + gfx::Point origPt = pt; + + if (drawPartial) { + DrawPartialLigature(font, Range(runRange.start, ligatureRange.start), &pt, + aParams.provider, params, + iter.GetGlyphRun()->mOrientation); + } + + DrawGlyphs(font, ligatureRange, &pt, aParams.provider, ligatureRange, + params, iter.GetGlyphRun()->mOrientation); + + if (drawPartial) { + DrawPartialLigature(font, Range(ligatureRange.end, runRange.end), &pt, + aParams.provider, params, + iter.GetGlyphRun()->mOrientation); + } + + if (params.isVerticalRun) { + advance += (pt.y - origPt.y) * params.direction; + } else { + advance += (pt.x - origPt.x) * params.direction; + } + + // composite result when synthetic bolding used + if (needToRestore) { + syntheticBoldBuffer.PopAlpha(); + } + } + + if (aParams.advanceWidth) { + *aParams.advanceWidth = advance; + } +} + +// This method is mostly parallel to Draw(). +void gfxTextRun::DrawEmphasisMarks(gfxContext* aContext, gfxTextRun* aMark, + gfxFloat aMarkAdvance, gfx::Point aPt, + Range aRange, + PropertyProvider* aProvider) const { + MOZ_ASSERT(aRange.end <= GetLength()); + + EmphasisMarkDrawParams params; + params.context = aContext; + params.mark = aMark; + params.advance = aMarkAdvance; + params.direction = GetDirection(); + params.isVertical = IsVertical(); + + float& inlineCoord = params.isVertical ? aPt.y : aPt.x; + float direction = params.direction; + + GlyphRunIterator iter(this, aRange); + while (iter.NextRun()) { + gfxFont* font = iter.GetGlyphRun()->mFont; + uint32_t start = iter.GetStringStart(); + uint32_t end = iter.GetStringEnd(); + Range ligatureRange(start, end); + ShrinkToLigatureBoundaries(&ligatureRange); + + inlineCoord += + direction * ComputePartialLigatureWidth( + Range(start, ligatureRange.start), aProvider); + + AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer; + bool haveSpacing = GetAdjustedSpacingArray(ligatureRange, aProvider, + ligatureRange, &spacingBuffer); + params.spacing = haveSpacing ? spacingBuffer.Elements() : nullptr; + font->DrawEmphasisMarks(this, &aPt, ligatureRange.start, + ligatureRange.Length(), params); + + inlineCoord += direction * ComputePartialLigatureWidth( + Range(ligatureRange.end, end), aProvider); + } +} + +void gfxTextRun::AccumulateMetricsForRun( + gfxFont* aFont, Range aRange, gfxFont::BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, PropertyProvider* aProvider, + Range aSpacingRange, gfx::ShapedTextFlags aOrientation, + Metrics* aMetrics) const { + AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer; + bool haveSpacing = + GetAdjustedSpacingArray(aRange, aProvider, aSpacingRange, &spacingBuffer); + Metrics metrics = aFont->Measure( + this, aRange.start, aRange.end, aBoundingBoxType, aRefDrawTarget, + haveSpacing ? spacingBuffer.Elements() : nullptr, aOrientation); + aMetrics->CombineWith(metrics, IsRightToLeft()); +} + +void gfxTextRun::AccumulatePartialLigatureMetrics( + gfxFont* aFont, Range aRange, gfxFont::BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, PropertyProvider* aProvider, + gfx::ShapedTextFlags aOrientation, Metrics* aMetrics) const { + if (aRange.start >= aRange.end) return; + + // Measure partial ligature. We hack this by clipping the metrics in the + // same way we clip the drawing. + LigatureData data = ComputeLigatureData(aRange, aProvider); + + // First measure the complete ligature + Metrics metrics; + AccumulateMetricsForRun(aFont, data.mRange, aBoundingBoxType, aRefDrawTarget, + aProvider, aRange, aOrientation, &metrics); + + // Clip the bounding box to the ligature part + gfxFloat bboxLeft = metrics.mBoundingBox.X(); + gfxFloat bboxRight = metrics.mBoundingBox.XMost(); + // Where we are going to start "drawing" relative to our left baseline origin + gfxFloat origin = + IsRightToLeft() ? metrics.mAdvanceWidth - data.mPartAdvance : 0; + ClipPartialLigature(this, &bboxLeft, &bboxRight, origin, &data); + metrics.mBoundingBox.SetBoxX(bboxLeft, bboxRight); + + // mBoundingBox is now relative to the left baseline origin for the entire + // ligature. Shift it left. + metrics.mBoundingBox.MoveByX( + -(IsRightToLeft() + ? metrics.mAdvanceWidth - (data.mPartAdvance + data.mPartWidth) + : data.mPartAdvance)); + metrics.mAdvanceWidth = data.mPartWidth; + + aMetrics->CombineWith(metrics, IsRightToLeft()); +} + +gfxTextRun::Metrics gfxTextRun::MeasureText( + Range aRange, gfxFont::BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, 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 + +void gfxTextRun::ClassifyAutoHyphenations(uint32_t aStart, Range aRange, + nsTArray<HyphenType>& aHyphenBuffer, + HyphenationState* aWordState) { + MOZ_ASSERT( + aRange.end - aStart <= aHyphenBuffer.Length() && aRange.start >= aStart, + "Range out of bounds"); + MOZ_ASSERT(aWordState->mostRecentBoundary >= aStart, + "Unexpected aMostRecentWordBoundary!!"); + + uint32_t start = + std::min<uint32_t>(aRange.start, aWordState->mostRecentBoundary); + + for (uint32_t i = start; i < aRange.end; ++i) { + if (aHyphenBuffer[i - aStart] == HyphenType::Explicit && + !aWordState->hasExplicitHyphen) { + aWordState->hasExplicitHyphen = true; + } + if (!aWordState->hasManualHyphen && + (aHyphenBuffer[i - aStart] == HyphenType::Soft || + aHyphenBuffer[i - aStart] == HyphenType::Explicit)) { + aWordState->hasManualHyphen = true; + // This is the first manual hyphen in the current word. We can only + // know if the current word has a manual hyphen until now. So, we need + // to run a sub loop to update the auto hyphens between the start of + // the current word and this manual hyphen. + if (aWordState->hasAutoHyphen) { + for (uint32_t j = aWordState->mostRecentBoundary; j < i; j++) { + if (aHyphenBuffer[j - aStart] == + HyphenType::AutoWithoutManualInSameWord) { + aHyphenBuffer[j - aStart] = HyphenType::AutoWithManualInSameWord; + } + } + } + } + if (aHyphenBuffer[i - aStart] == HyphenType::AutoWithoutManualInSameWord) { + if (!aWordState->hasAutoHyphen) { + aWordState->hasAutoHyphen = true; + } + if (aWordState->hasManualHyphen) { + aHyphenBuffer[i - aStart] = HyphenType::AutoWithManualInSameWord; + } + } + + // If we're at the word boundary, clear/reset couple states. + if (mCharacterGlyphs[i].CharIsSpace() || mCharacterGlyphs[i].CharIsTab() || + mCharacterGlyphs[i].CharIsNewline() || + // Since we will not have a boundary in the end of the string, let's + // call the end of the string a special case for word boundary. + i == GetLength() - 1) { + // We can only get to know whether we should raise/clear an explicit + // manual hyphen until we get to the end of a word, because this depends + // on whether there exists at least one auto hyphen in the same word. + if (!aWordState->hasAutoHyphen && aWordState->hasExplicitHyphen) { + for (uint32_t j = aWordState->mostRecentBoundary; j <= i; j++) { + if (aHyphenBuffer[j - aStart] == HyphenType::Explicit) { + aHyphenBuffer[j - aStart] = HyphenType::None; + } + } + } + aWordState->mostRecentBoundary = i; + aWordState->hasManualHyphen = false; + aWordState->hasAutoHyphen = false; + aWordState->hasExplicitHyphen = false; + } + } +} + +uint32_t gfxTextRun::BreakAndMeasureText( + uint32_t aStart, uint32_t aMaxLength, bool aLineBreakBefore, + gfxFloat aWidth, PropertyProvider* aProvider, SuppressBreak aSuppressBreak, + gfxFloat* aTrimWhitespace, bool aWhitespaceCanHang, Metrics* aMetrics, + gfxFont::BoundingBoxType aBoundingBoxType, DrawTarget* aRefDrawTarget, + bool* aUsedHyphenation, uint32_t* aLastBreak, bool aCanWordWrap, + bool aCanWhitespaceWrap, gfxBreakPriority* aBreakPriority) { + aMaxLength = std::min(aMaxLength, GetLength() - aStart); + + NS_ASSERTION(aStart + aMaxLength <= GetLength(), "Substring out of range"); + + Range bufferRange( + aStart, aStart + std::min<uint32_t>(aMaxLength, MEASUREMENT_BUFFER_SIZE)); + PropertyProvider::Spacing spacingBuffer[MEASUREMENT_BUFFER_SIZE]; + bool haveSpacing = + aProvider && !!(mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING); + if (haveSpacing) { + GetAdjustedSpacing(this, bufferRange, aProvider, spacingBuffer); + } + AutoTArray<HyphenType, 4096> hyphenBuffer; + HyphenationState wordState; + wordState.mostRecentBoundary = aStart; + bool haveHyphenation = + aProvider && + (aProvider->GetHyphensOption() == StyleHyphens::Auto || + (aProvider->GetHyphensOption() == StyleHyphens::Manual && + !!(mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS))); + if (haveHyphenation) { + if (hyphenBuffer.AppendElements(bufferRange.Length(), fallible)) { + aProvider->GetHyphenationBreaks(bufferRange, hyphenBuffer.Elements()); + if (aProvider->GetHyphensOption() == StyleHyphens::Auto) { + ClassifyAutoHyphenations(aStart, bufferRange, hyphenBuffer, &wordState); + } + } else { + haveHyphenation = false; + } + } + + gfxFloat width = 0; + gfxFloat advance = 0; + // The number of space characters that can be trimmed or hang at a soft-wrap + uint32_t trimmableChars = 0; + // The amount of space removed by ignoring trimmableChars + gfxFloat trimmableAdvance = 0; + int32_t lastBreak = -1; + int32_t lastBreakTrimmableChars = -1; + gfxFloat lastBreakTrimmableAdvance = -1; + // Cache the last candidate break + int32_t lastCandidateBreak = -1; + int32_t lastCandidateBreakTrimmableChars = -1; + gfxFloat lastCandidateBreakTrimmableAdvance = -1; + bool lastCandidateBreakUsedHyphenation = false; + gfxBreakPriority lastCandidateBreakPriority = gfxBreakPriority::eNoBreak; + bool aborted = false; + uint32_t end = aStart + aMaxLength; + bool lastBreakUsedHyphenation = false; + Range ligatureRange(aStart, end); + ShrinkToLigatureBoundaries(&ligatureRange); + + // We may need to move `i` backwards in the following loop, and re-scan + // part of the textrun; we'll use `rescanLimit` so we can tell when that + // is happening: if `i < rescanLimit` then we're rescanning. + uint32_t rescanLimit = aStart; + for (uint32_t i = aStart; i < end; ++i) { + if (i >= bufferRange.end) { + // Fetch more spacing and hyphenation data + uint32_t oldHyphenBufferLength = hyphenBuffer.Length(); + bufferRange.start = i; + bufferRange.end = + std::min(aStart + aMaxLength, i + MEASUREMENT_BUFFER_SIZE); + // For spacing, we always overwrite the old data with the newly + // fetched one. However, for hyphenation, hyphenation data sometimes + // depends on the context in every word (if "hyphens: auto" is set). + // To ensure we get enough information between neighboring buffers, + // we grow the hyphenBuffer instead of overwrite it. + // NOTE that this means bufferRange does not correspond to the + // entire hyphenBuffer, but only to the most recently added portion. + // Therefore, we need to add the old length to hyphenBuffer.Elements() + // when getting more data. + if (haveSpacing) { + GetAdjustedSpacing(this, bufferRange, aProvider, spacingBuffer); + } + if (haveHyphenation) { + if (hyphenBuffer.AppendElements(bufferRange.Length(), fallible)) { + aProvider->GetHyphenationBreaks( + bufferRange, hyphenBuffer.Elements() + oldHyphenBufferLength); + if (aProvider->GetHyphensOption() == StyleHyphens::Auto) { + uint32_t prevMostRecentWordBoundary = wordState.mostRecentBoundary; + ClassifyAutoHyphenations(aStart, bufferRange, hyphenBuffer, + &wordState); + // If the buffer boundary is in the middle of a word, + // we need to go back to the start of the current word. + // So, we can correct the wrong candidates that we set + // in the previous runs of the loop. + if (prevMostRecentWordBoundary < oldHyphenBufferLength) { + rescanLimit = i; + i = prevMostRecentWordBoundary - 1; + continue; + } + } + } else { + haveHyphenation = false; + } + } + } + + // There can't be a word-wrap break opportunity at the beginning of the + // line: if the width is too small for even one character to fit, it + // could be the first and last break opportunity on the line, and that + // would trigger an infinite loop. + if (aSuppressBreak != eSuppressAllBreaks && + (aSuppressBreak != eSuppressInitialBreak || i > aStart)) { + bool atNaturalBreak = mCharacterGlyphs[i].CanBreakBefore() == 1; + // atHyphenationBreak indicates we're at a "soft" hyphen, where an extra + // hyphen glyph will need to be painted. It is NOT set for breaks at an + // explicit hyphen present in the text. + bool atHyphenationBreak = !atNaturalBreak && haveHyphenation && + hyphenBuffer[i - aStart] > HyphenType::Explicit; + bool atAutoHyphenWithManualHyphenInSameWord = + atHyphenationBreak && + hyphenBuffer[i - aStart] == HyphenType::AutoWithManualInSameWord; + bool atBreak = atNaturalBreak || atHyphenationBreak; + bool wordWrapping = aCanWordWrap && + mCharacterGlyphs[i].IsClusterStart() && + *aBreakPriority <= gfxBreakPriority::eWordWrapBreak; + + bool whitespaceWrapping = false; + if (i > aStart) { + // The spec says the breaking opportunity is *after* whitespace. + auto const& g = mCharacterGlyphs[i - 1]; + whitespaceWrapping = + aCanWhitespaceWrap && + (g.CharIsSpace() || g.CharIsTab() || g.CharIsNewline()); + } + + if (atBreak || wordWrapping || whitespaceWrapping) { + gfxFloat hyphenatedAdvance = advance; + if (atHyphenationBreak) { + hyphenatedAdvance += aProvider->GetHyphenWidth(); + } + + if (lastBreak < 0 || + width + hyphenatedAdvance - trimmableAdvance <= aWidth) { + // We can break here. + lastBreak = i; + lastBreakTrimmableChars = trimmableChars; + lastBreakTrimmableAdvance = trimmableAdvance; + lastBreakUsedHyphenation = atHyphenationBreak; + *aBreakPriority = (atBreak || whitespaceWrapping) + ? gfxBreakPriority::eNormalBreak + : gfxBreakPriority::eWordWrapBreak; + } + + width += advance; + advance = 0; + if (width - trimmableAdvance > aWidth) { + // No more text fits. Abort + aborted = true; + break; + } + // There are various kinds of break opportunities: + // 1. word wrap break, + // 2. natural break, + // 3. manual hyphenation break, + // 4. auto hyphenation break without any manual hyphenation + // in the same word, + // 5. auto hyphenation break with another manual hyphenation + // in the same word. + // Allow all of them except the last one to be a candidate. + // So, we can ensure that we don't use an automatic + // hyphenation opportunity within a word that contains another + // manual hyphenation, unless it is the only choice. + if (wordWrapping || !atAutoHyphenWithManualHyphenInSameWord) { + lastCandidateBreak = lastBreak; + lastCandidateBreakTrimmableChars = lastBreakTrimmableChars; + lastCandidateBreakTrimmableAdvance = lastBreakTrimmableAdvance; + lastCandidateBreakUsedHyphenation = lastBreakUsedHyphenation; + lastCandidateBreakPriority = *aBreakPriority; + } + } + } + + // If we're re-scanning part of a word (to re-process potential + // hyphenation types) then we don't want to accumulate widths again + // for the characters that were already added to `advance`. + if (i < rescanLimit) { + continue; + } + + gfxFloat charAdvance; + if (i >= ligatureRange.start && i < ligatureRange.end) { + charAdvance = GetAdvanceForGlyphs(Range(i, i + 1)); + if (haveSpacing) { + PropertyProvider::Spacing* space = + &spacingBuffer[i - bufferRange.start]; + charAdvance += space->mBefore + space->mAfter; + } + } else { + charAdvance = ComputePartialLigatureWidth(Range(i, i + 1), aProvider); + } + + advance += charAdvance; + if (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) { + if (lastCandidateBreak >= 0 && lastCandidateBreak != lastBreak) { + lastBreak = lastCandidateBreak; + lastBreakTrimmableChars = lastCandidateBreakTrimmableChars; + lastBreakTrimmableAdvance = lastCandidateBreakTrimmableAdvance; + lastBreakUsedHyphenation = lastCandidateBreakUsedHyphenation; + *aBreakPriority = lastCandidateBreakPriority; + } + 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 & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING)) { + uint32_t i; + AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer; + if (spacingBuffer.AppendElements(aRange.Length(), fallible)) { + GetAdjustedSpacing(this, ligatureRange, aProvider, + spacingBuffer.Elements()); + for (i = 0; i < ligatureRange.Length(); ++i) { + PropertyProvider::Spacing* space = &spacingBuffer[i]; + result += space->mBefore + space->mAfter; + } + if (aSpacing) { + aSpacing->mBefore = spacingBuffer[0].mBefore; + aSpacing->mAfter = spacingBuffer.LastElement().mAfter; + } + } + } + + return result + GetAdvanceForGlyphs(ligatureRange); +} + +gfxFloat gfxTextRun::GetMinAdvanceWidth(Range aRange) { + MOZ_ASSERT(aRange.end <= GetLength(), "Substring out of range"); + + Range ligatureRange = aRange; + ShrinkToLigatureBoundaries(&ligatureRange); + + gfxFloat result = + std::max(ComputePartialLigatureWidth( + Range(aRange.start, ligatureRange.start), nullptr), + ComputePartialLigatureWidth(Range(ligatureRange.end, aRange.end), + nullptr)); + + // XXX Do we need to take spacing into account? When each grapheme cluster + // takes its own line, we shouldn't be adding spacings around them. + gfxFloat clusterAdvance = 0; + for (uint32_t i = ligatureRange.start; i < ligatureRange.end; ++i) { + clusterAdvance += GetAdvanceForGlyph(i); + if (i + 1 == ligatureRange.end || IsClusterStart(i + 1)) { + result = std::max(result, clusterAdvance); + clusterAdvance = 0; + } + } + + return result; +} + +bool gfxTextRun::SetLineBreaks(Range aRange, bool aLineBreakBefore, + bool aLineBreakAfter, + gfxFloat* aAdvanceWidthDelta) { + // Do nothing because our shaping does not currently take linebreaks into + // account. There is no change in advance width. + if (aAdvanceWidthDelta) { + *aAdvanceWidthDelta = 0; + } + return false; +} + +uint32_t gfxTextRun::FindFirstGlyphRunContaining(uint32_t aOffset) const { + NS_ASSERTION(aOffset <= GetLength(), "Bad offset looking for glyphrun"); + NS_ASSERTION(GetLength() == 0 || + (!mHasGlyphRunArray && mSingleGlyphRun.mFont) || + (mHasGlyphRunArray && mGlyphRunArray.Length() > 0), + "non-empty text but no glyph runs present!"); + if (!mHasGlyphRunArray) { + return 0; + } + if (aOffset == GetLength()) { + return mGlyphRunArray.Length(); + } + uint32_t start = 0; + uint32_t end = mGlyphRunArray.Length(); + while (end - start > 1) { + uint32_t mid = (start + end) / 2; + if (mGlyphRunArray[mid].mCharacterOffset <= aOffset) { + start = mid; + } else { + end = mid; + } + } + NS_ASSERTION(mGlyphRunArray[start].mCharacterOffset <= aOffset, + "Hmm, something went wrong, aOffset should have been found"); + return start; +} + +void gfxTextRun::AddGlyphRun(gfxFont* aFont, FontMatchType aMatchType, + uint32_t aUTF16Offset, bool aForceNewRun, + gfx::ShapedTextFlags aOrientation, bool aIsCJK) { + NS_ASSERTION(aFont, "adding glyph run for null font!"); + NS_ASSERTION(aOrientation != gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED, + "mixed orientation should have been resolved"); + if (!aFont) { + return; + } + if (!mHasGlyphRunArray) { + // We don't currently have an array. + if (!mSingleGlyphRun.mFont) { + // This is the first glyph run: just store it directly. + mSingleGlyphRun.SetProperties(aFont, aOrientation, aIsCJK, aMatchType); + mSingleGlyphRun.mCharacterOffset = aUTF16Offset; + return; + } + } + uint32_t numGlyphRuns = mHasGlyphRunArray ? mGlyphRunArray.Length() : 1; + if (!aForceNewRun && numGlyphRuns > 0) { + GlyphRun* lastGlyphRun = mHasGlyphRunArray + ? &mGlyphRunArray[numGlyphRuns - 1] + : &mSingleGlyphRun; + + 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->Matches(aFont, aOrientation, aIsCJK, aMatchType)) { + return; + } + + // If the offset has not changed, avoid leaving a zero-length run + // by overwriting the last entry instead of appending... + if (lastGlyphRun->mCharacterOffset == aUTF16Offset) { + // ...except that if the run before the last entry had the same + // font as the new one wants, merge with it instead of creating + // adjacent runs with the same font + if (numGlyphRuns > 1 && mGlyphRunArray[numGlyphRuns - 2].Matches( + aFont, aOrientation, aIsCJK, aMatchType)) { + mGlyphRunArray.TruncateLength(numGlyphRuns - 1); + if (mGlyphRunArray.Length() == 1) { + ConvertFromGlyphRunArray(); + } + return; + } + + lastGlyphRun->SetProperties(aFont, aOrientation, aIsCJK, aMatchType); + return; + } + } + + NS_ASSERTION( + aForceNewRun || numGlyphRuns > 0 || aUTF16Offset == 0, + "First run doesn't cover the first character (and run not forced)?"); + + if (!mHasGlyphRunArray) { + ConvertToGlyphRunArray(); + } + + GlyphRun* glyphRun = mGlyphRunArray.AppendElement(); + glyphRun->SetProperties(aFont, aOrientation, aIsCJK, aMatchType); + glyphRun->mCharacterOffset = aUTF16Offset; +} + +void gfxTextRun::SortGlyphRuns() { + if (!mHasGlyphRunArray) { + return; + } + + // We should never have an empty or one-element array here; if there's only + // one glyphrun, it should be stored directly in the textrun without using + // an array at all. + MOZ_ASSERT(mGlyphRunArray.Length() > 1); + + AutoTArray<GlyphRun, 16> runs(std::move(mGlyphRunArray)); + GlyphRunOffsetComparator comp; + runs.Sort(comp); + + // Now copy back, coalescing adjacent glyph runs that have the same + // properties. + mGlyphRunArray.Clear(); + GlyphRun* prevRun = nullptr; + for (auto& run : runs) { + // A GlyphRun with the same font and orientation as the previous can + // just be skipped; the last GlyphRun will cover its character range. + MOZ_ASSERT(run.mFont != nullptr); + if (!prevRun || !prevRun->Matches(run.mFont, run.mOrientation, run.mIsCJK, + run.mMatchType)) { + // If two font runs have the same character offset, Sort() will have + // randomized their order! + MOZ_ASSERT(prevRun == nullptr || + prevRun->mCharacterOffset < run.mCharacterOffset, + "Two fonts for the same run, glyph indices unreliable"); + prevRun = mGlyphRunArray.AppendElement(std::move(run)); + } + } + + MOZ_ASSERT(mGlyphRunArray.Length() > 0); + if (mGlyphRunArray.Length() == 1) { + ConvertFromGlyphRunArray(); + } +} + +// 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 (!mHasGlyphRunArray) { + return; + } + + MOZ_ASSERT(mGlyphRunArray.Length() > 1); + + // 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 = mGlyphRunArray.Length() - 1; + const CompressedGlyph* charGlyphs = mCharacterGlyphs; + for (i = lastRunIndex; i >= 0; --i) { + GlyphRun& run = mGlyphRunArray[i]; + while (charGlyphs[run.mCharacterOffset].IsLigatureContinuation() && + run.mCharacterOffset < GetLength()) { + run.mCharacterOffset++; + } + // if the run has become empty, eliminate it + if ((i < lastRunIndex && + run.mCharacterOffset >= mGlyphRunArray[i + 1].mCharacterOffset) || + (i == lastRunIndex && run.mCharacterOffset == GetLength())) { + mGlyphRunArray.RemoveElementAt(i); + --lastRunIndex; + } + } + + MOZ_ASSERT(mGlyphRunArray.Length() > 0); + if (mGlyphRunArray.Length() == 1) { + ConvertFromGlyphRunArray(); + } +} + +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()) { + const DetailedGlyph* details = + g.GetGlyphCount() > 0 ? aShapedWord->GetDetailedGlyphs(i) : nullptr; + SetDetailedGlyphs(aOffset, g.GetGlyphCount(), details); + } + charGlyphs[aOffset] = g; + } + } else { + memcpy(charGlyphs + aOffset, wordGlyphs, wordLen * sizeof(CompressedGlyph)); + } +} + +void gfxTextRun::CopyGlyphDataFrom(gfxTextRun* aSource, Range aRange, + uint32_t aDest) { + NS_ASSERTION(aRange.end <= aSource->GetLength(), + "Source substring out of range"); + NS_ASSERTION(aDest + aRange.Length() <= GetLength(), + "Destination substring out of range"); + + if (aSource->mDontSkipDrawing) { + mDontSkipDrawing = true; + } + + // Copy base glyph data, and DetailedGlyph data where present + const CompressedGlyph* srcGlyphs = aSource->mCharacterGlyphs + aRange.start; + CompressedGlyph* dstGlyphs = mCharacterGlyphs + aDest; + for (uint32_t i = 0; i < aRange.Length(); ++i) { + CompressedGlyph g = srcGlyphs[i]; + g.SetCanBreakBefore(!g.IsClusterStart() + ? CompressedGlyph::FLAG_BREAK_TYPE_NONE + : dstGlyphs[i].CanBreakBefore()); + if (!g.IsSimpleGlyph()) { + uint32_t count = g.GetGlyphCount(); + if (count > 0) { + // DetailedGlyphs allocation is infallible, so this should never be + // null unless the source textrun is somehow broken. + DetailedGlyph* src = aSource->GetDetailedGlyphs(i + aRange.start); + MOZ_ASSERT(src, "missing DetailedGlyphs?"); + if (src) { + DetailedGlyph* dst = AllocateDetailedGlyphs(i + aDest, count); + ::memcpy(dst, src, count * sizeof(DetailedGlyph)); + } else { + g.SetMissing(); + } + } + } + dstGlyphs[i] = g; + } + + // Copy glyph runs + GlyphRunIterator iter(aSource, aRange); +#ifdef DEBUG + GlyphRun* prevRun = nullptr; +#endif + while (iter.NextRun()) { + gfxFont* font = iter.GetGlyphRun()->mFont; + MOZ_ASSERT(!prevRun || !prevRun->Matches(iter.GetGlyphRun()->mFont, + iter.GetGlyphRun()->mOrientation, + iter.GetGlyphRun()->mIsCJK, + FontMatchType::Kind::kUnspecified), + "Glyphruns not coalesced?"); +#ifdef DEBUG + prevRun = const_cast<GlyphRun*>(iter.GetGlyphRun()); + uint32_t end = iter.GetStringEnd(); +#endif + uint32_t start = iter.GetStringStart(); + + // These used to be NS_ASSERTION()s, but WARNING is more appropriate. + // Although it's unusual (and not desirable), it's possible for us to assign + // different fonts to a base character and a following diacritic. + // Example on OSX 10.5/10.6 with default fonts installed: + // data:text/html,<p style="font-family:helvetica, arial, sans-serif;"> + // &%23x043E;&%23x0486;&%23x20;&%23x043E;&%23x0486; + // This means the rendering of the cluster will probably not be very good, + // but it's the best we can do for now if the specified font only covered + // the initial base character and not its applied marks. + NS_WARNING_ASSERTION(aSource->IsClusterStart(start), + "Started font run in the middle of a cluster"); + NS_WARNING_ASSERTION( + end == aSource->GetLength() || aSource->IsClusterStart(end), + "Ended font run in the middle of a cluster"); + + AddGlyphRun(font, iter.GetGlyphRun()->mMatchType, + start - aRange.start + aDest, false, + iter.GetGlyphRun()->mOrientation, iter.GetGlyphRun()->mIsCJK); + } +} + +void gfxTextRun::ClearGlyphsAndCharacters() { + ResetGlyphRuns(); + memset(reinterpret_cast<char*>(mCharacterGlyphs), 0, + mLength * sizeof(CompressedGlyph)); + mDetailedGlyphs = nullptr; +} + +void gfxTextRun::SetSpaceGlyph(gfxFont* aFont, DrawTarget* aDrawTarget, + uint32_t aCharIndex, + gfx::ShapedTextFlags aOrientation) { + if (SetSpaceGlyphIfSimple(aFont, aCharIndex, ' ', aOrientation)) { + return; + } + + aFont->InitWordCache(); + static const uint8_t space = ' '; + gfx::ShapedTextFlags flags = + gfx::ShapedTextFlags::TEXT_IS_8BIT | aOrientation; + bool vertical = + !!(GetFlags() & gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT); + gfxFontShaper::RoundingFlags roundingFlags = + aFont->GetRoundOffsetsToPixels(aDrawTarget); + gfxShapedWord* sw = aFont->GetShapedWord( + aDrawTarget, &space, 1, gfxShapedWord::HashMix(0, ' '), Script::LATIN, + /* aLanguage = */ nullptr, vertical, mAppUnitsPerDevUnit, flags, + roundingFlags, nullptr); + if (sw) { + const GlyphRun* prevRun = TrailingGlyphRun(); + bool isCJK = prevRun && prevRun->mFont == aFont && + prevRun->mOrientation == aOrientation + ? prevRun->mIsCJK + : false; + AddGlyphRun(aFont, FontMatchType::Kind::kUnspecified, aCharIndex, false, + aOrientation, isCJK); + CopyGlyphDataFrom(sw, aCharIndex); + GetCharacterGlyphs()[aCharIndex].SetIsSpace(); + } +} + +bool gfxTextRun::SetSpaceGlyphIfSimple(gfxFont* aFont, uint32_t aCharIndex, + char16_t aSpaceChar, + gfx::ShapedTextFlags aOrientation) { + uint32_t spaceGlyph = aFont->GetSpaceGlyph(); + if (!spaceGlyph || !CompressedGlyph::IsSimpleGlyphID(spaceGlyph)) { + return false; + } + + gfxFont::Orientation fontOrientation = + (aOrientation & gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT) + ? nsFontMetrics::eVertical + : nsFontMetrics::eHorizontal; + uint32_t spaceWidthAppUnits = NS_lroundf( + aFont->GetMetrics(fontOrientation).spaceWidth * mAppUnitsPerDevUnit); + if (!CompressedGlyph::IsSimpleAdvance(spaceWidthAppUnits)) { + return false; + } + + const GlyphRun* prevRun = TrailingGlyphRun(); + bool isCJK = prevRun && prevRun->mFont == aFont && + prevRun->mOrientation == aOrientation + ? prevRun->mIsCJK + : false; + AddGlyphRun(aFont, FontMatchType::Kind::kUnspecified, aCharIndex, false, + aOrientation, isCJK); + CompressedGlyph g = + CompressedGlyph::MakeSimpleGlyph(spaceWidthAppUnits, spaceGlyph); + if (aSpaceChar == ' ') { + g.SetIsSpace(); + } + GetCharacterGlyphs()[aCharIndex] = g; + return true; +} + +void gfxTextRun::FetchGlyphExtents(DrawTarget* aRefDrawTarget) { + bool needsGlyphExtents = NeedsGlyphExtents(this); + if (!needsGlyphExtents && !mDetailedGlyphs) return; + + uint32_t runCount; + const GlyphRun* glyphRuns = GetGlyphRuns(&runCount); + CompressedGlyph* charGlyphs = mCharacterGlyphs; + for (uint32_t i = 0; i < runCount; ++i) { + const GlyphRun& run = glyphRuns[i]; + gfxFont* font = run.mFont; + if (MOZ_UNLIKELY(font->GetStyle()->size == 0) || + MOZ_UNLIKELY(font->GetStyle()->sizeAdjust == 0.0f)) { + continue; + } + + uint32_t start = run.mCharacterOffset; + uint32_t end = + i + 1 < runCount ? glyphRuns[i + 1].mCharacterOffset : GetLength(); + 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)) { +#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)) { +#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 = mHasGlyphRunArray + ? mGlyphRunArray.ShallowSizeOfExcludingThis(aMallocSizeOf) + : 0; + + if (mDetailedGlyphs) { + total += mDetailedGlyphs->SizeOfIncludingThis(aMallocSizeOf); + } + + return total; +} + +size_t gfxTextRun::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +#ifdef DEBUG_FRAME_DUMP +void gfxTextRun::Dump(FILE* out) { +# define APPEND_FLAG(string_, enum_, field_, flag_) \ + if (field_ & enum_::flag_) { \ + string_.AppendPrintf(remaining != field_ ? " %s" : "%s", #flag_); \ + remaining &= ~enum_::flag_; \ + } +# define APPEND_FLAGS(string_, enum_, field_, flags_) \ + { \ + auto remaining = field_; \ + MOZ_FOR_EACH(APPEND_FLAG, (string_, enum_, field_, ), flags_) \ + if (int(remaining)) { \ + string_.AppendPrintf(" %s(0x%0x)", #enum_, int(remaining)); \ + } \ + } + + nsCString flagsString; + ShapedTextFlags orient = mFlags & ShapedTextFlags::TEXT_ORIENT_MASK; + ShapedTextFlags otherFlags = mFlags & ~ShapedTextFlags::TEXT_ORIENT_MASK; + APPEND_FLAGS(flagsString, ShapedTextFlags, otherFlags, + (TEXT_IS_RTL, TEXT_ENABLE_SPACING, TEXT_IS_8BIT, + TEXT_ENABLE_HYPHEN_BREAKS, TEXT_NEED_BOUNDING_BOX, + TEXT_DISABLE_OPTIONAL_LIGATURES, TEXT_OPTIMIZE_SPEED, + TEXT_HIDE_CONTROL_CHARACTERS, TEXT_TRAILING_ARABICCHAR, + TEXT_INCOMING_ARABICCHAR, TEXT_USE_MATH_SCRIPT)) + + if (orient != ShapedTextFlags::TEXT_ORIENT_HORIZONTAL && + !flagsString.IsEmpty()) { + flagsString += ' '; + } + + switch (orient) { + case ShapedTextFlags::TEXT_ORIENT_HORIZONTAL: + break; + case ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT: + flagsString += "TEXT_ORIENT_VERTICAL_UPRIGHT"; + break; + case ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT: + flagsString += "TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT"; + break; + case ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED: + flagsString += "TEXT_ORIENT_VERTICAL_MIXED"; + break; + case ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT: + flagsString += "TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT"; + break; + default: + flagsString.AppendPrintf("UNKNOWN_TEXT_ORIENT_MASK(0x%0x)", int(orient)); + break; + } + + nsCString flags2String; + APPEND_FLAGS( + flags2String, nsTextFrameUtils::Flags, mFlags2, + (HasTab, HasShy, DontSkipDrawingForPendingUserFonts, IsSimpleFlow, + IncomingWhitespace, TrailingWhitespace, CompressedLeadingWhitespace, + NoBreaks, IsTransformed, HasTrailingBreak, IsSingleCharMi, + MightHaveGlyphChanges, RunSizeAccounted)) + +# undef APPEND_FLAGS +# undef APPEND_FLAG + + nsAutoCString lang; + mFontGroup->Language()->ToUTF8String(lang); + fprintf(out, "gfxTextRun@%p (length %u) [%s] [%s] [%s]\n", this, mLength, + flagsString.get(), flags2String.get(), lang.get()); + + uint32_t numGlyphRuns; + const GlyphRun* glyphRuns = GetGlyphRuns(&numGlyphRuns); + fprintf(out, " Glyph runs:\n"); + for (uint32_t i = 0; i < numGlyphRuns; ++i) { + gfxFont* font = glyphRuns[i].mFont; + const gfxFontStyle* style = font->GetStyle(); + nsAutoString styleString; + nsStyleUtil::AppendFontSlantStyle(style->style, styleString); + fprintf(out, " [%d] offset=%d %s %f/%g/%s\n", i, + glyphRuns[i].mCharacterOffset, font->GetName().get(), style->size, + style->weight.ToFloat(), NS_ConvertUTF16toUTF8(styleString).get()); + } + + fprintf(out, " Glyphs:\n"); + for (uint32_t i = 0; i < mLength; ++i) { + auto glyphData = GetCharacterGlyphs()[i]; + + nsCString line; + line.AppendPrintf(" [%d] 0x%p %s", i, GetCharacterGlyphs() + i, + glyphData.IsSimpleGlyph() ? "simple" : "detailed"); + + if (glyphData.IsSimpleGlyph()) { + line.AppendPrintf(" id=%d adv=%d", glyphData.GetSimpleGlyph(), + glyphData.GetSimpleAdvance()); + } else { + uint32_t count = glyphData.GetGlyphCount(); + if (count) { + line += " ids="; + for (uint32_t j = 0; j < count; j++) { + line.AppendPrintf(j ? ",%d" : "%d", GetDetailedGlyphs(i)[j].mGlyphID); + } + line += " advs="; + for (uint32_t j = 0; j < count; j++) { + line.AppendPrintf(j ? ",%d" : "%d", GetDetailedGlyphs(i)[j].mAdvance); + } + line += " offsets="; + for (uint32_t j = 0; j < count; j++) { + auto offset = GetDetailedGlyphs(i)[j].mOffset; + line.AppendPrintf(j ? ",(%g,%g)" : "(%g,%g)", offset.x, offset.y); + } + } else { + line += " (no glyphs)"; + } + } + + if (glyphData.CharIsSpace()) { + line += " CHAR_IS_SPACE"; + } + if (glyphData.CharIsTab()) { + line += " CHAR_IS_TAB"; + } + if (glyphData.CharIsNewline()) { + line += " CHAR_IS_NEWLINE"; + } + if (glyphData.CharIsFormattingControl()) { + line += " CHAR_IS_FORMATTING_CONTROL"; + } + if (glyphData.CharTypeFlags() & + CompressedGlyph::FLAG_CHAR_NO_EMPHASIS_MARK) { + line += " CHAR_NO_EMPHASIS_MARK"; + } + + if (!glyphData.IsSimpleGlyph()) { + if (!glyphData.IsMissing()) { + line += " NOT_MISSING"; + } + if (!glyphData.IsClusterStart()) { + line += " NOT_IS_CLUSTER_START"; + } + if (!glyphData.IsLigatureGroupStart()) { + line += " NOT_LIGATURE_GROUP_START"; + } + } + + switch (glyphData.CanBreakBefore()) { + case CompressedGlyph::FLAG_BREAK_TYPE_NORMAL: + line += " BREAK_TYPE_NORMAL"; + break; + case CompressedGlyph::FLAG_BREAK_TYPE_HYPHEN: + line += " BREAK_TYPE_HYPHEN"; + break; + } + + fprintf(out, "%s\n", line.get()); + } +} +#endif + +gfxFontGroup::gfxFontGroup(const FontFamilyList& aFontFamilyList, + const gfxFontStyle* aStyle, nsAtom* aLanguage, + bool aExplicitLanguage, + gfxTextPerfMetrics* aTextPerf, + FontMatchingStats* aFontMatchingStats, + gfxUserFontSet* aUserFontSet, gfxFloat aDevToCssSize) + : mFamilyList(aFontFamilyList), + mStyle(*aStyle), + mLanguage(aLanguage), + mUnderlineOffset(UNDERLINE_OFFSET_NOT_SET), + mHyphenWidth(-1), + mDevToCssSize(aDevToCssSize), + mUserFontSet(aUserFontSet), + mTextPerf(aTextPerf), + mFontMatchingStats(aFontMatchingStats), + mLastPrefLang(eFontPrefLang_Western), + mPageLang(gfxPlatformFontList::GetFontPrefLangFor(aLanguage)), + mLastPrefFirstFont(false), + mSkipDrawing(false), + mExplicitLanguage(aExplicitLanguage) { + // We don't use SetUserFontSet() here, as we want to unconditionally call + // BuildFontList() rather than only do UpdateUserFonts() if it changed. + mCurrGeneration = GetGeneration(); + BuildFontList(); +} + +gfxFontGroup::~gfxFontGroup() { + // Should not be dropped by stylo + MOZ_ASSERT(NS_IsMainThread()); +} + +void gfxFontGroup::BuildFontList() { + // initialize fonts in the font family list + AutoTArray<FamilyAndGeneric, 10> fonts; + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + + // lookup fonts in the fontlist + for (const FontFamilyName& name : mFamilyList.GetFontlist()->mNames) { + if (name.IsNamed()) { + if (name.mName) { + AddPlatformFont(nsAtomCString(name.mName), name.IsQuoted(), fonts); + } else { + MOZ_ASSERT_UNREACHABLE("broken FontFamilyName, no atom!"); + } + } else { + pfl->AddGenericFonts(name.mGeneric, mLanguage, fonts); + if (mTextPerf) { + mTextPerf->current.genericLookups++; + } + } + } + + if (mFontMatchingStats) { + for (const auto& f : fonts) { + nsCString key; // not nsAutoCString, as the assignment to it won't copy + // characters, it'll just share the string buffer + FontVisibility visibility; + if (f.mFamily.mIsShared) { + key = f.mFamily.mShared->Key().AsString(pfl->SharedFontList()); + visibility = f.mFamily.mShared->Visibility(); + } else { + key = f.mFamily.mUnshared->Name(); + ToLowerCase(key); + visibility = f.mFamily.mUnshared->Visibility(); + } + if (mFontMatchingStats->mFamilyNames.EnsureInserted(key)) { + switch (visibility) { + case FontVisibility::Base: + mFontMatchingStats->mBaseFonts++; + break; + case FontVisibility::LangPack: + mFontMatchingStats->mLangPackFonts++; + break; + case FontVisibility::User: + mFontMatchingStats->mUserFonts++; + break; + case FontVisibility::Webfont: + mFontMatchingStats->mWebFonts++; + break; + default: + break; + } + } + } + } + + // if necessary, append default generic onto the end + if (mFamilyList.GetDefaultFontType() != StyleGenericFontFamily::None && + !mFamilyList.HasDefaultGeneric()) { + pfl->AddGenericFonts(mFamilyList.GetDefaultFontType(), mLanguage, fonts); + if (mTextPerf) { + mTextPerf->current.genericLookups++; + } + } + + // build the fontlist from the specified families + for (const auto& f : fonts) { + if (f.mFamily.mIsShared) { + AddFamilyToFontList(f.mFamily.mShared, f.mGeneric); + } else { + AddFamilyToFontList(f.mFamily.mUnshared, f.mGeneric); + } + } + + mFontListGeneration = pfl->GetGeneration(); +} + +void gfxFontGroup::AddPlatformFont(const nsACString& aName, bool aQuotedName, + nsTArray<FamilyAndGeneric>& aFamilyList) { + // First, look up in the user font set... + // If the fontSet matches the family, we must not look for a platform + // font of the same name, even if we fail to actually get a fontEntry + // here; we'll fall back to the next name in the CSS font-family list. + if (mUserFontSet) { + // Add userfonts to the fontlist whether already loaded + // or not. Loading is initiated during font matching. + gfxFontFamily* family = mUserFontSet->LookupFamily(aName); + if (family) { + aFamilyList.AppendElement(family); + return; + } + } + + // Not known in the user font set ==> check system fonts + gfxPlatformFontList::PlatformFontList()->FindAndAddFamilies( + StyleGenericFontFamily::None, aName, &aFamilyList, + aQuotedName ? gfxPlatformFontList::FindFamiliesFlags::eQuotedFamilyName + : gfxPlatformFontList::FindFamiliesFlags(0), + &mStyle, mLanguage.get(), mDevToCssSize); +} + +void gfxFontGroup::AddFamilyToFontList(gfxFontFamily* aFamily, + StyleGenericFontFamily aGeneric) { + if (!aFamily) { + MOZ_ASSERT_UNREACHABLE("don't try to add a null font family!"); + return; + } + AutoTArray<gfxFontEntry*, 4> fontEntryList; + aFamily->FindAllFontsForStyle(mStyle, fontEntryList); + // add these to the fontlist + for (gfxFontEntry* fe : fontEntryList) { + if (!HasFont(fe)) { + FamilyFace ff(aFamily, fe, aGeneric); + if (fe->mIsUserFontContainer) { + ff.CheckState(mSkipDrawing); + } + mFonts.AppendElement(ff); + } + } + // for a family marked as "check fallback faces", only mark the last + // entry so that fallbacks for a family are only checked once + if (aFamily->CheckForFallbackFaces() && !fontEntryList.IsEmpty() && + !mFonts.IsEmpty()) { + mFonts.LastElement().SetCheckForFallbackFaces(); + } +} + +void gfxFontGroup::AddFamilyToFontList(fontlist::Family* aFamily, + StyleGenericFontFamily aGeneric) { + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + if (!aFamily->IsInitialized()) { + if (!NS_IsMainThread()) { + // If we need to initialize a Family record, but we're on a style + // worker thread, we have to defer it. + ServoStyleSet* set = ServoStyleSet::Current(); + MOZ_ASSERT(set); + set->AppendTask(PostTraversalTask::InitializeFamily(aFamily)); + set->AppendTask(PostTraversalTask::FontInfoUpdate(set)); + return; + } + if (!pfl->InitializeFamily(aFamily)) { + return; + } + } + AutoTArray<fontlist::Face*, 4> faceList; + aFamily->FindAllFacesForStyle(pfl->SharedFontList(), mStyle, faceList); + for (auto face : faceList) { + gfxFontEntry* fe = pfl->GetOrCreateFontEntry(face, aFamily); + if (fe && !HasFont(fe)) { + FamilyFace ff(aFamily, fe, aGeneric); + mFonts.AppendElement(ff); + } + } +} + +bool gfxFontGroup::HasFont(const gfxFontEntry* aFontEntry) { + for (auto& f : mFonts) { + if (f.FontEntry() == aFontEntry) { + return true; + } + } + return false; +} + +gfxFont* gfxFontGroup::GetFontAt(int32_t i, uint32_t aCh, bool* aLoading) { + if (uint32_t(i) >= mFonts.Length()) { + return nullptr; + } + + FamilyFace& ff = mFonts[i]; + if (ff.IsInvalid() || ff.IsLoading()) { + return nullptr; + } + + gfxFont* font = ff.Font(); + if (!font) { + gfxFontEntry* fe = ff.FontEntry(); + if (!fe) { + return nullptr; + } + gfxCharacterMap* unicodeRangeMap = nullptr; + if (fe->mIsUserFontContainer) { + gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe); + if (ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED && + ufe->CharacterInUnicodeRange(aCh) && !*aLoading) { + ufe->Load(); + ff.CheckState(mSkipDrawing); + *aLoading = ff.IsLoading(); + } + fe = ufe->GetPlatformFontEntry(); + if (!fe) { + return nullptr; + } + unicodeRangeMap = ufe->GetUnicodeRangeMap(); + } + font = fe->FindOrMakeFont(&mStyle, unicodeRangeMap); + if (!font || !font->Valid()) { + ff.SetInvalid(); + // We can't just |delete font| here, in case there are other + // references to the object FindOrMakeFont returned. + RefPtr<gfxFont> ref(font); + return nullptr; + } + ff.SetFont(font); + } + return font; +} + +void gfxFontGroup::FamilyFace::CheckState(bool& aSkipDrawing) { + gfxFontEntry* fe = FontEntry(); + if (!fe) { + return; + } + if (fe->mIsUserFontContainer) { + gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe); + gfxUserFontEntry::UserFontLoadState state = ufe->LoadState(); + switch (state) { + case gfxUserFontEntry::STATUS_LOAD_PENDING: + case gfxUserFontEntry::STATUS_LOADING: + SetLoading(true); + break; + case gfxUserFontEntry::STATUS_FAILED: + SetInvalid(); + // fall-thru to the default case + [[fallthrough]]; + default: + SetLoading(false); + } + if (ufe->WaitForUserFont()) { + aSkipDrawing = true; + } + } +} + +bool gfxFontGroup::FamilyFace::EqualsUserFont( + const gfxUserFontEntry* aUserFont) const { + gfxFontEntry* fe = FontEntry(); + // if there's a font, the entry is the underlying platform font + if (mFontCreated) { + gfxFontEntry* pfe = aUserFont->GetPlatformFontEntry(); + if (pfe == fe) { + return true; + } + } else if (fe == aUserFont) { + return true; + } + return false; +} + +gfxFont* gfxFontGroup::GetDefaultFont() { + if (mDefaultFont) { + return mDefaultFont.get(); + } + + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + FontFamily family = pfl->GetDefaultFont(&mStyle); + MOZ_ASSERT(!family.IsNull(), + "invalid default font returned by GetDefaultFont"); + + gfxFontEntry* fe = nullptr; + if (family.mIsShared) { + fontlist::Family* fam = family.mShared; + if (!fam->IsInitialized()) { + // If this fails, FindFaceForStyle will just safely return nullptr + Unused << pfl->InitializeFamily(fam); + } + fontlist::Face* face = fam->FindFaceForStyle(pfl->SharedFontList(), mStyle); + if (face) { + fe = pfl->GetOrCreateFontEntry(face, fam); + } + } else { + fe = family.mUnshared->FindFontForStyle(mStyle); + } + if (fe) { + mDefaultFont = fe->FindOrMakeFont(&mStyle); + } + + uint32_t numInits, loaderState; + pfl->GetFontlistInitInfo(numInits, loaderState); + + MOZ_ASSERT(numInits != 0, + "must initialize system fontlist before getting default font!"); + + uint32_t numFonts = 0; + if (!mDefaultFont) { + // Try for a "font of last resort...." + // Because an empty font list would be Really Bad for later code + // that assumes it will be able to get valid metrics for layout, + // just look for the first usable font and put in the list. + // (see bug 554544) + if (pfl->SharedFontList()) { + fontlist::FontList* list = pfl->SharedFontList(); + numFonts = list->NumFamilies(); + fontlist::Family* families = list->Families(); + for (uint32_t i = 0; i < numFonts; ++i) { + fontlist::Family* fam = &families[i]; + if (!fam->IsInitialized()) { + Unused << pfl->InitializeFamily(fam); + } + fontlist::Face* face = + fam->FindFaceForStyle(pfl->SharedFontList(), mStyle); + if (face) { + fe = pfl->GetOrCreateFontEntry(face, fam); + if (fe) { + mDefaultFont = fe->FindOrMakeFont(&mStyle); + if (mDefaultFont) { + break; + } + NS_WARNING("FindOrMakeFont failed"); + } + } + } + } else { + AutoTArray<RefPtr<gfxFontFamily>, 200> familyList; + pfl->GetFontFamilyList(familyList); + numFonts = familyList.Length(); + for (uint32_t i = 0; i < numFonts; ++i) { + gfxFontEntry* fe = familyList[i]->FindFontForStyle(mStyle, true); + if (fe) { + mDefaultFont = fe->FindOrMakeFont(&mStyle); + if (mDefaultFont) { + break; + } + } + } + } + } + + if (!mDefaultFont && pfl->SharedFontList() && !XRE_IsParentProcess()) { + // If we're a content process, it's possible this is failing because the + // chrome process has just updated the shared font list and we haven't yet + // refreshed our reference to it. If that's the case, update and retry. + // But if we're not on the main thread, we can't do this, so just use + // the platform default font directly. + if (NS_IsMainThread()) { + uint32_t oldGeneration = pfl->SharedFontList()->GetGeneration(); + pfl->UpdateFontList(); + if (pfl->SharedFontList()->GetGeneration() != oldGeneration) { + return GetDefaultFont(); + } + } else { + gfxFontEntry* fe = pfl->GetDefaultFontEntry(); + if (fe) { + return fe->FindOrMakeFont(&mStyle); + } + } + } + + 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 + nsAutoCString familiesString; + mFamilyList.ToString(familiesString); + SprintfLiteral(msg, "unable to find a usable font (%.220s)", + familiesString.get()); + MOZ_CRASH_UNSAFE(msg); + } + + return mDefaultFont.get(); +} + +gfxFont* gfxFontGroup::GetFirstValidFont(uint32_t aCh, + StyleGenericFontFamily* aGeneric) { + uint32_t count = mFonts.Length(); + bool loading = false; + 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) { + if (aGeneric) { + *aGeneric = ff.Generic(); + } + return font; + } + + // Need to build a font, loading userfont if not loaded. In + // cases where unicode range might apply, use the character + // provided. + gfxFontEntry* fe = ff.FontEntry(); + if (fe && fe->mIsUserFontContainer) { + gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe); + bool inRange = ufe->CharacterInUnicodeRange(aCh); + if (inRange) { + if (!loading && + ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED) { + ufe->Load(); + ff.CheckState(mSkipDrawing); + } + if (ff.IsLoading()) { + loading = true; + } + } + if (ufe->LoadState() != gfxUserFontEntry::STATUS_LOADED || !inRange) { + continue; + } + } + + font = GetFontAt(i, aCh, &loading); + if (font) { + if (aGeneric) { + *aGeneric = ff.Generic(); + } + return font; + } + } + if (aGeneric) { + *aGeneric = StyleGenericFontFamily::None; + } + 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; +} + +bool gfxFontGroup::IsInvalidChar(uint8_t ch) { + return ((ch & 0x7f) < 0x20 || ch == 0x7f); +} + +bool gfxFontGroup::IsInvalidChar(char16_t ch) { + // All printable 7-bit ASCII values are OK + if (ch >= ' ' && ch < 0x7f) { + return false; + } + // No point in sending non-printing control chars through font shaping + if (ch <= 0x9f) { + return true; + } + // Word-separating format/bidi control characters are not shaped as part + // of words. + return (((ch & 0xFF00) == 0x2000 /* Unicode control character */ && + (ch == 0x200B /*ZWSP*/ || ch == 0x2028 /*LSEP*/ || + ch == 0x2029 /*PSEP*/ || ch == 0x2060 /*WJ*/)) || + ch == 0xfeff /*ZWNBSP*/ || IsBidiControl(ch)); +} + +already_AddRefed<gfxTextRun> gfxFontGroup::MakeEmptyTextRun( + const Parameters* aParams, gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2) { + aFlags |= ShapedTextFlags::TEXT_IS_8BIT; + return gfxTextRun::Create(aParams, 0, this, aFlags, aFlags2); +} + +already_AddRefed<gfxTextRun> gfxFontGroup::MakeSpaceTextRun( + const Parameters* aParams, gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2) { + aFlags |= ShapedTextFlags::TEXT_IS_8BIT; + + RefPtr<gfxTextRun> textRun = + gfxTextRun::Create(aParams, 1, this, aFlags, aFlags2); + if (!textRun) { + return nullptr; + } + + gfx::ShapedTextFlags orientation = aFlags & ShapedTextFlags::TEXT_ORIENT_MASK; + if (orientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) { + orientation = ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; + } + + 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, FontMatchType::Kind::kUnspecified, 0, false, + orientation, false); + } else { + if (font->GetSpaceGlyph()) { + // Normally, the font has a cached space glyph, so we can avoid + // the cost of calling FindFontForChar. + textRun->SetSpaceGlyph(font, aParams->mDrawTarget, 0, orientation); + } else { + // In case the primary font doesn't have <space> (bug 970891), + // find one that does. + FontMatchType matchType; + gfxFont* spaceFont = + FindFontForChar(' ', 0, 0, Script::LATIN, nullptr, &matchType); + if (spaceFont) { + textRun->SetSpaceGlyph(spaceFont, aParams->mDrawTarget, 0, orientation); + } + } + } + + // Note that the gfxGlyphExtents glyph bounds storage for the font will + // always contain an entry for the font's space glyph, so we don't have + // to call FetchGlyphExtents here. + return textRun.forget(); +} + +template <typename T> +already_AddRefed<gfxTextRun> gfxFontGroup::MakeBlankTextRun( + const T* aString, uint32_t aLength, const Parameters* aParams, + gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2) { + RefPtr<gfxTextRun> textRun = + gfxTextRun::Create(aParams, aLength, this, aFlags, aFlags2); + if (!textRun) { + return nullptr; + } + + gfx::ShapedTextFlags orientation = aFlags & ShapedTextFlags::TEXT_ORIENT_MASK; + if (orientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) { + orientation = ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT; + } + textRun->AddGlyphRun(GetFirstValidFont(), FontMatchType::Kind::kUnspecified, + 0, false, orientation, false); + + for (uint32_t i = 0; i < aLength; i++) { + if (aString[i] == '\n') { + textRun->SetIsNewline(i); + } else if (aString[i] == '\t') { + textRun->SetIsTab(i); + } + } + + return textRun.forget(); +} + +already_AddRefed<gfxTextRun> gfxFontGroup::MakeHyphenTextRun( + DrawTarget* aDrawTarget, 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, + ShapedTextFlags(), nsTextFrameUtils::Flags(), nullptr); + } + + static const uint8_t dash = '-'; + return MakeTextRun(&dash, 1, aDrawTarget, aAppUnitsPerDevUnit, + ShapedTextFlags(), nsTextFrameUtils::Flags(), nullptr); +} + +gfxFloat gfxFontGroup::GetHyphenWidth( + const gfxTextRun::PropertyProvider* aProvider) { + if (mHyphenWidth < 0) { + RefPtr<DrawTarget> dt(aProvider->GetDrawTarget()); + if (dt) { + RefPtr<gfxTextRun> hyphRun( + MakeHyphenTextRun(dt, aProvider->GetAppUnitsPerDevUnit())); + mHyphenWidth = hyphRun.get() ? hyphRun->GetAdvanceWidth() : 0; + } + } + return mHyphenWidth; +} + +already_AddRefed<gfxTextRun> gfxFontGroup::MakeTextRun( + const uint8_t* aString, uint32_t aLength, const Parameters* aParams, + gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2, + gfxMissingFontRecorder* aMFR) { + if (aLength == 0) { + return MakeEmptyTextRun(aParams, aFlags, aFlags2); + } + if (aLength == 1 && aString[0] == ' ') { + return MakeSpaceTextRun(aParams, aFlags, aFlags2); + } + + aFlags |= ShapedTextFlags::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(aString, aLength, aParams, aFlags, aFlags2); + } + + RefPtr<gfxTextRun> textRun = + gfxTextRun::Create(aParams, aLength, this, aFlags, aFlags2); + if (!textRun) { + return nullptr; + } + + InitTextRun(aParams->mDrawTarget, textRun.get(), aString, aLength, aMFR); + + textRun->FetchGlyphExtents(aParams->mDrawTarget); + + return textRun.forget(); +} + +already_AddRefed<gfxTextRun> gfxFontGroup::MakeTextRun( + const char16_t* aString, uint32_t aLength, const Parameters* aParams, + gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2, + gfxMissingFontRecorder* aMFR) { + if (aLength == 0) { + return MakeEmptyTextRun(aParams, aFlags, aFlags2); + } + if (aLength == 1 && aString[0] == ' ') { + return MakeSpaceTextRun(aParams, aFlags, aFlags2); + } + if (MOZ_UNLIKELY(GetStyle()->size == 0) || + MOZ_UNLIKELY(GetStyle()->sizeAdjust == 0.0f)) { + return MakeBlankTextRun(aString, aLength, aParams, aFlags, aFlags2); + } + + RefPtr<gfxTextRun> textRun = + gfxTextRun::Create(aParams, aLength, this, aFlags, aFlags2); + if (!textRun) { + return nullptr; + } + + InitTextRun(aParams->mDrawTarget, textRun.get(), aString, aLength, aMFR); + + textRun->FetchGlyphExtents(aParams->mDrawTarget); + + return textRun.forget(); +} + +template <typename T> +void gfxFontGroup::InitTextRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun, + const T* aString, uint32_t aLength, + gfxMissingFontRecorder* aMFR) { + NS_ASSERTION(aLength > 0, "don't call InitTextRun for a zero-length run"); + + // we need to do numeral processing even on 8-bit text, + // in case we're converting Western to Hindi/Arabic digits + int32_t numOption = gfxPlatform::GetPlatform()->GetBidiNumeralOption(); + UniquePtr<char16_t[]> transformedString; + if (numOption != IBMBIDI_NUMERAL_NOMINAL) { + // scan the string for numerals that may need to be transformed; + // if we find any, we'll make a local copy here and use that for + // font matching and glyph generation/shaping + bool prevIsArabic = + !!(aTextRun->GetFlags() & ShapedTextFlags::TEXT_INCOMING_ARABICCHAR); + for (uint32_t i = 0; i < aLength; ++i) { + char16_t origCh = aString[i]; + char16_t newCh = HandleNumberInChar(origCh, prevIsArabic, numOption); + if (newCh != origCh) { + if (!transformedString) { + transformedString = MakeUnique<char16_t[]>(aLength); + if (sizeof(T) == sizeof(char16_t)) { + memcpy(transformedString.get(), aString, i * sizeof(char16_t)); + } else { + for (uint32_t j = 0; j < i; ++j) { + transformedString[j] = aString[j]; + } + } + } + } + if (transformedString) { + transformedString[i] = newCh; + } + prevIsArabic = IS_ARABIC_CHAR(newCh); + } + } + + LogModule* log = mStyle.systemFont ? gfxPlatform::GetLog(eGfxLog_textrunui) + : gfxPlatform::GetLog(eGfxLog_textrun); + + // variant fallback handling may end up passing through this twice + bool redo; + do { + redo = false; + + if (sizeof(T) == sizeof(uint8_t) && !transformedString) { + if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Warning))) { + nsAutoCString lang; + mLanguage->ToUTF8String(lang); + nsAutoCString families; + mFamilyList.ToString(families); + nsAutoCString str((const char*)aString, aLength); + nsAutoString styleString; + nsStyleUtil::AppendFontSlantStyle(mStyle.style, styleString); + MOZ_LOG( + log, LogLevel::Warning, + ("(%s) fontgroup: [%s] default: %s lang: %s script: %d " + "len %d weight: %g stretch: %g%% style: %s size: %6.2f %zu-byte " + "TEXTRUN [%s] ENDTEXTRUN\n", + (mStyle.systemFont ? "textrunui" : "textrun"), families.get(), + (mFamilyList.GetDefaultFontType() == StyleGenericFontFamily::Serif + ? "serif" + : (mFamilyList.GetDefaultFontType() == + StyleGenericFontFamily::SansSerif + ? "sans-serif" + : "none")), + lang.get(), static_cast<int>(Script::LATIN), aLength, + mStyle.weight.ToFloat(), mStyle.stretch.Percentage(), + NS_ConvertUTF16toUTF8(styleString).get(), mStyle.size, sizeof(T), + str.get())); + } + + // the text is still purely 8-bit; bypass the script-run itemizer + // and treat it as a single Latin run + InitScriptRun(aDrawTarget, aTextRun, aString, 0, aLength, Script::LATIN, + aMFR); + } else { + const char16_t* textPtr; + if (transformedString) { + textPtr = transformedString.get(); + } else { + // typecast to avoid compilation error for the 8-bit version, + // even though this is dead code in that case + textPtr = reinterpret_cast<const char16_t*>(aString); + } + + // split into script runs so that script can potentially influence + // the font matching process below + gfxScriptItemizer scriptRuns(textPtr, aLength); + + uint32_t runStart = 0, runLimit = aLength; + Script runScript = Script::LATIN; + while (scriptRuns.Next(runStart, runLimit, runScript)) { + if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Warning))) { + nsAutoCString lang; + mLanguage->ToUTF8String(lang); + nsAutoCString families; + mFamilyList.ToString(families); + nsAutoString styleString; + nsStyleUtil::AppendFontSlantStyle(mStyle.style, styleString); + uint32_t runLen = runLimit - runStart; + MOZ_LOG( + log, LogLevel::Warning, + ("(%s) fontgroup: [%s] default: %s lang: %s script: %d " + "len %d weight: %g stretch: %g%% style: %s size: %6.2f " + "%zu-byte TEXTRUN [%s] ENDTEXTRUN\n", + (mStyle.systemFont ? "textrunui" : "textrun"), families.get(), + (mFamilyList.GetDefaultFontType() == + StyleGenericFontFamily::Serif + ? "serif" + : (mFamilyList.GetDefaultFontType() == + StyleGenericFontFamily::SansSerif + ? "sans-serif" + : "none")), + lang.get(), static_cast<int>(runScript), runLen, + mStyle.weight.ToFloat(), mStyle.stretch.Percentage(), + NS_ConvertUTF16toUTF8(styleString).get(), mStyle.size, sizeof(T), + NS_ConvertUTF16toUTF8(textPtr + runStart, runLen).get())); + } + + InitScriptRun(aDrawTarget, aTextRun, textPtr + runStart, runStart, + runLimit - runStart, runScript, aMFR); + } + } + + // if shaping was aborted due to lack of feature support, clear out + // glyph runs and redo shaping with fallback forced on + if (aTextRun->GetShapingState() == gfxTextRun::eShapingState_Aborted) { + redo = true; + aTextRun->SetShapingState(gfxTextRun::eShapingState_ForceFallbackFeature); + aTextRun->ClearGlyphsAndCharacters(); + } + + } while (redo); + + if (sizeof(T) == sizeof(char16_t) && aLength > 0) { + gfxTextRun::CompressedGlyph* glyph = aTextRun->GetCharacterGlyphs(); + if (!glyph->IsSimpleGlyph()) { + glyph->SetClusterStart(true); + } + } + + // It's possible for CoreText to omit glyph runs if it decides they contain + // only invisibles (e.g., U+FEFF, see reftest 474417-1). In this case, we + // need to eliminate them from the glyph run array to avoid drawing "partial + // ligatures" with the wrong font. + // We don't do this during InitScriptRun (or gfxFont::InitTextRun) because + // it will iterate back over all glyphruns in the textrun, which leads to + // pathologically-bad perf in the case where a textrun contains many script + // changes (see bug 680402) - we'd end up re-sanitizing all the earlier runs + // every time a new script subrun is processed. + aTextRun->SanitizeGlyphRuns(); + + aTextRun->SortGlyphRuns(); +} + +static inline bool IsPUA(uint32_t aUSV) { + // We could look up the General Category of the codepoint here, + // but it's simpler to check PUA codepoint ranges. + return (aUSV >= 0xE000 && aUSV <= 0xF8FF) || (aUSV >= 0xF0000); +} + +template <typename T> +void gfxFontGroup::InitScriptRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun, + const T* aString, // text for this script run, + // not the entire textrun + uint32_t aOffset, // position of the script + // run within the textrun + uint32_t aLength, // length of the script run + Script aRunScript, + gfxMissingFontRecorder* aMFR) { + NS_ASSERTION(aLength > 0, "don't call InitScriptRun for a 0-length run"); + NS_ASSERTION(aTextRun->GetShapingState() != gfxTextRun::eShapingState_Aborted, + "don't call InitScriptRun with aborted shaping state"); + + // confirm the load state of userfonts in the list + if (mUserFontSet && mCurrGeneration != mUserFontSet->GetGeneration()) { + UpdateUserFonts(); + } + + gfxFont* mainFont = GetFirstValidFont(); + + ShapedTextFlags orientation = + aTextRun->GetFlags() & ShapedTextFlags::TEXT_ORIENT_MASK; + + if (orientation != ShapedTextFlags::TEXT_ORIENT_HORIZONTAL && + (aRunScript == Script::MONGOLIAN || aRunScript == Script::PHAGS_PA)) { + // Mongolian and Phags-pa text should ignore text-orientation and + // always render in its "native" vertical mode, implemented by fonts + // as sideways-right (i.e as if shaped horizontally, and then the + // entire line is rotated to render vertically). Therefore, we ignore + // the aOrientation value from the textrun's flags, and make all + // vertical Mongolian/Phags-pa use sideways-right. + orientation = ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; + } + + uint32_t runStart = 0; + AutoTArray<TextRange, 3> fontRanges; + ComputeRanges(fontRanges, aString, aLength, aRunScript, orientation); + uint32_t numRanges = fontRanges.Length(); + bool missingChars = false; + bool isCJK = gfxTextRun::IsCJKScript(aRunScript); + + for (uint32_t r = 0; r < numRanges; r++) { + const TextRange& range = fontRanges[r]; + uint32_t matchedLength = range.Length(); + gfxFont* matchedFont = range.font; + // create the glyph run for this range + if (matchedFont && mStyle.noFallbackVariantFeatures) { + // common case - just do glyph layout and record the + // resulting positioned glyphs + aTextRun->AddGlyphRun(matchedFont, range.matchType, aOffset + runStart, + (matchedLength > 0), range.orientation, isCJK); + if (!matchedFont->SplitAndInitTextRun( + aDrawTarget, aTextRun, aString + runStart, aOffset + runStart, + matchedLength, aRunScript, mLanguage, range.orientation)) { + // glyph layout failed! treat as missing glyphs + matchedFont = nullptr; + } + } else if (matchedFont) { + // shape with some variant feature that requires fallback handling + bool petiteToSmallCaps = false; + bool syntheticLower = false; + bool syntheticUpper = false; + + if (mStyle.variantSubSuper != NS_FONT_VARIANT_POSITION_NORMAL && + (aTextRun->GetShapingState() == + gfxTextRun::eShapingState_ForceFallbackFeature || + !matchedFont->SupportsSubSuperscript(mStyle.variantSubSuper, aString, + aLength, aRunScript))) { + // fallback for subscript/superscript variant glyphs + + // if the feature was already used, abort and force + // fallback across the entire textrun + gfxTextRun::ShapingState ss = aTextRun->GetShapingState(); + + if (ss == gfxTextRun::eShapingState_Normal) { + aTextRun->SetShapingState( + gfxTextRun::eShapingState_ShapingWithFallback); + } else if (ss == gfxTextRun::eShapingState_ShapingWithFeature) { + aTextRun->SetShapingState(gfxTextRun::eShapingState_Aborted); + return; + } + + RefPtr<gfxFont> subSuperFont = matchedFont->GetSubSuperscriptFont( + aTextRun->GetAppUnitsPerDevUnit()); + aTextRun->AddGlyphRun(subSuperFont, range.matchType, aOffset + runStart, + (matchedLength > 0), range.orientation, isCJK); + if (!subSuperFont->SplitAndInitTextRun( + aDrawTarget, aTextRun, aString + runStart, aOffset + runStart, + matchedLength, aRunScript, mLanguage, range.orientation)) { + // glyph layout failed! treat as missing glyphs + matchedFont = nullptr; + } + } else if (mStyle.variantCaps != NS_FONT_VARIANT_CAPS_NORMAL && + !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, + mExplicitLanguage ? mLanguage.get() : nullptr, syntheticLower, + syntheticUpper)) { + matchedFont = nullptr; + } + } else { + // shape normally with variant feature enabled + gfxTextRun::ShapingState ss = aTextRun->GetShapingState(); + + // adjust the shaping state if necessary + if (ss == gfxTextRun::eShapingState_Normal) { + aTextRun->SetShapingState( + gfxTextRun::eShapingState_ShapingWithFeature); + } else if (ss == gfxTextRun::eShapingState_ShapingWithFallback) { + // already have shaping results using fallback, need to redo + aTextRun->SetShapingState(gfxTextRun::eShapingState_Aborted); + return; + } + + // do glyph layout and record the resulting positioned glyphs + aTextRun->AddGlyphRun(matchedFont, range.matchType, aOffset + runStart, + (matchedLength > 0), range.orientation, isCJK); + if (!matchedFont->SplitAndInitTextRun( + aDrawTarget, aTextRun, aString + runStart, aOffset + runStart, + matchedLength, aRunScript, mLanguage, range.orientation)) { + // glyph layout failed! treat as missing glyphs + matchedFont = nullptr; + } + } + } else { + aTextRun->AddGlyphRun(mainFont, FontMatchType::Kind::kFontGroup, + aOffset + runStart, (matchedLength > 0), + range.orientation, isCJK); + } + + if (!matchedFont) { + // We need to set cluster boundaries (and mark spaces) so that + // surrogate pairs, combining characters, etc behave properly, + // even if we don't have glyphs for them + aTextRun->SetupClusterBoundaries(aOffset + runStart, aString + runStart, + matchedLength); + + // various "missing" characters may need special handling, + // so we check for them here + uint32_t runLimit = runStart + matchedLength; + for (uint32_t index = runStart; index < runLimit; index++) { + T ch = aString[index]; + + // tab and newline are not to be displayed as hexboxes, + // but do need to be recorded in the textrun + if (ch == '\n') { + aTextRun->SetIsNewline(aOffset + index); + continue; + } + if (ch == '\t') { + aTextRun->SetIsTab(aOffset + index); + continue; + } + + // for 16-bit textruns only, check for surrogate pairs and + // special Unicode spaces; omit these checks in 8-bit runs + if (sizeof(T) == sizeof(char16_t)) { + if (index + 1 < aLength && + NS_IS_SURROGATE_PAIR(ch, aString[index + 1])) { + uint32_t usv = SURROGATE_TO_UCS4(ch, aString[index + 1]); + aTextRun->SetMissingGlyph(aOffset + index, usv, mainFont); + index++; + if (!mSkipDrawing && !IsPUA(usv)) { + missingChars = true; + if (mFontMatchingStats) { + mFontMatchingStats->mFallbacks |= FallbackTypes::MissingFont; + } + } + continue; + } + + // check if this is a known Unicode whitespace character that + // we can render using the space glyph with a custom width + gfxFloat wid = mainFont->SynthesizeSpaceWidth(ch); + if (wid >= 0.0) { + nscoord advance = + aTextRun->GetAppUnitsPerDevUnit() * floor(wid + 0.5); + if (gfxShapedText::CompressedGlyph::IsSimpleAdvance(advance)) { + aTextRun->GetCharacterGlyphs()[aOffset + index].SetSimpleGlyph( + advance, mainFont->GetSpaceGlyph()); + } else { + gfxTextRun::DetailedGlyph detailedGlyph; + detailedGlyph.mGlyphID = mainFont->GetSpaceGlyph(); + detailedGlyph.mAdvance = advance; + aTextRun->SetDetailedGlyphs(aOffset + index, 1, &detailedGlyph); + } + continue; + } + } + + if (IsInvalidChar(ch)) { + // invalid chars are left as zero-width/invisible + continue; + } + + // record char code so we can draw a box with the Unicode value + aTextRun->SetMissingGlyph(aOffset + index, ch, mainFont); + if (!mSkipDrawing && !IsPUA(ch)) { + missingChars = true; + if (mFontMatchingStats) { + mFontMatchingStats->mFallbacks |= FallbackTypes::MissingFont; + } + } + } + } + + runStart += matchedLength; + } + + if (aMFR && missingChars) { + aMFR->RecordScript(aRunScript); + } +} + +gfxTextRun* gfxFontGroup::GetEllipsisTextRun( + int32_t aAppUnitsPerDevPixel, gfx::ShapedTextFlags aFlags, + LazyReferenceDrawTargetGetter& aRefDrawTargetGetter) { + MOZ_ASSERT(!(aFlags & ~ShapedTextFlags::TEXT_ORIENT_MASK), + "flags here should only be used to specify orientation"); + if (mCachedEllipsisTextRun && + (mCachedEllipsisTextRun->GetFlags() & + ShapedTextFlags::TEXT_ORIENT_MASK) == aFlags && + mCachedEllipsisTextRun->GetAppUnitsPerDevUnit() == aAppUnitsPerDevPixel) { + return mCachedEllipsisTextRun.get(); + } + + // Use a Unicode ellipsis if the font supports it, + // otherwise use three ASCII periods as fallback. + gfxFont* firstFont = GetFirstValidFont(uint32_t(kEllipsisChar[0])); + nsString ellipsis = + firstFont->HasCharacter(kEllipsisChar[0]) + ? nsDependentString(kEllipsisChar, ArrayLength(kEllipsisChar) - 1) + : nsDependentString(kASCIIPeriodsChar, + ArrayLength(kASCIIPeriodsChar) - 1); + + RefPtr<DrawTarget> refDT = aRefDrawTargetGetter.GetRefDrawTarget(); + Parameters params = {refDT, nullptr, nullptr, + nullptr, 0, aAppUnitsPerDevPixel}; + mCachedEllipsisTextRun = + MakeTextRun(ellipsis.get(), ellipsis.Length(), ¶ms, aFlags, + nsTextFrameUtils::Flags(), nullptr); + if (!mCachedEllipsisTextRun) { + return nullptr; + } + // don't let the presence of a cached ellipsis textrun prolong the + // fontgroup's life + mCachedEllipsisTextRun->ReleaseFontGroup(); + return mCachedEllipsisTextRun.get(); +} + +gfxFont* gfxFontGroup::FindFallbackFaceForChar( + gfxFontFamily* aFamily, uint32_t aCh, uint32_t aNextCh, + eFontPresentation aPresentation) { + GlobalFontMatch data(aCh, aNextCh, mStyle, aPresentation); + aFamily->SearchAllFontsForChar(&data); + gfxFontEntry* fe = data.mBestMatch; + if (!fe) { + return nullptr; + } + return fe->FindOrMakeFont(&mStyle); +} + +gfxFont* gfxFontGroup::FindFallbackFaceForChar( + fontlist::Family* aFamily, uint32_t aCh, uint32_t aNextCh, + eFontPresentation aPresentation) { + auto* pfl = gfxPlatformFontList::PlatformFontList(); + auto* list = pfl->SharedFontList(); + + // If async fallback is enabled, and the family isn't fully initialized yet, + // just start the async cmap loading and return. + if (!aFamily->IsFullyInitialized() && + StaticPrefs::gfx_font_rendering_fallback_async()) { + pfl->StartCmapLoadingFromFamily(aFamily - list->Families()); + return nullptr; + } + + GlobalFontMatch data(aCh, aNextCh, mStyle, aPresentation); + aFamily->SearchAllFontsForChar(list, &data); + gfxFontEntry* fe = data.mBestMatch; + if (!fe) { + return nullptr; + } + return fe->FindOrMakeFont(&mStyle); +} + +gfxFont* gfxFontGroup::FindFallbackFaceForChar( + const FamilyFace& aFamily, uint32_t aCh, uint32_t aNextCh, + eFontPresentation aPresentation) { + if (aFamily.IsSharedFamily()) { + return FindFallbackFaceForChar(aFamily.SharedFamily(), aCh, aNextCh, + aPresentation); + } + return FindFallbackFaceForChar(aFamily.OwnedFamily(), aCh, aNextCh, + aPresentation); +} + +gfxFloat gfxFontGroup::GetUnderlineOffset() { + if (mUnderlineOffset == UNDERLINE_OFFSET_NOT_SET) { + // if the fontlist contains a bad underline font, make the underline + // offset the min of the first valid font and bad font underline offsets + uint32_t len = mFonts.Length(); + for (uint32_t i = 0; i < len; i++) { + FamilyFace& ff = mFonts[i]; + gfxFontEntry* fe = ff.FontEntry(); + if (!fe) { + continue; + } + if (!fe->mIsUserFontContainer && !fe->IsUserFont() && + ((ff.IsSharedFamily() && ff.SharedFamily() && + ff.SharedFamily()->IsBadUnderlineFamily()) || + (!ff.IsSharedFamily() && ff.OwnedFamily() && + ff.OwnedFamily()->IsBadUnderlineFamily()))) { + gfxFont* font = GetFontAt(i); + if (!font) { + continue; + } + gfxFloat bad = + font->GetMetrics(nsFontMetrics::eHorizontal).underlineOffset; + gfxFloat first = GetFirstValidFont() + ->GetMetrics(nsFontMetrics::eHorizontal) + .underlineOffset; + mUnderlineOffset = std::min(first, bad); + return mUnderlineOffset; + } + } + + // no bad underline fonts, use the first valid font's metric + mUnderlineOffset = GetFirstValidFont() + ->GetMetrics(nsFontMetrics::eHorizontal) + .underlineOffset; + } + + return mUnderlineOffset; +} + +#define NARROW_NO_BREAK_SPACE 0x202fu + +gfxFont* gfxFontGroup::FindFontForChar(uint32_t aCh, uint32_t aPrevCh, + uint32_t aNextCh, Script aRunScript, + gfxFont* aPrevMatchedFont, + FontMatchType* aMatchType) { + // If the char is a cluster extender, we want to use the same font as the + // preceding character if possible. This is preferable to using the font + // group because it avoids breaks in shaping within a cluster. + if (aPrevMatchedFont && IsClusterExtender(aCh)) { + if (aPrevMatchedFont->HasCharacter(aCh) || IsDefaultIgnorable(aCh)) { + return aPrevMatchedFont; + } + // Get the singleton NFC normalizer; this does not need to be deleted. + static UErrorCode err = U_ZERO_ERROR; + static const UNormalizer2* nfc = unorm2_getNFCInstance(&err); + // Check if this char and preceding char can compose; if so, is the + // combination supported by the current font. + int32_t composed = unorm2_composePair(nfc, aPrevCh, aCh); + if (composed > 0 && aPrevMatchedFont->HasCharacter(composed)) { + return aPrevMatchedFont; + } + } + + // Special cases for NNBSP (as used in Mongolian): + if (aCh == NARROW_NO_BREAK_SPACE) { + // If there is no preceding character, try the font that we'd use + // for the next char (unless it's just another NNBSP; we don't try + // to look ahead through a whole run of them). + if (!aPrevCh && aNextCh && aNextCh != NARROW_NO_BREAK_SPACE) { + gfxFont* nextFont = FindFontForChar(aNextCh, 0, 0, aRunScript, + aPrevMatchedFont, aMatchType); + if (nextFont && nextFont->HasCharacter(aCh)) { + return nextFont; + } + } + // Otherwise, treat NNBSP like a cluster extender (as above) and try + // to continue the preceding font run. + if (aPrevMatchedFont && aPrevMatchedFont->HasCharacter(aCh)) { + return aPrevMatchedFont; + } + } + + // To optimize common cases, try the first font in the font-group + // before going into the more detailed checks below + uint32_t fontListLength = mFonts.Length(); + uint32_t nextIndex = 0; + bool isJoinControl = gfxFontUtils::IsJoinControl(aCh); + bool wasJoinCauser = gfxFontUtils::IsJoinCauser(aPrevCh); + bool isVarSelector = gfxFontUtils::IsVarSelector(aCh); + bool nextIsVarSelector = gfxFontUtils::IsVarSelector(aNextCh); + + // For Unicode hyphens, if not supported in the font then we'll try for + // the ASCII hyphen-minus as a fallback. + uint32_t fallbackChar = (aCh == 0x2010 || aCh == 0x2011) ? '-' : 0; + + // Whether we've seen a font that is currently loading a resource that may + // provide this character (so we should not start a new load). + bool loading = false; + + // Do we need to explicitly look for a font that does or does not provide a + // color glyph for the given character? + // For characters with no `EMOJI` property, we'll use whatever the family + // list calls for; but if it's a potential emoji codepoint, we need to check + // if there's a variation selector specifically asking for Text-style or + // Emoji-style rendering and look for a suitable font. + eFontPresentation presentation = eFontPresentation::Any; + EmojiPresentation emojiPresentation = GetEmojiPresentation(aCh); + if (emojiPresentation != TextOnly) { + // If the prefer-emoji selector is present, or if it's a default-emoji char + // and the prefer-text selector is NOT present, or if there's a skin-tone + // modifier, we specifically look for a font with a color glyph. + // If the prefer-text selector is present, we specifically look for a font + // that will provide a monochrome glyph. + // Otherwise, we'll accept either color or monochrome font-family entries, + // so that a color font can be explicitly applied via font-family even to + // characters that are not inherently emoji-style. + if (aNextCh == kVariationSelector16 || + (aNextCh >= kEmojiSkinToneFirst && aNextCh <= kEmojiSkinToneLast)) { + // Emoji presentation is explicitly requested by a variation selector or + // the presence of a skin-tone codepoint. + presentation = eFontPresentation::EmojiExplicit; + } else if (emojiPresentation == EmojiPresentation::EmojiDefault && + aNextCh != kVariationSelector15) { + // Emoji presentation is the default for this Unicode character. but we + // will allow an explicitly-specified webfont to apply to it, regardless + // of its glyph type. + presentation = eFontPresentation::EmojiDefault; + } else if (aNextCh == kVariationSelector15) { + // Text presentation is explicitly requested. + presentation = eFontPresentation::Text; + } + } + + if (!isJoinControl && !wasJoinCauser && !isVarSelector && + !nextIsVarSelector && presentation == eFontPresentation::Any) { + gfxFont* firstFont = GetFontAt(0, aCh, &loading); + if (firstFont) { + if (firstFont->HasCharacter(aCh) || + (fallbackChar && firstFont->HasCharacter(fallbackChar))) { + *aMatchType = {FontMatchType::Kind::kFontGroup, mFonts[0].Generic()}; + return firstFont; + } + + gfxFont* font = nullptr; + if (mFonts[0].CheckForFallbackFaces()) { + font = FindFallbackFaceForChar(mFonts[0], aCh, aNextCh, presentation); + } else if (!firstFont->GetFontEntry()->IsUserFont()) { + // For platform fonts (but not userfonts), we may need to do + // fallback within the family to handle cases where some faces + // such as Italic or Black have reduced character sets compared + // to the family's Regular face. + font = FindFallbackFaceForChar(mFonts[0], aCh, aNextCh, presentation); + } + if (font) { + *aMatchType = {FontMatchType::Kind::kFontGroup, mFonts[0].Generic()}; + return font; + } + } else { + if (fontListLength > 0) { + loading = loading || mFonts[0].IsLoadingFor(aCh); + } + } + + // we don't need to check the first font again below + ++nextIndex; + } + + if (aPrevMatchedFont) { + // Don't switch fonts for control characters, regardless of + // whether they are present in the current font, as they won't + // actually be rendered (see bug 716229) + if (isJoinControl || + GetGeneralCategory(aCh) == HB_UNICODE_GENERAL_CATEGORY_CONTROL) { + return aPrevMatchedFont; + } + + // if previous character was a join-causer (ZWJ), + // use the same font as the previous range if we can + if (wasJoinCauser) { + if (aPrevMatchedFont->HasCharacter(aCh)) { + return aPrevMatchedFont; + } + } + } + + // If this character is a variation selector or default-ignorable, use the + // previous font regardless of whether it supports the codepoint or not. + // (We don't want to unnecessarily split glyph runs, and the character will + // not be visibly rendered.) + if (isVarSelector || IsDefaultIgnorable(aCh)) { + return aPrevMatchedFont; + } + + // Used to remember the first "candidate" font that would provide a fallback + // text-style rendering if no color glyph can be found. + gfxFont* candidateFont = nullptr; + FontMatchType candidateMatchType; + + // Handle a candidate font that could support the character, returning true + // if we should go ahead and return |f|, false to continue searching. + auto CheckCandidate = [&](gfxFont* f, FontMatchType t) -> bool { + // If no preference, then just accept the font. + if (presentation == eFontPresentation::Any || + (presentation == eFontPresentation::EmojiDefault && + f->GetFontEntry()->IsUserFont())) { + RefPtr<gfxFont> autoRefDeref(candidateFont); + *aMatchType = t; + return true; + } + // Does the candidate font provide a color glyph for the current character? + bool hasColorGlyph = f->HasColorGlyphFor(aCh, aNextCh); + // If the provided glyph matches the preference, accept the font. + if (hasColorGlyph == PrefersColor(presentation)) { + RefPtr<gfxFont> autoRefDeref(candidateFont); + *aMatchType = t; + return true; + } + // Otherwise, remember the first potential fallback, but keep searching. + if (!candidateFont) { + candidateFont = f; + candidateMatchType = t; + } + return false; + }; + + // 1. check remaining fonts in the font group + for (uint32_t i = nextIndex; i < fontListLength; i++) { + FamilyFace& ff = mFonts[i]; + if (ff.IsInvalid() || ff.IsLoading()) { + if (ff.IsLoadingFor(aCh)) { + loading = true; + } + continue; + } + + gfxFont* font = ff.Font(); + if (font) { + // if available, use already-made gfxFont and check for character + if (font->HasCharacter(aCh) || + (fallbackChar && font->HasCharacter(fallbackChar))) { + if (CheckCandidate(font, + {FontMatchType::Kind::kFontGroup, ff.Generic()})) { + return font; + } + } + } else { + // don't have a gfxFont yet, test charmap before instantiating + gfxFontEntry* fe = ff.FontEntry(); + if (fe && fe->mIsUserFontContainer) { + // for userfonts, need to test both the unicode range map and + // the cmap of the platform font entry + gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe); + + // never match a character outside the defined unicode range + if (!ufe->CharacterInUnicodeRange(aCh)) { + continue; + } + + // Load if not already loaded, unless we've already seen an in- + // progress load that is expected to satisfy this request. + if (!loading && + ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED) { + ufe->Load(); + ff.CheckState(mSkipDrawing); + } + + if (ff.IsLoading()) { + loading = true; + } + + gfxFontEntry* pfe = ufe->GetPlatformFontEntry(); + if (pfe && (pfe->HasCharacter(aCh) || + (fallbackChar && pfe->HasCharacter(fallbackChar)))) { + font = GetFontAt(i, aCh, &loading); + if (font) { + if (CheckCandidate(font, {FontMatchType::Kind::kFontGroup, + mFonts[i].Generic()})) { + return font; + } + } + } + } else if (fe && (fe->HasCharacter(aCh) || + (fallbackChar && fe->HasCharacter(fallbackChar)))) { + // for normal platform fonts, after checking the cmap + // build the font via GetFontAt + font = GetFontAt(i, aCh, &loading); + if (font) { + if (CheckCandidate(font, {FontMatchType::Kind::kFontGroup, + mFonts[i].Generic()})) { + return font; + } + } + } + } + + // check other family faces if needed + if (ff.CheckForFallbackFaces()) { +#ifdef DEBUG + if (i > 0) { + fontlist::FontList* list = + gfxPlatformFontList::PlatformFontList()->SharedFontList(); + nsCString s1 = mFonts[i - 1].IsSharedFamily() + ? mFonts[i - 1].SharedFamily()->Key().AsString(list) + : mFonts[i - 1].OwnedFamily()->Name(); + nsCString s2 = ff.IsSharedFamily() + ? ff.SharedFamily()->Key().AsString(list) + : ff.OwnedFamily()->Name(); + MOZ_ASSERT(!mFonts[i - 1].CheckForFallbackFaces() || !s1.Equals(s2), + "should only do fallback once per font family"); + } +#endif + font = FindFallbackFaceForChar(ff, aCh, aNextCh, presentation); + if (font) { + if (CheckCandidate(font, + {FontMatchType::Kind::kFontGroup, ff.Generic()})) { + return font; + } + } + } else { + // For platform fonts, but not user fonts, consider intra-family + // fallback to handle styles with reduced character sets (see + // also above). + gfxFontEntry* fe = ff.FontEntry(); + if (fe && !fe->mIsUserFontContainer && !fe->IsUserFont()) { + font = FindFallbackFaceForChar(ff, aCh, aNextCh, presentation); + if (font) { + if (CheckCandidate(font, + {FontMatchType::Kind::kFontGroup, ff.Generic()})) { + return font; + } + } + } + } + } + + if (fontListLength == 0) { + gfxFont* defaultFont = GetDefaultFont(); + if (defaultFont->HasCharacter(aCh) || + (fallbackChar && defaultFont->HasCharacter(fallbackChar))) { + if (CheckCandidate(defaultFont, FontMatchType::Kind::kFontGroup)) { + return defaultFont; + } + } + } + + // If character is in Private Use Area, or is unassigned in Unicode, don't do + // matching against pref or system fonts. We only support such codepoints + // when used with an explicitly-specified font, as they have no standard/ + // interoperable meaning. + // Also don't attempt any fallback for control characters or noncharacters, + // where we won't be rendering a glyph anyhow, or for codepoints where global + // fallback has already noted a failure. + if (gfxPlatformFontList::PlatformFontList()->SkipFontFallbackForChar(aCh) || + GetGeneralCategory(aCh) == HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED) { + if (candidateFont) { + *aMatchType = candidateMatchType; + } + return candidateFont; + } + + // 2. search pref fonts + gfxFont* font = WhichPrefFontSupportsChar(aCh, aNextCh, presentation); + if (font) { + if (PrefersColor(presentation)) { + // For emoji, always accept the font from preferences even if it isn't + // actually a color-emoji font, as some users may explicitly choose to + // set their emoji font preference to a monochrome font like Symbola. + // So the font.name-list.emoji preference takes precedence over the + // Unicode presentation style here. + *aMatchType = FontMatchType::Kind::kPrefsFallback; + return font; + } + if (CheckCandidate(font, FontMatchType::Kind::kPrefsFallback)) { + return font; + } + // Don't leak `font` if we decided not to return it. + RefPtr<gfxFont> autoRefDeref(font); + } + + // For fallback searches, we don't want to use a color-emoji font unless + // emoji-style presentation is specifically required, so we map Any to + // Text here. + if (presentation == eFontPresentation::Any) { + presentation = eFontPresentation::Text; + } + + // 3. use fallback fonts + // -- before searching for something else check the font used for the + // previous character + if (aPrevMatchedFont && + (aPrevMatchedFont->HasCharacter(aCh) || + (fallbackChar && aPrevMatchedFont->HasCharacter(fallbackChar)))) { + if (CheckCandidate(aPrevMatchedFont, + FontMatchType::Kind::kSystemFallback)) { + return aPrevMatchedFont; + } + } + + // 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) { + RefPtr<gfxFont> autoRefDeref(candidateFont); + return nullptr; + } + + // -- otherwise look for other stuff + font = WhichSystemFontSupportsChar(aCh, aNextCh, aRunScript, presentation); + if (font) { + if (CheckCandidate(font, FontMatchType::Kind::kSystemFallback)) { + return font; + } + RefPtr<gfxFont> autoRefDeref(font); + } + if (candidateFont) { + *aMatchType = candidateMatchType; + } + return candidateFont; +} + +template <typename T> +void gfxFontGroup::ComputeRanges(nsTArray<TextRange>& aRanges, const T* aString, + uint32_t aLength, Script aRunScript, + gfx::ShapedTextFlags aOrientation) { + NS_ASSERTION(aRanges.Length() == 0, "aRanges must be initially empty"); + NS_ASSERTION(aLength > 0, "don't call ComputeRanges for zero-length text"); + + uint32_t prevCh = 0; + uint32_t nextCh = aString[0]; + if (sizeof(T) == sizeof(char16_t)) { + if (aLength > 1 && NS_IS_SURROGATE_PAIR(nextCh, aString[1])) { + nextCh = SURROGATE_TO_UCS4(nextCh, aString[1]); + } + } + int32_t lastRangeIndex = -1; + + // initialize prevFont to the group's primary font, so that this will be + // used for string-initial control chars, etc rather than risk hitting font + // fallback for these (bug 716229) + StyleGenericFontFamily generic = StyleGenericFontFamily::None; + gfxFont* prevFont = GetFirstValidFont(' ', &generic); + + // if we use the initial value of prevFont, we treat this as a match from + // the font group; fixes bug 978313 + FontMatchType matchType = {FontMatchType::Kind::kFontGroup, generic}; + + for (uint32_t i = 0; i < aLength; i++) { + const uint32_t origI = i; // save off in case we increase for surrogate + + // set up current ch + uint32_t ch = nextCh; + + // Get next char (if any) so that FindFontForChar can look ahead + // for a possible variation selector. + + if (sizeof(T) == sizeof(char16_t)) { + // In 16-bit case only, check for surrogate pairs. + if (ch > 0xffffu) { + i++; + } + if (i < aLength - 1) { + nextCh = aString[i + 1]; + if (i + 2 < aLength && NS_IS_SURROGATE_PAIR(nextCh, aString[i + 2])) { + nextCh = SURROGATE_TO_UCS4(nextCh, aString[i + 2]); + } + } else { + nextCh = 0; + } + } else { + // 8-bit case is trivial. + nextCh = i < aLength - 1 ? aString[i + 1] : 0; + } + + if (ch == 0xa0) { + ch = ' '; + } + + gfxFont* font; + + // Find the font for this char; but try to avoid calling the expensive + // FindFontForChar method for the most common case, where the first + // font in the list supports the current char, and it is not one of + // the special cases where FindFontForChar will attempt to propagate + // the font selected for an adjacent character. + if ((font = GetFontAt(0, ch)) != nullptr && font->HasCharacter(ch) && + (sizeof(T) == sizeof(uint8_t) || + (!IsClusterExtender(ch) && ch != NARROW_NO_BREAK_SPACE && + !gfxFontUtils::IsJoinControl(ch) && + !gfxFontUtils::IsJoinCauser(prevCh) && + !gfxFontUtils::IsVarSelector(ch) && + GetEmojiPresentation(ch) == TextOnly))) { + matchType = {FontMatchType::Kind::kFontGroup, mFonts[0].Generic()}; + } else { + font = + FindFontForChar(ch, prevCh, nextCh, aRunScript, prevFont, &matchType); + } + +#ifndef RELEASE_OR_BETA + if (MOZ_UNLIKELY(mTextPerf)) { + if (matchType.kind == FontMatchType::Kind::kPrefsFallback) { + mTextPerf->current.fallbackPrefs++; + } else if (matchType.kind == FontMatchType::Kind::kSystemFallback) { + mTextPerf->current.fallbackSystem++; + } + } +#endif + + prevCh = ch; + + ShapedTextFlags orient = aOrientation; + if (aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) { + // For CSS text-orientation:mixed, we need to resolve orientation + // on a per-character basis using the UTR50 orientation property. + switch (GetVerticalOrientation(ch)) { + case VERTICAL_ORIENTATION_U: + case VERTICAL_ORIENTATION_Tu: + orient = ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT; + break; + case VERTICAL_ORIENTATION_Tr: { + // We check for a vertical presentation form first as that's + // likely to be cheaper than inspecting lookups to see if the + // 'vert' feature is going to handle this character, and if the + // presentation form is available then it will be used as + // fallback if needed, so it's OK if the feature is missing. + // + // Because "common" CJK punctuation characters in isolation will be + // resolved to Bopomofo script (as the first script listed in their + // ScriptExtensions property), but this is not always well supported + // by fonts' OpenType tables, we also try Han script; harfbuzz will + // apply a 'vert' feature from any available script (see + // https://github.com/harfbuzz/harfbuzz/issues/63) when shaping, + // so this is OK. It's not quite as general as what harfbuzz does + // (it will find the feature in *any* script), but should be enough + // for likely real-world examples. + uint32_t v = gfxHarfBuzzShaper::GetVerticalPresentationForm(ch); + const uint32_t kVert = HB_TAG('v', 'e', 'r', 't'); + orient = (!font || (v && font->HasCharacter(v)) || + font->FeatureWillHandleChar(aRunScript, kVert, ch) || + (aRunScript == Script::BOPOMOFO && + font->FeatureWillHandleChar(Script::HAN, kVert, ch))) + ? ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT + : ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; + break; + } + case VERTICAL_ORIENTATION_R: + orient = ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; + break; + } + } + + if (lastRangeIndex == -1) { + // first char ==> make a new range + aRanges.AppendElement(TextRange(0, 1, font, matchType, orient)); + lastRangeIndex++; + prevFont = font; + } else { + // if font or orientation has changed, make a new range... + // unless ch is a variation selector (bug 1248248) + TextRange& prevRange = aRanges[lastRangeIndex]; + if (prevRange.font != font || + (prevRange.orientation != orient && !IsClusterExtender(ch))) { + // close out the previous range + prevRange.end = origI; + aRanges.AppendElement(TextRange(origI, i + 1, font, matchType, orient)); + lastRangeIndex++; + + // update prevFont for the next match, *unless* we switched + // fonts on a ZWJ, in which case propagating the changed font + // is probably not a good idea (see bug 619511) + if (sizeof(T) == sizeof(uint8_t) || !gfxFontUtils::IsJoinCauser(ch)) { + prevFont = font; + } + } else { + prevRange.matchType |= matchType; + } + } + } + + aRanges[lastRangeIndex].end = aLength; + +#ifndef RELEASE_OR_BETA + LogModule* log = mStyle.systemFont ? gfxPlatform::GetLog(eGfxLog_textrunui) + : gfxPlatform::GetLog(eGfxLog_textrun); + + if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Debug))) { + nsAutoCString lang; + mLanguage->ToUTF8String(lang); + nsAutoCString 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 TextRange& r = aRanges[i]; + nsAutoCString matchTypes; + if (r.matchType.kind & FontMatchType::Kind::kFontGroup) { + matchTypes.AppendLiteral("list"); + } + if (r.matchType.kind & FontMatchType::Kind::kPrefsFallback) { + if (!matchTypes.IsEmpty()) { + matchTypes.AppendLiteral(","); + } + matchTypes.AppendLiteral("prefs"); + } + if (r.matchType.kind & FontMatchType::Kind::kPrefsFallback) { + if (!matchTypes.IsEmpty()) { + matchTypes.AppendLiteral(","); + } + matchTypes.AppendLiteral("sys"); + } + fontMatches.AppendPrintf( + " [%u:%u] %.200s (%s)", r.start, r.end, + (r.font.get() ? r.font->GetName().get() : "<null>"), + matchTypes.get()); + } + MOZ_LOG(log, LogLevel::Debug, + ("(%s-fontmatching) fontgroup: [%s] default: %s lang: %s script: %d" + "%s\n", + (mStyle.systemFont ? "textrunui" : "textrun"), families.get(), + (mFamilyList.GetDefaultFontType() == StyleGenericFontFamily::Serif + ? "serif" + : (mFamilyList.GetDefaultFontType() == + StyleGenericFontFamily::SansSerif + ? "sans-serif" + : "none")), + lang.get(), static_cast<int>(aRunScript), fontMatches.get())); + } +#endif +} + +gfxUserFontSet* gfxFontGroup::GetUserFontSet() { return mUserFontSet; } + +void gfxFontGroup::SetUserFontSet(gfxUserFontSet* aUserFontSet) { + if (aUserFontSet == mUserFontSet) { + return; + } + mUserFontSet = aUserFontSet; + mCurrGeneration = GetGeneration() - 1; + UpdateUserFonts(); +} + +uint64_t gfxFontGroup::GetGeneration() { + if (!mUserFontSet) return 0; + return mUserFontSet->GetGeneration(); +} + +uint64_t gfxFontGroup::GetRebuildGeneration() { + if (!mUserFontSet) return 0; + return mUserFontSet->GetRebuildGeneration(); +} + +void gfxFontGroup::UpdateUserFonts() { + if (mCurrGeneration < GetRebuildGeneration()) { + // fonts in userfont set changed, need to redo the fontlist + mFonts.Clear(); + ClearCachedData(); + BuildFontList(); + mCurrGeneration = GetGeneration(); + } else if (mCurrGeneration != GetGeneration()) { + // load state change occurred, verify load state and validity of fonts + ClearCachedData(); + + uint32_t len = mFonts.Length(); + for (uint32_t i = 0; i < len; i++) { + FamilyFace& ff = mFonts[i]; + if (ff.Font() || !ff.IsUserFontContainer()) { + continue; + } + ff.CheckState(mSkipDrawing); + } + + mCurrGeneration = GetGeneration(); + } +} + +bool gfxFontGroup::ContainsUserFont(const gfxUserFontEntry* aUserFont) { + UpdateUserFonts(); + // search through the fonts list for a specific user font + uint32_t len = mFonts.Length(); + for (uint32_t i = 0; i < len; i++) { + FamilyFace& ff = mFonts[i]; + if (ff.EqualsUserFont(aUserFont)) { + return true; + } + } + return false; +} + +gfxFont* gfxFontGroup::WhichPrefFontSupportsChar( + uint32_t aCh, uint32_t aNextCh, eFontPresentation aPresentation) { + eFontPrefLang charLang; + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + + if (PrefersColor(aPresentation)) { + charLang = eFontPrefLang_Emoji; + } else { + // get the pref font list if it hasn't been set up already + charLang = pfl->GetFontPrefLangFor(aCh); + } + + // if the last pref font was the first family in the pref list, no need to + // recheck through a list of families + if (mLastPrefFont && charLang == mLastPrefLang && mLastPrefFirstFont && + mLastPrefFont->HasCharacter(aCh)) { + return mLastPrefFont; + } + + // based on char lang and page lang, set up list of pref lang fonts to check + eFontPrefLang prefLangs[kMaxLenPrefLangList]; + uint32_t i, numLangs = 0; + + pfl->GetLangPrefs(prefLangs, numLangs, charLang, mPageLang); + + for (i = 0; i < numLangs; i++) { + eFontPrefLang currentLang = prefLangs[i]; + StyleGenericFontFamily defaultGeneric = pfl->GetDefaultGeneric(currentLang); + gfxPlatformFontList::PrefFontList* 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 + FontFamily family = (*families)[j]; + if (family.IsNull()) { + continue; + } + + // if a pref font is used, it's likely to be used again in the same text + // run. the style doesn't change so the face lookup can be cached rather + // than calling FindOrMakeFont repeatedly. speeds up FindFontForChar + // lookup times for subsequent pref font lookups + if (family == mLastPrefFamily && mLastPrefFont->HasCharacter(aCh)) { + return mLastPrefFont; + } + + gfxFontEntry* fe = nullptr; + if (family.mIsShared) { + fontlist::Family* fam = family.mShared; + if (!fam->IsInitialized()) { + Unused << pfl->InitializeFamily(fam); + } + fontlist::Face* face = + fam->FindFaceForStyle(pfl->SharedFontList(), mStyle); + if (face) { + fe = pfl->GetOrCreateFontEntry(face, fam); + } + } else { + fe = family.mUnshared->FindFontForStyle(mStyle); + } + if (!fe) { + continue; + } + + // if ch in cmap, create and return a gfxFont + gfxFont* prefFont = nullptr; + if (fe->HasCharacter(aCh)) { + prefFont = fe->FindOrMakeFont(&mStyle); + if (!prefFont) { + continue; + } + } + + // If the char was not available, see if we can fall back to an + // alternative face in the same family. + if (!prefFont) { + prefFont = family.mIsShared + ? FindFallbackFaceForChar(family.mShared, aCh, aNextCh, + aPresentation) + : FindFallbackFaceForChar(family.mUnshared, aCh, aNextCh, + aPresentation); + } + if (prefFont) { + mLastPrefFamily = family; + mLastPrefFont = prefFont; + mLastPrefLang = charLang; + mLastPrefFirstFont = (i == 0 && j == 0); + if (mFontMatchingStats) { + mFontMatchingStats->mFallbacks |= FallbackTypes::FallbackToPrefsFont; + } + return prefFont; + } + } + } + + return nullptr; +} + +gfxFont* gfxFontGroup::WhichSystemFontSupportsChar( + uint32_t aCh, uint32_t aNextCh, Script aRunScript, + eFontPresentation aPresentation) { + FontVisibility visibility; + gfxFont* font = + gfxPlatformFontList::PlatformFontList()->SystemFindFontForChar( + aCh, aNextCh, aRunScript, aPresentation, &mStyle, &visibility, + mFontMatchingStats); + if (font) { + if (mFontMatchingStats) { + switch (visibility) { + case FontVisibility::Unknown: + // We don't currently track stats for systems lacking font visibility + // categories, so just ignore this. + break; + case FontVisibility::Base: + mFontMatchingStats->mFallbacks |= FallbackTypes::FallbackToBaseFont; + break; + case FontVisibility::LangPack: + mFontMatchingStats->mFallbacks |= + FallbackTypes::FallbackToLangPackFont; + break; + case FontVisibility::User: + mFontMatchingStats->mFallbacks |= FallbackTypes::FallbackToUserFont; + break; + case FontVisibility::Hidden: + // If macOS font fallback uses the system font, we consider this a + // base OS font even though it wouldn't be exposed via font-family. + mFontMatchingStats->mFallbacks |= FallbackTypes::FallbackToBaseFont; + break; + case FontVisibility::Webfont: + default: + MOZ_ASSERT_UNREACHABLE("this can't happen!"); + break; + } + } + return font; + } + + return nullptr; +} + +void gfxMissingFontRecorder::Flush() { + static bool mNotifiedFontsInitialized = false; + static uint32_t mNotifiedFonts[gfxMissingFontRecorder::kNumScriptBitsWords]; + if (!mNotifiedFontsInitialized) { + memset(&mNotifiedFonts, 0, sizeof(mNotifiedFonts)); + mNotifiedFontsInitialized = true; + } + + nsAutoString fontNeeded; + for (uint32_t i = 0; i < kNumScriptBitsWords; ++i) { + mMissingFonts[i] &= ~mNotifiedFonts[i]; + if (!mMissingFonts[i]) { + continue; + } + for (uint32_t j = 0; j < 32; ++j) { + if (!(mMissingFonts[i] & (1 << j))) { + continue; + } + mNotifiedFonts[i] |= (1 << j); + if (!fontNeeded.IsEmpty()) { + fontNeeded.Append(char16_t(',')); + } + uint32_t sc = i * 32 + j; + MOZ_ASSERT(sc < static_cast<uint32_t>(Script::NUM_SCRIPT_CODES), + "how did we set the bit for an invalid script code?"); + uint32_t tag = GetScriptTagForCode(static_cast<Script>(sc)); + fontNeeded.Append(char16_t(tag >> 24)); + fontNeeded.Append(char16_t((tag >> 16) & 0xff)); + fontNeeded.Append(char16_t((tag >> 8) & 0xff)); + fontNeeded.Append(char16_t(tag & 0xff)); + } + mMissingFonts[i] = 0; + } + if (!fontNeeded.IsEmpty()) { + nsCOMPtr<nsIObserverService> service = GetObserverService(); + service->NotifyObservers(nullptr, "font-needed", fontNeeded.get()); + } +} diff --git a/gfx/thebes/gfxTextRun.h b/gfx/thebes/gfxTextRun.h new file mode 100644 index 0000000000..e5720b21e6 --- /dev/null +++ b/gfx/thebes/gfxTextRun.h @@ -0,0 +1,1551 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=4 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_TEXTRUN_H +#define GFX_TEXTRUN_H + +#include <stdint.h> + +#include "gfxTypes.h" +#include "gfxPoint.h" +#include "gfxFont.h" +#include "gfxFontConstants.h" +#include "gfxSkipChars.h" +#include "gfxPlatform.h" +#include "gfxPlatformFontList.h" +#include "gfxUserFontSet.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefPtr.h" +#include "nsPoint.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsTextFrameUtils.h" +#include "DrawMode.h" +#include "harfbuzz/hb.h" +#include "nsUnicodeScriptCodes.h" +#include "nsColor.h" +#include "nsFrameList.h" +#include "X11UndefineNone.h" + +#ifdef DEBUG_FRAME_DUMP +# include <stdio.h> +#endif + +class gfxContext; +class gfxFontGroup; +class nsAtom; +class nsLanguageAtomService; +class gfxMissingFontRecorder; + +namespace mozilla { +class PostTraversalTask; +class SVGContextPaint; +enum class StyleHyphens : uint8_t; +}; // namespace mozilla + +/** + * Callback for Draw() to use when drawing text with mode + * DrawMode::GLYPH_PATH. + */ +struct gfxTextRunDrawCallbacks { + /** + * Constructs a new DrawCallbacks object. + * + * @param aShouldPaintSVGGlyphs If true, SVG glyphs will be painted. If + * false, SVG glyphs will not be painted; fallback plain glyphs are not + * emitted either. + */ + explicit gfxTextRunDrawCallbacks(bool aShouldPaintSVGGlyphs = false) + : mShouldPaintSVGGlyphs(aShouldPaintSVGGlyphs) {} + + /** + * Called when a path has been emitted to the gfxContext when + * painting a text run. This can be called any number of times, + * due to partial ligatures and intervening SVG glyphs. + */ + virtual void NotifyGlyphPathEmitted() = 0; + + bool mShouldPaintSVGGlyphs; +}; + +/** + * gfxTextRun is an abstraction for drawing and measuring substrings of a run + * of text. It stores runs of positioned glyph data, each run having a single + * gfxFont. The glyphs are associated with a string of source text, and the + * gfxTextRun APIs take parameters that are offsets into that source text. + * + * gfxTextRuns are mostly immutable. The only things that can change are + * inter-cluster spacing and line break placement. Spacing is always obtained + * lazily by methods that need it, it is not cached. Line breaks are stored + * persistently (insofar as they affect the shaping of glyphs; gfxTextRun does + * not actually do anything to explicitly account for line breaks). Initially + * there are no line breaks. The textrun can record line breaks before or after + * any given cluster. (Line breaks specified inside clusters are ignored.) + * + * It is important that zero-length substrings are handled correctly. This will + * be on the test! + */ +class gfxTextRun : public gfxShapedText { + NS_INLINE_DECL_REFCOUNTING(gfxTextRun); + + protected: + // Override operator delete to properly free the object that was + // allocated via malloc. + void operator delete(void* p) { free(p); } + + virtual ~gfxTextRun(); + + public: + typedef gfxFont::RunMetrics Metrics; + typedef mozilla::gfx::DrawTarget DrawTarget; + + // Public textrun API for general use + + bool IsClusterStart(uint32_t aPos) const { + MOZ_ASSERT(aPos < GetLength()); + return mCharacterGlyphs[aPos].IsClusterStart(); + } + bool IsLigatureGroupStart(uint32_t aPos) const { + MOZ_ASSERT(aPos < GetLength()); + return mCharacterGlyphs[aPos].IsLigatureGroupStart(); + } + bool CanBreakLineBefore(uint32_t aPos) const { + return CanBreakBefore(aPos) == CompressedGlyph::FLAG_BREAK_TYPE_NORMAL; + } + bool CanHyphenateBefore(uint32_t aPos) const { + return CanBreakBefore(aPos) == CompressedGlyph::FLAG_BREAK_TYPE_HYPHEN; + } + + // Returns a gfxShapedText::CompressedGlyph::FLAG_BREAK_TYPE_* value + // as defined in gfxFont.h (may be NONE, NORMAL or HYPHEN). + uint8_t CanBreakBefore(uint32_t aPos) const { + MOZ_ASSERT(aPos < GetLength()); + return mCharacterGlyphs[aPos].CanBreakBefore(); + } + + bool CharIsSpace(uint32_t aPos) const { + MOZ_ASSERT(aPos < GetLength()); + return mCharacterGlyphs[aPos].CharIsSpace(); + } + bool CharIsTab(uint32_t aPos) const { + MOZ_ASSERT(aPos < GetLength()); + return mCharacterGlyphs[aPos].CharIsTab(); + } + bool CharIsNewline(uint32_t aPos) const { + MOZ_ASSERT(aPos < GetLength()); + return mCharacterGlyphs[aPos].CharIsNewline(); + } + bool CharMayHaveEmphasisMark(uint32_t aPos) const { + MOZ_ASSERT(aPos < GetLength()); + return mCharacterGlyphs[aPos].CharMayHaveEmphasisMark(); + } + bool CharIsFormattingControl(uint32_t aPos) const { + MOZ_ASSERT(aPos < GetLength()); + return mCharacterGlyphs[aPos].CharIsFormattingControl(); + } + + // All offsets are in terms of the string passed into MakeTextRun. + + // Describe range [start, end) of a text run. The range is + // restricted to grapheme cluster boundaries. + struct Range { + uint32_t start; + uint32_t end; + uint32_t Length() const { return end - start; } + + Range() : start(0), end(0) {} + Range(uint32_t aStart, uint32_t aEnd) : start(aStart), end(aEnd) {} + explicit Range(const gfxTextRun* aTextRun) + : start(0), end(aTextRun->GetLength()) {} + }; + + // All coordinates are in layout/app units + + /** + * Set the potential linebreaks for a substring of the textrun. These are + * the "allow break before" points. Initially, there are no potential + * linebreaks. + * + * This can change glyphs and/or geometry! Some textruns' shapes + * depend on potential line breaks (e.g., title-case-converting textruns). + * This function is virtual so that those textruns can reshape themselves. + * + * @return true if this changed the linebreaks, false if the new line + * breaks are the same as the old + */ + virtual bool SetPotentialLineBreaks(Range aRange, + const uint8_t* aBreakBefore); + + enum class HyphenType : uint8_t { + // Code in BreakAndMeasureText depends on the ordering of these values! + None, + Explicit, + Soft, + AutoWithManualInSameWord, + AutoWithoutManualInSameWord + }; + + struct HyphenationState { + uint32_t mostRecentBoundary = 0; + bool hasManualHyphen = false; + bool hasExplicitHyphen = false; + bool hasAutoHyphen = false; + }; + + /** + * Layout provides PropertyProvider objects. These allow detection of + * potential line break points and computation of spacing. We pass the data + * this way to allow lazy data acquisition; for example BreakAndMeasureText + * will want to only ask for properties of text it's actually looking at. + * + * NOTE that requested spacing may not actually be applied, if the textrun + * is unable to apply it in some context. Exception: spacing around a + * whitespace character MUST always be applied. + */ + class PropertyProvider { + public: + // Detect hyphenation break opportunities in the given range; breaks + // not at cluster boundaries will be ignored. + virtual void GetHyphenationBreaks(Range aRange, + HyphenType* aBreakBefore) const = 0; + + // Returns the provider's hyphenation setting, so callers can decide + // whether it is necessary to call GetHyphenationBreaks. + // Result is an StyleHyphens value. + virtual mozilla::StyleHyphens GetHyphensOption() const = 0; + + // Returns the extra width that will be consumed by a hyphen. This should + // be constant for a given textrun. + virtual gfxFloat GetHyphenWidth() const = 0; + + typedef gfxFont::Spacing Spacing; + + /** + * Get the spacing around the indicated characters. Spacing must be zero + * inside clusters. In other words, if character i is not + * CLUSTER_START, then character i-1 must have zero after-spacing and + * character i must have zero before-spacing. + */ + virtual void GetSpacing(Range aRange, Spacing* aSpacing) const = 0; + + // Returns a gfxContext that can be used to measure the hyphen glyph. + // Only called if the hyphen width is requested. + virtual already_AddRefed<DrawTarget> GetDrawTarget() const = 0; + + // Return the appUnitsPerDevUnit value to be used when measuring. + // Only called if the hyphen width is requested. + virtual uint32_t GetAppUnitsPerDevUnit() const = 0; + }; + + struct MOZ_STACK_CLASS DrawParams { + gfxContext* context; + DrawMode drawMode = DrawMode::GLYPH_FILL; + nscolor textStrokeColor = 0; + gfxPattern* textStrokePattern = nullptr; + const mozilla::gfx::StrokeOptions* strokeOpts = nullptr; + const mozilla::gfx::DrawOptions* drawOpts = nullptr; + PropertyProvider* provider = nullptr; + // If non-null, the advance width of the substring is set. + gfxFloat* advanceWidth = nullptr; + mozilla::SVGContextPaint* contextPaint = nullptr; + gfxTextRunDrawCallbacks* callbacks = nullptr; + explicit DrawParams(gfxContext* aContext) : context(aContext) {} + }; + + /** + * Draws a substring. Uses only GetSpacing from aBreakProvider. + * The provided point is the baseline origin on the left of the string + * for LTR, on the right of the string for RTL. + * + * Drawing should respect advance widths in the sense that for LTR runs, + * Draw(Range(start, middle), pt, ...) followed by + * Draw(Range(middle, end), gfxPoint(pt.x + advance, pt.y), ...) + * should have the same effect as + * Draw(Range(start, end), pt, ...) + * + * For RTL runs the rule is: + * Draw(Range(middle, end), pt, ...) followed by + * Draw(Range(start, middle), gfxPoint(pt.x + advance, pt.y), ...) + * should have the same effect as + * Draw(Range(start, end), pt, ...) + * + * Glyphs should be drawn in logical content order, which can be significant + * if they overlap (perhaps due to negative spacing). + */ + void Draw(const Range aRange, const mozilla::gfx::Point aPt, + const DrawParams& aParams) const; + + /** + * Draws the emphasis marks for this text run. Uses only GetSpacing + * from aProvider. The provided point is the baseline origin of the + * line of emphasis marks. + */ + void DrawEmphasisMarks(gfxContext* aContext, gfxTextRun* aMark, + gfxFloat aMarkAdvance, mozilla::gfx::Point aPt, + Range aRange, PropertyProvider* aProvider) const; + + /** + * Computes the ReflowMetrics for a substring. + * Uses GetSpacing from aBreakProvider. + * @param aBoundingBoxType which kind of bounding box (loose/tight) + */ + Metrics MeasureText(Range aRange, gfxFont::BoundingBoxType aBoundingBoxType, + DrawTarget* aDrawTargetForTightBoundingBox, + PropertyProvider* aProvider) const; + + Metrics MeasureText(gfxFont::BoundingBoxType aBoundingBoxType, + DrawTarget* aDrawTargetForTightBoundingBox, + PropertyProvider* aProvider = nullptr) const { + return MeasureText(Range(this), aBoundingBoxType, + aDrawTargetForTightBoundingBox, aProvider); + } + + /** + * Computes just the advance width for a substring. + * Uses GetSpacing from aBreakProvider. + * If aSpacing is not null, the spacing attached before and after + * the substring would be returned in it. NOTE: the spacing is + * included in the advance width. + */ + gfxFloat GetAdvanceWidth(Range aRange, PropertyProvider* aProvider, + PropertyProvider::Spacing* aSpacing = nullptr) const; + + gfxFloat GetAdvanceWidth() const { + return GetAdvanceWidth(Range(this), nullptr); + } + + /** + * Computes the minimum advance width for a substring assuming line + * breaking is allowed everywhere. + */ + gfxFloat GetMinAdvanceWidth(Range aRange); + + /** + * Clear all stored line breaks for the given range (both before and after), + * and then set the line-break state before aRange.start to aBreakBefore and + * after the last cluster to aBreakAfter. + * + * We require that before and after line breaks be consistent. For clusters + * i and i+1, we require that if there is a break after cluster i, a break + * will be specified before cluster i+1. This may be temporarily violated + * (e.g. after reflowing line L and before reflowing line L+1); to handle + * these temporary violations, we say that there is a break betwen i and i+1 + * if a break is specified after i OR a break is specified before i+1. + * + * This can change textrun geometry! The existence of a linebreak can affect + * the advance width of the cluster before the break (when kerning) or the + * geometry of one cluster before the break or any number of clusters + * after the break. (The one-cluster-before-the-break limit is somewhat + * arbitrary; if some scripts require breaking it, then we need to + * alter nsTextFrame::TrimTrailingWhitespace, perhaps drastically becase + * it could affect the layout of frames before it...) + * + * We return true if glyphs or geometry changed, false otherwise. This + * function is virtual so that gfxTextRun subclasses can reshape + * properly. + * + * @param aAdvanceWidthDelta if non-null, returns the change in advance + * width of the given range. + */ + virtual bool SetLineBreaks(Range aRange, bool aLineBreakBefore, + bool aLineBreakAfter, + gfxFloat* aAdvanceWidthDelta); + + enum SuppressBreak { + eNoSuppressBreak, + // Measure the range of text as if there is no break before it. + eSuppressInitialBreak, + // Measure the range of text as if it contains no break + eSuppressAllBreaks + }; + + void ClassifyAutoHyphenations(uint32_t aStart, Range aRange, + nsTArray<HyphenType>& aHyphenBuffer, + HyphenationState* aWordState); + + /** + * Finds the longest substring that will fit into the given width. + * Uses GetHyphenationBreaks and GetSpacing from aProvider. + * Guarantees the following: + * -- 0 <= result <= aMaxLength + * -- result is the maximal value of N such that either + * N < aMaxLength && line break at N && + * GetAdvanceWidth(Range(aStart, N), aProvider) <= aWidth + * OR N < aMaxLength && hyphen break at N && + * GetAdvanceWidth(Range(aStart, N), aProvider) + + * GetHyphenWidth() <= aWidth + * OR N == aMaxLength && + * GetAdvanceWidth(Range(aStart, N), aProvider) <= aWidth + * where GetAdvanceWidth assumes the effect of + * SetLineBreaks(Range(aStart, N), + * aLineBreakBefore, N < aMaxLength, aProvider) + * -- if no such N exists, then result is the smallest N such that + * N < aMaxLength && line break at N + * OR N < aMaxLength && hyphen break at N + * OR N == aMaxLength + * + * The call has the effect of + * SetLineBreaks(Range(aStart, result), aLineBreakBefore, + * result < aMaxLength, aProvider) + * and the returned metrics and the invariants above reflect this. + * + * @param aMaxLength this can be UINT32_MAX, in which case the length used + * is up to the end of the string + * @param aLineBreakBefore set to true if and only if there is an actual + * line break at the start of this string. + * @param aSuppressBreak what break should be suppressed. + * @param aTrimWhitespace if non-null, then we allow a trailing run of + * spaces to be trimmed; the width of the space(s) will not be included in + * the measured string width for comparison with the limit aWidth, and + * trimmed spaces will not be included in returned metrics. The width + * of the trimmed spaces will be returned in aTrimWhitespace. + * Trimmed spaces are still counted in the "characters fit" result. + * @param aHangWhitespace true if we allow whitespace to overflow the + * container at a soft-wrap + * @param aMetrics if non-null, we fill this in for the returned substring. + * If a hyphenation break was used, the hyphen is NOT included in the returned + * metrics. + * @param aBoundingBoxType whether to make the bounding box in aMetrics tight + * @param aDrawTargetForTightBoundingbox a reference DrawTarget to get the + * tight bounding box, if requested + * @param aUsedHyphenation if non-null, records if we selected a hyphenation + * break + * @param aLastBreak if non-null and result is aMaxLength, we set this to + * the maximal N such that + * N < aMaxLength && line break at N && + * GetAdvanceWidth(Range(aStart, N), aProvider) <= aWidth + * OR N < aMaxLength && hyphen break at N && + * GetAdvanceWidth(Range(aStart, N), aProvider) + + * GetHyphenWidth() <= aWidth + * or UINT32_MAX if no such N exists, where GetAdvanceWidth assumes + * the effect of + * SetLineBreaks(Range(aStart, N), aLineBreakBefore, + * N < aMaxLength, aProvider) + * + * @param aCanWordWrap true if we can break between any two grapheme + * clusters. This is set by overflow-wrap|word-wrap: break-word + * + * @param aBreakPriority in/out the priority of the break opportunity + * saved in the line. If we are prioritizing break opportunities, we will + * not set a break with a lower priority. @see gfxBreakPriority. + * + * Note that negative advance widths are possible especially if negative + * spacing is provided. + */ + uint32_t BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength, + bool aLineBreakBefore, gfxFloat aWidth, + PropertyProvider* aProvider, + SuppressBreak aSuppressBreak, + gfxFloat* aTrimWhitespace, bool aHangWhitespace, + Metrics* aMetrics, + gfxFont::BoundingBoxType aBoundingBoxType, + DrawTarget* aDrawTargetForTightBoundingBox, + bool* aUsedHyphenation, uint32_t* aLastBreak, + bool aCanWordWrap, bool aCanWhitespaceWrap, + gfxBreakPriority* aBreakPriority); + + // Utility getters + + void* GetUserData() const { return mUserData; } + void SetUserData(void* aUserData) { mUserData = aUserData; } + + void SetFlagBits(nsTextFrameUtils::Flags aFlags) { mFlags2 |= aFlags; } + void ClearFlagBits(nsTextFrameUtils::Flags aFlags) { mFlags2 &= ~aFlags; } + const gfxSkipChars& GetSkipChars() const { return mSkipChars; } + gfxFontGroup* GetFontGroup() const { return mFontGroup; } + + // Call this, don't call "new gfxTextRun" directly. This does custom + // allocation and initialization + static already_AddRefed<gfxTextRun> Create( + const gfxTextRunFactory::Parameters* aParams, uint32_t aLength, + gfxFontGroup* aFontGroup, mozilla::gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2); + + // The text is divided into GlyphRuns as necessary. (In the vast majority + // of cases, a gfxTextRun contains just a single GlyphRun.) + struct GlyphRun { + RefPtr<gfxFont> mFont; // never null in a valid GlyphRun + uint32_t mCharacterOffset; // into original UTF16 string + mozilla::gfx::ShapedTextFlags + mOrientation; // gfxTextRunFactory::TEXT_ORIENT_* value + FontMatchType mMatchType; + bool mIsCJK; // Whether the text was a CJK script run (used to decide if + // text-decoration-skip-ink should not be applied) + + // Set up the properties (but NOT offset) of the GlyphRun. + void SetProperties(gfxFont* aFont, + mozilla::gfx::ShapedTextFlags aOrientation, bool aIsCJK, + FontMatchType aMatchType) { + mFont = aFont; + mOrientation = aOrientation; + mIsCJK = aIsCJK; + mMatchType = aMatchType; + } + + // Return whether the GlyphRun matches the given properties; + // the given FontMatchType will be added to the run if not present. + bool Matches(gfxFont* aFont, mozilla::gfx::ShapedTextFlags aOrientation, + bool aIsCJK, FontMatchType aMatchType) { + if (mFont == aFont && mOrientation == aOrientation && mIsCJK == aIsCJK) { + mMatchType.kind |= aMatchType.kind; + if (mMatchType.generic == mozilla::StyleGenericFontFamily::None) { + mMatchType.generic = aMatchType.generic; + } + return true; + } + return false; + } + }; + + // Script run codes that we will mark as CJK to suppress skip-ink behavior. + static inline bool IsCJKScript(Script aScript) { + switch (aScript) { + case Script::BOPOMOFO: + case Script::HAN: + case Script::HANGUL: + case Script::HIRAGANA: + case Script::KATAKANA: + case Script::KATAKANA_OR_HIRAGANA: + case Script::SIMPLIFIED_HAN: + case Script::TRADITIONAL_HAN: + case Script::JAPANESE: + case Script::KOREAN: + case Script::HAN_WITH_BOPOMOFO: + case Script::JAMO: + return true; + default: + return false; + } + } + + class MOZ_STACK_CLASS GlyphRunIterator { + public: + GlyphRunIterator(const gfxTextRun* aTextRun, Range aRange, + bool aReverse = false) + : mTextRun(aTextRun), + mDirection(aReverse ? -1 : 1), + mStartOffset(aRange.start), + mEndOffset(aRange.end) { + mNextIndex = mTextRun->FindFirstGlyphRunContaining( + aReverse ? aRange.end - 1 : aRange.start); + } + bool NextRun(); + const GlyphRun* GetGlyphRun() const { return mGlyphRun; } + uint32_t GetStringStart() const { return mStringStart; } + uint32_t GetStringEnd() const { return mStringEnd; } + + private: + const gfxTextRun* mTextRun; + MOZ_INIT_OUTSIDE_CTOR const GlyphRun* mGlyphRun; + MOZ_INIT_OUTSIDE_CTOR uint32_t mStringStart; + MOZ_INIT_OUTSIDE_CTOR uint32_t mStringEnd; + const int32_t mDirection; + int32_t mNextIndex; + uint32_t mStartOffset; + uint32_t mEndOffset; + }; + + class GlyphRunOffsetComparator { + public: + bool Equals(const GlyphRun& a, const GlyphRun& b) const { + return a.mCharacterOffset == b.mCharacterOffset; + } + + bool LessThan(const GlyphRun& a, const GlyphRun& b) const { + return a.mCharacterOffset < b.mCharacterOffset; + } + }; + + friend class GlyphRunIterator; + friend class FontSelector; + + // API for setting up the textrun glyphs. Should only be called by + // things that construct textruns. + /** + * We've found a run of text that should use a particular font. Call this + * only during initialization when font substitution has been computed. + * Call it before setting up the glyphs for the characters in this run; + * SetMissingGlyph requires that the correct glyphrun be installed. + * + * If aForceNewRun, a new glyph run will be added, even if the + * previously added run uses the same font. If glyph runs are + * added out of strictly increasing aStartCharIndex order (via + * force), then SortGlyphRuns must be called after all glyph runs + * are added before any further operations are performed with this + * TextRun. + */ + void AddGlyphRun(gfxFont* aFont, FontMatchType aMatchType, + uint32_t aUTF16Offset, bool aForceNewRun, + mozilla::gfx::ShapedTextFlags aOrientation, bool aIsCJK); + void ResetGlyphRuns() { + if (mHasGlyphRunArray) { + MOZ_ASSERT(mGlyphRunArray.Length() > 1); + // Discard all but the first GlyphRun... + mGlyphRunArray.TruncateLength(1); + // ...and then convert to the single-run representation. + ConvertFromGlyphRunArray(); + } + // Clear out the one remaining GlyphRun. + mSingleGlyphRun.mFont = nullptr; + } + void SortGlyphRuns(); + void SanitizeGlyphRuns(); + + const CompressedGlyph* GetCharacterGlyphs() const final { + MOZ_ASSERT(mCharacterGlyphs, "failed to initialize mCharacterGlyphs"); + return mCharacterGlyphs; + } + CompressedGlyph* GetCharacterGlyphs() final { + MOZ_ASSERT(mCharacterGlyphs, "failed to initialize mCharacterGlyphs"); + return mCharacterGlyphs; + } + + // clean out results from shaping in progress, used for fallback scenarios + void ClearGlyphsAndCharacters(); + + void SetSpaceGlyph(gfxFont* aFont, DrawTarget* aDrawTarget, + uint32_t aCharIndex, + mozilla::gfx::ShapedTextFlags aOrientation); + + // Set the glyph data for the given character index to the font's + // space glyph, IF this can be done as a "simple" glyph record + // (not requiring a DetailedGlyph entry). This avoids the need to call + // the font shaper and go through the shaped-word cache for most spaces. + // + // The parameter aSpaceChar is the original character code for which + // this space glyph is being used; if this is U+0020, we need to record + // that it could be trimmed at a run edge, whereas other kinds of space + // (currently just U+00A0) would not be trimmable/breakable. + // + // Returns true if it was able to set simple glyph data for the space; + // if it returns false, the caller needs to fall back to some other + // means to create the necessary (detailed) glyph data. + bool SetSpaceGlyphIfSimple(gfxFont* aFont, uint32_t aCharIndex, + char16_t aSpaceChar, + mozilla::gfx::ShapedTextFlags aOrientation); + + // Record the positions of specific characters that layout may need to + // detect in the textrun, even though it doesn't have an explicit copy + // of the original text. These are recorded using flag bits in the + // CompressedGlyph record; if necessary, we convert "simple" glyph records + // to "complex" ones as the Tab and Newline flags are not present in + // simple CompressedGlyph records. + void SetIsTab(uint32_t aIndex) { EnsureComplexGlyph(aIndex).SetIsTab(); } + void SetIsNewline(uint32_t aIndex) { + EnsureComplexGlyph(aIndex).SetIsNewline(); + } + void SetNoEmphasisMark(uint32_t aIndex) { + EnsureComplexGlyph(aIndex).SetNoEmphasisMark(); + } + void SetIsFormattingControl(uint32_t aIndex) { + EnsureComplexGlyph(aIndex).SetIsFormattingControl(); + } + + /** + * Prefetch all the glyph extents needed to ensure that Measure calls + * on this textrun not requesting tight boundingBoxes will succeed. Note + * that some glyph extents might not be fetched due to OOM or other + * errors. + */ + void FetchGlyphExtents(DrawTarget* aRefDrawTarget); + + const GlyphRun* GetGlyphRuns(uint32_t* aNumGlyphRuns) const { + if (mHasGlyphRunArray) { + *aNumGlyphRuns = mGlyphRunArray.Length(); + return mGlyphRunArray.Elements(); + } else { + *aNumGlyphRuns = mSingleGlyphRun.mFont ? 1 : 0; + return &mSingleGlyphRun; + } + } + + const GlyphRun* TrailingGlyphRun() const { + uint32_t count; + const GlyphRun* runs = GetGlyphRuns(&count); + return count ? runs + count - 1 : nullptr; + } + + // Returns the index of the GlyphRun containing the given offset. + // Returns mGlyphRuns.Length() when aOffset is mCharacterCount. + uint32_t FindFirstGlyphRunContaining(uint32_t aOffset) const; + + // Copy glyph data from a ShapedWord into this textrun. + void CopyGlyphDataFrom(gfxShapedWord* aSource, uint32_t aStart); + + // Copy glyph data for a range of characters from aSource to this + // textrun. + void CopyGlyphDataFrom(gfxTextRun* aSource, Range aRange, uint32_t aDest); + + // Tell the textrun to release its reference to its creating gfxFontGroup + // immediately, rather than on destruction. This is used for textruns + // that are actually owned by a gfxFontGroup, so that they don't keep it + // permanently alive due to a circular reference. (The caller of this is + // taking responsibility for ensuring the textrun will not outlive its + // mFontGroup.) + void ReleaseFontGroup(); + + struct LigatureData { + // textrun range of the containing ligature + Range mRange; + // appunits advance to the start of the ligature part within the ligature; + // never includes any spacing + gfxFloat mPartAdvance; + // appunits width of the ligature part; includes before-spacing + // when the part is at the start of the ligature, and after-spacing + // when the part is as the end of the ligature + gfxFloat mPartWidth; + + bool mClipBeforePart; + bool mClipAfterPart; + }; + + // return storage used by this run, for memory reporter; + // nsTransformedTextRun needs to override this as it holds additional data + virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) + MOZ_MUST_OVERRIDE; + virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) + MOZ_MUST_OVERRIDE; + + nsTextFrameUtils::Flags GetFlags2() const { return mFlags2; } + + // Get the size, if it hasn't already been gotten, marking as it goes. + size_t MaybeSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) { + if (mFlags2 & nsTextFrameUtils::Flags::RunSizeAccounted) { + return 0; + } + mFlags2 |= nsTextFrameUtils::Flags::RunSizeAccounted; + return SizeOfIncludingThis(aMallocSizeOf); + } + void ResetSizeOfAccountingFlags() { + mFlags2 &= ~nsTextFrameUtils::Flags::RunSizeAccounted; + } + + // shaping state - for some font features, fallback is required that + // affects the entire run. for example, fallback for one script/font + // portion of a textrun requires fallback to be applied to the entire run + + enum ShapingState : uint8_t { + eShapingState_Normal, // default state + eShapingState_ShapingWithFeature, // have shaped with feature + eShapingState_ShapingWithFallback, // have shaped with fallback + eShapingState_Aborted, // abort initial iteration + eShapingState_ForceFallbackFeature // redo with fallback forced on + }; + + ShapingState GetShapingState() const { return mShapingState; } + void SetShapingState(ShapingState aShapingState) { + mShapingState = aShapingState; + } + + int32_t GetAdvanceForGlyph(uint32_t aIndex) const { + const CompressedGlyph& glyphData = mCharacterGlyphs[aIndex]; + if (glyphData.IsSimpleGlyph()) { + return glyphData.GetSimpleAdvance(); + } + uint32_t glyphCount = glyphData.GetGlyphCount(); + if (!glyphCount) { + return 0; + } + const DetailedGlyph* details = GetDetailedGlyphs(aIndex); + int32_t advance = 0; + for (uint32_t j = 0; j < glyphCount; ++j, ++details) { + advance += details->mAdvance; + } + return advance; + } + +#ifdef DEBUG_FRAME_DUMP + void Dump(FILE* aOutput = stderr); +#endif + + protected: + /** + * Create a textrun, and set its mCharacterGlyphs to point immediately + * after the base object; this is ONLY used in conjunction with placement + * new, after allocating a block large enough for the glyph records to + * follow the base textrun object. + */ + gfxTextRun(const gfxTextRunFactory::Parameters* aParams, uint32_t aLength, + gfxFontGroup* aFontGroup, mozilla::gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2); + + /** + * Helper for the Create() factory method to allocate the required + * glyph storage for a textrun object with the basic size aSize, + * plus room for aLength glyph records. + */ + static void* AllocateStorageForTextRun(size_t aSize, uint32_t aLength); + + // Pointer to the array of CompressedGlyph records; must be initialized + // when the object is constructed. + CompressedGlyph* mCharacterGlyphs; + + private: + // **** general helpers **** + + // Get the total advance for a range of glyphs. + int32_t GetAdvanceForGlyphs(Range aRange) const; + + // Spacing for characters outside the range aSpacingStart/aSpacingEnd + // is assumed to be zero; such characters are not passed to aProvider. + // This is useful to protect aProvider from being passed character indices + // it is not currently able to handle. + bool GetAdjustedSpacingArray( + Range aRange, PropertyProvider* aProvider, Range aSpacingRange, + nsTArray<PropertyProvider::Spacing>* aSpacing) const; + + CompressedGlyph& EnsureComplexGlyph(uint32_t aIndex) { + gfxShapedText::EnsureComplexGlyph(aIndex, mCharacterGlyphs[aIndex]); + return mCharacterGlyphs[aIndex]; + } + + // **** ligature helpers **** + // (Platforms do the actual ligaturization, but we need to do a bunch of stuff + // to handle requests that begin or end inside a ligature) + + // if aProvider is null then mBeforeSpacing and mAfterSpacing are set to zero + LigatureData ComputeLigatureData(Range aPartRange, + PropertyProvider* aProvider) const; + gfxFloat ComputePartialLigatureWidth(Range aPartRange, + PropertyProvider* aProvider) const; + void DrawPartialLigature(gfxFont* aFont, Range aRange, + mozilla::gfx::Point* aPt, + PropertyProvider* aProvider, + TextRunDrawParams& aParams, + mozilla::gfx::ShapedTextFlags aOrientation) const; + // Advance aRange.start to the start of the nearest ligature, back + // up aRange.end to the nearest ligature end; may result in + // aRange->start == aRange->end. + void ShrinkToLigatureBoundaries(Range* aRange) const; + // result in appunits + gfxFloat GetPartialLigatureWidth(Range aRange, + PropertyProvider* aProvider) const; + void AccumulatePartialLigatureMetrics( + gfxFont* aFont, Range aRange, gfxFont::BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, PropertyProvider* aProvider, + mozilla::gfx::ShapedTextFlags aOrientation, Metrics* aMetrics) const; + + // **** measurement helper **** + void AccumulateMetricsForRun(gfxFont* aFont, Range aRange, + gfxFont::BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, + PropertyProvider* aProvider, Range aSpacingRange, + mozilla::gfx::ShapedTextFlags aOrientation, + Metrics* aMetrics) const; + + // **** drawing helper **** + void DrawGlyphs(gfxFont* aFont, Range aRange, mozilla::gfx::Point* aPt, + PropertyProvider* aProvider, Range aSpacingRange, + TextRunDrawParams& aParams, + mozilla::gfx::ShapedTextFlags aOrientation) const; + + // The textrun holds either a single GlyphRun -or- an array; + // the flag mHasGlyphRunArray tells us which is present. + union { + GlyphRun mSingleGlyphRun; + nsTArray<GlyphRun> mGlyphRunArray; + }; + + void ConvertToGlyphRunArray() { + MOZ_ASSERT(!mHasGlyphRunArray && mSingleGlyphRun.mFont); + GlyphRun tmp = std::move(mSingleGlyphRun); + mSingleGlyphRun.~GlyphRun(); + new (&mGlyphRunArray) nsTArray<GlyphRun>(2); + mGlyphRunArray.AppendElement(std::move(tmp)); + mHasGlyphRunArray = true; + } + + void ConvertFromGlyphRunArray() { + MOZ_ASSERT(mHasGlyphRunArray && mGlyphRunArray.Length() == 1); + GlyphRun tmp = std::move(mGlyphRunArray[0]); + mGlyphRunArray.~nsTArray<GlyphRun>(); + new (&mSingleGlyphRun) GlyphRun(std::move(tmp)); + mHasGlyphRunArray = false; + } + + void* mUserData; + + // mFontGroup is usually a strong reference, but refcounting is managed + // manually because it may be explicitly released by ReleaseFontGroup() + // in the case where the font group actually owns the textrun. + gfxFontGroup* MOZ_OWNING_REF mFontGroup; + + gfxSkipChars mSkipChars; + + nsTextFrameUtils::Flags + mFlags2; // additional flags (see also gfxShapedText::mFlags) + + bool mDontSkipDrawing; // true if the text run must not skip drawing, even if + // waiting for a user font download, e.g. because we + // are using it to draw canvas text + bool mReleasedFontGroup; // we already called NS_RELEASE on + // mFontGroup, so don't do it again + bool mReleasedFontGroupSkippedDrawing; // whether our old mFontGroup value + // was set to skip drawing + bool mHasGlyphRunArray; // whether we're using an array or + // just storing a single glyphrun + + // shaping state for handling variant fallback features + // such as subscript/superscript variant glyphs + ShapingState mShapingState; +}; + +enum class FallbackTypes : uint8_t { + // Font fallback used a font configured in Preferences + FallbackToPrefsFont = 1 << 0, + // Font fallback used a font with FontVisibility::Base + FallbackToBaseFont = 1 << 1, + // Font fallback used a font with FontVisibility::LangPack + FallbackToLangPackFont = 1 << 2, + // Font fallback used a font with FontVisibility::User + FallbackToUserFont = 1 << 3, + // Rendered missing-glyph because no font available for the character + MissingFont = 1 << 4, + // Rendered missing-glyph but a LangPack font could have been used + MissingFontLangPack = 1 << 5, + // Rendered missing-glyph but a User font could have been used + MissingFontUser = 1 << 6, +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(FallbackTypes) + +struct FontMatchingStats { + // Set of names that have been looked up (whether successfully or not). + nsTHashtable<nsCStringHashKey> mFamilyNames; + // Number of font-family names resolved at each level of visibility. + uint32_t mBaseFonts = 0; + uint32_t mLangPackFonts = 0; + uint32_t mUserFonts = 0; + uint32_t mWebFonts = 0; + FallbackTypes mFallbacks = FallbackTypes(0); +}; + +class gfxFontGroup final : public gfxTextRunFactory { + public: + typedef mozilla::unicode::Script Script; + typedef gfxShapedText::CompressedGlyph CompressedGlyph; + + static void + Shutdown(); // platform must call this to release the languageAtomService + + gfxFontGroup(const mozilla::FontFamilyList& aFontFamilyList, + const gfxFontStyle* aStyle, nsAtom* aLanguage, + bool aExplicitLanguage, gfxTextPerfMetrics* aTextPerf, + FontMatchingStats* aFontMatchingStats, + gfxUserFontSet* aUserFontSet, gfxFloat aDevToCssSize); + + virtual ~gfxFontGroup(); + + gfxFontGroup(const gfxFontGroup& aOther) = delete; + + // Returns first valid font in the fontlist or default font. + // Initiates userfont loads if userfont not loaded. + // aGeneric: if non-null, returns the CSS generic type that was mapped to + // this font + gfxFont* GetFirstValidFont( + uint32_t aCh = 0x20, mozilla::StyleGenericFontFamily* aGeneric = nullptr); + + // Returns the first font in the font-group that has an OpenType MATH table, + // or null if no such font is available. The GetMathConstant methods may be + // called on the returned font. + gfxFont* GetFirstMathFont(); + + const gfxFontStyle* GetStyle() const { return &mStyle; } + + /** + * The listed characters should be treated as invisible and zero-width + * when creating textruns. + */ + static bool IsInvalidChar(uint8_t ch); + static bool IsInvalidChar(char16_t ch); + + /** + * Make a textrun for a given string. + * If aText is not persistent (aFlags & TEXT_IS_PERSISTENT), the + * textrun will copy it. + * This calls FetchGlyphExtents on the textrun. + */ + already_AddRefed<gfxTextRun> MakeTextRun(const char16_t* aString, + uint32_t aLength, + const Parameters* aParams, + mozilla::gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2, + gfxMissingFontRecorder* aMFR); + /** + * Make a textrun for a given string. + * If aText is not persistent (aFlags & TEXT_IS_PERSISTENT), the + * textrun will copy it. + * This calls FetchGlyphExtents on the textrun. + */ + already_AddRefed<gfxTextRun> MakeTextRun(const uint8_t* aString, + uint32_t aLength, + const Parameters* aParams, + mozilla::gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2, + gfxMissingFontRecorder* aMFR); + + /** + * Textrun creation helper for clients that don't want to pass + * a full Parameters record. + */ + template <typename T> + already_AddRefed<gfxTextRun> MakeTextRun(const T* aString, uint32_t aLength, + DrawTarget* aRefDrawTarget, + int32_t aAppUnitsPerDevUnit, + mozilla::gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2, + gfxMissingFontRecorder* aMFR) { + gfxTextRunFactory::Parameters params = { + aRefDrawTarget, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevUnit}; + return MakeTextRun(aString, aLength, ¶ms, aFlags, aFlags2, aMFR); + } + + // Get the (possibly-cached) width of the hyphen character. + gfxFloat GetHyphenWidth(const gfxTextRun::PropertyProvider* aProvider); + + /** + * Make a text run representing a single hyphen character. + * This will use U+2010 HYPHEN if available in the first font, + * otherwise fall back to U+002D HYPHEN-MINUS. + * The caller is responsible for deleting the returned text run + * when no longer required. + */ + already_AddRefed<gfxTextRun> MakeHyphenTextRun(DrawTarget* aDrawTarget, + uint32_t aAppUnitsPerDevUnit); + + /** + * Check whether a given font (specified by its gfxFontEntry) + * is already in the fontgroup's list of actual fonts + */ + bool HasFont(const gfxFontEntry* aFontEntry); + + // This returns the preferred underline for this font group. + // Some CJK fonts have wrong underline offset in its metrics. + // If this group has such "bad" font, each platform's gfxFontGroup + // initialized mUnderlineOffset. The value should be lower value of + // first font's metrics and the bad font's metrics. Otherwise, this + // returns from first font's metrics. + enum { UNDERLINE_OFFSET_NOT_SET = INT16_MAX }; + gfxFloat GetUnderlineOffset(); + + gfxFont* FindFontForChar(uint32_t ch, uint32_t prevCh, uint32_t aNextCh, + Script aRunScript, gfxFont* aPrevMatchedFont, + FontMatchType* aMatchType); + + gfxUserFontSet* GetUserFontSet(); + + // With downloadable fonts, the composition of the font group can change as + // fonts are downloaded for each change in state of the user font set, the + // generation value is bumped to avoid picking up previously created text runs + // in the text run word cache. For font groups based on stylesheets with no + // @font-face rule, this always returns 0. + uint64_t GetGeneration(); + + // generation of the latest fontset rebuild, 0 when no fontset present + uint64_t GetRebuildGeneration(); + + // used when logging text performance + gfxTextPerfMetrics* GetTextPerfMetrics() { return mTextPerf; } + + // This will call UpdateUserFonts() if the user font set is changed. + void SetUserFontSet(gfxUserFontSet* aUserFontSet); + + void ClearCachedData() { + mUnderlineOffset = UNDERLINE_OFFSET_NOT_SET; + mSkipDrawing = false; + mHyphenWidth = -1; + mCachedEllipsisTextRun = nullptr; + } + + // If there is a user font set, check to see whether the font list or any + // caches need updating. + void UpdateUserFonts(); + + // search for a specific userfont in the list of fonts + bool ContainsUserFont(const gfxUserFontEntry* aUserFont); + + bool ShouldSkipDrawing() const { return mSkipDrawing; } + + class LazyReferenceDrawTargetGetter { + public: + virtual already_AddRefed<DrawTarget> GetRefDrawTarget() = 0; + }; + // The gfxFontGroup keeps ownership of this textrun. + // It is only guaranteed to exist until the next call to GetEllipsisTextRun + // (which might use a different appUnitsPerDev value or flags) for the font + // group, or until UpdateUserFonts is called, or the fontgroup is destroyed. + // Get it/use it/forget it :) - don't keep a reference that might go stale. + gfxTextRun* GetEllipsisTextRun( + int32_t aAppUnitsPerDevPixel, mozilla::gfx::ShapedTextFlags aFlags, + LazyReferenceDrawTargetGetter& aRefDrawTargetGetter); + + void CheckForUpdatedPlatformList() { + auto* pfl = gfxPlatformFontList::PlatformFontList(); + if (mFontListGeneration != pfl->GetGeneration()) { + // Forget cached fonts that may no longer be valid. + mLastPrefFamily = FontFamily(); + mLastPrefFont = nullptr; + mFonts.Clear(); + BuildFontList(); + } + } + + nsAtom* Language() const { return mLanguage.get(); } + + protected: + friend class mozilla::PostTraversalTask; + + struct TextRange { + TextRange(uint32_t aStart, uint32_t aEnd, gfxFont* aFont, + FontMatchType aMatchType, + mozilla::gfx::ShapedTextFlags aOrientation) + : start(aStart), + end(aEnd), + font(aFont), + matchType(aMatchType), + orientation(aOrientation) {} + uint32_t Length() const { return end - start; } + uint32_t start, end; + RefPtr<gfxFont> font; + FontMatchType matchType; + mozilla::gfx::ShapedTextFlags orientation; + }; + + // search through pref fonts for a character, return nullptr if no matching + // pref font + gfxFont* WhichPrefFontSupportsChar(uint32_t aCh, uint32_t aNextCh, + eFontPresentation aPresentation); + + gfxFont* WhichSystemFontSupportsChar(uint32_t aCh, uint32_t aNextCh, + Script aRunScript, + eFontPresentation aPresentation); + + template <typename T> + void ComputeRanges(nsTArray<TextRange>& aRanges, const T* aString, + uint32_t aLength, Script aRunScript, + mozilla::gfx::ShapedTextFlags aOrientation); + + class FamilyFace { + public: + FamilyFace() + : mOwnedFamily(nullptr), + mFontEntry(nullptr), + mGeneric(mozilla::StyleGenericFontFamily::None), + mFontCreated(false), + mLoading(false), + mInvalid(false), + mCheckForFallbackFaces(false), + mIsSharedFamily(false), + mHasFontEntry(false) {} + + FamilyFace(gfxFontFamily* aFamily, gfxFont* aFont, + mozilla::StyleGenericFontFamily aGeneric) + : mOwnedFamily(aFamily), + mGeneric(aGeneric), + mFontCreated(true), + mLoading(false), + mInvalid(false), + mCheckForFallbackFaces(false), + mIsSharedFamily(false), + mHasFontEntry(false) { + NS_ASSERTION(aFont, "font pointer must not be null"); + NS_ASSERTION(!aFamily || aFamily->ContainsFace(aFont->GetFontEntry()), + "font is not a member of the given family"); + NS_IF_ADDREF(aFamily); + mFont = aFont; + NS_ADDREF(aFont); + } + + FamilyFace(gfxFontFamily* aFamily, gfxFontEntry* aFontEntry, + mozilla::StyleGenericFontFamily aGeneric) + : mOwnedFamily(aFamily), + mGeneric(aGeneric), + mFontCreated(false), + mLoading(false), + mInvalid(false), + mCheckForFallbackFaces(false), + mIsSharedFamily(false), + mHasFontEntry(true) { + NS_ASSERTION(aFontEntry, "font entry pointer must not be null"); + NS_ASSERTION(!aFamily || aFamily->ContainsFace(aFontEntry), + "font is not a member of the given family"); + NS_IF_ADDREF(aFamily); + mFontEntry = aFontEntry; + NS_ADDREF(aFontEntry); + } + + FamilyFace(mozilla::fontlist::Family* aFamily, gfxFontEntry* aFontEntry, + mozilla::StyleGenericFontFamily aGeneric) + : mSharedFamily(aFamily), + mGeneric(aGeneric), + mFontCreated(false), + mLoading(false), + mInvalid(false), + mCheckForFallbackFaces(false), + mIsSharedFamily(true), + mHasFontEntry(true) { + MOZ_ASSERT(aFamily && aFontEntry && aFontEntry->mShmemFace); + mFontEntry = aFontEntry; + NS_ADDREF(aFontEntry); + } + + FamilyFace(const FamilyFace& aOtherFamilyFace) + : mGeneric(aOtherFamilyFace.mGeneric), + mFontCreated(aOtherFamilyFace.mFontCreated), + mLoading(aOtherFamilyFace.mLoading), + mInvalid(aOtherFamilyFace.mInvalid), + mCheckForFallbackFaces(aOtherFamilyFace.mCheckForFallbackFaces), + mIsSharedFamily(aOtherFamilyFace.mIsSharedFamily), + mHasFontEntry(aOtherFamilyFace.mHasFontEntry) { + if (mIsSharedFamily) { + mSharedFamily = aOtherFamilyFace.mSharedFamily; + if (mFontCreated) { + mFont = aOtherFamilyFace.mFont; + NS_ADDREF(mFont); + } else if (mHasFontEntry) { + mFontEntry = aOtherFamilyFace.mFontEntry; + NS_ADDREF(mFontEntry); + } else { + mSharedFace = aOtherFamilyFace.mSharedFace; + } + } else { + mOwnedFamily = aOtherFamilyFace.mOwnedFamily; + NS_IF_ADDREF(mOwnedFamily); + if (mFontCreated) { + mFont = aOtherFamilyFace.mFont; + NS_ADDREF(mFont); + } else { + mFontEntry = aOtherFamilyFace.mFontEntry; + NS_IF_ADDREF(mFontEntry); + } + } + } + + ~FamilyFace() { + if (mFontCreated) { + NS_RELEASE(mFont); + } + if (!mIsSharedFamily) { + NS_IF_RELEASE(mOwnedFamily); + } + if (mHasFontEntry) { + NS_RELEASE(mFontEntry); + } + } + + FamilyFace& operator=(const FamilyFace& aOther) { + if (mFontCreated) { + NS_RELEASE(mFont); + } + if (!mIsSharedFamily) { + NS_IF_RELEASE(mOwnedFamily); + } + if (mHasFontEntry) { + NS_RELEASE(mFontEntry); + } + + mGeneric = aOther.mGeneric; + mFontCreated = aOther.mFontCreated; + mLoading = aOther.mLoading; + mInvalid = aOther.mInvalid; + mIsSharedFamily = aOther.mIsSharedFamily; + mHasFontEntry = aOther.mHasFontEntry; + + if (mIsSharedFamily) { + mSharedFamily = aOther.mSharedFamily; + if (mFontCreated) { + mFont = aOther.mFont; + NS_ADDREF(mFont); + } else if (mHasFontEntry) { + mFontEntry = aOther.mFontEntry; + NS_ADDREF(mFontEntry); + } else { + mSharedFace = aOther.mSharedFace; + } + } else { + mOwnedFamily = aOther.mOwnedFamily; + NS_IF_ADDREF(mOwnedFamily); + if (mFontCreated) { + mFont = aOther.mFont; + NS_ADDREF(mFont); + } else { + mFontEntry = aOther.mFontEntry; + NS_IF_ADDREF(mFontEntry); + } + } + + return *this; + } + + gfxFontFamily* OwnedFamily() const { + MOZ_ASSERT(!mIsSharedFamily); + return mOwnedFamily; + } + mozilla::fontlist::Family* SharedFamily() const { + MOZ_ASSERT(mIsSharedFamily); + return mSharedFamily; + } + gfxFont* Font() const { return mFontCreated ? mFont : nullptr; } + + gfxFontEntry* FontEntry() const { + if (mFontCreated) { + return mFont->GetFontEntry(); + } + if (mHasFontEntry) { + return mFontEntry; + } + if (mIsSharedFamily) { + return gfxPlatformFontList::PlatformFontList()->GetOrCreateFontEntry( + mSharedFace, SharedFamily()); + } + return nullptr; + } + + mozilla::StyleGenericFontFamily Generic() const { return mGeneric; } + + bool IsSharedFamily() const { return mIsSharedFamily; } + bool IsUserFontContainer() const { + gfxFontEntry* fe = FontEntry(); + return fe && fe->mIsUserFontContainer; + } + bool IsLoading() const { return mLoading; } + bool IsInvalid() const { return mInvalid; } + void CheckState(bool& aSkipDrawing); + void SetLoading(bool aIsLoading) { mLoading = aIsLoading; } + void SetInvalid() { mInvalid = true; } + bool CheckForFallbackFaces() const { return mCheckForFallbackFaces; } + void SetCheckForFallbackFaces() { mCheckForFallbackFaces = true; } + + // Return true if we're currently loading (or waiting for) a resource that + // may support the given character. + bool IsLoadingFor(uint32_t aCh) { + if (!IsLoading()) { + return false; + } + MOZ_ASSERT(IsUserFontContainer()); + auto* ufe = static_cast<gfxUserFontEntry*>(FontEntry()); + return ufe && ufe->CharacterInUnicodeRange(aCh); + } + + void SetFont(gfxFont* aFont) { + NS_ASSERTION(aFont, "font pointer must not be null"); + NS_ADDREF(aFont); + if (mFontCreated) { + NS_RELEASE(mFont); + } else if (mHasFontEntry) { + NS_RELEASE(mFontEntry); + mHasFontEntry = false; + } + mFont = aFont; + mFontCreated = true; + mLoading = false; + } + + bool EqualsUserFont(const gfxUserFontEntry* aUserFont) const; + + private: + union { + gfxFontFamily* MOZ_OWNING_REF mOwnedFamily; + mozilla::fontlist::Family* MOZ_NON_OWNING_REF mSharedFamily; + }; + // either a font or a font entry exists + union { + // Whichever of these fields is actually present will be a strong + // reference, with refcounting handled manually. + gfxFont* MOZ_OWNING_REF mFont; + gfxFontEntry* MOZ_OWNING_REF mFontEntry; + mozilla::fontlist::Face* MOZ_NON_OWNING_REF mSharedFace; + }; + mozilla::StyleGenericFontFamily mGeneric; + bool mFontCreated : 1; + bool mLoading : 1; + bool mInvalid : 1; + bool mCheckForFallbackFaces : 1; + bool mIsSharedFamily : 1; + bool mHasFontEntry : 1; + }; + + // List of font families, either named or generic. + // Generic names map to system pref fonts based on language. + mozilla::FontFamilyList mFamilyList; + + // Fontlist containing a font entry for each family found. gfxFont objects + // are created as needed and userfont loads are initiated when needed. + // Code should be careful about addressing this array directly. + nsTArray<FamilyFace> mFonts; + + RefPtr<gfxFont> mDefaultFont; + gfxFontStyle mStyle; + + RefPtr<nsAtom> mLanguage; + + gfxFloat mUnderlineOffset; + gfxFloat mHyphenWidth; + gfxFloat mDevToCssSize; + + RefPtr<gfxUserFontSet> mUserFontSet; + uint64_t mCurrGeneration; // track the current user font set generation, + // rebuild font list if needed + + gfxTextPerfMetrics* mTextPerf; + + FontMatchingStats* mFontMatchingStats; + + // Cache a textrun representing an ellipsis (useful for CSS text-overflow) + // at a specific appUnitsPerDevPixel size and orientation + RefPtr<gfxTextRun> mCachedEllipsisTextRun; + + // cache the most recent pref font to avoid general pref font lookup + FontFamily mLastPrefFamily; + RefPtr<gfxFont> mLastPrefFont; + eFontPrefLang mLastPrefLang; // lang group for last pref font + eFontPrefLang mPageLang; + bool mLastPrefFirstFont; // is this the first font in the list of pref fonts + // for this lang group? + + bool mSkipDrawing; // hide text while waiting for a font + // download to complete (or fallback + // timer to fire) + + bool mExplicitLanguage; // Does mLanguage come from an explicit attribute? + + uint32_t mFontListGeneration = 0; // platform font list generation for this + // fontgroup + + /** + * Textrun creation short-cuts for special cases where we don't need to + * call a font shaper to generate glyphs. + */ + already_AddRefed<gfxTextRun> MakeEmptyTextRun( + const Parameters* aParams, mozilla::gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2); + + already_AddRefed<gfxTextRun> MakeSpaceTextRun( + const Parameters* aParams, mozilla::gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2); + + template <typename T> + already_AddRefed<gfxTextRun> MakeBlankTextRun( + const T* aString, uint32_t aLength, const Parameters* aParams, + mozilla::gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2); + + // Initialize the list of fonts + void BuildFontList(); + + // Get the font at index i within the fontlist, for character aCh (in case + // of fonts with multiple resources and unicode-range partitioning). + // Will initiate userfont load if not already loaded. + // May return null if userfont not loaded or if font invalid. + // If *aLoading is true, a relevant resource is already being loaded so no + // new download will be initiated; if a download is started, *aLoading will + // be set to true on return. + gfxFont* GetFontAt(int32_t i, uint32_t aCh, bool* aLoading); + + // Simplified version of GetFontAt() for use where we just need a font for + // metrics, math layout tables, etc. + gfxFont* GetFontAt(int32_t i, uint32_t aCh = 0x20) { + bool loading = false; + return GetFontAt(i, aCh, &loading); + } + + // will always return a font or force a shutdown + gfxFont* GetDefaultFont(); + + // Init this font group's font metrics. If there no bad fonts, you don't need + // to call this. But if there are one or more bad fonts which have bad + // underline offset, you should call this with the *first* bad font. + void InitMetricsForBadFont(gfxFont* aBadFont); + + // Set up the textrun glyphs for an entire text run: + // find script runs, and then call InitScriptRun for each + template <typename T> + void InitTextRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun, + const T* aString, uint32_t aLength, + gfxMissingFontRecorder* aMFR); + + // InitTextRun helper to handle a single script run, by finding font ranges + // and calling each font's InitTextRun() as appropriate + template <typename T> + void InitScriptRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun, + const T* aString, uint32_t aScriptRunStart, + uint32_t aScriptRunEnd, Script aRunScript, + gfxMissingFontRecorder* aMFR); + + // Helper for font-matching: + // search all faces in a family for a fallback in cases where it's unclear + // whether the family might have a font for a given character + gfxFont* FindFallbackFaceForChar(const FamilyFace& aFamily, uint32_t aCh, + uint32_t aNextCh, + eFontPresentation aPresentation); + + gfxFont* FindFallbackFaceForChar(mozilla::fontlist::Family* aFamily, + uint32_t aCh, uint32_t aNextCh, + eFontPresentation aPresentation); + + gfxFont* FindFallbackFaceForChar(gfxFontFamily* aFamily, uint32_t aCh, + uint32_t aNextCh, + eFontPresentation aPresentation); + + // helper methods for looking up fonts + + // lookup and add a font with a given name (i.e. *not* a generic!) + void AddPlatformFont(const nsACString& aName, bool aQuotedName, + nsTArray<FamilyAndGeneric>& aFamilyList); + + // do style selection and add entries to list + void AddFamilyToFontList(gfxFontFamily* aFamily, + mozilla::StyleGenericFontFamily aGeneric); + void AddFamilyToFontList(mozilla::fontlist::Family* aFamily, + mozilla::StyleGenericFontFamily aGeneric); +}; + +// A "missing font recorder" is to be used during text-run creation to keep +// a record of any scripts encountered for which font coverage was lacking; +// when Flush() is called, it sends a notification that front-end code can use +// to download fonts on demand (or whatever else it wants to do). + +#define GFX_MISSING_FONTS_NOTIFY_PREF "gfx.missing_fonts.notify" + +class gfxMissingFontRecorder { + public: + gfxMissingFontRecorder() { + MOZ_COUNT_CTOR(gfxMissingFontRecorder); + memset(&mMissingFonts, 0, sizeof(mMissingFonts)); + } + + ~gfxMissingFontRecorder() { +#ifdef DEBUG + for (uint32_t i = 0; i < kNumScriptBitsWords; i++) { + NS_ASSERTION(mMissingFonts[i] == 0, + "failed to flush the missing-font recorder"); + } +#endif + MOZ_COUNT_DTOR(gfxMissingFontRecorder); + } + + // record this script code in our mMissingFonts bitset + void RecordScript(mozilla::unicode::Script aScriptCode) { + mMissingFonts[static_cast<uint32_t>(aScriptCode) >> 5] |= + (1 << (static_cast<uint32_t>(aScriptCode) & 0x1f)); + } + + // send a notification of any missing-scripts that have been + // recorded, and clear the mMissingFonts set for re-use + void Flush(); + + // forget any missing-scripts that have been recorded up to now; + // called before discarding a recorder we no longer care about + void Clear() { memset(&mMissingFonts, 0, sizeof(mMissingFonts)); } + + private: + // Number of 32-bit words needed for the missing-script flags + static const uint32_t kNumScriptBitsWords = + ((static_cast<int>(mozilla::unicode::Script::NUM_SCRIPT_CODES) + 31) / + 32); + uint32_t mMissingFonts[kNumScriptBitsWords]; +}; + +#endif diff --git a/gfx/thebes/gfxTypes.h b/gfx/thebes/gfxTypes.h new file mode 100644 index 0000000000..74e99c8b34 --- /dev/null +++ b/gfx/thebes/gfxTypes.h @@ -0,0 +1,149 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_TYPES_H +#define GFX_TYPES_H + +#include <stdint.h> +#include "mozilla/TypedEnumBits.h" + +namespace mozilla { +enum class StyleGenericFontFamily : uint8_t; +} + +typedef struct _cairo_surface cairo_surface_t; +typedef struct _cairo_user_data_key cairo_user_data_key_t; + +typedef void (*thebes_destroy_func_t)(void* data); + +/** + * Currently needs to be 'double' for Cairo compatibility. Could + * become 'float', perhaps, in some configurations. + */ +typedef double gfxFloat; + +/** + * Priority of a line break opportunity. + * + * eNoBreak The line has no break opportunities + * eWordWrapBreak The line has a break opportunity only within a word. With + * overflow-wrap|word-wrap: break-word we will break at this + * point only if there are no other break opportunities in the + * line. + * eNormalBreak The line has a break opportunity determined by the standard + * line-breaking algorithm. + * + * Future expansion: split eNormalBreak into multiple priorities, e.g. + * punctuation break and whitespace break (bug 389710). + * As and when we implement it, text-wrap: unrestricted will + * mean that priorities are ignored and all line-break + * opportunities are equal. + * + * @see gfxTextRun::BreakAndMeasureText + * @see nsLineLayout::NotifyOptionalBreakPosition + */ +enum class gfxBreakPriority { eNoBreak = 0, eWordWrapBreak, eNormalBreak }; + +enum class gfxSurfaceType { + Image, + PDF, + PS, + Xlib, + Xcb, + Glitz, // unused, but needed for cairo parity + Quartz, + Win32, + BeOS, + DirectFB, // unused, but needed for cairo parity + SVG, + OS2, + Win32Printing, + QuartzImage, + Script, + QPainter, + Recording, + VG, + GL, + DRM, + Tee, + XML, + Skia, + Subsurface, + Max +}; + +enum class gfxContentType { + COLOR = 0x1000, + ALPHA = 0x2000, + COLOR_ALPHA = 0x3000, + SENTINEL = 0xffff +}; + +enum class gfxAlphaType { + Opaque, + Premult, + NonPremult, +}; + +/** + * Type used to record how a particular font is selected during the font- + * matching process, so that this can be exposed to the Inspector. + */ +struct FontMatchType { + enum class Kind : uint8_t { + kUnspecified = 0, + kFontGroup = 1, + kPrefsFallback = 1 << 1, + kSystemFallback = 1 << 2, + }; + + inline FontMatchType& operator|=(const FontMatchType& aOther); + + bool operator==(const FontMatchType& aOther) const { + return kind == aOther.kind && generic == aOther.generic; + } + + bool operator!=(const FontMatchType& aOther) const { + return !(*this == aOther); + } + + MOZ_IMPLICIT FontMatchType() = default; + MOZ_IMPLICIT FontMatchType(Kind aKind) : kind(aKind) {} + FontMatchType(Kind aKind, mozilla::StyleGenericFontFamily aGeneric) + : kind(aKind), generic(aGeneric) {} + + Kind kind = static_cast<Kind>(0); + // Using 0 to avoid pulling ServoStyleConsts.h everywhere. + mozilla::StyleGenericFontFamily generic = mozilla::StyleGenericFontFamily(0); +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(FontMatchType::Kind) + +FontMatchType& FontMatchType::operator|=(const FontMatchType& aOther) { + kind |= aOther.kind; + // We only keep track of one generic. + if (generic != aOther.generic) { + generic = mozilla::StyleGenericFontFamily(0); + } + return *this; +} + +struct HwStretchingSupport { + uint32_t mBoth; + uint32_t mWindowOnly; + uint32_t mFullScreenOnly; + uint32_t mNone; + uint32_t mError; + + HwStretchingSupport() + : mBoth(0), mWindowOnly(0), mFullScreenOnly(0), mNone(0), mError(0) {} + + bool IsFullySupported() const { + return mBoth > 0 && mWindowOnly == 0 && mFullScreenOnly == 0 && + mNone == 0 && mError == 0; + } +}; + +#endif /* GFX_TYPES_H */ diff --git a/gfx/thebes/gfxUserFontSet.cpp b/gfx/thebes/gfxUserFontSet.cpp new file mode 100644 index 0000000000..1d9ce7e046 --- /dev/null +++ b/gfx/thebes/gfxUserFontSet.cpp @@ -0,0 +1,1355 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Logging.h" + +#include "GeckoProfiler.h" +#include "gfxUserFontSet.h" +#include "gfxPlatform.h" +#include "gfxFontConstants.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/Telemetry.h" +#include "mozilla/gfx/2D.h" +#include "gfxPlatformFontList.h" +#include "mozilla/ServoStyleSet.h" +#include "mozilla/PostTraversalTask.h" +#include "gfxOTSUtils.h" +#include "nsIFontLoadCompleteCallback.h" +#include "nsProxyRelease.h" + +using namespace mozilla; + +mozilla::LogModule* gfxUserFontSet::GetUserFontsLog() { + static LazyLogModule sLog("userfonts"); + return sLog; +} + +#define LOG(args) \ + MOZ_LOG(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug, args) +#define LOG_ENABLED() \ + MOZ_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug) + +static uint64_t sFontSetGeneration = 0; + +gfxUserFontEntry::gfxUserFontEntry( + gfxUserFontSet* aFontSet, const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, + WeightRange aWeight, StretchRange aStretch, SlantStyleRange aStyle, + const nsTArray<gfxFontFeature>& aFeatureSettings, + const nsTArray<gfxFontVariation>& aVariationSettings, + uint32_t aLanguageOverride, gfxCharacterMap* aUnicodeRanges, + StyleFontDisplay aFontDisplay, RangeFlags aRangeFlags) + : gfxFontEntry("userfont"_ns), + mUserFontLoadState(STATUS_NOT_LOADED), + mFontDataLoadingState(NOT_LOADING), + mUnsupportedFormat(false), + mFontDisplay(aFontDisplay), + mLoader(nullptr), + mFontSet(aFontSet) { + mIsUserFontContainer = true; + mSrcList = aFontFaceSrcList.Clone(); + mSrcIndex = 0; + mWeightRange = aWeight; + mStretchRange = aStretch; + mStyleRange = aStyle; + mFeatureSettings.AppendElements(aFeatureSettings); + mVariationSettings.AppendElements(aVariationSettings); + mLanguageOverride = aLanguageOverride; + mCharacterMap = aUnicodeRanges; + mRangeFlags = aRangeFlags; +} + +void gfxUserFontEntry::UpdateAttributes( + WeightRange aWeight, StretchRange aStretch, SlantStyleRange aStyle, + const nsTArray<gfxFontFeature>& aFeatureSettings, + const nsTArray<gfxFontVariation>& aVariationSettings, + uint32_t aLanguageOverride, gfxCharacterMap* aUnicodeRanges, + StyleFontDisplay aFontDisplay, RangeFlags aRangeFlags) { + // Remove the entry from the user font cache, if present there, as the cache + // key may no longer be correct with the new attributes. + gfxUserFontSet::UserFontCache::ForgetFont(this); + + mFontDisplay = aFontDisplay; + mWeightRange = aWeight; + mStretchRange = aStretch; + mStyleRange = aStyle; + mFeatureSettings = aFeatureSettings.Clone(); + mVariationSettings = aVariationSettings.Clone(); + mLanguageOverride = aLanguageOverride; + mCharacterMap = aUnicodeRanges; + mRangeFlags = aRangeFlags; +} + +gfxUserFontEntry::~gfxUserFontEntry() { + // Assert that we don't drop any gfxUserFontEntry objects during a Servo + // traversal, since PostTraversalTask objects can hold raw pointers to + // gfxUserFontEntry objects. + MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal()); +} + +bool gfxUserFontEntry::Matches( + const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, WeightRange aWeight, + StretchRange aStretch, SlantStyleRange aStyle, + const nsTArray<gfxFontFeature>& aFeatureSettings, + const nsTArray<gfxFontVariation>& aVariationSettings, + uint32_t aLanguageOverride, gfxCharacterMap* aUnicodeRanges, + StyleFontDisplay aFontDisplay, RangeFlags aRangeFlags) { + return Weight() == aWeight && Stretch() == aStretch && + SlantStyle() == aStyle && mFeatureSettings == aFeatureSettings && + mVariationSettings == aVariationSettings && + mLanguageOverride == aLanguageOverride && + mSrcList == aFontFaceSrcList && mFontDisplay == aFontDisplay && + mRangeFlags == aRangeFlags && + ((!aUnicodeRanges && !mCharacterMap) || + (aUnicodeRanges && mCharacterMap && + mCharacterMap->Equals(aUnicodeRanges))); +} + +gfxFont* gfxUserFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle) { + MOZ_ASSERT_UNREACHABLE( + "should only be creating a gfxFont" + " with an actual platform font entry"); + + // userfont entry is a container, can't create font from the container + return nullptr; +} + +class MOZ_STACK_CLASS gfxOTSMessageContext : public gfxOTSContext { + public: + virtual ~gfxOTSMessageContext() { + MOZ_ASSERT(mMessages.IsEmpty(), "should have called TakeMessages"); + } + + virtual void Message(int level, const char* format, + ...) MSGFUNC_FMT_ATTR override { + va_list va; + va_start(va, format); + + nsCString msg; + msg.AppendVprintf(format, va); + + va_end(va); + + if (level > 0) { + // For warnings (rather than errors that cause the font to fail), + // we only report the first instance of any given message. + if (mWarningsIssued.Contains(msg)) { + return; + } + mWarningsIssued.PutEntry(msg); + } + + mMessages.AppendElement(gfxUserFontEntry::OTSMessage{msg, level}); + } + + bool Process(ots::OTSStream* aOutput, const uint8_t* aInput, size_t aLength, + nsTArray<gfxUserFontEntry::OTSMessage>& aMessages) { + bool ok = ots::OTSContext::Process(aOutput, aInput, aLength); + aMessages = TakeMessages(); + return ok; + } + + nsTArray<gfxUserFontEntry::OTSMessage>&& TakeMessages() { + return std::move(mMessages); + } + + private: + nsTHashtable<nsCStringHashKey> mWarningsIssued; + nsTArray<gfxUserFontEntry::OTSMessage> mMessages; +}; + +// Call the OTS library to sanitize an sfnt before attempting to use it. +// Returns a newly-allocated block, or nullptr in case of fatal errors. +const uint8_t* gfxUserFontEntry::SanitizeOpenTypeData( + const uint8_t* aData, uint32_t aLength, uint32_t& aSaneLength, + gfxUserFontType& aFontType, nsTArray<OTSMessage>& aMessages) { + aFontType = gfxFontUtils::DetermineFontDataType(aData, aLength); + Telemetry::Accumulate(Telemetry::WEBFONT_FONTTYPE, uint32_t(aFontType)); + + size_t lengthHint = gfxOTSContext::GuessSanitizedFontSize(aLength, aFontType); + if (!lengthHint) { + aSaneLength = 0; + return nullptr; + } + + gfxOTSExpandingMemoryStream<gfxOTSMozAlloc> output(lengthHint); + + gfxOTSMessageContext otsContext; + if (!otsContext.Process(&output, aData, aLength, aMessages)) { + // Failed to decode/sanitize the font, so discard it. + aSaneLength = 0; + return nullptr; + } + + aSaneLength = output.Tell(); + return static_cast<const uint8_t*>(output.forget()); +} + +void gfxUserFontEntry::StoreUserFontData(gfxFontEntry* aFontEntry, + bool aPrivate, + const nsACString& aOriginalName, + FallibleTArray<uint8_t>* aMetadata, + uint32_t aMetaOrigLen, + uint8_t aCompression) { + if (!aFontEntry->mUserFontData) { + aFontEntry->mUserFontData = MakeUnique<gfxUserFontData>(); + } + gfxUserFontData* userFontData = aFontEntry->mUserFontData.get(); + userFontData->mSrcIndex = mSrcIndex; + const gfxFontFaceSrc& src = mSrcList[mSrcIndex]; + switch (src.mSourceType) { + case gfxFontFaceSrc::eSourceType_Local: + userFontData->mLocalName = src.mLocalName; + break; + case gfxFontFaceSrc::eSourceType_URL: + userFontData->mURI = src.mURI; + userFontData->mPrincipal = mPrincipal; + break; + case gfxFontFaceSrc::eSourceType_Buffer: + userFontData->mIsBuffer = true; + break; + } + userFontData->mPrivate = aPrivate; + userFontData->mFormat = src.mFormatFlags; + userFontData->mRealName = aOriginalName; + if (aMetadata) { + userFontData->mMetadata = std::move(*aMetadata); + userFontData->mMetaOrigLen = aMetaOrigLen; + userFontData->mCompression = aCompression; + } +} + +size_t gfxUserFontData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + + mMetadata.ShallowSizeOfExcludingThis(aMallocSizeOf) + + mLocalName.SizeOfExcludingThisIfUnshared(aMallocSizeOf) + + mRealName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + // Not counting mURI and mPrincipal, as those will be shared. +} + +/*virtual*/ +gfxUserFontFamily::~gfxUserFontFamily() { + // Should not be dropped by stylo + MOZ_ASSERT(NS_IsMainThread()); +} + +gfxFontSrcPrincipal* gfxFontFaceSrc::LoadPrincipal( + const gfxUserFontSet& aFontSet) const { + MOZ_ASSERT(mSourceType == eSourceType_URL); + if (mUseOriginPrincipal && mOriginPrincipal) { + return mOriginPrincipal; + } + return aFontSet.GetStandardFontLoadPrincipal(); +} + +void gfxUserFontEntry::GetFamilyNameAndURIForLogging(nsACString& aFamilyName, + nsACString& aURI) { + aFamilyName = mFamilyName; + + aURI.Truncate(); + if (mSrcIndex == mSrcList.Length()) { + aURI.AppendLiteral("(end of source list)"); + } else { + if (mSrcList[mSrcIndex].mURI) { + mSrcList[mSrcIndex].mURI->GetSpec(aURI); + // If the source URI was very long, elide the middle of it. + // In principle, the byte-oriented chopping here could leave us + // with partial UTF-8 characters at the point where we cut it, + // but it really doesn't matter as this is just for logging. + const uint32_t kMaxURILengthForLogging = 256; + // UTF-8 ellipsis, with spaces to allow additional wrap opportunities + // in the resulting log message + const char kEllipsis[] = {' ', '\xE2', '\x80', '\xA6', ' '}; + if (aURI.Length() > kMaxURILengthForLogging) { + aURI.Replace(kMaxURILengthForLogging / 2, + aURI.Length() - kMaxURILengthForLogging, kEllipsis, + ArrayLength(kEllipsis)); + } + } else { + aURI.AppendLiteral("(invalid URI)"); + } + } +} + +struct WOFFHeader { + AutoSwap_PRUint32 signature; + AutoSwap_PRUint32 flavor; + AutoSwap_PRUint32 length; + AutoSwap_PRUint16 numTables; + AutoSwap_PRUint16 reserved; + AutoSwap_PRUint32 totalSfntSize; + AutoSwap_PRUint16 majorVersion; + AutoSwap_PRUint16 minorVersion; + AutoSwap_PRUint32 metaOffset; + AutoSwap_PRUint32 metaCompLen; + AutoSwap_PRUint32 metaOrigLen; + AutoSwap_PRUint32 privOffset; + AutoSwap_PRUint32 privLen; +}; + +struct WOFF2Header { + AutoSwap_PRUint32 signature; + AutoSwap_PRUint32 flavor; + AutoSwap_PRUint32 length; + AutoSwap_PRUint16 numTables; + AutoSwap_PRUint16 reserved; + AutoSwap_PRUint32 totalSfntSize; + AutoSwap_PRUint32 totalCompressedSize; + AutoSwap_PRUint16 majorVersion; + AutoSwap_PRUint16 minorVersion; + AutoSwap_PRUint32 metaOffset; + AutoSwap_PRUint32 metaCompLen; + AutoSwap_PRUint32 metaOrigLen; + AutoSwap_PRUint32 privOffset; + AutoSwap_PRUint32 privLen; +}; + +template <typename HeaderT> +void CopyWOFFMetadata(const uint8_t* aFontData, uint32_t aLength, + FallibleTArray<uint8_t>* aMetadata, + uint32_t* aMetaOrigLen) { + // This function may be called with arbitrary, unvalidated "font" data + // from @font-face, so it needs to be careful to bounds-check, etc., + // before trying to read anything. + // This just saves a copy of the compressed data block; it does NOT check + // that the block can be successfully decompressed, or that it contains + // well-formed/valid XML metadata. + if (aLength < sizeof(HeaderT)) { + return; + } + const HeaderT* woff = reinterpret_cast<const HeaderT*>(aFontData); + uint32_t metaOffset = woff->metaOffset; + uint32_t metaCompLen = woff->metaCompLen; + if (!metaOffset || !metaCompLen || !woff->metaOrigLen) { + return; + } + if (metaOffset >= aLength || metaCompLen > aLength - metaOffset) { + return; + } + if (!aMetadata->SetLength(woff->metaCompLen, fallible)) { + return; + } + memcpy(aMetadata->Elements(), aFontData + metaOffset, metaCompLen); + *aMetaOrigLen = woff->metaOrigLen; +} + +void gfxUserFontEntry::LoadNextSrc() { + NS_ASSERTION(mSrcIndex < mSrcList.Length(), + "already at the end of the src list for user font"); + NS_ASSERTION((mUserFontLoadState == STATUS_NOT_LOADED || + mUserFontLoadState == STATUS_LOAD_PENDING || + mUserFontLoadState == STATUS_LOADING) && + mFontDataLoadingState < LOADING_FAILED, + "attempting to load a font that has either completed or failed"); + + if (mUserFontLoadState == STATUS_NOT_LOADED) { + SetLoadState(STATUS_LOADING); + mFontDataLoadingState = LOADING_STARTED; + mUnsupportedFormat = false; + } else { + // we were already loading; move to the next source, + // but don't reset state - if we've already timed out, + // that counts against the new download + mSrcIndex++; + } + + DoLoadNextSrc(false); +} + +void gfxUserFontEntry::ContinueLoad() { + MOZ_ASSERT(mUserFontLoadState == STATUS_LOAD_PENDING); + MOZ_ASSERT(mSrcList[mSrcIndex].mSourceType == + gfxFontFaceSrc::eSourceType_URL); + + SetLoadState(STATUS_LOADING); + DoLoadNextSrc(true); + if (LoadState() != STATUS_LOADING) { + MOZ_ASSERT(mUserFontLoadState != STATUS_LOAD_PENDING, + "Not in parallel traversal, shouldn't get LOAD_PENDING again"); + // Loading is synchronously finished (loaded from cache or failed). We + // need to increment the generation so that we flush the style data to + // use the new loaded font face. + // Without parallel traversal, we would simply get the right font data + // after the first call to DoLoadNextSrc() in this case, so we don't need + // to touch the generation to trigger another restyle. + // XXX We may want to return synchronously in parallel traversal in those + // cases as well if possible, so that we don't have an additional restyle. + // That doesn't work currently because Document::GetDocShell (called from + // FontFaceSet::CheckFontLoad) dereferences a weak pointer, which is not + // allowed in parallel traversal. + IncrementGeneration(); + } +} + +static bool IgnorePrincipal(gfxFontSrcURI* aURI) { + return aURI->InheritsSecurityContext(); +} + +void gfxUserFontEntry::DoLoadNextSrc(bool aForceAsync) { + uint32_t numSrc = mSrcList.Length(); + + // load each src entry in turn, until a local face is found + // or a download begins successfully + while (mSrcIndex < numSrc) { + gfxFontFaceSrc& currSrc = mSrcList[mSrcIndex]; + + // src local ==> lookup and load immediately + + if (currSrc.mSourceType == gfxFontFaceSrc::eSourceType_Local) { + // Don't look up local fonts if the font whitelist is being used. + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + gfxFontEntry* fe = + pfl && pfl->IsFontFamilyWhitelistActive() + ? nullptr + : gfxPlatform::GetPlatform()->LookupLocalFont( + currSrc.mLocalName, Weight(), Stretch(), SlantStyle()); + nsTArray<gfxUserFontSet*> fontSets; + GetUserFontSets(fontSets); + for (gfxUserFontSet* fontSet : fontSets) { + // We need to note on each gfxUserFontSet that contains the user + // font entry that we used a local() rule. + fontSet->SetLocalRulesUsed(); + } + if (fe) { + LOG(("userfonts (%p) [src %d] loaded local: (%s) for (%s) gen: %8.8x\n", + mFontSet, mSrcIndex, currSrc.mLocalName.get(), mFamilyName.get(), + uint32_t(mFontSet->mGeneration))); + fe->mFeatureSettings.AppendElements(mFeatureSettings); + fe->mVariationSettings.AppendElements(mVariationSettings); + fe->mLanguageOverride = mLanguageOverride; + fe->mFamilyName = mFamilyName; + fe->mRangeFlags = mRangeFlags; + // For src:local(), we don't care whether the request is from + // a private window as there's no issue of caching resources; + // local fonts are just available all the time. + StoreUserFontData(fe, false, nsCString(), nullptr, 0, + gfxUserFontData::kUnknownCompression); + mPlatformFontEntry = fe; + SetLoadState(STATUS_LOADED); + Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE, + currSrc.mSourceType + 1); + return; + } else { + LOG(("userfonts (%p) [src %d] failed local: (%s) for (%s)\n", mFontSet, + mSrcIndex, currSrc.mLocalName.get(), mFamilyName.get())); + } + } + + // src url ==> start the load process + else if (currSrc.mSourceType == gfxFontFaceSrc::eSourceType_URL) { + if (gfxPlatform::GetPlatform()->IsFontFormatSupported( + currSrc.mFormatFlags)) { + if (ServoStyleSet* set = ServoStyleSet::Current()) { + // Only support style worker threads synchronously getting + // entries from the font cache when it's not a data: URI + // @font-face that came from UA or user sheets, since we + // were not able to call IsFontLoadAllowed ahead of time + // for these entries. + if (currSrc.mUseOriginPrincipal && IgnorePrincipal(currSrc.mURI)) { + set->AppendTask(PostTraversalTask::LoadFontEntry(this)); + SetLoadState(STATUS_LOAD_PENDING); + return; + } + } + + // see if we have an existing entry for this source + gfxFontEntry* fe = + gfxUserFontSet::UserFontCache::GetFont(currSrc, *this); + if (fe) { + mPlatformFontEntry = fe; + SetLoadState(STATUS_LOADED); + if (LOG_ENABLED()) { + LOG( + ("userfonts (%p) [src %d] " + "loaded uri from cache: (%s) for (%s)\n", + mFontSet, mSrcIndex, currSrc.mURI->GetSpecOrDefault().get(), + mFamilyName.get())); + } + return; + } + + if (ServoStyleSet* set = ServoStyleSet::Current()) { + // If we need to start a font load and we're on a style + // worker thread, we have to defer it. + set->AppendTask(PostTraversalTask::LoadFontEntry(this)); + SetLoadState(STATUS_LOAD_PENDING); + return; + } + + // record the principal we should use for the load for use when + // creating a channel and when caching the loaded entry. + mPrincipal = currSrc.LoadPrincipal(*mFontSet); + + bool loadDoesntSpin = !aForceAsync && currSrc.mURI->SyncLoadIsOK(); + + if (loadDoesntSpin) { + uint8_t* buffer = nullptr; + uint32_t bufferLength = 0; + + // sync load font immediately + nsresult rv = + mFontSet->SyncLoadFontData(this, &currSrc, buffer, bufferLength); + + if (NS_SUCCEEDED(rv) && LoadPlatformFontSync(buffer, bufferLength)) { + SetLoadState(STATUS_LOADED); + Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE, + currSrc.mSourceType + 1); + return; + } else { + mFontSet->LogMessage(this, "font load failed", + nsIScriptError::errorFlag, rv); + } + + } else { + // otherwise load font async + nsresult rv = mFontSet->StartLoad(this, &currSrc); + bool loadOK = NS_SUCCEEDED(rv); + + if (loadOK) { + if (LOG_ENABLED()) { + LOG(("userfonts (%p) [src %d] loading uri: (%s) for (%s)\n", + mFontSet, mSrcIndex, currSrc.mURI->GetSpecOrDefault().get(), + mFamilyName.get())); + } + return; + } else { + mFontSet->LogMessage(this, "failed to start download", + nsIScriptError::errorFlag, rv); + } + } + } else { + // We don't log a warning to the web console yet, + // as another source may load successfully + mUnsupportedFormat = true; + } + } + + // FontFace buffer ==> load immediately + + else { + MOZ_ASSERT(currSrc.mSourceType == gfxFontFaceSrc::eSourceType_Buffer); + + uint8_t* buffer = nullptr; + uint32_t bufferLength = 0; + + // sync load font immediately + currSrc.mBuffer->TakeBuffer(buffer, bufferLength); + if (buffer && LoadPlatformFontSync(buffer, bufferLength)) { + // LoadPlatformFontSync takes ownership of the buffer, so no need + // to free it here. + SetLoadState(STATUS_LOADED); + Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE, + currSrc.mSourceType + 1); + return; + } else { + mFontSet->LogMessage(this, "font load failed", + nsIScriptError::errorFlag); + } + } + + mSrcIndex++; + } + + if (mUnsupportedFormat) { + mFontSet->LogMessage(this, "no supported format found", + nsIScriptError::warningFlag); + } + + // all src's failed; mark this entry as unusable (so fallback will occur) + LOG(("userfonts (%p) failed all src for (%s)\n", mFontSet, + mFamilyName.get())); + mFontDataLoadingState = LOADING_FAILED; + SetLoadState(STATUS_FAILED); +} + +void gfxUserFontEntry::SetLoadState(UserFontLoadState aLoadState) { + mUserFontLoadState = aLoadState; +} + +MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(UserFontMallocSizeOfOnAlloc) + +bool gfxUserFontEntry::LoadPlatformFontSync(const uint8_t* aFontData, + uint32_t aLength) { + AUTO_PROFILER_LABEL("gfxUserFontEntry::LoadPlatformFontSync", OTHER); + NS_ASSERTION((mUserFontLoadState == STATUS_NOT_LOADED || + mUserFontLoadState == STATUS_LOAD_PENDING || + mUserFontLoadState == STATUS_LOADING) && + mFontDataLoadingState < LOADING_FAILED, + "attempting to load a font that has either completed or failed"); + + // Unwrap/decompress/sanitize or otherwise munge the downloaded data + // to make a usable sfnt structure. + + // Call the OTS sanitizer; this will also decode WOFF to sfnt + // if necessary. The original data in aFontData is left unchanged. + uint32_t saneLen; + gfxUserFontType fontType; + nsTArray<OTSMessage> messages; + const uint8_t* saneData = + SanitizeOpenTypeData(aFontData, aLength, saneLen, fontType, messages); + + return LoadPlatformFont(aFontData, aLength, fontType, saneData, saneLen, + std::move(messages)); +} + +void gfxUserFontEntry::StartPlatformFontLoadOnBackgroundThread( + const uint8_t* aFontData, uint32_t aLength, + nsMainThreadPtrHandle<nsIFontLoadCompleteCallback> aCallback) { + MOZ_ASSERT(!NS_IsMainThread()); + + uint32_t saneLen; + gfxUserFontType fontType; + nsTArray<OTSMessage> messages; + const uint8_t* saneData = + SanitizeOpenTypeData(aFontData, aLength, saneLen, fontType, messages); + + nsCOMPtr<nsIRunnable> event = + NewRunnableMethod<const uint8_t*, uint32_t, gfxUserFontType, + const uint8_t*, uint32_t, nsTArray<OTSMessage>&&, + nsMainThreadPtrHandle<nsIFontLoadCompleteCallback>>( + "gfxUserFontEntry::ContinuePlatformFontLoadOnMainThread", this, + &gfxUserFontEntry::ContinuePlatformFontLoadOnMainThread, aFontData, + aLength, fontType, saneData, saneLen, std::move(messages), aCallback); + NS_DispatchToMainThread(event.forget()); +} + +bool gfxUserFontEntry::LoadPlatformFont(const uint8_t* aOriginalFontData, + uint32_t aOriginalLength, + gfxUserFontType aFontType, + const uint8_t* aSanitizedFontData, + uint32_t aSanitizedLength, + nsTArray<OTSMessage>&& aMessages) { + MOZ_ASSERT(NS_IsMainThread()); + + for (const auto& msg : aMessages) { + mFontSet->LogMessage(this, msg.mMessage.get(), + msg.mLevel > 0 ? nsIScriptError::warningFlag + : nsIScriptError::errorFlag); + } + + if (!aSanitizedFontData) { + mFontSet->LogMessage(this, "rejected by sanitizer"); + } else { + // Check whether aSanitizedFontData is a known OpenType format; it might be + // a TrueType Collection, which OTS would accept but we don't yet + // know how to handle. If so, discard. + if (gfxFontUtils::DetermineFontDataType( + aSanitizedFontData, aSanitizedLength) != GFX_USERFONT_OPENTYPE) { + mFontSet->LogMessage(this, "not a supported OpenType format"); + free((void*)aSanitizedFontData); + aSanitizedFontData = nullptr; + } + } + + // Because platform font activation code may replace the name table + // in the font with a synthetic one, we save the original name so that + // it can be reported via the InspectorUtils API. + nsAutoCString originalFullName; + + gfxFontEntry* fe = nullptr; + uint32_t fontCompressionRatio = 0; + size_t computedSize = 0; + + if (aSanitizedFontData) { + if (aSanitizedLength) { + fontCompressionRatio = + uint32_t(100.0 * aOriginalLength / aSanitizedLength + 0.5); + if (aFontType == GFX_USERFONT_WOFF || aFontType == GFX_USERFONT_WOFF2) { + Telemetry::Accumulate(aFontType == GFX_USERFONT_WOFF + ? Telemetry::WEBFONT_COMPRESSION_WOFF + : Telemetry::WEBFONT_COMPRESSION_WOFF2, + fontCompressionRatio); + } + } + + // The sanitizer ensures that we have a valid sfnt and a usable + // name table, so this should never fail unless we're out of + // memory, and GetFullNameFromSFNT is not directly exposed to + // arbitrary/malicious data from the web. + gfxFontUtils::GetFullNameFromSFNT(aSanitizedFontData, aSanitizedLength, + originalFullName); + + // Record size for memory reporting purposes. We measure this now + // because by the time we potentially want to collect reports, this + // data block may have been handed off to opaque OS font APIs that + // don't allow us to retrieve or measure it directly. + // The *OnAlloc function will also tell DMD about this block, as the + // OS font code may hold on to it for an extended period. + computedSize = UserFontMallocSizeOfOnAlloc(aSanitizedFontData); + + // Here ownership of aSanitizedFontData is passed to the platform, + // which will delete it when no longer required + fe = gfxPlatform::GetPlatform()->MakePlatformFont( + mName, Weight(), Stretch(), SlantStyle(), aSanitizedFontData, + aSanitizedLength); + if (!fe) { + mFontSet->LogMessage(this, "not usable by platform"); + } + } + + if (fe) { + fe->mComputedSizeOfUserFont = computedSize; + + // Save a copy of the metadata block (if present) for InspectorUtils + // to use if required. Ownership of the metadata block will be passed + // to the gfxUserFontData record below. + FallibleTArray<uint8_t> metadata; + uint32_t metaOrigLen = 0; + uint8_t compression = gfxUserFontData::kUnknownCompression; + if (aFontType == GFX_USERFONT_WOFF) { + CopyWOFFMetadata<WOFFHeader>(aOriginalFontData, aOriginalLength, + &metadata, &metaOrigLen); + compression = gfxUserFontData::kZlibCompression; + } else if (aFontType == GFX_USERFONT_WOFF2) { + CopyWOFFMetadata<WOFF2Header>(aOriginalFontData, aOriginalLength, + &metadata, &metaOrigLen); + compression = gfxUserFontData::kBrotliCompression; + } + + // copy OpenType feature/language settings from the userfont entry to the + // newly-created font entry + fe->mFeatureSettings.AppendElements(mFeatureSettings); + fe->mVariationSettings.AppendElements(mVariationSettings); + fe->mLanguageOverride = mLanguageOverride; + fe->mFamilyName = mFamilyName; + fe->mRangeFlags = mRangeFlags; + StoreUserFontData(fe, mFontSet->GetPrivateBrowsing(), originalFullName, + &metadata, metaOrigLen, compression); + if (LOG_ENABLED()) { + LOG(( + "userfonts (%p) [src %d] loaded uri: (%s) for (%s) " + "(%p) gen: %8.8x compress: %d%%\n", + mFontSet, mSrcIndex, + mSrcList[mSrcIndex].mURI->GetSpecOrDefault().get(), mFamilyName.get(), + this, uint32_t(mFontSet->mGeneration), fontCompressionRatio)); + } + mPlatformFontEntry = fe; + SetLoadState(STATUS_LOADED); + gfxUserFontSet::UserFontCache::CacheFont(fe); + } else { + if (LOG_ENABLED()) { + LOG( + ("userfonts (%p) [src %d] failed uri: (%s) for (%s)" + " error making platform font\n", + mFontSet, mSrcIndex, + mSrcList[mSrcIndex].mURI->GetSpecOrDefault().get(), + mFamilyName.get())); + } + } + + // The downloaded data can now be discarded; the font entry is using the + // sanitized copy + free((void*)aOriginalFontData); + + return fe != nullptr; +} + +void gfxUserFontEntry::Load() { + if (mUserFontLoadState == STATUS_NOT_LOADED) { + LoadNextSrc(); + } +} + +void gfxUserFontEntry::IncrementGeneration() { + nsTArray<gfxUserFontSet*> fontSets; + GetUserFontSets(fontSets); + for (gfxUserFontSet* fontSet : fontSets) { + fontSet->IncrementGeneration(); + } +} + +// This is called when a font download finishes. +// Ownership of aFontData passes in here, and the font set must +// ensure that it is eventually deleted via free(). +void gfxUserFontEntry::FontDataDownloadComplete( + const uint8_t* aFontData, uint32_t aLength, nsresult aDownloadStatus, + nsIFontLoadCompleteCallback* aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + + // forget about the loader, as we no longer potentially need to cancel it + // if the entry is obsoleted + mLoader = nullptr; + + // download successful, make platform font using font data + if (NS_SUCCEEDED(aDownloadStatus) && + mFontDataLoadingState != LOADING_TIMED_OUT) { + if (StaticPrefs::gfx_downloadable_fonts_sanitize_omt()) { + LoadPlatformFontAsync(aFontData, aLength, aCallback); + } else { + bool loaded = LoadPlatformFontSync(aFontData, aLength); + aFontData = nullptr; + if (loaded) { + IncrementGeneration(); + aCallback->FontLoadComplete(); + } else { + FontLoadFailed(aCallback); + } + } + return; + } + + // download failed or font-display timeout passed + if (mFontDataLoadingState == LOADING_TIMED_OUT) { + mFontSet->LogMessage(this, "font-display timeout, webfont not used", + nsIScriptError::infoFlag, aDownloadStatus); + } else { + mFontSet->LogMessage(this, "download failed", nsIScriptError::errorFlag, + aDownloadStatus); + } + + if (aFontData) { + free((void*)aFontData); + } + + FontLoadFailed(aCallback); +} + +void gfxUserFontEntry::LoadPlatformFontAsync( + const uint8_t* aFontData, uint32_t aLength, + nsIFontLoadCompleteCallback* aCallback) { + nsMainThreadPtrHandle<nsIFontLoadCompleteCallback> cb( + new nsMainThreadPtrHolder<nsIFontLoadCompleteCallback>("FontLoader", + aCallback)); + + // Do the OpenType sanitization over on the font loading thread. Once that is + // complete, we'll continue in ContinuePlatformFontLoadOnMainThread. + // + // We hold a strong reference to the gfxUserFontSet during this work, since + // the document might be closed while we are OMT, and release it at the end + // of ContinuePlatformFontLoadOnMainThread. + + mFontSet->AddRef(); + + nsCOMPtr<nsIRunnable> event = + NewRunnableMethod<const uint8_t*, uint32_t, + nsMainThreadPtrHandle<nsIFontLoadCompleteCallback>>( + "gfxUserFontEntry::StartPlatformFontLoadOnBackgroundThread", this, + &gfxUserFontEntry::StartPlatformFontLoadOnBackgroundThread, aFontData, + aLength, cb); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchBackgroundTask(event.forget())); +} + +void gfxUserFontEntry::ContinuePlatformFontLoadOnMainThread( + const uint8_t* aOriginalFontData, uint32_t aOriginalLength, + gfxUserFontType aFontType, const uint8_t* aSanitizedFontData, + uint32_t aSanitizedLength, nsTArray<OTSMessage>&& aMessages, + nsMainThreadPtrHandle<nsIFontLoadCompleteCallback> aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + + bool loaded = LoadPlatformFont(aOriginalFontData, aOriginalLength, aFontType, + aSanitizedFontData, aSanitizedLength, + std::move(aMessages)); + aOriginalFontData = nullptr; + aSanitizedFontData = nullptr; + + if (loaded) { + IncrementGeneration(); + aCallback->FontLoadComplete(); + } else { + FontLoadFailed(aCallback); + } + + mFontSet->Release(); // for the AddRef in LoadPlatformFontAsync +} + +void gfxUserFontEntry::FontLoadFailed(nsIFontLoadCompleteCallback* aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + + // Error occurred. Make sure the FontFace's promise is rejected if the + // load timed out, or else load the next src. + if (mFontDataLoadingState == LOADING_TIMED_OUT) { + mFontDataLoadingState = LOADING_FAILED; + SetLoadState(STATUS_FAILED); + } else { + LoadNextSrc(); + } + + // We ignore the status returned by LoadNext(); + // even if loading failed, we need to bump the font-set generation + // and return true in order to trigger reflow, so that fallback + // will be used where the text was "masked" by the pending download + IncrementGeneration(); + aCallback->FontLoadComplete(); +} + +void gfxUserFontEntry::GetUserFontSets(nsTArray<gfxUserFontSet*>& aResult) { + aResult.Clear(); + aResult.AppendElement(mFontSet); +} + +gfxUserFontSet::gfxUserFontSet() + : mFontFamilies(4), + mRebuildGeneration(0), + mLocalRulesUsed(false), + mRebuildLocalRules(false), + mDownloadCount(0), + mDownloadSize(0) { + IncrementGeneration(true); + gfxPlatformFontList* fp = gfxPlatformFontList::PlatformFontList(); + if (fp) { + fp->AddUserFontSet(this); + } +} + +gfxUserFontSet::~gfxUserFontSet() { + gfxPlatformFontList* fp = gfxPlatformFontList::PlatformFontList(); + if (fp) { + fp->RemoveUserFontSet(this); + } +} + +already_AddRefed<gfxUserFontEntry> gfxUserFontSet::FindOrCreateUserFontEntry( + const nsACString& aFamilyName, + const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, WeightRange aWeight, + StretchRange aStretch, SlantStyleRange aStyle, + const nsTArray<gfxFontFeature>& aFeatureSettings, + const nsTArray<gfxFontVariation>& aVariationSettings, + uint32_t aLanguageOverride, gfxCharacterMap* aUnicodeRanges, + StyleFontDisplay aFontDisplay, RangeFlags aRangeFlags) { + RefPtr<gfxUserFontEntry> entry; + + // If there's already a userfont entry in the family whose descriptors all + // match, we can just move it to the end of the list instead of adding a new + // face that will always "shadow" the old one. + // Note that we can't do this for platform font entries, even if the + // style descriptors match, as they might have had a different source list, + // but we no longer have the old source list available to check. + gfxUserFontFamily* family = LookupFamily(aFamilyName); + if (family) { + entry = FindExistingUserFontEntry( + family, aFontFaceSrcList, aWeight, aStretch, aStyle, aFeatureSettings, + aVariationSettings, aLanguageOverride, aUnicodeRanges, aFontDisplay, + aRangeFlags); + } + + if (!entry) { + entry = CreateUserFontEntry(aFontFaceSrcList, aWeight, aStretch, aStyle, + aFeatureSettings, aVariationSettings, + aLanguageOverride, aUnicodeRanges, aFontDisplay, + aRangeFlags); + entry->mFamilyName = aFamilyName; + } + + return entry.forget(); +} + +gfxUserFontEntry* gfxUserFontSet::FindExistingUserFontEntry( + gfxUserFontFamily* aFamily, + const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, WeightRange aWeight, + StretchRange aStretch, SlantStyleRange aStyle, + const nsTArray<gfxFontFeature>& aFeatureSettings, + const nsTArray<gfxFontVariation>& aVariationSettings, + uint32_t aLanguageOverride, gfxCharacterMap* aUnicodeRanges, + StyleFontDisplay aFontDisplay, RangeFlags aRangeFlags) { + nsTArray<RefPtr<gfxFontEntry>>& fontList = aFamily->GetFontList(); + + for (size_t i = 0, count = fontList.Length(); i < count; i++) { + if (!fontList[i]->mIsUserFontContainer) { + continue; + } + + gfxUserFontEntry* existingUserFontEntry = + static_cast<gfxUserFontEntry*>(fontList[i].get()); + if (!existingUserFontEntry->Matches( + aFontFaceSrcList, aWeight, aStretch, aStyle, aFeatureSettings, + aVariationSettings, aLanguageOverride, aUnicodeRanges, aFontDisplay, + aRangeFlags)) { + continue; + } + + return existingUserFontEntry; + } + + return nullptr; +} + +void gfxUserFontSet::AddUserFontEntry(const nsCString& aFamilyName, + gfxUserFontEntry* aUserFontEntry) { + gfxUserFontFamily* family = GetFamily(aFamilyName); + family->AddFontEntry(aUserFontEntry); + + if (LOG_ENABLED()) { + nsAutoCString weightString; + aUserFontEntry->Weight().ToString(weightString); + nsAutoCString stretchString; + aUserFontEntry->Stretch().ToString(stretchString); + LOG( + ("userfonts (%p) added to \"%s\" (%p) style: %s weight: %s " + "stretch: %s display: %d", + this, aFamilyName.get(), aUserFontEntry, + (aUserFontEntry->IsItalic() + ? "italic" + : (aUserFontEntry->IsOblique() ? "oblique" : "normal")), + weightString.get(), stretchString.get(), + static_cast<int>(aUserFontEntry->GetFontDisplay()))); + } +} + +void gfxUserFontSet::IncrementGeneration(bool aIsRebuild) { + // add one, increment again if zero + ++sFontSetGeneration; + if (sFontSetGeneration == 0) ++sFontSetGeneration; + mGeneration = sFontSetGeneration; + if (aIsRebuild) { + mRebuildGeneration = mGeneration; + } +} + +void gfxUserFontSet::RebuildLocalRules() { + if (mLocalRulesUsed) { + mRebuildLocalRules = true; + DoRebuildUserFontSet(); + } +} + +gfxUserFontFamily* gfxUserFontSet::LookupFamily( + const nsACString& aFamilyName) const { + nsAutoCString key(aFamilyName); + ToLowerCase(key); + + return mFontFamilies.GetWeak(key); +} + +gfxUserFontFamily* gfxUserFontSet::GetFamily(const nsACString& aFamilyName) { + nsAutoCString key(aFamilyName); + ToLowerCase(key); + + gfxUserFontFamily* family = mFontFamilies.GetWeak(key); + if (!family) { + family = new gfxUserFontFamily(aFamilyName); + mFontFamilies.Put(key, RefPtr{family}); + } + return family; +} + +void gfxUserFontSet::ForgetLocalFaces() { + for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { + const auto fam = iter.Data(); + const auto& fonts = fam->GetFontList(); + for (const auto& f : fonts) { + auto ufe = static_cast<gfxUserFontEntry*>(f.get()); + // If the user font entry has loaded an entry using src:local(), + // discard it as no longer valid, and reset the load state so that + // the load will be re-done based on the updated font list. + if (ufe->GetPlatformFontEntry() && + ufe->GetPlatformFontEntry()->IsLocalUserFont()) { + ufe->mPlatformFontEntry = nullptr; + ufe->LoadCanceled(); + } + } + } +} + +/////////////////////////////////////////////////////////////////////////////// +// gfxUserFontSet::UserFontCache - re-use platform font entries for user fonts +// across pages/fontsets rather than instantiating new platform fonts. +// +// Entries are added to this cache when a platform font is instantiated from +// downloaded data, and removed when the platform font entry is destroyed. +// We don't need to use a timed expiration scheme here because the gfxFontEntry +// for a downloaded font will be kept alive by its corresponding gfxFont +// instance(s) until they are deleted, and *that* happens using an expiration +// tracker (gfxFontCache). The result is that the downloaded font instances +// recorded here will persist between pages and can get reused (provided the +// source URI and principal match, of course). +/////////////////////////////////////////////////////////////////////////////// + +nsTHashtable<gfxUserFontSet::UserFontCache::Entry>* + gfxUserFontSet::UserFontCache::sUserFonts = nullptr; + +NS_IMPL_ISUPPORTS(gfxUserFontSet::UserFontCache::Flusher, nsIObserver) + +NS_IMETHODIMP +gfxUserFontSet::UserFontCache::Flusher::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + if (!sUserFonts) { + return NS_OK; + } + + if (!strcmp(aTopic, "cacheservice:empty-cache")) { + for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) { + i.Remove(); + } + } else if (!strcmp(aTopic, "last-pb-context-exited")) { + for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) { + if (i.Get()->IsPrivate()) { + i.Remove(); + } + } + } else if (!strcmp(aTopic, "xpcom-shutdown")) { + for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) { + i.Get()->GetFontEntry()->DisconnectSVG(); + } + } else { + MOZ_ASSERT_UNREACHABLE("unexpected topic"); + } + + return NS_OK; +} + +bool gfxUserFontSet::UserFontCache::Entry::KeyEquals( + const KeyTypePointer aKey) const { + const gfxFontEntry* fe = aKey->mFontEntry; + + if (!mURI->Equals(aKey->mURI)) { + return false; + } + + // For data: URIs, we don't care about the principal; otherwise, check it. + if (!IgnorePrincipal(mURI)) { + NS_ASSERTION(mPrincipal && aKey->mPrincipal, + "only data: URIs are allowed to omit the principal"); + if (!mPrincipal->Equals(aKey->mPrincipal)) { + return false; + } + } + + if (mPrivate != aKey->mPrivate) { + return false; + } + + if (mFontEntry->SlantStyle() != fe->SlantStyle() || + mFontEntry->Weight() != fe->Weight() || + mFontEntry->Stretch() != fe->Stretch() || + mFontEntry->mFeatureSettings != fe->mFeatureSettings || + mFontEntry->mVariationSettings != fe->mVariationSettings || + mFontEntry->mLanguageOverride != fe->mLanguageOverride || + mFontEntry->mFamilyName != fe->mFamilyName) { + return false; + } + + return true; +} + +void gfxUserFontSet::UserFontCache::CacheFont(gfxFontEntry* aFontEntry) { + NS_ASSERTION(aFontEntry->mFamilyName.Length() != 0, + "caching a font associated with no family yet"); + + // if caching is disabled, simply return + if (Preferences::GetBool("gfx.downloadable_fonts.disable_cache")) { + return; + } + + gfxUserFontData* data = aFontEntry->mUserFontData.get(); + if (data->mIsBuffer) { +#ifdef DEBUG_USERFONT_CACHE + printf("userfontcache skipped fontentry with buffer source: %p\n", + aFontEntry); +#endif + return; + } + + if (!sUserFonts) { + sUserFonts = new nsTHashtable<Entry>; + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + Flusher* flusher = new Flusher; + obs->AddObserver(flusher, "cacheservice:empty-cache", false); + obs->AddObserver(flusher, "last-pb-context-exited", false); + obs->AddObserver(flusher, "xpcom-shutdown", false); + } + + // Create and register a memory reporter for sUserFonts. + // This reporter is never unregistered, but that's OK because + // the reporter checks whether sUserFonts is null, so it would + // be safe to call even after UserFontCache::Shutdown has deleted + // the cache. + RegisterStrongMemoryReporter(new MemoryReporter()); + } + + // For data: URIs, the principal is ignored; anyone who has the same + // data: URI is able to load it and get an equivalent font. + // Otherwise, the principal is used as part of the cache key. + gfxFontSrcPrincipal* principal; + if (IgnorePrincipal(data->mURI)) { + principal = nullptr; + } else { + principal = data->mPrincipal; + } + sUserFonts->PutEntry(Key(data->mURI, principal, aFontEntry, data->mPrivate)); + +#ifdef DEBUG_USERFONT_CACHE + printf("userfontcache added fontentry: %p\n", aFontEntry); + Dump(); +#endif +} + +void gfxUserFontSet::UserFontCache::ForgetFont(gfxFontEntry* aFontEntry) { + if (!sUserFonts) { + // if we've already deleted the cache (i.e. during shutdown), + // just ignore this + return; + } + + // We can't simply use RemoveEntry here because it's possible the principal + // may have changed since the font was cached, in which case the lookup + // would no longer find the entry (bug 838105). + for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) { + if (i.Get()->GetFontEntry() == aFontEntry) { + i.Remove(); + } + } + +#ifdef DEBUG_USERFONT_CACHE + printf("userfontcache removed fontentry: %p\n", aFontEntry); + Dump(); +#endif +} + +gfxFontEntry* gfxUserFontSet::UserFontCache::GetFont( + const gfxFontFaceSrc& aSrc, const gfxUserFontEntry& aUserFontEntry) { + if (!sUserFonts || aUserFontEntry.mFontSet->BypassCache() || + Preferences::GetBool("gfx.downloadable_fonts.disable_cache")) { + return nullptr; + } + + // Ignore principal when looking up a data: URI. + gfxFontSrcPrincipal* principal = + IgnorePrincipal(aSrc.mURI) ? nullptr + : aSrc.LoadPrincipal(*aUserFontEntry.mFontSet); + + Entry* entry = sUserFonts->GetEntry( + Key(aSrc.mURI, principal, const_cast<gfxUserFontEntry*>(&aUserFontEntry), + aUserFontEntry.mFontSet->GetPrivateBrowsing())); + if (!entry) { + return nullptr; + } + + // We have to perform another content policy check here to prevent + // cache poisoning. E.g. a.com loads a font into the cache but + // b.com has a CSP not allowing any fonts to be loaded. + if (!aUserFontEntry.mFontSet->IsFontLoadAllowed(aSrc)) { + return nullptr; + } + + return entry->GetFontEntry(); +} + +void gfxUserFontSet::UserFontCache::Shutdown() { + if (sUserFonts) { + delete sUserFonts; + sUserFonts = nullptr; + } +} + +MOZ_DEFINE_MALLOC_SIZE_OF(UserFontsMallocSizeOf) + +void gfxUserFontSet::UserFontCache::Entry::ReportMemory( + nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) { + MOZ_ASSERT(mFontEntry); + nsAutoCString path("explicit/gfx/user-fonts/font("); + + if (aAnonymize) { + path.AppendPrintf("<anonymized-%p>", this); + } else { + path.AppendPrintf("family=%s", mFontEntry->mFamilyName.get()); + if (mURI) { + nsCString spec = mURI->GetSpecOrDefault(); + spec.ReplaceChar('/', '\\'); + // Some fonts are loaded using horrendously-long data: URIs; + // truncate those before reporting them. + if (mURI->get()->SchemeIs("data") && spec.Length() > 255) { + spec.Truncate(252); + spec.AppendLiteral("..."); + } + path.AppendPrintf(", url=%s", spec.get()); + } + if (mPrincipal) { + nsAutoCString spec; + mPrincipal->NodePrincipal()->GetAsciiSpec(spec); + if (!spec.IsEmpty()) { + // Include a clue as to who loaded this resource. (Note + // that because of font entry sharing, other pages may now + // be using this resource, and the original page may not + // even be loaded any longer.) + spec.ReplaceChar('/', '\\'); + path.AppendPrintf(", principal=%s", spec.get()); + } + } + } + path.Append(')'); + + aHandleReport->Callback( + ""_ns, path, nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, + mFontEntry->ComputedSizeOfExcludingThis(UserFontsMallocSizeOf), + "Memory used by @font-face resource."_ns, aData); +} + +NS_IMPL_ISUPPORTS(gfxUserFontSet::UserFontCache::MemoryReporter, + nsIMemoryReporter) + +NS_IMETHODIMP +gfxUserFontSet::UserFontCache::MemoryReporter::CollectReports( + nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) { + if (!sUserFonts) { + return NS_OK; + } + + for (auto it = sUserFonts->Iter(); !it.Done(); it.Next()) { + it.Get()->ReportMemory(aHandleReport, aData, aAnonymize); + } + + MOZ_COLLECT_REPORT( + "explicit/gfx/user-fonts/cache-overhead", KIND_HEAP, UNITS_BYTES, + sUserFonts->ShallowSizeOfIncludingThis(UserFontsMallocSizeOf), + "Memory used by the @font-face cache, not counting the actual font " + "resources."); + + return NS_OK; +} + +#ifdef DEBUG_USERFONT_CACHE + +void gfxUserFontSet::UserFontCache::Entry::Dump() { + nsresult rv; + + nsAutoCString principalURISpec("(null)"); + bool setDomain = false; + + if (mPrincipal) { + nsCOMPtr<nsIURI> principalURI; + rv = mPrincipal->NodePrincipal()->GetURI(getter_AddRefs(principalURI)); + if (NS_SUCCEEDED(rv)) { + principalURI->GetSpec(principalURISpec); + } + + nsCOMPtr<nsIURI> domainURI; + mPrincipal->NodePrincipal()->GetDomain(getter_AddRefs(domainURI)); + if (domainURI) { + setDomain = true; + } + } + + NS_ASSERTION(mURI, "null URI in userfont cache entry"); + + printf( + "userfontcache fontEntry: %p fonturihash: %8.8x " + "family: %s domainset: %s principal: [%s]\n", + mFontEntry, mURI->Hash(), mFontEntry->FamilyName().get(), + setDomain ? "true" : "false", principalURISpec.get()); +} + +void gfxUserFontSet::UserFontCache::Dump() { + if (!sUserFonts) { + return; + } + + printf("userfontcache dump count: %d ========\n", sUserFonts->Count()); + for (auto it = sUserFonts->Iter(); !it.Done(); it.Next()) { + it.Get()->Dump(); + } + printf("userfontcache dump ==================\n"); +} + +#endif + +#undef LOG +#undef LOG_ENABLED diff --git a/gfx/thebes/gfxUserFontSet.h b/gfx/thebes/gfxUserFontSet.h new file mode 100644 index 0000000000..71ae925f13 --- /dev/null +++ b/gfx/thebes/gfxUserFontSet.h @@ -0,0 +1,774 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_USER_FONT_SET_H +#define GFX_USER_FONT_SET_H + +#include <new> +#include "PLDHashTable.h" +#include "gfxFontEntry.h" +#include "gfxFontUtils.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Attributes.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefPtr.h" +#include "nsCOMPtr.h" +#include "nsHashKeys.h" +#include "nsIMemoryReporter.h" +#include "nsIObserver.h" +#include "nsIScriptError.h" +#include "nsISupports.h" +#include "nsRefPtrHashtable.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nscore.h" + +// Only needed for function bodies. +#include <utility> // for move, forward +#include "MainThreadUtils.h" // for NS_IsMainThread +#include "gfxFontFeatures.h" // for gfxFontFeature +#include "gfxFontSrcPrincipal.h" // for gfxFontSrcPrincipal +#include "gfxFontSrcURI.h" // for gfxFontSrcURI +#include "mozilla/Assertions.h" // for AssertionConditionType, MOZ_ASSERT_HELPER2, MOZ_ASSERT, MOZ_ASSERT_UNREACHABLE, MOZ_ASSER... +#include "mozilla/HashFunctions.h" // for HashBytes, HashGeneric +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "mozilla/gfx/FontVariation.h" // for FontVariation +#include "nsDebug.h" // for NS_WARNING +#include "nsIReferrerInfo.h" // for nsIReferrerInfo + +class gfxFont; +class gfxUserFontSet; +class nsIFontLoadCompleteCallback; +class nsIRunnable; +struct gfxFontStyle; +struct gfxFontVariationAxis; +struct gfxFontVariationInstance; +template <class T> +class nsMainThreadPtrHandle; + +namespace mozilla { +class LogModule; +class PostTraversalTask; +enum class StyleFontDisplay : uint8_t; +} // namespace mozilla +class nsFontFaceLoader; + +//#define DEBUG_USERFONT_CACHE + +class gfxFontFaceBufferSource { + NS_INLINE_DECL_REFCOUNTING(gfxFontFaceBufferSource) + public: + virtual void TakeBuffer(uint8_t*& aBuffer, uint32_t& aLength) = 0; + + protected: + virtual ~gfxFontFaceBufferSource() = default; +}; + +// parsed CSS @font-face rule information +// lifetime: from when @font-face rule processed until font is loaded +struct gfxFontFaceSrc { + enum SourceType { eSourceType_Local, eSourceType_URL, eSourceType_Buffer }; + + SourceType mSourceType; + + // if url, whether to use the origin principal or not + bool mUseOriginPrincipal; + + // format hint flags, union of all possible formats + // (e.g. TrueType, EOT, SVG, etc.) + // see FLAG_FORMAT_* enum values below + uint32_t mFormatFlags; + + nsCString mLocalName; // full font name if local + RefPtr<gfxFontSrcURI> mURI; // uri if url + nsCOMPtr<nsIReferrerInfo> mReferrerInfo; // referrer info if url + RefPtr<gfxFontSrcPrincipal> mOriginPrincipal; // principal if url + + RefPtr<gfxFontFaceBufferSource> mBuffer; + + // The principal that should be used for the load. Should only be used for + // URL sources. + gfxFontSrcPrincipal* LoadPrincipal(const gfxUserFontSet&) const; +}; + +inline bool operator==(const gfxFontFaceSrc& a, const gfxFontFaceSrc& b) { + // The mReferrer and mOriginPrincipal comparisons aren't safe OMT. + MOZ_ASSERT(NS_IsMainThread()); + + if (a.mSourceType != b.mSourceType) { + return false; + } + switch (a.mSourceType) { + case gfxFontFaceSrc::eSourceType_Local: + return a.mLocalName == b.mLocalName; + case gfxFontFaceSrc::eSourceType_URL: { + bool equals; + return a.mUseOriginPrincipal == b.mUseOriginPrincipal && + a.mFormatFlags == b.mFormatFlags && + (a.mURI == b.mURI || a.mURI->Equals(b.mURI)) && + NS_SUCCEEDED(a.mReferrerInfo->Equals(b.mReferrerInfo, &equals)) && + equals && a.mOriginPrincipal->Equals(b.mOriginPrincipal); + } + case gfxFontFaceSrc::eSourceType_Buffer: + return a.mBuffer == b.mBuffer; + } + NS_WARNING("unexpected mSourceType"); + return false; +} + +// Subclassed to store platform-specific code cleaned out when font entry is +// deleted. +// Lifetime: from when platform font is created until it is deactivated. +// If the platform does not need to add any platform-specific code/data here, +// then the gfxUserFontSet will allocate a base gfxUserFontData and attach +// to the entry to track the basic user font info fields here. +class gfxUserFontData { + public: + gfxUserFontData() + : mSrcIndex(0), + mFormat(0), + mMetaOrigLen(0), + mCompression(kUnknownCompression), + mPrivate(false), + mIsBuffer(false) {} + virtual ~gfxUserFontData() = default; + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + nsTArray<uint8_t> mMetadata; // woff metadata block (compressed), if any + RefPtr<gfxFontSrcURI> mURI; // URI of the source, if it was url() + RefPtr<gfxFontSrcPrincipal> + mPrincipal; // principal for the download, if url() + nsCString mLocalName; // font name used for the source, if local() + nsCString mRealName; // original fullname from the font resource + uint32_t mSrcIndex; // index in the rule's source list + uint32_t mFormat; // format hint for the source used, if any + uint32_t mMetaOrigLen; // length needed to decompress metadata + uint8_t mCompression; // compression type + bool mPrivate; // whether font belongs to a private window + bool mIsBuffer; // whether the font source was a buffer + + enum { + kUnknownCompression = 0, + kZlibCompression = 1, + kBrotliCompression = 2 + }; +}; + +// initially contains a set of userfont font entry objects, replaced with +// platform/user fonts as downloaded + +class gfxUserFontFamily : public gfxFontFamily { + public: + friend class gfxUserFontSet; + + explicit gfxUserFontFamily(const nsACString& aName) + : gfxFontFamily(aName, FontVisibility::Webfont) {} + + virtual ~gfxUserFontFamily(); + + // add the given font entry to the end of the family's list + void AddFontEntry(gfxFontEntry* aFontEntry) { + MOZ_ASSERT(!mIsSimpleFamily, "not valid for user-font families"); + // keep ref while removing existing entry + RefPtr<gfxFontEntry> fe = aFontEntry; + // remove existing entry, if already present + mAvailableFonts.RemoveElement(aFontEntry); + // insert at the beginning so that the last-defined font is the first + // one in the fontlist used for matching, as per CSS Fonts spec + mAvailableFonts.InsertElementAt(0, aFontEntry); + + if (aFontEntry->mFamilyName.IsEmpty()) { + aFontEntry->mFamilyName = Name(); + } else { +#ifdef DEBUG + nsCString thisName = Name(); + nsCString entryName = aFontEntry->mFamilyName; + ToLowerCase(thisName); + ToLowerCase(entryName); + MOZ_ASSERT(thisName.Equals(entryName)); +#endif + } + ResetCharacterMap(); + } + + void RemoveFontEntry(gfxFontEntry* aFontEntry) { + MOZ_ASSERT(!mIsSimpleFamily, "not valid for user-font families"); + mAvailableFonts.RemoveElement(aFontEntry); + } + + // Remove all font entries from the family + void DetachFontEntries() { mAvailableFonts.Clear(); } +}; + +class gfxUserFontEntry; +class gfxOTSMessageContext; + +class gfxUserFontSet { + friend class gfxUserFontEntry; + friend class gfxOTSMessageContext; + + public: + typedef mozilla::FontStretch FontStretch; + typedef mozilla::StretchRange StretchRange; + typedef mozilla::FontSlantStyle FontSlantStyle; + typedef mozilla::SlantStyleRange SlantStyleRange; + typedef mozilla::FontWeight FontWeight; + typedef mozilla::WeightRange WeightRange; + typedef gfxFontEntry::RangeFlags RangeFlags; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxUserFontSet) + + gfxUserFontSet(); + + enum { + // no flags ==> no hint set + // unknown ==> unknown format hint set + FLAG_FORMAT_UNKNOWN = 1, + FLAG_FORMAT_OPENTYPE = 1 << 1, + FLAG_FORMAT_TRUETYPE = 1 << 2, + FLAG_FORMAT_TRUETYPE_AAT = 1 << 3, + FLAG_FORMAT_EOT = 1 << 4, + FLAG_FORMAT_SVG = 1 << 5, + FLAG_FORMAT_WOFF = 1 << 6, + FLAG_FORMAT_WOFF2 = 1 << 7, + + FLAG_FORMAT_OPENTYPE_VARIATIONS = 1 << 8, + FLAG_FORMAT_TRUETYPE_VARIATIONS = 1 << 9, + FLAG_FORMAT_WOFF_VARIATIONS = 1 << 10, + FLAG_FORMAT_WOFF2_VARIATIONS = 1 << 11, + + // the common formats that we support everywhere + FLAG_FORMATS_COMMON = + FLAG_FORMAT_OPENTYPE | FLAG_FORMAT_TRUETYPE | FLAG_FORMAT_WOFF | + FLAG_FORMAT_WOFF2 | FLAG_FORMAT_OPENTYPE_VARIATIONS | + FLAG_FORMAT_TRUETYPE_VARIATIONS | FLAG_FORMAT_WOFF_VARIATIONS | + FLAG_FORMAT_WOFF2_VARIATIONS, + + // mask of all unused bits, update when adding new formats + FLAG_FORMAT_NOT_USED = ~((1 << 12) - 1) + }; + + // creates a font face without adding it to a particular family + // weight - [100, 900] (multiples of 100) + // stretch = [FontStretch::UltraCondensed(), FontStretch::UltraExpanded()] + // italic style = constants in gfxFontConstants.h, e.g. NS_FONT_STYLE_NORMAL + // language override = result of calling + // nsLayoutUtils::ParseFontLanguageOverride + // TODO: support for unicode ranges not yet implemented + virtual already_AddRefed<gfxUserFontEntry> CreateUserFontEntry( + const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, WeightRange aWeight, + StretchRange aStretch, SlantStyleRange aStyle, + const nsTArray<gfxFontFeature>& aFeatureSettings, + const nsTArray<mozilla::gfx::FontVariation>& aVariationSettings, + uint32_t aLanguageOverride, gfxCharacterMap* aUnicodeRanges, + mozilla::StyleFontDisplay aFontDisplay, RangeFlags aRangeFlags) = 0; + + // creates a font face for the specified family, or returns an existing + // matching entry on the family if there is one + already_AddRefed<gfxUserFontEntry> FindOrCreateUserFontEntry( + const nsACString& aFamilyName, + const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, WeightRange aWeight, + StretchRange aStretch, SlantStyleRange aStyle, + const nsTArray<gfxFontFeature>& aFeatureSettings, + const nsTArray<mozilla::gfx::FontVariation>& aVariationSettings, + uint32_t aLanguageOverride, gfxCharacterMap* aUnicodeRanges, + mozilla::StyleFontDisplay aFontDisplay, RangeFlags aRangeFlags); + + // add in a font face for which we have the gfxUserFontEntry already + void AddUserFontEntry(const nsCString& aFamilyName, + gfxUserFontEntry* aUserFontEntry); + + // Whether there is a face with this family name + bool HasFamily(const nsACString& aFamilyName) const { + return LookupFamily(aFamilyName) != nullptr; + } + + // Look up and return the gfxUserFontFamily in mFontFamilies with + // the given name + gfxUserFontFamily* LookupFamily(const nsACString& aName) const; + + virtual gfxFontSrcPrincipal* GetStandardFontLoadPrincipal() const = 0; + + // check whether content policies allow the given URI to load. + virtual bool IsFontLoadAllowed(const gfxFontFaceSrc&) = 0; + + // Dispatches all of the specified runnables to the font face set's + // document's event queue. + virtual void DispatchFontLoadViolations( + nsTArray<nsCOMPtr<nsIRunnable>>& aViolations) = 0; + + // initialize the process that loads external font data, which upon + // completion will call FontDataDownloadComplete method + virtual nsresult StartLoad(gfxUserFontEntry* aUserFontEntry, + const gfxFontFaceSrc* aFontFaceSrc) = 0; + + // generation - each time a face is loaded, generation is + // incremented so that the change can be recognized + uint64_t GetGeneration() { return mGeneration; } + + // increment the generation on font load + void IncrementGeneration(bool aIsRebuild = false); + + // Generation is bumped on font loads but that doesn't affect name-style + // mappings. Rebuilds do however affect name-style mappings so need to + // lookup fontlists again when that happens. + uint64_t GetRebuildGeneration() { return mRebuildGeneration; } + + // rebuild if local rules have been used + void RebuildLocalRules(); + + // Discard any font entries created for src:local(), so that they will + // be reloaded next time they're needed. This is called when the platform + // font list has changed, which means local font entries that were set up + // may no longer be valid. + void ForgetLocalFaces(); + + class UserFontCache { + public: + // Record a loaded user-font in the cache. This requires that the + // font-entry's userFontData has been set up already, as it relies + // on the URI and Principal recorded there. + static void CacheFont(gfxFontEntry* aFontEntry); + + // The given gfxFontEntry is being destroyed, so remove any record that + // refers to it. + static void ForgetFont(gfxFontEntry* aFontEntry); + + // Return the gfxFontEntry corresponding to a given URI and principal, + // and the features of the given userfont entry, or nullptr if none is + // available. The aPrivate flag is set for requests coming from private + // windows, so we can avoid leaking fonts cached in private windows mode out + // to normal windows. + static gfxFontEntry* GetFont(const gfxFontFaceSrc&, + const gfxUserFontEntry&); + + // Clear everything so that we don't leak URIs and Principals. + static void Shutdown(); + + // Memory-reporting support. + class MemoryReporter final : public nsIMemoryReporter { + private: + ~MemoryReporter() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMEMORYREPORTER + }; + +#ifdef DEBUG_USERFONT_CACHE + // dump contents + static void Dump(); +#endif + + private: + // Helper that we use to observe the empty-cache notification + // from nsICacheService. + class Flusher : public nsIObserver { + virtual ~Flusher() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + Flusher() = default; + }; + + // Key used to look up entries in the user-font cache. + // Note that key comparison does *not* use the mFontEntry field + // as a whole; it only compares specific fields within the entry + // (weight/width/style/features) that could affect font selection + // or rendering, and that must match between a font-set's userfont + // entry and the corresponding "real" font entry. + struct Key { + RefPtr<gfxFontSrcURI> mURI; + RefPtr<gfxFontSrcPrincipal> mPrincipal; // use nullptr with data: URLs + // The font entry MUST notify the cache when it is destroyed + // (by calling ForgetFont()). + gfxFontEntry* MOZ_NON_OWNING_REF mFontEntry; + bool mPrivate; + + Key(gfxFontSrcURI* aURI, gfxFontSrcPrincipal* aPrincipal, + gfxFontEntry* aFontEntry, bool aPrivate) + : mURI(aURI), + mPrincipal(aPrincipal), + mFontEntry(aFontEntry), + mPrivate(aPrivate) {} + }; + + class Entry : public PLDHashEntryHdr { + public: + typedef const Key& KeyType; + typedef const Key* KeyTypePointer; + + explicit Entry(KeyTypePointer aKey) + : mURI(aKey->mURI), + mPrincipal(aKey->mPrincipal), + mFontEntry(aKey->mFontEntry), + mPrivate(aKey->mPrivate) {} + + Entry(Entry&& aOther) + : PLDHashEntryHdr(std::move(aOther)), + mURI(std::move(aOther.mURI)), + mPrincipal(std::move(aOther.mPrincipal)), + mFontEntry(std::move(aOther.mFontEntry)), + mPrivate(std::move(aOther.mPrivate)) {} + + ~Entry() = default; + + bool KeyEquals(const KeyTypePointer aKey) const; + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + + static PLDHashNumber HashKey(const KeyTypePointer aKey) { + PLDHashNumber principalHash = + aKey->mPrincipal ? aKey->mPrincipal->Hash() : 0; + return mozilla::HashGeneric( + principalHash + int(aKey->mPrivate), aKey->mURI->Hash(), + HashFeatures(aKey->mFontEntry->mFeatureSettings), + HashVariations(aKey->mFontEntry->mVariationSettings), + mozilla::HashString(aKey->mFontEntry->mFamilyName), + aKey->mFontEntry->Weight().AsScalar(), + aKey->mFontEntry->SlantStyle().AsScalar(), + aKey->mFontEntry->Stretch().AsScalar(), + aKey->mFontEntry->mLanguageOverride); + } + + enum { ALLOW_MEMMOVE = false }; + + gfxFontSrcURI* GetURI() const { return mURI; } + gfxFontSrcPrincipal* GetPrincipal() const { return mPrincipal; } + gfxFontEntry* GetFontEntry() const { return mFontEntry; } + bool IsPrivate() const { return mPrivate; } + + void ReportMemory(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize); + +#ifdef DEBUG_USERFONT_CACHE + void Dump(); +#endif + + private: + static uint32_t HashFeatures(const nsTArray<gfxFontFeature>& aFeatures) { + return mozilla::HashBytes(aFeatures.Elements(), + aFeatures.Length() * sizeof(gfxFontFeature)); + } + + static uint32_t HashVariations( + const nsTArray<mozilla::gfx::FontVariation>& aVariations) { + return mozilla::HashBytes( + aVariations.Elements(), + aVariations.Length() * sizeof(mozilla::gfx::FontVariation)); + } + + RefPtr<gfxFontSrcURI> mURI; + RefPtr<gfxFontSrcPrincipal> mPrincipal; // or nullptr for data: URLs + + // The "real" font entry corresponding to this downloaded font. + // The font entry MUST notify the cache when it is destroyed + // (by calling ForgetFont()). + gfxFontEntry* MOZ_NON_OWNING_REF mFontEntry; + + // Whether this font was loaded from a private window. + bool mPrivate; + }; + + static nsTHashtable<Entry>* sUserFonts; + }; + + void SetLocalRulesUsed() { mLocalRulesUsed = true; } + + static mozilla::LogModule* GetUserFontsLog(); + + // record statistics about font completion + virtual void RecordFontLoadDone(uint32_t aFontSize, + mozilla::TimeStamp aDoneTime) {} + + void GetLoadStatistics(uint32_t& aLoadCount, uint64_t& aLoadSize) const { + aLoadCount = mDownloadCount; + aLoadSize = mDownloadSize; + } + + protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~gfxUserFontSet(); + + // Return whether the font set is associated with a private-browsing tab. + virtual bool GetPrivateBrowsing() = 0; + + // Return whether the font set is associated with a document that was + // shift-reloaded, for example, and thus should bypass the font cache. + virtual bool BypassCache() = 0; + + // parse data for a data URL + virtual nsresult SyncLoadFontData(gfxUserFontEntry* aFontToLoad, + const gfxFontFaceSrc* aFontFaceSrc, + uint8_t*& aBuffer, + uint32_t& aBufferLength) = 0; + + // report a problem of some kind (implemented in nsUserFontSet) + virtual nsresult LogMessage(gfxUserFontEntry* aUserFontEntry, + const char* aMessage, + uint32_t aFlags = nsIScriptError::errorFlag, + nsresult aStatus = NS_OK) = 0; + + // helper method for performing the actual userfont set rebuild + virtual void DoRebuildUserFontSet() = 0; + + // helper method for FindOrCreateUserFontEntry + gfxUserFontEntry* FindExistingUserFontEntry( + gfxUserFontFamily* aFamily, + const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, WeightRange aWeight, + StretchRange aStretch, SlantStyleRange aStyle, + const nsTArray<gfxFontFeature>& aFeatureSettings, + const nsTArray<mozilla::gfx::FontVariation>& aVariationSettings, + uint32_t aLanguageOverride, gfxCharacterMap* aUnicodeRanges, + mozilla::StyleFontDisplay aFontDisplay, RangeFlags aRangeFlags); + + // creates a new gfxUserFontFamily in mFontFamilies, or returns an existing + // family if there is one + gfxUserFontFamily* GetFamily(const nsACString& aFamilyName); + + // font families defined by @font-face rules + nsRefPtrHashtable<nsCStringHashKey, gfxUserFontFamily> mFontFamilies; + + uint64_t mGeneration; // bumped on any font load change + uint64_t mRebuildGeneration; // only bumped on rebuilds + + // true when local names have been looked up, false otherwise + bool mLocalRulesUsed; + + // true when rules using local names need to be redone + bool mRebuildLocalRules; + + // performance stats + uint32_t mDownloadCount; + uint64_t mDownloadSize; +}; + +// acts a placeholder until the real font is downloaded + +class gfxUserFontEntry : public gfxFontEntry { + friend class mozilla::PostTraversalTask; + friend class gfxUserFontSet; + friend class nsUserFontSet; + friend class nsFontFaceLoader; + friend class gfxOTSMessageContext; + + public: + enum UserFontLoadState { + STATUS_NOT_LOADED = 0, + STATUS_LOAD_PENDING, + STATUS_LOADING, + STATUS_LOADED, + STATUS_FAILED + }; + + gfxUserFontEntry( + gfxUserFontSet* aFontSet, + const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, WeightRange aWeight, + StretchRange aStretch, SlantStyleRange aStyle, + const nsTArray<gfxFontFeature>& aFeatureSettings, + const nsTArray<mozilla::gfx::FontVariation>& aVariationSettings, + uint32_t aLanguageOverride, gfxCharacterMap* aUnicodeRanges, + mozilla::StyleFontDisplay aFontDisplay, RangeFlags aRangeFlags); + + virtual ~gfxUserFontEntry(); + + // Update the attributes of the entry to the given values, without disturbing + // the associated platform font entry or in-progress downloads. + void UpdateAttributes( + WeightRange aWeight, StretchRange aStretch, SlantStyleRange aStyle, + const nsTArray<gfxFontFeature>& aFeatureSettings, + const nsTArray<mozilla::gfx::FontVariation>& aVariationSettings, + uint32_t aLanguageOverride, gfxCharacterMap* aUnicodeRanges, + mozilla::StyleFontDisplay aFontDisplay, RangeFlags aRangeFlags); + + // Return whether the entry matches the given list of attributes + bool Matches(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, + WeightRange aWeight, StretchRange aStretch, + SlantStyleRange aStyle, + const nsTArray<gfxFontFeature>& aFeatureSettings, + const nsTArray<mozilla::gfx::FontVariation>& aVariationSettings, + uint32_t aLanguageOverride, gfxCharacterMap* aUnicodeRanges, + mozilla::StyleFontDisplay aFontDisplay, RangeFlags aRangeFlags); + + gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle) override; + + gfxFontEntry* GetPlatformFontEntry() const { return mPlatformFontEntry; } + + // is the font loading or loaded, or did it fail? + UserFontLoadState LoadState() const { return mUserFontLoadState; } + + void LoadCanceled() { + mUserFontLoadState = STATUS_NOT_LOADED; + mFontDataLoadingState = NOT_LOADING; + mLoader = nullptr; + } + + // whether to wait before using fallback font or not + bool WaitForUserFont() const { + return (mUserFontLoadState == STATUS_LOAD_PENDING || + mUserFontLoadState == STATUS_LOADING) && + mFontDataLoadingState < LOADING_SLOWLY; + } + + // for userfonts, cmap is used to store the unicode range data + // no cmap ==> all codepoints permitted + bool CharacterInUnicodeRange(uint32_t ch) const { + if (mCharacterMap) { + return mCharacterMap->test(ch); + } + return true; + } + + gfxCharacterMap* GetUnicodeRangeMap() const { return mCharacterMap.get(); } + + mozilla::StyleFontDisplay GetFontDisplay() const { return mFontDisplay; } + + // load the font - starts the loading of sources which continues until + // a valid font resource is found or all sources fail + void Load(); + + // methods to expose some information to FontFaceSet::UserFontSet + // since we can't make that class a friend + void SetLoader(nsFontFaceLoader* aLoader) { mLoader = aLoader; } + nsFontFaceLoader* GetLoader() const { return mLoader; } + gfxFontSrcPrincipal* GetPrincipal() const { return mPrincipal; } + uint32_t GetSrcIndex() const { return mSrcIndex; } + void GetFamilyNameAndURIForLogging(nsACString& aFamilyName, nsACString& aURI); + + gfxFontEntry* Clone() const override { + MOZ_ASSERT_UNREACHABLE("cannot Clone user fonts"); + return nullptr; + } + +#ifdef DEBUG + gfxUserFontSet* GetUserFontSet() const { return mFontSet; } +#endif + + const nsTArray<gfxFontFaceSrc>& SourceList() const { return mSrcList; } + + // The variation-query APIs should not be called on placeholders. + bool HasVariations() override { + MOZ_ASSERT_UNREACHABLE("not meaningful for a userfont placeholder"); + return false; + } + void GetVariationAxes(nsTArray<gfxFontVariationAxis>&) override { + MOZ_ASSERT_UNREACHABLE("not meaningful for a userfont placeholder"); + } + void GetVariationInstances(nsTArray<gfxFontVariationInstance>&) override { + MOZ_ASSERT_UNREACHABLE("not meaningful for a userfont placeholder"); + } + + protected: + struct OTSMessage { + nsCString mMessage; + int mLevel; // see OTSContext in gfx/ots/include/opentype-sanitizer.h + }; + + const uint8_t* SanitizeOpenTypeData(const uint8_t* aData, uint32_t aLength, + uint32_t& aSaneLength, + gfxUserFontType& aFontType, + nsTArray<OTSMessage>& aMessages); + + // attempt to load the next resource in the src list. + void LoadNextSrc(); + void ContinueLoad(); + void DoLoadNextSrc(bool aForceAsync); + + // change the load state + virtual void SetLoadState(UserFontLoadState aLoadState); + + // when download has been completed, pass back data here + // aDownloadStatus == NS_OK ==> download succeeded, error otherwise + // Ownership of aFontData is passed in here; the font set must + // ensure that it is eventually deleted with free(). + void FontDataDownloadComplete(const uint8_t* aFontData, uint32_t aLength, + nsresult aDownloadStatus, + nsIFontLoadCompleteCallback* aCallback); + + // helper method for creating a platform font + // returns true if platform font creation successful + // Ownership of aFontData is passed in here; the font must + // ensure that it is eventually deleted with free(). + bool LoadPlatformFontSync(const uint8_t* aFontData, uint32_t aLength); + + void LoadPlatformFontAsync(const uint8_t* aFontData, uint32_t aLength, + nsIFontLoadCompleteCallback* aCallback); + + // helper method for LoadPlatformFontAsync; runs on a background thread + void StartPlatformFontLoadOnBackgroundThread( + const uint8_t* aFontData, uint32_t aLength, + nsMainThreadPtrHandle<nsIFontLoadCompleteCallback> aCallback); + + // helper method for LoadPlatformFontAsync; runs on the main thread + void ContinuePlatformFontLoadOnMainThread( + const uint8_t* aOriginalFontData, uint32_t aOriginalLength, + gfxUserFontType aFontType, const uint8_t* aSanitizedFontData, + uint32_t aSanitizedLength, nsTArray<OTSMessage>&& aMessages, + nsMainThreadPtrHandle<nsIFontLoadCompleteCallback> aCallback); + + // helper method for LoadPlatformFontSync and + // ContinuePlatformFontLoadOnMainThread; runs on the main thread + bool LoadPlatformFont(const uint8_t* aOriginalFontData, + uint32_t aOriginalLength, gfxUserFontType aFontType, + const uint8_t* aSanitizedFontData, + uint32_t aSanitizedLength, + nsTArray<OTSMessage>&& aMessages); + + // helper method for FontDataDownloadComplete and + // ContinuePlatformFontLoadOnMainThread; runs on the main thread + void FontLoadFailed(nsIFontLoadCompleteCallback* aCallback); + + // store metadata and src details for current src into aFontEntry + void StoreUserFontData(gfxFontEntry* aFontEntry, bool aPrivate, + const nsACString& aOriginalName, + FallibleTArray<uint8_t>* aMetadata, + uint32_t aMetaOrigLen, uint8_t aCompression); + + // Clears and then adds to aResult all of the user font sets that this user + // font entry has been added to. This will at least include mFontSet, the + // owner of this user font entry. + virtual void GetUserFontSets(nsTArray<gfxUserFontSet*>& aResult); + + // Calls IncrementGeneration() on all user font sets that contain this + // user font entry. + void IncrementGeneration(); + + // general load state + UserFontLoadState mUserFontLoadState; + + // detailed load state while font data is loading + // used to determine whether to use fallback font or not + // note that code depends on the ordering of these values! + enum FontDataLoadingState { + NOT_LOADING = 0, // not started to load any font resources yet + LOADING_STARTED, // loading has started; hide fallback font + LOADING_ALMOST_DONE, // timeout happened but we're nearly done, + // so keep hiding fallback font + LOADING_SLOWLY, // timeout happened and we're not nearly done, + // so use the fallback font + LOADING_TIMED_OUT, // font load took too long + LOADING_FAILED // failed to load any source: use fallback + }; + FontDataLoadingState mFontDataLoadingState; + + bool mUnsupportedFormat; + mozilla::StyleFontDisplay mFontDisplay; // timing of userfont fallback + + RefPtr<gfxFontEntry> mPlatformFontEntry; + nsTArray<gfxFontFaceSrc> mSrcList; + uint32_t mSrcIndex; // index of loading src item + // This field is managed by the nsFontFaceLoader. In the destructor and + // Cancel() methods of nsFontFaceLoader this reference is nulled out. + nsFontFaceLoader* MOZ_NON_OWNING_REF + mLoader; // current loader for this entry, if any + gfxUserFontSet* MOZ_NON_OWNING_REF + mFontSet; // font-set which owns this userfont entry + RefPtr<gfxFontSrcPrincipal> mPrincipal; +}; + +#endif /* GFX_USER_FONT_SET_H */ diff --git a/gfx/thebes/gfxUtils.cpp b/gfx/thebes/gfxUtils.cpp new file mode 100644 index 0000000000..29d6904508 --- /dev/null +++ b/gfx/thebes/gfxUtils.cpp @@ -0,0 +1,1487 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxUtils.h" + +#include "cairo.h" +#include "gfxContext.h" +#include "gfxEnv.h" +#include "gfxImageSurface.h" +#include "gfxPlatform.h" +#include "gfxDrawable.h" +#include "gfxQuad.h" +#include "imgIEncoder.h" +#include "mozilla/Base64.h" +#include "mozilla/dom/ImageEncoder.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerRunnable.h" +#include "mozilla/ipc/CrossProcessSemaphore.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/gfx/Swizzle.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/image/nsBMPEncoder.h" +#include "mozilla/image/nsICOEncoder.h" +#include "mozilla/image/nsJPEGEncoder.h" +#include "mozilla/image/nsPNGEncoder.h" +#include "mozilla/layers/SynchronousTask.h" +#include "mozilla/Maybe.h" +#include "mozilla/Preferences.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ServoStyleConsts.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/Unused.h" +#include "mozilla/Vector.h" +#include "mozilla/webrender/webrender_ffi.h" +#include "nsAppRunner.h" +#include "nsComponentManagerUtils.h" +#include "nsIClipboardHelper.h" +#include "nsIFile.h" +#include "nsIGfxInfo.h" +#include "nsMimeTypes.h" +#include "nsPresContext.h" +#include "nsRegion.h" +#include "nsServiceManagerUtils.h" +#include "GeckoProfiler.h" +#include "ImageContainer.h" +#include "ImageRegion.h" +#include "gfx2DGlue.h" + +#ifdef XP_WIN +# include "gfxWindowsPlatform.h" +#endif + +using namespace mozilla; +using namespace mozilla::image; +using namespace mozilla::layers; +using namespace mozilla::gfx; + +#undef compress +#include "mozilla/Compression.h" + +using namespace mozilla::Compression; +extern "C" { + +/** + * Dump a raw image to the default log. This function is exported + * from libxul, so it can be called from any library in addition to + * (of course) from a debugger. + * + * Note: this helper currently assumes that all 2-bytepp images are + * r5g6b5, and that all 4-bytepp images are r8g8b8a8. + */ +NS_EXPORT +void mozilla_dump_image(void* bytes, int width, int height, int bytepp, + int strideBytes) { + if (0 == strideBytes) { + strideBytes = width * bytepp; + } + SurfaceFormat format; + // TODO more flexible; parse string? + switch (bytepp) { + case 2: + format = SurfaceFormat::R5G6B5_UINT16; + break; + case 4: + default: + format = SurfaceFormat::R8G8B8A8; + break; + } + + RefPtr<DataSourceSurface> surf = Factory::CreateWrappingDataSourceSurface( + (uint8_t*)bytes, strideBytes, IntSize(width, height), format); + gfxUtils::DumpAsDataURI(surf); +} +} + +static bool MapSrcDest(DataSourceSurface* srcSurf, DataSourceSurface* destSurf, + DataSourceSurface::MappedSurface* out_srcMap, + DataSourceSurface::MappedSurface* out_destMap) { + MOZ_ASSERT(srcSurf && destSurf); + MOZ_ASSERT(out_srcMap && out_destMap); + + if (srcSurf->GetSize() != destSurf->GetSize()) { + MOZ_ASSERT(false, "Width and height must match."); + return false; + } + + if (srcSurf == destSurf) { + DataSourceSurface::MappedSurface map; + if (!srcSurf->Map(DataSourceSurface::MapType::READ_WRITE, &map)) { + NS_WARNING("Couldn't Map srcSurf/destSurf."); + return false; + } + + *out_srcMap = map; + *out_destMap = map; + return true; + } + + // Map src for reading. + DataSourceSurface::MappedSurface srcMap; + if (!srcSurf->Map(DataSourceSurface::MapType::READ, &srcMap)) { + NS_WARNING("Couldn't Map srcSurf."); + return false; + } + + // Map dest for writing. + DataSourceSurface::MappedSurface destMap; + if (!destSurf->Map(DataSourceSurface::MapType::WRITE, &destMap)) { + NS_WARNING("Couldn't Map aDest."); + srcSurf->Unmap(); + return false; + } + + *out_srcMap = srcMap; + *out_destMap = destMap; + return true; +} + +static void UnmapSrcDest(DataSourceSurface* srcSurf, + DataSourceSurface* destSurf) { + if (srcSurf == destSurf) { + srcSurf->Unmap(); + } else { + srcSurf->Unmap(); + destSurf->Unmap(); + } +} + +bool gfxUtils::PremultiplyDataSurface(DataSourceSurface* srcSurf, + DataSourceSurface* destSurf) { + MOZ_ASSERT(srcSurf && destSurf); + + DataSourceSurface::MappedSurface srcMap; + DataSourceSurface::MappedSurface destMap; + if (!MapSrcDest(srcSurf, destSurf, &srcMap, &destMap)) return false; + + PremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(), + destMap.mData, destMap.mStride, destSurf->GetFormat(), + srcSurf->GetSize()); + + UnmapSrcDest(srcSurf, destSurf); + return true; +} + +bool gfxUtils::UnpremultiplyDataSurface(DataSourceSurface* srcSurf, + DataSourceSurface* destSurf) { + MOZ_ASSERT(srcSurf && destSurf); + + DataSourceSurface::MappedSurface srcMap; + DataSourceSurface::MappedSurface destMap; + if (!MapSrcDest(srcSurf, destSurf, &srcMap, &destMap)) return false; + + UnpremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(), + destMap.mData, destMap.mStride, destSurf->GetFormat(), + srcSurf->GetSize()); + + UnmapSrcDest(srcSurf, destSurf); + return true; +} + +static bool MapSrcAndCreateMappedDest( + DataSourceSurface* srcSurf, RefPtr<DataSourceSurface>* out_destSurf, + DataSourceSurface::MappedSurface* out_srcMap, + DataSourceSurface::MappedSurface* out_destMap) { + MOZ_ASSERT(srcSurf); + MOZ_ASSERT(out_destSurf && out_srcMap && out_destMap); + + // Ok, map source for reading. + DataSourceSurface::MappedSurface srcMap; + if (!srcSurf->Map(DataSourceSurface::MapType::READ, &srcMap)) { + MOZ_ASSERT(false, "Couldn't Map srcSurf."); + return false; + } + + // Make our dest surface based on the src. + RefPtr<DataSourceSurface> destSurf = + Factory::CreateDataSourceSurfaceWithStride( + srcSurf->GetSize(), srcSurf->GetFormat(), srcMap.mStride); + if (NS_WARN_IF(!destSurf)) { + return false; + } + + DataSourceSurface::MappedSurface destMap; + if (!destSurf->Map(DataSourceSurface::MapType::WRITE, &destMap)) { + MOZ_ASSERT(false, "Couldn't Map destSurf."); + srcSurf->Unmap(); + return false; + } + + *out_destSurf = destSurf; + *out_srcMap = srcMap; + *out_destMap = destMap; + return true; +} + +already_AddRefed<DataSourceSurface> gfxUtils::CreatePremultipliedDataSurface( + DataSourceSurface* srcSurf) { + RefPtr<DataSourceSurface> destSurf; + DataSourceSurface::MappedSurface srcMap; + DataSourceSurface::MappedSurface destMap; + if (!MapSrcAndCreateMappedDest(srcSurf, &destSurf, &srcMap, &destMap)) { + MOZ_ASSERT(false, "MapSrcAndCreateMappedDest failed."); + RefPtr<DataSourceSurface> surface(srcSurf); + return surface.forget(); + } + + PremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(), + destMap.mData, destMap.mStride, destSurf->GetFormat(), + srcSurf->GetSize()); + + UnmapSrcDest(srcSurf, destSurf); + return destSurf.forget(); +} + +already_AddRefed<DataSourceSurface> gfxUtils::CreateUnpremultipliedDataSurface( + DataSourceSurface* srcSurf) { + RefPtr<DataSourceSurface> destSurf; + DataSourceSurface::MappedSurface srcMap; + DataSourceSurface::MappedSurface destMap; + if (!MapSrcAndCreateMappedDest(srcSurf, &destSurf, &srcMap, &destMap)) { + MOZ_ASSERT(false, "MapSrcAndCreateMappedDest failed."); + RefPtr<DataSourceSurface> surface(srcSurf); + return surface.forget(); + } + + UnpremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(), + destMap.mData, destMap.mStride, destSurf->GetFormat(), + srcSurf->GetSize()); + + UnmapSrcDest(srcSurf, destSurf); + return destSurf.forget(); +} + +void gfxUtils::ConvertBGRAtoRGBA(uint8_t* aData, uint32_t aLength) { + MOZ_ASSERT((aLength % 4) == 0, "Loop below will pass srcEnd!"); + SwizzleData(aData, aLength, SurfaceFormat::B8G8R8A8, aData, aLength, + SurfaceFormat::R8G8B8A8, IntSize(aLength / 4, 1)); +} + +#if !defined(MOZ_GFX_OPTIMIZE_MOBILE) +/** + * This returns the fastest operator to use for solid surfaces which have no + * alpha channel or their alpha channel is uniformly opaque. + * This differs per render mode. + */ +static CompositionOp OptimalFillOp() { +# ifdef XP_WIN + if (gfxWindowsPlatform::GetPlatform()->IsDirect2DBackend()) { + // D2D -really- hates operator source. + return CompositionOp::OP_OVER; + } +# endif + return CompositionOp::OP_SOURCE; +} + +// EXTEND_PAD won't help us here; we have to create a temporary surface to hold +// the subimage of pixels we're allowed to sample. +static already_AddRefed<gfxDrawable> CreateSamplingRestrictedDrawable( + gfxDrawable* aDrawable, gfxContext* aContext, const ImageRegion& aRegion, + const SurfaceFormat aFormat, bool aUseOptimalFillOp) { + AUTO_PROFILER_LABEL("CreateSamplingRestrictedDrawable", GRAPHICS); + + DrawTarget* destDrawTarget = aContext->GetDrawTarget(); + if (destDrawTarget->GetBackendType() == BackendType::DIRECT2D1_1) { + return nullptr; + } + + gfxRect clipExtents = aContext->GetClipExtents(); + + // Inflate by one pixel because bilinear filtering will sample at most + // one pixel beyond the computed image pixel coordinate. + clipExtents.Inflate(1.0); + + gfxRect needed = aRegion.IntersectAndRestrict(clipExtents); + needed.RoundOut(); + + // if 'needed' is empty, nothing will be drawn since aFill + // must be entirely outside the clip region, so it doesn't + // matter what we do here, but we should avoid trying to + // create a zero-size surface. + if (needed.IsEmpty()) return nullptr; + + IntSize size(int32_t(needed.Width()), int32_t(needed.Height())); + + RefPtr<DrawTarget> target = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(size, + aFormat); + if (!target || !target->IsValid()) { + return nullptr; + } + + RefPtr<gfxContext> tmpCtx = gfxContext::CreateOrNull(target); + MOZ_ASSERT(tmpCtx); // already checked the target above + + if (aUseOptimalFillOp) { + tmpCtx->SetOp(OptimalFillOp()); + } + aDrawable->Draw(tmpCtx, needed - needed.TopLeft(), ExtendMode::REPEAT, + SamplingFilter::LINEAR, 1.0, + gfxMatrix::Translation(needed.TopLeft())); + RefPtr<SourceSurface> surface = target->Snapshot(); + + RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable( + surface, size, gfxMatrix::Translation(-needed.TopLeft())); + return drawable.forget(); +} +#endif // !MOZ_GFX_OPTIMIZE_MOBILE + +/* These heuristics are based on + * Source/WebCore/platform/graphics/skia/ImageSkia.cpp:computeResamplingMode() + */ +#ifdef MOZ_GFX_OPTIMIZE_MOBILE +static SamplingFilter ReduceResamplingFilter(SamplingFilter aSamplingFilter, + int aImgWidth, int aImgHeight, + float aSourceWidth, + float aSourceHeight) { + // Images smaller than this in either direction are considered "small" and + // are not resampled ever (see below). + const int kSmallImageSizeThreshold = 8; + + // The amount an image can be stretched in a single direction before we + // say that it is being stretched so much that it must be a line or + // background that doesn't need resampling. + const float kLargeStretch = 3.0f; + + if (aImgWidth <= kSmallImageSizeThreshold || + aImgHeight <= kSmallImageSizeThreshold) { + // Never resample small images. These are often used for borders and + // rules (think 1x1 images used to make lines). + return SamplingFilter::POINT; + } + + if (aImgHeight * kLargeStretch <= aSourceHeight || + aImgWidth * kLargeStretch <= aSourceWidth) { + // Large image tiling detected. + + // Don't resample if it is being tiled a lot in only one direction. + // This is trying to catch cases where somebody has created a border + // (which might be large) and then is stretching it to fill some part + // of the page. + if (fabs(aSourceWidth - aImgWidth) / aImgWidth < 0.5 || + fabs(aSourceHeight - aImgHeight) / aImgHeight < 0.5) + return SamplingFilter::POINT; + + // The image is growing a lot and in more than one direction. Resampling + // is slow and doesn't give us very much when growing a lot. + return aSamplingFilter; + } + + /* Some notes on other heuristics: + The Skia backend also uses nearest for backgrounds that are stretched by + a large amount. I'm not sure this is common enough for us to worry about + now. It also uses nearest for backgrounds/avoids high quality for images + that are very slightly scaled. I'm also not sure that very slightly + scaled backgrounds are common enough us to worry about. + + We don't currently have much support for doing high quality interpolation. + The only place this currently happens is on Quartz and we don't have as + much control over it as would be needed. Webkit avoids using high quality + resampling during load. It also avoids high quality if the transformation + is not just a scale and translation + + WebKit bug #40045 added code to avoid resampling different parts + of an image with different methods by using a resampling hint size. + It currently looks unused in WebKit but it's something to watch out for. + */ + + return aSamplingFilter; +} +#else +static SamplingFilter ReduceResamplingFilter(SamplingFilter aSamplingFilter, + int aImgWidth, int aImgHeight, + int aSourceWidth, + int aSourceHeight) { + // Just pass the filter through unchanged + return aSamplingFilter; +} +#endif + +#ifdef MOZ_WIDGET_COCOA +// Only prescale a temporary surface if we're going to repeat it often. +// Scaling is expensive on OS X and without prescaling, we'd scale +// every tile of the repeated rect. However, using a temp surface also +// potentially uses more memory if the scaled image is large. So only prescale +// on a temp surface if we know we're going to repeat the image in either the X +// or Y axis multiple times. +static bool ShouldUseTempSurface(Rect aImageRect, Rect aNeededRect) { + int repeatX = aNeededRect.width / aImageRect.width; + int repeatY = aNeededRect.height / aImageRect.height; + return (repeatX >= 5) || (repeatY >= 5); +} + +static bool PrescaleAndTileDrawable(gfxDrawable* aDrawable, + gfxContext* aContext, + const ImageRegion& aRegion, Rect aImageRect, + const SamplingFilter aSamplingFilter, + const SurfaceFormat aFormat, + gfxFloat aOpacity, ExtendMode aExtendMode) { + Size scaleFactor = aContext->CurrentMatrix().ScaleFactors(); + Matrix scaleMatrix = Matrix::Scaling(scaleFactor.width, scaleFactor.height); + const float fuzzFactor = 0.01; + + // If we aren't scaling or translating, don't go down this path + if ((FuzzyEqual(scaleFactor.width, 1.0, fuzzFactor) && + FuzzyEqual(scaleFactor.width, 1.0, fuzzFactor)) || + aContext->CurrentMatrix().HasNonAxisAlignedTransform()) { + return false; + } + + gfxRect clipExtents = aContext->GetClipExtents(); + + // Inflate by one pixel because bilinear filtering will sample at most + // one pixel beyond the computed image pixel coordinate. + clipExtents.Inflate(1.0); + + gfxRect needed = aRegion.IntersectAndRestrict(clipExtents); + Rect scaledNeededRect = scaleMatrix.TransformBounds(ToRect(needed)); + scaledNeededRect.RoundOut(); + if (scaledNeededRect.IsEmpty()) { + return false; + } + + Rect scaledImageRect = scaleMatrix.TransformBounds(aImageRect); + if (!ShouldUseTempSurface(scaledImageRect, scaledNeededRect)) { + return false; + } + + IntSize scaledImageSize((int32_t)scaledImageRect.width, + (int32_t)scaledImageRect.height); + if (scaledImageSize.width != scaledImageRect.width || + scaledImageSize.height != scaledImageRect.height) { + // If the scaled image isn't pixel aligned, we'll get artifacts + // so we have to take the slow path. + return false; + } + + RefPtr<DrawTarget> scaledDT = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( + scaledImageSize, aFormat); + if (!scaledDT || !scaledDT->IsValid()) { + return false; + } + + RefPtr<gfxContext> tmpCtx = gfxContext::CreateOrNull(scaledDT); + MOZ_ASSERT(tmpCtx); // already checked the target above + + scaledDT->SetTransform(scaleMatrix); + gfxRect gfxImageRect(aImageRect.x, aImageRect.y, aImageRect.width, + aImageRect.height); + + // Since this is just the scaled image, we don't want to repeat anything yet. + aDrawable->Draw(tmpCtx, gfxImageRect, ExtendMode::CLAMP, aSamplingFilter, 1.0, + gfxMatrix()); + + RefPtr<SourceSurface> scaledImage = scaledDT->Snapshot(); + + { + gfxContextMatrixAutoSaveRestore autoSR(aContext); + Matrix withoutScale = aContext->CurrentMatrix(); + DrawTarget* destDrawTarget = aContext->GetDrawTarget(); + + // The translation still is in scaled units + withoutScale.PreScale(1.0 / scaleFactor.width, 1.0 / scaleFactor.height); + aContext->SetMatrix(withoutScale); + + DrawOptions drawOptions(aOpacity, aContext->CurrentOp(), + aContext->CurrentAntialiasMode()); + + SurfacePattern scaledImagePattern(scaledImage, aExtendMode, Matrix(), + aSamplingFilter); + destDrawTarget->FillRect(scaledNeededRect, scaledImagePattern, drawOptions); + } + return true; +} +#endif // MOZ_WIDGET_COCOA + +/* static */ +void gfxUtils::DrawPixelSnapped(gfxContext* aContext, gfxDrawable* aDrawable, + const gfxSize& aImageSize, + const ImageRegion& aRegion, + const SurfaceFormat aFormat, + SamplingFilter aSamplingFilter, + uint32_t aImageFlags, gfxFloat aOpacity, + bool aUseOptimalFillOp) { + AUTO_PROFILER_LABEL("gfxUtils::DrawPixelSnapped", GRAPHICS); + + gfxRect imageRect(gfxPoint(0, 0), aImageSize); + gfxRect region(aRegion.Rect()); + ExtendMode extendMode = aRegion.GetExtendMode(); + + RefPtr<gfxDrawable> drawable = aDrawable; + + aSamplingFilter = ReduceResamplingFilter(aSamplingFilter, imageRect.Width(), + imageRect.Height(), region.Width(), + region.Height()); + + // OK now, the hard part left is to account for the subimage sampling + // restriction. If all the transforms involved are just integer + // translations, then we assume no resampling will occur so there's + // nothing to do. + // XXX if only we had source-clipping in cairo! + + if (aContext->CurrentMatrix().HasNonIntegerTranslation()) { + if ((extendMode != ExtendMode::CLAMP) || + !aRegion.RestrictionContains(imageRect)) { + if (drawable->DrawWithSamplingRect( + aContext->GetDrawTarget(), aContext->CurrentOp(), + aContext->CurrentAntialiasMode(), aRegion.Rect(), + aRegion.Restriction(), extendMode, aSamplingFilter, aOpacity)) { + return; + } + +#ifdef MOZ_WIDGET_COCOA + if (PrescaleAndTileDrawable(aDrawable, aContext, aRegion, + ToRect(imageRect), aSamplingFilter, aFormat, + aOpacity, extendMode)) { + return; + } +#endif + + // On Mobile, we don't ever want to do this; it has the potential for + // allocating very large temporary surfaces, especially since we'll + // do full-page snapshots often (see bug 749426). +#if !defined(MOZ_GFX_OPTIMIZE_MOBILE) + RefPtr<gfxDrawable> restrictedDrawable = CreateSamplingRestrictedDrawable( + aDrawable, aContext, aRegion, aFormat, aUseOptimalFillOp); + if (restrictedDrawable) { + drawable.swap(restrictedDrawable); + + // We no longer need to tile: Either we never needed to, or we already + // filled a surface with the tiled pattern; this surface can now be + // drawn without tiling. + extendMode = ExtendMode::CLAMP; + } +#endif + } + } + + drawable->Draw(aContext, aRegion.Rect(), extendMode, aSamplingFilter, + aOpacity, gfxMatrix()); +} + +/* static */ +int gfxUtils::ImageFormatToDepth(gfxImageFormat aFormat) { + switch (aFormat) { + case SurfaceFormat::A8R8G8B8_UINT32: + return 32; + case SurfaceFormat::X8R8G8B8_UINT32: + return 24; + case SurfaceFormat::R5G6B5_UINT16: + return 16; + default: + break; + } + return 0; +} + +/*static*/ +void gfxUtils::ClipToRegion(gfxContext* aContext, const nsIntRegion& aRegion) { + aContext->NewPath(); + for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& r = iter.Get(); + aContext->Rectangle(gfxRect(r.X(), r.Y(), r.Width(), r.Height())); + } + aContext->Clip(); +} + +/*static*/ +void gfxUtils::ClipToRegion(DrawTarget* aTarget, const nsIntRegion& aRegion) { + uint32_t numRects = aRegion.GetNumRects(); + // If there is only one rect, then the region bounds are equivalent to the + // contents. So just use push a single clip rect with the bounds. + if (numRects == 1) { + aTarget->PushClipRect(Rect(aRegion.GetBounds())); + return; + } + + // Check if the target's transform will preserve axis-alignment and + // pixel-alignment for each rect. For now, just handle the common case + // of integer translations. + Matrix transform = aTarget->GetTransform(); + if (transform.IsIntegerTranslation()) { + IntPoint translation = RoundedToInt(transform.GetTranslation()); + AutoTArray<IntRect, 16> rects; + rects.SetLength(numRects); + uint32_t i = 0; + // Build the list of transformed rects by adding in the translation. + for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) { + IntRect rect = iter.Get(); + rect.MoveBy(translation); + rects[i++] = rect; + } + aTarget->PushDeviceSpaceClipRects(rects.Elements(), rects.Length()); + } else { + // The transform does not produce axis-aligned rects or a rect was not + // pixel-aligned. So just build a path with all the rects and clip to it + // instead. + RefPtr<PathBuilder> pathBuilder = aTarget->CreatePathBuilder(); + for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) { + AppendRectToPath(pathBuilder, Rect(iter.Get())); + } + RefPtr<Path> path = pathBuilder->Finish(); + aTarget->PushClip(path); + } +} + +/*static*/ +float gfxUtils::ClampToScaleFactor(float aVal, bool aRoundDown) { + // Arbitary scale factor limitation. We can increase this + // for better scaling performance at the cost of worse + // quality. + static const float kScaleResolution = 2; + + // Negative scaling is just a flip and irrelevant to + // our resolution calculation. + if (aVal < 0.0) { + aVal = -aVal; + } + + bool inverse = false; + if (aVal < 1.0) { + inverse = true; + aVal = 1 / aVal; + } + + float power = logf(aVal) / logf(kScaleResolution); + + // If power is within 1e-5 of an integer, round to nearest to + // prevent floating point errors, otherwise round up to the + // next integer value. + if (fabs(power - NS_round(power)) < 1e-5) { + power = NS_round(power); + // Use floor when we are either inverted or rounding down, but + // not both. + } else if (inverse != aRoundDown) { + power = floor(power); + // Otherwise, ceil when we are not inverted and not rounding + // down, or we are inverted and rounding down. + } else { + power = ceil(power); + } + + float scale = powf(kScaleResolution, power); + + if (inverse) { + scale = 1 / scale; + } + + return scale; +} + +gfxMatrix gfxUtils::TransformRectToRect(const gfxRect& aFrom, + const gfxPoint& aToTopLeft, + const gfxPoint& aToTopRight, + const gfxPoint& aToBottomRight) { + gfxMatrix m; + if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) { + // Not a rotation, so xy and yx are zero + m._21 = m._12 = 0.0; + m._11 = (aToBottomRight.x - aToTopLeft.x) / aFrom.Width(); + m._22 = (aToBottomRight.y - aToTopLeft.y) / aFrom.Height(); + m._31 = aToTopLeft.x - m._11 * aFrom.X(); + m._32 = aToTopLeft.y - m._22 * aFrom.Y(); + } else { + NS_ASSERTION( + aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x, + "Destination rectangle not axis-aligned"); + m._11 = m._22 = 0.0; + m._21 = (aToBottomRight.x - aToTopLeft.x) / aFrom.Height(); + m._12 = (aToBottomRight.y - aToTopLeft.y) / aFrom.Width(); + m._31 = aToTopLeft.x - m._21 * aFrom.Y(); + m._32 = aToTopLeft.y - m._12 * aFrom.X(); + } + return m; +} + +Matrix gfxUtils::TransformRectToRect(const gfxRect& aFrom, + const IntPoint& aToTopLeft, + const IntPoint& aToTopRight, + const IntPoint& aToBottomRight) { + Matrix m; + if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) { + // Not a rotation, so xy and yx are zero + m._12 = m._21 = 0.0; + m._11 = (aToBottomRight.x - aToTopLeft.x) / aFrom.Width(); + m._22 = (aToBottomRight.y - aToTopLeft.y) / aFrom.Height(); + m._31 = aToTopLeft.x - m._11 * aFrom.X(); + m._32 = aToTopLeft.y - m._22 * aFrom.Y(); + } else { + NS_ASSERTION( + aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x, + "Destination rectangle not axis-aligned"); + m._11 = m._22 = 0.0; + m._21 = (aToBottomRight.x - aToTopLeft.x) / aFrom.Height(); + m._12 = (aToBottomRight.y - aToTopLeft.y) / aFrom.Width(); + m._31 = aToTopLeft.x - m._21 * aFrom.Y(); + m._32 = aToTopLeft.y - m._12 * aFrom.X(); + } + return m; +} + +/* This function is sort of shitty. We truncate doubles + * to ints then convert those ints back to doubles to make sure that + * they equal the doubles that we got in. */ +bool gfxUtils::GfxRectToIntRect(const gfxRect& aIn, IntRect* aOut) { + *aOut = IntRect(int32_t(aIn.X()), int32_t(aIn.Y()), int32_t(aIn.Width()), + int32_t(aIn.Height())); + return gfxRect(aOut->X(), aOut->Y(), aOut->Width(), aOut->Height()) + .IsEqualEdges(aIn); +} + +/* Clamp r to CAIRO_COORD_MIN .. CAIRO_COORD_MAX + * these are to be device coordinates. + * + * Cairo is currently using 24.8 fixed point, + * so -2^24 .. 2^24-1 is our valid + */ +/*static*/ +void gfxUtils::ConditionRect(gfxRect& aRect) { +#define CAIRO_COORD_MAX (16777215.0) +#define CAIRO_COORD_MIN (-16777216.0) + // if either x or y is way out of bounds; + // note that we don't handle negative w/h here + if (aRect.X() > CAIRO_COORD_MAX) { + aRect.SetRectX(CAIRO_COORD_MAX, 0.0); + } + + if (aRect.Y() > CAIRO_COORD_MAX) { + aRect.SetRectY(CAIRO_COORD_MAX, 0.0); + } + + if (aRect.X() < CAIRO_COORD_MIN) { + aRect.SetWidth(aRect.XMost() - CAIRO_COORD_MIN); + if (aRect.Width() < 0.0) { + aRect.SetWidth(0.0); + } + aRect.MoveToX(CAIRO_COORD_MIN); + } + + if (aRect.Y() < CAIRO_COORD_MIN) { + aRect.SetHeight(aRect.YMost() - CAIRO_COORD_MIN); + if (aRect.Height() < 0.0) { + aRect.SetHeight(0.0); + } + aRect.MoveToY(CAIRO_COORD_MIN); + } + + if (aRect.XMost() > CAIRO_COORD_MAX) { + aRect.SetRightEdge(CAIRO_COORD_MAX); + } + + if (aRect.YMost() > CAIRO_COORD_MAX) { + aRect.SetBottomEdge(CAIRO_COORD_MAX); + } +#undef CAIRO_COORD_MAX +#undef CAIRO_COORD_MIN +} + +/*static*/ +gfxQuad gfxUtils::TransformToQuad(const gfxRect& aRect, + const mozilla::gfx::Matrix4x4& aMatrix) { + gfxPoint points[4]; + + points[0] = aMatrix.TransformPoint(aRect.TopLeft()); + points[1] = aMatrix.TransformPoint(aRect.TopRight()); + points[2] = aMatrix.TransformPoint(aRect.BottomRight()); + points[3] = aMatrix.TransformPoint(aRect.BottomLeft()); + + // Could this ever result in lines that intersect? I don't think so. + return gfxQuad(points[0], points[1], points[2], points[3]); +} + +/* static */ +void gfxUtils::ClearThebesSurface(gfxASurface* aSurface) { + if (aSurface->CairoStatus()) { + return; + } + cairo_surface_t* surf = aSurface->CairoSurface(); + if (cairo_surface_status(surf)) { + return; + } + cairo_t* ctx = cairo_create(surf); + cairo_set_source_rgba(ctx, 0.0, 0.0, 0.0, 0.0); + cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE); + IntRect bounds(nsIntPoint(0, 0), aSurface->GetSize()); + cairo_rectangle(ctx, bounds.X(), bounds.Y(), bounds.Width(), bounds.Height()); + cairo_fill(ctx); + cairo_destroy(ctx); +} + +/* static */ +already_AddRefed<DataSourceSurface> +gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface, + SurfaceFormat aFormat) { + MOZ_ASSERT(aFormat != aSurface->GetFormat(), + "Unnecessary - and very expersive - surface format conversion"); + + Rect bounds(0, 0, aSurface->GetSize().width, aSurface->GetSize().height); + + if (!aSurface->IsDataSourceSurface()) { + // If the surface is NOT of type DATA then its data is not mapped into main + // memory. Format conversion is probably faster on the GPU, and by doing it + // there we can avoid any expensive uploads/readbacks except for (possibly) + // a single readback due to the unavoidable GetDataSurface() call. Using + // CreateOffscreenContentDrawTarget ensures the conversion happens on the + // GPU. + RefPtr<DrawTarget> dt = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( + aSurface->GetSize(), aFormat); + if (!dt) { + gfxWarning() << "gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat " + "failed in CreateOffscreenContentDrawTarget"; + return nullptr; + } + + // Using DrawSurface() here rather than CopySurface() because CopySurface + // is optimized for memcpy and therefore isn't good for format conversion. + // Using OP_OVER since in our case it's equivalent to OP_SOURCE and + // generally more optimized. + dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(), + DrawOptions(1.0f, CompositionOp::OP_OVER)); + RefPtr<SourceSurface> surface = dt->Snapshot(); + return surface->GetDataSurface(); + } + + // If the surface IS of type DATA then it may or may not be in main memory + // depending on whether or not it has been mapped yet. We have no way of + // knowing, so we can't be sure if it's best to create a data wrapping + // DrawTarget for the conversion or an offscreen content DrawTarget. We could + // guess it's not mapped and create an offscreen content DrawTarget, but if + // it is then we'll end up uploading the surface data, and most likely the + // caller is going to be accessing the resulting surface data, resulting in a + // readback (both very expensive operations). Alternatively we could guess + // the data is mapped and create a data wrapping DrawTarget and, if the + // surface is not in main memory, then we will incure a readback. The latter + // of these two "wrong choices" is the least costly (a readback, vs an + // upload and a readback), and more than likely the DATA surface that we've + // been passed actually IS in main memory anyway. For these reasons it's most + // likely best to create a data wrapping DrawTarget here to do the format + // conversion. + RefPtr<DataSourceSurface> dataSurface = + Factory::CreateDataSourceSurface(aSurface->GetSize(), aFormat); + DataSourceSurface::MappedSurface map; + if (!dataSurface || + !dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) { + return nullptr; + } + RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData( + BackendType::CAIRO, map.mData, dataSurface->GetSize(), map.mStride, + aFormat); + if (!dt) { + dataSurface->Unmap(); + return nullptr; + } + // Using DrawSurface() here rather than CopySurface() because CopySurface + // is optimized for memcpy and therefore isn't good for format conversion. + // Using OP_OVER since in our case it's equivalent to OP_SOURCE and + // generally more optimized. + dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(), + DrawOptions(1.0f, CompositionOp::OP_OVER)); + dataSurface->Unmap(); + return dataSurface.forget(); +} + +const uint32_t gfxUtils::sNumFrameColors = 8; + +/* static */ +const gfx::DeviceColor& gfxUtils::GetColorForFrameNumber( + uint64_t aFrameNumber) { + static bool initialized = false; + static gfx::DeviceColor colors[sNumFrameColors]; + + if (!initialized) { + // This isn't truly device color, but it is just for debug. + uint32_t i = 0; + colors[i++] = gfx::DeviceColor::FromABGR(0xffff0000); + colors[i++] = gfx::DeviceColor::FromABGR(0xffcc00ff); + colors[i++] = gfx::DeviceColor::FromABGR(0xff0066cc); + colors[i++] = gfx::DeviceColor::FromABGR(0xff00ff00); + colors[i++] = gfx::DeviceColor::FromABGR(0xff33ffff); + colors[i++] = gfx::DeviceColor::FromABGR(0xffff0099); + colors[i++] = gfx::DeviceColor::FromABGR(0xff0000ff); + colors[i++] = gfx::DeviceColor::FromABGR(0xff999999); + MOZ_ASSERT(i == sNumFrameColors); + initialized = true; + } + + return colors[aFrameNumber % sNumFrameColors]; +} + +/* static */ +nsresult gfxUtils::EncodeSourceSurface(SourceSurface* aSurface, + const ImageType aImageType, + const nsAString& aOutputOptions, + BinaryOrData aBinaryOrData, FILE* aFile, + nsACString* aStrOut) { + MOZ_ASSERT(aBinaryOrData == gfxUtils::eDataURIEncode || aFile || aStrOut, + "Copying binary encoding to clipboard not currently supported"); + + const IntSize size = aSurface->GetSize(); + if (size.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + const Size floatSize(size.width, size.height); + + RefPtr<DataSourceSurface> dataSurface; + if (aSurface->GetFormat() != SurfaceFormat::B8G8R8A8) { + // FIXME bug 995807 (B8G8R8X8), bug 831898 (R5G6B5) + dataSurface = gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat( + aSurface, SurfaceFormat::B8G8R8A8); + } else { + dataSurface = aSurface->GetDataSurface(); + } + if (!dataSurface) { + return NS_ERROR_FAILURE; + } + + DataSourceSurface::MappedSurface map; + if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) { + return NS_ERROR_FAILURE; + } + + RefPtr<imgIEncoder> encoder = nullptr; + + switch (aImageType) { + case ImageType::BMP: + encoder = MakeRefPtr<nsBMPEncoder>(); + break; + + case ImageType::ICO: + encoder = MakeRefPtr<nsICOEncoder>(); + break; + + case ImageType::JPEG: + encoder = MakeRefPtr<nsJPEGEncoder>(); + break; + + case ImageType::PNG: + encoder = MakeRefPtr<nsPNGEncoder>(); + break; + + default: + break; + } + + MOZ_RELEASE_ASSERT(encoder != nullptr); + + nsresult rv = encoder->InitFromData( + map.mData, BufferSizeFromStrideAndHeight(map.mStride, size.height), + size.width, size.height, map.mStride, imgIEncoder::INPUT_FORMAT_HOSTARGB, + aOutputOptions); + dataSurface->Unmap(); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIInputStream> imgStream(encoder); + if (!imgStream) { + return NS_ERROR_FAILURE; + } + + uint64_t bufSize64; + rv = imgStream->Available(&bufSize64); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(bufSize64 < UINT32_MAX - 16, NS_ERROR_FAILURE); + + uint32_t bufSize = (uint32_t)bufSize64; + + // ...leave a little extra room so we can call read again and make sure we + // got everything. 16 bytes for better padding (maybe) + bufSize += 16; + uint32_t imgSize = 0; + Vector<char> imgData; + if (!imgData.initCapacity(bufSize)) { + return NS_ERROR_OUT_OF_MEMORY; + } + uint32_t numReadThisTime = 0; + while ((rv = imgStream->Read(imgData.begin() + imgSize, bufSize - imgSize, + &numReadThisTime)) == NS_OK && + numReadThisTime > 0) { + // Update the length of the vector without overwriting the new data. + if (!imgData.growByUninitialized(numReadThisTime)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + imgSize += numReadThisTime; + if (imgSize == bufSize) { + // need a bigger buffer, just double + bufSize *= 2; + if (!imgData.resizeUninitialized(bufSize)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + } + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(!imgData.empty(), NS_ERROR_FAILURE); + + if (aBinaryOrData == gfxUtils::eBinaryEncode) { + if (aFile) { + Unused << fwrite(imgData.begin(), 1, imgSize, aFile); + } + return NS_OK; + } + + nsCString stringBuf; + nsACString& dataURI = aStrOut ? *aStrOut : stringBuf; + dataURI.AppendLiteral("data:"); + + switch (aImageType) { + case ImageType::BMP: + dataURI.AppendLiteral(IMAGE_BMP); + break; + + case ImageType::ICO: + dataURI.AppendLiteral(IMAGE_ICO_MS); + break; + case ImageType::JPEG: + dataURI.AppendLiteral(IMAGE_JPEG); + break; + + case ImageType::PNG: + dataURI.AppendLiteral(IMAGE_PNG); + break; + + default: + break; + } + + dataURI.AppendLiteral(";base64,"); + rv = Base64EncodeAppend(imgData.begin(), imgSize, dataURI); + NS_ENSURE_SUCCESS(rv, rv); + + if (aFile) { +#ifdef ANDROID + if (aFile == stdout || aFile == stderr) { + // ADB logcat cuts off long strings so we will break it down + const char* cStr = dataURI.BeginReading(); + size_t len = strlen(cStr); + while (true) { + printf_stderr("IMG: %.140s\n", cStr); + if (len <= 140) break; + len -= 140; + cStr += 140; + } + } +#endif + fprintf(aFile, "%s", dataURI.BeginReading()); + } else if (!aStrOut) { + nsCOMPtr<nsIClipboardHelper> clipboard( + do_GetService("@mozilla.org/widget/clipboardhelper;1", &rv)); + if (clipboard) { + clipboard->CopyString(NS_ConvertASCIItoUTF16(dataURI)); + } + } + return NS_OK; +} + +static nsCString EncodeSourceSurfaceAsPNGURI(SourceSurface* aSurface) { + nsCString string; + gfxUtils::EncodeSourceSurface(aSurface, ImageType::PNG, u""_ns, + gfxUtils::eDataURIEncode, nullptr, &string); + return string; +} + +// https://jdashg.github.io/misc/colors/from-coeffs.html +const float kBT601NarrowYCbCrToRGB_RowMajor[16] = { + 1.16438f, 0.00000f, 1.59603f, -0.87420f, 1.16438f, -0.39176f, + -0.81297f, 0.53167f, 1.16438f, 2.01723f, 0.00000f, -1.08563f, + 0.00000f, 0.00000f, 0.00000f, 1.00000f}; +const float kBT709NarrowYCbCrToRGB_RowMajor[16] = { + 1.16438f, 0.00000f, 1.79274f, -0.97295f, 1.16438f, -0.21325f, + -0.53291f, 0.30148f, 1.16438f, 2.11240f, 0.00000f, -1.13340f, + 0.00000f, 0.00000f, 0.00000f, 1.00000f}; +const float kBT2020NarrowYCbCrToRGB_RowMajor[16] = { + 1.16438f, 0.00000f, 1.67867f, -0.91569f, 1.16438f, -0.18733f, + -0.65042f, 0.34746f, 1.16438f, 2.14177f, 0.00000f, -1.14815f, + 0.00000f, 0.00000f, 0.00000f, 1.00000f}; +const float kIdentityNarrowYCbCrToRGB_RowMajor[16] = { + 0.00000f, 0.00000f, 1.00000f, 0.00000f, 1.00000f, 0.00000f, + 0.00000f, 0.00000f, 0.00000f, 1.00000f, 0.00000f, 0.00000f, + 0.00000f, 0.00000f, 0.00000f, 1.00000f}; + +/* static */ const float* gfxUtils::YuvToRgbMatrix4x3RowMajor( + gfx::YUVColorSpace aYUVColorSpace) { +#define X(x) \ + { x[0], x[1], x[2], 0.0f, x[4], x[5], x[6], 0.0f, x[8], x[9], x[10], 0.0f } + + static const float rec601[12] = X(kBT601NarrowYCbCrToRGB_RowMajor); + static const float rec709[12] = X(kBT709NarrowYCbCrToRGB_RowMajor); + static const float rec2020[12] = X(kBT2020NarrowYCbCrToRGB_RowMajor); + static const float identity[12] = X(kIdentityNarrowYCbCrToRGB_RowMajor); + +#undef X + + switch (aYUVColorSpace) { + case gfx::YUVColorSpace::BT601: + return rec601; + case gfx::YUVColorSpace::BT709: + return rec709; + case gfx::YUVColorSpace::BT2020: + return rec2020; + case gfx::YUVColorSpace::Identity: + return identity; + default: // YUVColorSpace::UNKNOWN + MOZ_ASSERT(false, "unknown aYUVColorSpace"); + return rec601; + } +} + +/* static */ const float* gfxUtils::YuvToRgbMatrix3x3ColumnMajor( + gfx::YUVColorSpace aYUVColorSpace) { +#define X(x) \ + { x[0], x[4], x[8], x[1], x[5], x[9], x[2], x[6], x[10] } + + static const float rec601[9] = X(kBT601NarrowYCbCrToRGB_RowMajor); + static const float rec709[9] = X(kBT709NarrowYCbCrToRGB_RowMajor); + static const float rec2020[9] = X(kBT2020NarrowYCbCrToRGB_RowMajor); + static const float identity[9] = X(kIdentityNarrowYCbCrToRGB_RowMajor); + +#undef X + + switch (aYUVColorSpace) { + case gfx::YUVColorSpace::BT601: + return rec601; + case YUVColorSpace::BT709: + return rec709; + case YUVColorSpace::BT2020: + return rec2020; + case YUVColorSpace::Identity: + return identity; + default: // YUVColorSpace::UNKNOWN + MOZ_ASSERT(false, "unknown aYUVColorSpace"); + return rec601; + } +} + +/* static */ const float* gfxUtils::YuvToRgbMatrix4x4ColumnMajor( + YUVColorSpace aYUVColorSpace) { +#define X(x) \ + { \ + x[0], x[4], x[8], x[12], x[1], x[5], x[9], x[13], x[2], x[6], x[10], \ + x[14], x[3], x[7], x[11], x[15] \ + } + + static const float rec601[16] = X(kBT601NarrowYCbCrToRGB_RowMajor); + static const float rec709[16] = X(kBT709NarrowYCbCrToRGB_RowMajor); + static const float rec2020[16] = X(kBT2020NarrowYCbCrToRGB_RowMajor); + static const float identity[16] = X(kIdentityNarrowYCbCrToRGB_RowMajor); + +#undef X + + switch (aYUVColorSpace) { + case YUVColorSpace::BT601: + return rec601; + case YUVColorSpace::BT709: + return rec709; + case YUVColorSpace::BT2020: + return rec2020; + case YUVColorSpace::Identity: + return identity; + default: // YUVColorSpace::UNKNOWN + MOZ_ASSERT(false, "unknown aYUVColorSpace"); + return rec601; + } +} + +/* static */ +void gfxUtils::WriteAsPNG(SourceSurface* aSurface, const nsAString& aFile) { + WriteAsPNG(aSurface, NS_ConvertUTF16toUTF8(aFile).get()); +} + +/* static */ +void gfxUtils::WriteAsPNG(SourceSurface* aSurface, const char* aFile) { + FILE* file = fopen(aFile, "wb"); + + if (!file) { + // Maybe the directory doesn't exist; try creating it, then fopen again. + nsresult rv = NS_ERROR_FAILURE; + nsCOMPtr<nsIFile> comFile = do_CreateInstance("@mozilla.org/file/local;1"); + if (comFile) { + NS_ConvertUTF8toUTF16 utf16path((nsDependentCString(aFile))); + rv = comFile->InitWithPath(utf16path); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIFile> dirPath; + comFile->GetParent(getter_AddRefs(dirPath)); + if (dirPath) { + rv = dirPath->Create(nsIFile::DIRECTORY_TYPE, 0777); + if (NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_ALREADY_EXISTS) { + file = fopen(aFile, "wb"); + } + } + } + } + if (!file) { + NS_WARNING("Failed to open file to create PNG!"); + return; + } + } + + EncodeSourceSurface(aSurface, ImageType::PNG, u""_ns, eBinaryEncode, file); + fclose(file); +} + +/* static */ +void gfxUtils::WriteAsPNG(DrawTarget* aDT, const nsAString& aFile) { + WriteAsPNG(aDT, NS_ConvertUTF16toUTF8(aFile).get()); +} + +/* static */ +void gfxUtils::WriteAsPNG(DrawTarget* aDT, const char* aFile) { + RefPtr<SourceSurface> surface = aDT->Snapshot(); + if (surface) { + WriteAsPNG(surface, aFile); + } else { + NS_WARNING("Failed to get surface!"); + } +} + +/* static */ +void gfxUtils::DumpAsDataURI(SourceSurface* aSurface, FILE* aFile) { + EncodeSourceSurface(aSurface, ImageType::PNG, u""_ns, eDataURIEncode, aFile); +} + +/* static */ +nsCString gfxUtils::GetAsDataURI(SourceSurface* aSurface) { + return EncodeSourceSurfaceAsPNGURI(aSurface); +} + +/* static */ +void gfxUtils::DumpAsDataURI(DrawTarget* aDT, FILE* aFile) { + RefPtr<SourceSurface> surface = aDT->Snapshot(); + if (surface) { + DumpAsDataURI(surface, aFile); + } else { + NS_WARNING("Failed to get surface!"); + } +} + +/* static */ +nsCString gfxUtils::GetAsLZ4Base64Str(DataSourceSurface* aSourceSurface) { + DataSourceSurface::ScopedMap map(aSourceSurface, DataSourceSurface::READ); + int32_t dataSize = aSourceSurface->GetSize().height * map.GetStride(); + auto compressedData = MakeUnique<char[]>(LZ4::maxCompressedSize(dataSize)); + if (compressedData) { + int nDataSize = + LZ4::compress((char*)map.GetData(), dataSize, compressedData.get()); + if (nDataSize > 0) { + nsCString string; + string.AppendPrintf("data:image/lz4bgra;base64,%i,%i,%i,", + aSourceSurface->GetSize().width, map.GetStride(), + aSourceSurface->GetSize().height); + nsresult rv = Base64EncodeAppend(compressedData.get(), nDataSize, string); + if (rv == NS_OK) { + return string; + } + } + } + return {}; +} + +/* static */ +nsCString gfxUtils::GetAsDataURI(DrawTarget* aDT) { + RefPtr<SourceSurface> surface = aDT->Snapshot(); + if (surface) { + return EncodeSourceSurfaceAsPNGURI(surface); + } else { + NS_WARNING("Failed to get surface!"); + return nsCString(""); + } +} + +/* static */ +void gfxUtils::CopyAsDataURI(SourceSurface* aSurface) { + EncodeSourceSurface(aSurface, ImageType::PNG, u""_ns, eDataURIEncode, + nullptr); +} + +/* static */ +void gfxUtils::CopyAsDataURI(DrawTarget* aDT) { + RefPtr<SourceSurface> surface = aDT->Snapshot(); + if (surface) { + CopyAsDataURI(surface); + } else { + NS_WARNING("Failed to get surface!"); + } +} + +/* static */ UniquePtr<uint8_t[]> gfxUtils::GetImageBuffer( + gfx::DataSourceSurface* aSurface, bool aIsAlphaPremultiplied, + int32_t* outFormat) { + *outFormat = 0; + + DataSourceSurface::MappedSurface map; + if (!aSurface->Map(DataSourceSurface::MapType::READ, &map)) return nullptr; + + uint32_t bufferSize = + aSurface->GetSize().width * aSurface->GetSize().height * 4; + auto imageBuffer = MakeUniqueFallible<uint8_t[]>(bufferSize); + if (!imageBuffer) { + aSurface->Unmap(); + return nullptr; + } + memcpy(imageBuffer.get(), map.mData, bufferSize); + + aSurface->Unmap(); + + int32_t format = imgIEncoder::INPUT_FORMAT_HOSTARGB; + if (!aIsAlphaPremultiplied) { + // We need to convert to INPUT_FORMAT_RGBA, otherwise + // we are automatically considered premult, and unpremult'd. + // Yes, it is THAT silly. + // Except for different lossy conversions by color, + // we could probably just change the label, and not change the data. + gfxUtils::ConvertBGRAtoRGBA(imageBuffer.get(), bufferSize); + format = imgIEncoder::INPUT_FORMAT_RGBA; + } + + *outFormat = format; + return imageBuffer; +} + +/* static */ +nsresult gfxUtils::GetInputStream(gfx::DataSourceSurface* aSurface, + bool aIsAlphaPremultiplied, + const char* aMimeType, + const nsAString& aEncoderOptions, + nsIInputStream** outStream) { + nsCString enccid("@mozilla.org/image/encoder;2?type="); + enccid += aMimeType; + nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get()); + if (!encoder) return NS_ERROR_FAILURE; + + int32_t format = 0; + UniquePtr<uint8_t[]> imageBuffer = + GetImageBuffer(aSurface, aIsAlphaPremultiplied, &format); + if (!imageBuffer) return NS_ERROR_FAILURE; + + return dom::ImageEncoder::GetInputStream( + aSurface->GetSize().width, aSurface->GetSize().height, imageBuffer.get(), + format, encoder, aEncoderOptions, outStream); +} + +class GetFeatureStatusWorkerRunnable final + : public dom::WorkerMainThreadRunnable { + public: + GetFeatureStatusWorkerRunnable(dom::WorkerPrivate* workerPrivate, + const nsCOMPtr<nsIGfxInfo>& gfxInfo, + int32_t feature, nsACString& failureId, + int32_t* status) + : WorkerMainThreadRunnable(workerPrivate, "GFX :: GetFeatureStatus"_ns), + mGfxInfo(gfxInfo), + mFeature(feature), + mStatus(status), + mFailureId(failureId), + mNSResult(NS_OK) {} + + bool MainThreadRun() override { + if (mGfxInfo) { + mNSResult = mGfxInfo->GetFeatureStatus(mFeature, mFailureId, mStatus); + } + return true; + } + + nsresult GetNSResult() const { return mNSResult; } + + protected: + ~GetFeatureStatusWorkerRunnable() = default; + + private: + nsCOMPtr<nsIGfxInfo> mGfxInfo; + int32_t mFeature; + int32_t* mStatus; + nsACString& mFailureId; + nsresult mNSResult; +}; + +#define GFX_SHADER_CHECK_BUILD_VERSION_PREF "gfx-shader-check.build-version" +#define GFX_SHADER_CHECK_DEVICE_ID_PREF "gfx-shader-check.device-id" +#define GFX_SHADER_CHECK_DRIVER_VERSION_PREF "gfx-shader-check.driver-version" + +/* static */ +void gfxUtils::RemoveShaderCacheFromDiskIfNecessary() { + if (!gfxVars::UseWebRenderProgramBinaryDisk()) { + return; + } + + nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo(); + + // Get current values + nsCString buildID(mozilla::PlatformBuildID()); + nsString deviceID, driverVersion; + gfxInfo->GetAdapterDeviceID(deviceID); + gfxInfo->GetAdapterDriverVersion(driverVersion); + + // Get pref stored values + nsAutoCString buildIDChecked; + Preferences::GetCString(GFX_SHADER_CHECK_BUILD_VERSION_PREF, buildIDChecked); + nsAutoString deviceIDChecked, driverVersionChecked; + Preferences::GetString(GFX_SHADER_CHECK_DEVICE_ID_PREF, deviceIDChecked); + Preferences::GetString(GFX_SHADER_CHECK_DRIVER_VERSION_PREF, + driverVersionChecked); + + if (buildID == buildIDChecked && deviceID == deviceIDChecked && + driverVersion == driverVersionChecked) { + return; + } + + nsAutoString path(gfx::gfxVars::ProfDirectory()); + + if (!wr::remove_program_binary_disk_cache(&path)) { + // Failed to remove program binary disk cache. The disk cache might have + // invalid data. Disable program binary disk cache usage. + gfxVars::SetUseWebRenderProgramBinaryDisk(false); + return; + } + + Preferences::SetCString(GFX_SHADER_CHECK_BUILD_VERSION_PREF, buildID); + Preferences::SetString(GFX_SHADER_CHECK_DEVICE_ID_PREF, deviceID); + Preferences::SetString(GFX_SHADER_CHECK_DRIVER_VERSION_PREF, driverVersion); +} + +/* static */ +bool gfxUtils::DumpDisplayList() { + return StaticPrefs::layout_display_list_dump() || + (StaticPrefs::layout_display_list_dump_parent() && + XRE_IsParentProcess()) || + (StaticPrefs::layout_display_list_dump_content() && + XRE_IsContentProcess()); +} + +FILE* gfxUtils::sDumpPaintFile = stderr; + +namespace mozilla { +namespace gfx { + +DeviceColor ToDeviceColor(const sRGBColor& aColor) { + // aColor is pass-by-value since to get return value optimization goodness we + // need to return the same object from all return points in this function. We + // could declare a local Color variable and use that, but we might as well + // just use aColor. + if (gfxPlatform::GetCMSMode() == eCMSMode_All) { + qcms_transform* transform = gfxPlatform::GetCMSRGBTransform(); + if (transform) { + return gfxPlatform::TransformPixel(aColor, transform); + // Use the original alpha to avoid unnecessary float->byte->float + // conversion errors + } + } + return DeviceColor(aColor.r, aColor.g, aColor.b, aColor.a); +} + +DeviceColor ToDeviceColor(nscolor aColor) { + return ToDeviceColor(sRGBColor::FromABGR(aColor)); +} + +DeviceColor ToDeviceColor(const StyleRGBA& aColor) { + return ToDeviceColor(aColor.ToColor()); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/thebes/gfxUtils.h b/gfx/thebes/gfxUtils.h new file mode 100644 index 0000000000..1edcf651bd --- /dev/null +++ b/gfx/thebes/gfxUtils.h @@ -0,0 +1,347 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_UTILS_H +#define GFX_UTILS_H + +#include "gfxMatrix.h" +#include "gfxRect.h" +#include "gfxTypes.h" +#include "ImageTypes.h" +#include "imgIContainer.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "nsColor.h" +#include "nsPrintfCString.h" +#include "nsRegionFwd.h" +#include "mozilla/gfx/Rect.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/webrender/WebRenderTypes.h" + +class gfxASurface; +class gfxDrawable; +struct gfxQuad; +class nsIInputStream; +class nsIGfxInfo; + +namespace mozilla { +namespace dom { +class Element; +} +namespace layers { +class WebRenderBridgeChild; +class GlyphArray; +struct PlanarYCbCrData; +class WebRenderCommand; +} // namespace layers +namespace image { +class ImageRegion; +} // namespace image +namespace wr { +class DisplayListBuilder; +} // namespace wr +} // namespace mozilla + +enum class ImageType { + BMP, + ICO, + JPEG, + PNG, +}; + +class gfxUtils { + public: + typedef mozilla::gfx::DataSourceSurface DataSourceSurface; + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::IntPoint IntPoint; + typedef mozilla::gfx::Matrix Matrix; + typedef mozilla::gfx::SourceSurface SourceSurface; + typedef mozilla::gfx::SurfaceFormat SurfaceFormat; + typedef mozilla::image::ImageRegion ImageRegion; + + /* + * Premultiply or Unpremultiply aSourceSurface, writing the result + * to aDestSurface or back into aSourceSurface if aDestSurface is null. + * + * If aDestSurface is given, it must have identical format, dimensions, and + * stride as the source. + * + * If the source is not SurfaceFormat::A8R8G8B8_UINT32, no operation is + * performed. If aDestSurface is given, the data is copied over. + */ + static bool PremultiplyDataSurface(DataSourceSurface* srcSurf, + DataSourceSurface* destSurf); + static bool UnpremultiplyDataSurface(DataSourceSurface* srcSurf, + DataSourceSurface* destSurf); + + static already_AddRefed<DataSourceSurface> CreatePremultipliedDataSurface( + DataSourceSurface* srcSurf); + static already_AddRefed<DataSourceSurface> CreateUnpremultipliedDataSurface( + DataSourceSurface* srcSurf); + + static void ConvertBGRAtoRGBA(uint8_t* aData, uint32_t aLength); + + /** + * Draw something drawable while working around limitations like bad support + * for EXTEND_PAD, lack of source-clipping, or cairo / pixman bugs with + * extreme user-space-to-image-space transforms. + * + * The input parameters here usually come from the output of our image + * snapping algorithm in nsLayoutUtils.cpp. + * This method is split from nsLayoutUtils::DrawPixelSnapped to allow for + * adjusting the parameters. For example, certain images with transparent + * margins only have a drawable subimage. For those images, imgFrame::Draw + * will tweak the rects and transforms that it gets from the pixel snapping + * algorithm before passing them on to this method. + */ + static void DrawPixelSnapped(gfxContext* aContext, gfxDrawable* aDrawable, + const gfxSize& aImageSize, + const ImageRegion& aRegion, + const mozilla::gfx::SurfaceFormat aFormat, + mozilla::gfx::SamplingFilter aSamplingFilter, + uint32_t aImageFlags = imgIContainer::FLAG_NONE, + gfxFloat aOpacity = 1.0, + bool aUseOptimalFillOp = true); + + /** + * Clip aContext to the region aRegion. + */ + static void ClipToRegion(gfxContext* aContext, const nsIntRegion& aRegion); + + /** + * Clip aTarget to the region aRegion. + */ + static void ClipToRegion(mozilla::gfx::DrawTarget* aTarget, + const nsIntRegion& aRegion); + + /* + * Convert image format to depth value + */ + static int ImageFormatToDepth(gfxImageFormat aFormat); + + /** + * Return the transform matrix that maps aFrom to the rectangle defined by + * aToTopLeft/aToTopRight/aToBottomRight. aFrom must be + * nonempty and the destination rectangle must be axis-aligned. + */ + static gfxMatrix TransformRectToRect(const gfxRect& aFrom, + const gfxPoint& aToTopLeft, + const gfxPoint& aToTopRight, + const gfxPoint& aToBottomRight); + + static Matrix TransformRectToRect(const gfxRect& aFrom, + const IntPoint& aToTopLeft, + const IntPoint& aToTopRight, + const IntPoint& aToBottomRight); + + /** + * If aIn can be represented exactly using an gfx::IntRect (i.e. + * integer-aligned edges and coordinates in the int32_t range) then we + * set aOut to that rectangle, otherwise return failure. + */ + static bool GfxRectToIntRect(const gfxRect& aIn, mozilla::gfx::IntRect* aOut); + + /* Conditions this border to Cairo's max coordinate space. + * The caller can check IsEmpty() after Condition() -- if it's TRUE, + * the caller can possibly avoid doing any extra rendering. + */ + static void ConditionRect(gfxRect& aRect); + + /* + * Transform this rectangle with aMatrix, resulting in a gfxQuad. + */ + static gfxQuad TransformToQuad(const gfxRect& aRect, + const mozilla::gfx::Matrix4x4& aMatrix); + + /** + * Return the smallest power of kScaleResolution (2) greater than or equal to + * aVal. If aRoundDown is specified, the power of 2 will rather be less than + * or equal to aVal. + */ + static float ClampToScaleFactor(float aVal, bool aRoundDown = false); + + /** + * Clears surface to aColor (which defaults to transparent black). + */ + static void ClearThebesSurface(gfxASurface* aSurface); + + static const float* YuvToRgbMatrix4x3RowMajor( + mozilla::gfx::YUVColorSpace aYUVColorSpace); + static const float* YuvToRgbMatrix3x3ColumnMajor( + mozilla::gfx::YUVColorSpace aYUVColorSpace); + static const float* YuvToRgbMatrix4x4ColumnMajor( + mozilla::gfx::YUVColorSpace aYUVColorSpace); + + /** + * Creates a copy of aSurface, but having the SurfaceFormat aFormat. + * + * This function always creates a new surface. Do not call it if aSurface's + * format is the same as aFormat. Such a non-conversion would just be an + * unnecessary and wasteful copy (this function asserts to prevent that). + * + * This function is intended to be called by code that needs to access the + * pixel data of the surface, but doesn't want to have lots of branches + * to handle different pixel data formats (code which would become out of + * date if and when new formats are added). Callers can use this function + * to copy the surface to a specified format so that they only have to + * handle pixel data in that one format. + * + * WARNING: There are format conversions that will not be supported by this + * function. It very much depends on what the Moz2D backends support. If + * the temporary B8G8R8A8 DrawTarget that this function creates has a + * backend that supports DrawSurface() calls passing a surface with + * aSurface's format it will work. Otherwise it will not. + * + * *** IMPORTANT PERF NOTE *** + * + * This function exists partly because format conversion is fraught with + * non-obvious performance hazards, so we don't want Moz2D consumers to be + * doing their own format conversion. Do not try to do so, or at least read + * the comments in this functions implemtation. That said, the copy that + * this function carries out has a cost and, although this function tries + * to avoid perf hazards such as expensive uploads to/readbacks from the + * GPU, it can't guarantee that it always successfully does so. Perf + * critical code that can directly handle the common formats that it + * encounters in a way that is cheaper than a copy-with-format-conversion + * should consider doing so, and only use this function as a fallback to + * handle other formats. + * + * XXXjwatt it would be nice if SourceSurface::GetDataSurface took a + * SurfaceFormat argument (with a default argument meaning "use the + * existing surface's format") and returned a DataSourceSurface in that + * format. (There would then be an issue of callers maybe failing to + * realize format conversion may involve expensive copying/uploading/ + * readback.) + */ + static already_AddRefed<DataSourceSurface> + CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface, + SurfaceFormat aFormat); + + /** + * Return a color that can be used to identify a frame with a given frame + * number. The colors will cycle after sNumFrameColors. You can query colors + * 0 .. sNumFrameColors-1 to get all the colors back. + */ + static const mozilla::gfx::DeviceColor& GetColorForFrameNumber( + uint64_t aFrameNumber); + static const uint32_t sNumFrameColors; + + enum BinaryOrData { eBinaryEncode, eDataURIEncode }; + + /** + * Encodes the given surface to PNG/JPEG/BMP/etc. using imgIEncoder. + * If both aFile and aString are null, the encoded data is copied to the + * clipboard. + * + * @param aImageType The image type that the surface is to be encoded to. + * Used to create an appropriate imgIEncoder instance to do the encoding. + * + * @param aOutputOptions Passed directly to imgIEncoder::InitFromData as + * the value of the |outputOptions| parameter. Callers are responsible + * for making sure that this is a sane value for the passed MIME-type + * (i.e. for the type of encoder that will be created). + * + * @aBinaryOrData Flag used to determine if the surface is simply encoded + * to the requested binary image format, or if the binary image is + * further converted to base-64 and written out as a 'data:' URI. + * + * @aFile If specified, the encoded data is written out to aFile. + * + * @aString If specified, the encoded data is written out to aString. + * + * TODO: Copying to the clipboard as a binary file is not currently + * supported. + */ + static nsresult EncodeSourceSurface(SourceSurface* aSurface, + const ImageType aImageType, + const nsAString& aOutputOptions, + BinaryOrData aBinaryOrData, FILE* aFile, + nsACString* aString = nullptr); + + /** + * Write as a PNG file to the path aFile. + */ + static void WriteAsPNG(SourceSurface* aSurface, const nsAString& aFile); + static void WriteAsPNG(SourceSurface* aSurface, const char* aFile); + static void WriteAsPNG(DrawTarget* aDT, const nsAString& aFile); + static void WriteAsPNG(DrawTarget* aDT, const char* aFile); + + /** + * Dump as a PNG encoded Data URL to a FILE stream (using stdout by + * default). + * + * Rather than giving aFile a default argument we have separate functions + * to make them easier to use from a debugger. + */ + static void DumpAsDataURI(SourceSurface* aSourceSurface, FILE* aFile); + static inline void DumpAsDataURI(SourceSurface* aSourceSurface) { + DumpAsDataURI(aSourceSurface, stdout); + } + static void DumpAsDataURI(DrawTarget* aDT, FILE* aFile); + static inline void DumpAsDataURI(DrawTarget* aDT) { + DumpAsDataURI(aDT, stdout); + } + static nsCString GetAsDataURI(SourceSurface* aSourceSurface); + static nsCString GetAsDataURI(DrawTarget* aDT); + static nsCString GetAsLZ4Base64Str(DataSourceSurface* aSourceSurface); + + static mozilla::UniquePtr<uint8_t[]> GetImageBuffer( + DataSourceSurface* aSurface, bool aIsAlphaPremultiplied, + int32_t* outFormat); + + static nsresult GetInputStream(DataSourceSurface* aSurface, + bool aIsAlphaPremultiplied, + const char* aMimeType, + const nsAString& aEncoderOptions, + nsIInputStream** outStream); + + static void RemoveShaderCacheFromDiskIfNecessary(); + + /** + * Copy to the clipboard as a PNG encoded Data URL. + */ + static void CopyAsDataURI(SourceSurface* aSourceSurface); + static void CopyAsDataURI(DrawTarget* aDT); + + static bool DumpDisplayList(); + + static FILE* sDumpPaintFile; +}; + +namespace mozilla { + +struct StyleRGBA; + +namespace gfx { + +/** + * If the CMS mode is eCMSMode_All, these functions transform the passed + * color to a device color using the transform returened by gfxPlatform:: + * GetCMSRGBTransform(). If the CMS mode is some other value, the color is + * returned unchanged (other than a type change to Moz2D Color, if + * applicable). + */ +DeviceColor ToDeviceColor(const sRGBColor& aColor); +DeviceColor ToDeviceColor(const StyleRGBA& aColor); +DeviceColor ToDeviceColor(nscolor aColor); + +/** + * Performs a checked multiply of the given width, height, and bytes-per-pixel + * values. + */ +static inline CheckedInt<uint32_t> SafeBytesForBitmap(uint32_t aWidth, + uint32_t aHeight, + unsigned aBytesPerPixel) { + MOZ_ASSERT(aBytesPerPixel > 0); + CheckedInt<uint32_t> width = uint32_t(aWidth); + CheckedInt<uint32_t> height = uint32_t(aHeight); + return width * height * aBytesPerPixel; +} + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/thebes/gfxWindowsNativeDrawing.cpp b/gfx/thebes/gfxWindowsNativeDrawing.cpp new file mode 100644 index 0000000000..af9ccf3daa --- /dev/null +++ b/gfx/thebes/gfxWindowsNativeDrawing.cpp @@ -0,0 +1,302 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <windows.h> + +#include "nsMathUtils.h" + +#include "gfxWindowsNativeDrawing.h" +#include "gfxWindowsSurface.h" +#include "gfxAlphaRecovery.h" +#include "gfxPattern.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Helpers.h" +#include "gfx2DGlue.h" + +#include "cairo.h" +#include "cairo-win32.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +enum { + RENDER_STATE_INIT, + + RENDER_STATE_NATIVE_DRAWING, + RENDER_STATE_NATIVE_DRAWING_DONE, + + RENDER_STATE_ALPHA_RECOVERY_BLACK, + RENDER_STATE_ALPHA_RECOVERY_BLACK_DONE, + RENDER_STATE_ALPHA_RECOVERY_WHITE, + RENDER_STATE_ALPHA_RECOVERY_WHITE_DONE, + + RENDER_STATE_DONE +}; + +gfxWindowsNativeDrawing::gfxWindowsNativeDrawing(gfxContext* ctx, + const gfxRect& nativeRect, + uint32_t nativeDrawFlags) + : mContext(ctx), + mNativeRect(nativeRect), + mNativeDrawFlags(nativeDrawFlags), + mRenderState(RENDER_STATE_INIT) {} + +HDC gfxWindowsNativeDrawing::BeginNativeDrawing() { + if (mRenderState == RENDER_STATE_INIT) { + RefPtr<gfxASurface> surf; + DrawTarget* drawTarget = mContext->GetDrawTarget(); + cairo_t* cairo = nullptr; + if (drawTarget->GetBackendType() == BackendType::CAIRO) { + cairo = static_cast<cairo_t*>( + drawTarget->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT)); + if (cairo) { + cairo_surface_t* s = cairo_get_group_target(cairo); + if (s) { + mDeviceOffset = mContext->GetDeviceOffset(); + double sdx, sdy; + cairo_surface_get_device_offset(s, &sdx, &sdy); + mDeviceOffset.x -= sdx; + mDeviceOffset.y -= sdy; + surf = gfxASurface::Wrap(s); + } + } + } + + if (surf && surf->CairoStatus() != 0) return nullptr; + + gfxMatrix m = mContext->CurrentMatrixDouble(); + if (!m.HasNonTranslation()) + mTransformType = TRANSLATION_ONLY; + else if (m.HasNonAxisAlignedTransform()) + mTransformType = COMPLEX; + else + mTransformType = AXIS_ALIGNED_SCALE; + + // if this is a native win32 surface, we don't have to + // redirect rendering to our own HDC; in some cases, + // we may be able to use the HDC from the surface directly. + if (surf && ((surf->GetType() == gfxSurfaceType::Win32 || + surf->GetType() == gfxSurfaceType::Win32Printing) && + (surf->GetContentType() == gfxContentType::COLOR || + (surf->GetContentType() == gfxContentType::COLOR_ALPHA && + (mNativeDrawFlags & CAN_DRAW_TO_COLOR_ALPHA))))) { + // grab the DC. This can fail if there is a complex clipping path, + // in which case we'll have to fall back. + mWinSurface = static_cast<gfxWindowsSurface*>( + static_cast<gfxASurface*>(surf.get())); + mDC = cairo_win32_get_dc_with_clip(cairo); + + if (mDC) { + if (mTransformType == TRANSLATION_ONLY) { + mRenderState = RENDER_STATE_NATIVE_DRAWING; + + mTranslation = m.GetTranslation(); + } else if (((mTransformType == AXIS_ALIGNED_SCALE) && + (mNativeDrawFlags & CAN_AXIS_ALIGNED_SCALE)) || + (mNativeDrawFlags & CAN_COMPLEX_TRANSFORM)) { + mWorldTransform.eM11 = (FLOAT)m._11; + mWorldTransform.eM12 = (FLOAT)m._12; + mWorldTransform.eM21 = (FLOAT)m._21; + mWorldTransform.eM22 = (FLOAT)m._22; + mWorldTransform.eDx = (FLOAT)m._31; + mWorldTransform.eDy = (FLOAT)m._32; + + mRenderState = RENDER_STATE_NATIVE_DRAWING; + } + } + } + + // If we couldn't do native drawing, then we have to do two-buffer drawing + // and do alpha recovery + if (mRenderState == RENDER_STATE_INIT) { + mRenderState = RENDER_STATE_ALPHA_RECOVERY_BLACK; + + // We round out our native rect here, that way the snapping will + // happen correctly. + mNativeRect.RoundOut(); + + // we only do the scale bit if we can do an axis aligned + // scale; otherwise we scale (if necessary) after + // rendering with cairo. Note that if we're doing alpha recovery, + // we cannot do a full complex transform with win32 (I mean, we could, but + // it would require more code that's not here.) + if (mTransformType == TRANSLATION_ONLY || + !(mNativeDrawFlags & CAN_AXIS_ALIGNED_SCALE)) { + mScale = gfxSize(1.0, 1.0); + + // Add 1 to the surface size; it's guaranteed to not be incorrect, + // and it fixes bug 382458 + // There's probably a better fix, but I haven't figured out + // the root cause of the problem. + mTempSurfaceSize = IntSize((int32_t)ceil(mNativeRect.Width() + 1), + (int32_t)ceil(mNativeRect.Height() + 1)); + } else { + // figure out the scale factors + mScale = m.ScaleFactors(); + + mWorldTransform.eM11 = (FLOAT)mScale.width; + mWorldTransform.eM12 = 0.0f; + mWorldTransform.eM21 = 0.0f; + mWorldTransform.eM22 = (FLOAT)mScale.height; + mWorldTransform.eDx = 0.0f; + mWorldTransform.eDy = 0.0f; + + // See comment above about "+1" + mTempSurfaceSize = + IntSize((int32_t)ceil(mNativeRect.Width() * mScale.width + 1), + (int32_t)ceil(mNativeRect.Height() * mScale.height + 1)); + } + } + } + + if (mRenderState == RENDER_STATE_NATIVE_DRAWING) { + // we can just do native drawing directly to the context's surface + + // do we need to use SetWorldTransform? + if (mTransformType != TRANSLATION_ONLY) { + SetGraphicsMode(mDC, GM_ADVANCED); + GetWorldTransform(mDC, &mOldWorldTransform); + SetWorldTransform(mDC, &mWorldTransform); + } + GetViewportOrgEx(mDC, &mOrigViewportOrigin); + SetViewportOrgEx(mDC, mOrigViewportOrigin.x - (int)mDeviceOffset.x, + mOrigViewportOrigin.y - (int)mDeviceOffset.y, nullptr); + + return mDC; + } else if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_BLACK || + mRenderState == RENDER_STATE_ALPHA_RECOVERY_WHITE) { + // we're going to use mWinSurface to create our temporary surface here + + // get us a RGB24 DIB; DIB is important, because + // we can later call GetImageSurface on it. + mWinSurface = new gfxWindowsSurface(mTempSurfaceSize); + mDC = mWinSurface->GetDC(); + + RECT r = {0, 0, mTempSurfaceSize.width, mTempSurfaceSize.height}; + if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_BLACK) + FillRect(mDC, &r, (HBRUSH)GetStockObject(BLACK_BRUSH)); + else + FillRect(mDC, &r, (HBRUSH)GetStockObject(WHITE_BRUSH)); + + if ((mTransformType != TRANSLATION_ONLY) && + (mNativeDrawFlags & CAN_AXIS_ALIGNED_SCALE)) { + SetGraphicsMode(mDC, GM_ADVANCED); + SetWorldTransform(mDC, &mWorldTransform); + } + + return mDC; + } else { + NS_ERROR("Bogus render state!"); + return nullptr; + } +} + +bool gfxWindowsNativeDrawing::ShouldRenderAgain() { + switch (mRenderState) { + case RENDER_STATE_NATIVE_DRAWING_DONE: + return false; + + case RENDER_STATE_ALPHA_RECOVERY_BLACK_DONE: + mRenderState = RENDER_STATE_ALPHA_RECOVERY_WHITE; + return true; + + case RENDER_STATE_ALPHA_RECOVERY_WHITE_DONE: + return false; + + default: + NS_ERROR( + "Invalid RenderState in gfxWindowsNativeDrawing::ShouldRenderAgain"); + break; + } + + return false; +} + +void gfxWindowsNativeDrawing::EndNativeDrawing() { + if (mRenderState == RENDER_STATE_NATIVE_DRAWING) { + // we drew directly to the HDC in the context; undo our changes + SetViewportOrgEx(mDC, mOrigViewportOrigin.x, mOrigViewportOrigin.y, + nullptr); + + if (mTransformType != TRANSLATION_ONLY) + SetWorldTransform(mDC, &mOldWorldTransform); + + mWinSurface->MarkDirty(); + + mRenderState = RENDER_STATE_NATIVE_DRAWING_DONE; + } else if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_BLACK) { + mBlackSurface = mWinSurface; + mWinSurface = nullptr; + + mRenderState = RENDER_STATE_ALPHA_RECOVERY_BLACK_DONE; + } else if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_WHITE) { + mWhiteSurface = mWinSurface; + mWinSurface = nullptr; + + mRenderState = RENDER_STATE_ALPHA_RECOVERY_WHITE_DONE; + } else { + NS_ERROR( + "Invalid RenderState in gfxWindowsNativeDrawing::EndNativeDrawing"); + } +} + +void gfxWindowsNativeDrawing::PaintToContext() { + if (mRenderState == RENDER_STATE_NATIVE_DRAWING_DONE) { + // nothing to do, it already went to the context + mRenderState = RENDER_STATE_DONE; + } else if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_WHITE_DONE) { + RefPtr<gfxImageSurface> black = mBlackSurface->GetAsImageSurface(); + RefPtr<gfxImageSurface> white = mWhiteSurface->GetAsImageSurface(); + if (!gfxAlphaRecovery::RecoverAlpha(black, white)) { + NS_ERROR("Alpha recovery failure"); + return; + } + RefPtr<DataSourceSurface> source = Factory::CreateWrappingDataSourceSurface( + black->Data(), black->Stride(), black->GetSize(), + SurfaceFormat::B8G8R8A8); + { + DrawTarget* dt = mContext->GetDrawTarget(); + AutoRestoreTransform autoRestoreTransform(dt); + + Matrix newTransform = dt->GetTransform(); + newTransform.PreTranslate(ToPoint(mNativeRect.TopLeft())); + dt->SetTransform(newTransform); + + Rect rect(Point(0.0, 0.0), ToSize(mNativeRect.Size())); + Matrix m = Matrix::Scaling(1.0 / mScale.width, 1.0 / mScale.height); + SurfacePattern pat(source, ExtendMode::CLAMP, m); + dt->FillRect(rect, pat); + } + + mRenderState = RENDER_STATE_DONE; + } else { + NS_ERROR("Invalid RenderState in gfxWindowsNativeDrawing::PaintToContext"); + } +} + +void gfxWindowsNativeDrawing::TransformToNativeRect(const gfxRect& r, + RECT& rout) { + /* If we're doing native drawing, then we're still in the coordinate space + * of the context; otherwise, we're in our own little world, + * relative to the passed-in nativeRect. + */ + + gfxRect roundedRect(r); + + if (mRenderState == RENDER_STATE_NATIVE_DRAWING) { + if (mTransformType == TRANSLATION_ONLY) { + roundedRect.MoveBy(mTranslation); + } + } else { + roundedRect.MoveBy(-mNativeRect.TopLeft()); + } + + roundedRect.Round(); + + rout.left = LONG(roundedRect.X()); + rout.right = LONG(roundedRect.XMost()); + rout.top = LONG(roundedRect.Y()); + rout.bottom = LONG(roundedRect.YMost()); +} diff --git a/gfx/thebes/gfxWindowsNativeDrawing.h b/gfx/thebes/gfxWindowsNativeDrawing.h new file mode 100644 index 0000000000..a7713dd800 --- /dev/null +++ b/gfx/thebes/gfxWindowsNativeDrawing.h @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _GFXWINDOWSNATIVEDRAWING_H_ +#define _GFXWINDOWSNATIVEDRAWING_H_ + +#include <windows.h> + +#include "gfxContext.h" +#include "gfxWindowsSurface.h" + +class gfxWindowsNativeDrawing { + public: + /* Flags for notifying this class what kind of operations the native + * drawing supports + */ + + enum { + /* Whether the native drawing can draw to a surface of content COLOR_ALPHA + */ + CAN_DRAW_TO_COLOR_ALPHA = 1 << 0, + CANNOT_DRAW_TO_COLOR_ALPHA = 0 << 0, + + /* Whether the native drawing can be scaled using SetWorldTransform */ + CAN_AXIS_ALIGNED_SCALE = 1 << 1, + CANNOT_AXIS_ALIGNED_SCALE = 0 << 1, + + /* Whether the native drawing can be both scaled and rotated arbitrarily + using SetWorldTransform */ + CAN_COMPLEX_TRANSFORM = 1 << 2, + CANNOT_COMPLEX_TRANSFORM = 0 << 2, + }; + + /* Create native win32 drawing for a rectangle bounded by + * nativeRect. + * + * Typical usage looks like: + * + * gfxWindowsNativeDrawing nativeDraw(ctx, destGfxRect, capabilities); + * do { + * HDC dc = nativeDraw.BeginNativeDrawing(); + * if (!dc) + * return NS_ERROR_FAILURE; + * + * RECT winRect; + * nativeDraw.TransformToNativeRect(rect, winRect); + * + * ... call win32 operations on HDC to draw to winRect ... + * + * nativeDraw.EndNativeDrawing(); + * } while (nativeDraw.ShouldRenderAgain()); + * nativeDraw.PaintToContext(); + */ + gfxWindowsNativeDrawing( + gfxContext* ctx, const gfxRect& nativeRect, + uint32_t nativeDrawFlags = CANNOT_DRAW_TO_COLOR_ALPHA | + CANNOT_AXIS_ALIGNED_SCALE | + CANNOT_COMPLEX_TRANSFORM); + + /* Returns a HDC which may be used for native drawing. This HDC is valid + * until EndNativeDrawing is called; if it is used for drawing after that + * time, the result is undefined. */ + HDC BeginNativeDrawing(); + + /* Transform the native rect into something valid for rendering + * to the HDC. This may or may not change RECT, depending on + * whether SetWorldTransform is used or not. */ + void TransformToNativeRect(const gfxRect& r, RECT& rout); + + /* Marks the end of native drawing */ + void EndNativeDrawing(); + + /* Returns true if the native drawing should be executed again */ + bool ShouldRenderAgain(); + + /* Places the result to the context, if necessary */ + void PaintToContext(); + + private: + RefPtr<gfxContext> mContext; + gfxRect mNativeRect; + uint32_t mNativeDrawFlags; + + // what state the rendering is in + uint8_t mRenderState; + + mozilla::gfx::Point mDeviceOffset; + RefPtr<gfxPattern> mBlackPattern, mWhitePattern; + + enum TransformType { TRANSLATION_ONLY, AXIS_ALIGNED_SCALE, COMPLEX }; + + TransformType mTransformType; + gfxPoint mTranslation; + gfxSize mScale; + XFORM mWorldTransform; + + // saved state + RefPtr<gfxWindowsSurface> mWinSurface, mBlackSurface, mWhiteSurface; + HDC mDC; + XFORM mOldWorldTransform; + POINT mOrigViewportOrigin; + mozilla::gfx::IntSize mTempSurfaceSize; +}; + +#endif diff --git a/gfx/thebes/gfxWindowsPlatform.cpp b/gfx/thebes/gfxWindowsPlatform.cpp new file mode 100644 index 0000000000..e4e360f196 --- /dev/null +++ b/gfx/thebes/gfxWindowsPlatform.cpp @@ -0,0 +1,2088 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#define INITGUID // set before devguid.h + +#include "gfxWindowsPlatform.h" + +#include "cairo.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/layers/CompositorBridgeChild.h" + +#include "gfxBlur.h" +#include "gfxImageSurface.h" +#include "gfxWindowsSurface.h" + +#include "nsUnicharUtils.h" +#include "nsUnicodeProperties.h" + +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/Sprintf.h" +#include "mozilla/WindowsVersion.h" +#include "nsIGfxInfo.h" +#include "nsServiceManagerUtils.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" +#include "mozilla/Telemetry.h" +#include "GeckoProfiler.h" + +#include "plbase64.h" +#include "nsIXULRuntime.h" +#include "imgLoader.h" + +#include "nsIGfxInfo.h" + +#include "gfxCrashReporterUtils.h" + +#include "gfxGDIFontList.h" +#include "gfxGDIFont.h" + +#include "mozilla/layers/CanvasChild.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/PaintThread.h" +#include "mozilla/layers/ReadbackManagerD3D11.h" + +#include "gfxDWriteFontList.h" +#include "gfxDWriteFonts.h" +#include "gfxDWriteCommon.h" +#include <dwrite.h> + +#include "gfxTextRun.h" +#include "gfxUserFontSet.h" +#include "nsWindowsHelpers.h" +#include "gfx2DGlue.h" + +#include <string> + +#include <d3d10_1.h> + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/gfxVars.h" + +#include "nsMemory.h" + +#include <dwmapi.h> +#include <d3d11.h> +#include <d2d1_1.h> + +#include "nsIMemoryReporter.h" +#include <winternl.h> +#include "d3dkmtQueryStatistics.h" + +#include "base/thread.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layers.h" +#include "gfxConfig.h" +#include "VsyncSource.h" +#include "DriverCrashGuard.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/gfx/DeviceManagerDx.h" +#include "mozilla/gfx/DisplayConfigWindows.h" +#include "mozilla/layers/DeviceAttachmentsD3D11.h" +#include "D3D11Checks.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::layers; +using namespace mozilla::widget; +using namespace mozilla::image; +using namespace mozilla::unicode; + +DCForMetrics::DCForMetrics() { + // Get the whole screen DC: + mDC = GetDC(nullptr); + SetGraphicsMode(mDC, GM_ADVANCED); +} + +class GfxD2DVramReporter final : public nsIMemoryReporter { + ~GfxD2DVramReporter() {} + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + MOZ_COLLECT_REPORT("gfx-d2d-vram-draw-target", KIND_OTHER, UNITS_BYTES, + Factory::GetD2DVRAMUsageDrawTarget(), + "Video memory used by D2D DrawTargets."); + + MOZ_COLLECT_REPORT("gfx-d2d-vram-source-surface", KIND_OTHER, UNITS_BYTES, + Factory::GetD2DVRAMUsageSourceSurface(), + "Video memory used by D2D SourceSurfaces."); + + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(GfxD2DVramReporter, nsIMemoryReporter) + +#define GFX_CLEARTYPE_PARAMS "gfx.font_rendering.cleartype_params." +#define GFX_CLEARTYPE_PARAMS_GAMMA "gfx.font_rendering.cleartype_params.gamma" +#define GFX_CLEARTYPE_PARAMS_CONTRAST \ + "gfx.font_rendering.cleartype_params.enhanced_contrast" +#define GFX_CLEARTYPE_PARAMS_LEVEL \ + "gfx.font_rendering.cleartype_params.cleartype_level" +#define GFX_CLEARTYPE_PARAMS_STRUCTURE \ + "gfx.font_rendering.cleartype_params.pixel_structure" +#define GFX_CLEARTYPE_PARAMS_MODE \ + "gfx.font_rendering.cleartype_params.rendering_mode" + +class GPUAdapterReporter final : public nsIMemoryReporter { + // Callers must Release the DXGIAdapter after use or risk mem-leak + static bool GetDXGIAdapter(IDXGIAdapter** aDXGIAdapter) { + RefPtr<ID3D11Device> d3d11Device; + RefPtr<IDXGIDevice> dxgiDevice; + bool result = false; + + if ((d3d11Device = mozilla::gfx::Factory::GetDirect3D11Device())) { + if (d3d11Device->QueryInterface(__uuidof(IDXGIDevice), + getter_AddRefs(dxgiDevice)) == S_OK) { + result = (dxgiDevice->GetAdapter(aDXGIAdapter) == S_OK); + } + } + + return result; + } + + ~GPUAdapterReporter() {} + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD + CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) override { + HANDLE ProcessHandle = GetCurrentProcess(); + + int64_t dedicatedBytesUsed = 0; + int64_t sharedBytesUsed = 0; + int64_t committedBytesUsed = 0; + IDXGIAdapter* DXGIAdapter; + + HMODULE gdi32Handle; + PFND3DKMTQS queryD3DKMTStatistics = nullptr; + + if ((gdi32Handle = LoadLibrary(TEXT("gdi32.dll")))) + queryD3DKMTStatistics = + (PFND3DKMTQS)GetProcAddress(gdi32Handle, "D3DKMTQueryStatistics"); + + if (queryD3DKMTStatistics && GetDXGIAdapter(&DXGIAdapter)) { + // Most of this block is understood thanks to wj32's work on Process + // Hacker + + DXGI_ADAPTER_DESC adapterDesc; + D3DKMTQS queryStatistics; + + DXGIAdapter->GetDesc(&adapterDesc); + DXGIAdapter->Release(); + + memset(&queryStatistics, 0, sizeof(D3DKMTQS)); + queryStatistics.Type = D3DKMTQS_PROCESS; + queryStatistics.AdapterLuid = adapterDesc.AdapterLuid; + queryStatistics.hProcess = ProcessHandle; + if (NT_SUCCESS(queryD3DKMTStatistics(&queryStatistics))) { + committedBytesUsed = + queryStatistics.QueryResult.ProcessInfo.SystemMemory.BytesAllocated; + } + + memset(&queryStatistics, 0, sizeof(D3DKMTQS)); + queryStatistics.Type = D3DKMTQS_ADAPTER; + queryStatistics.AdapterLuid = adapterDesc.AdapterLuid; + if (NT_SUCCESS(queryD3DKMTStatistics(&queryStatistics))) { + ULONG i; + ULONG segmentCount = queryStatistics.QueryResult.AdapterInfo.NbSegments; + + for (i = 0; i < segmentCount; i++) { + memset(&queryStatistics, 0, sizeof(D3DKMTQS)); + queryStatistics.Type = D3DKMTQS_SEGMENT; + queryStatistics.AdapterLuid = adapterDesc.AdapterLuid; + queryStatistics.QuerySegment.SegmentId = i; + + if (NT_SUCCESS(queryD3DKMTStatistics(&queryStatistics))) { + bool aperture; + + // SegmentInformation has a different definition in Win7 than later + // versions + if (!IsWin8OrLater()) + aperture = queryStatistics.QueryResult.SegmentInfoWin7.Aperture; + else + aperture = queryStatistics.QueryResult.SegmentInfoWin8.Aperture; + + memset(&queryStatistics, 0, sizeof(D3DKMTQS)); + queryStatistics.Type = D3DKMTQS_PROCESS_SEGMENT; + queryStatistics.AdapterLuid = adapterDesc.AdapterLuid; + queryStatistics.hProcess = ProcessHandle; + queryStatistics.QueryProcessSegment.SegmentId = i; + if (NT_SUCCESS(queryD3DKMTStatistics(&queryStatistics))) { + ULONGLONG bytesCommitted; + if (!IsWin8OrLater()) + bytesCommitted = queryStatistics.QueryResult.ProcessSegmentInfo + .Win7.BytesCommitted; + else + bytesCommitted = queryStatistics.QueryResult.ProcessSegmentInfo + .Win8.BytesCommitted; + if (aperture) + sharedBytesUsed += bytesCommitted; + else + dedicatedBytesUsed += bytesCommitted; + } + } + } + } + } + + FreeLibrary(gdi32Handle); + + MOZ_COLLECT_REPORT("gpu-committed", KIND_OTHER, UNITS_BYTES, + committedBytesUsed, + "Memory committed by the Windows graphics system."); + + MOZ_COLLECT_REPORT( + "gpu-dedicated", KIND_OTHER, UNITS_BYTES, dedicatedBytesUsed, + "Out-of-process memory allocated for this process in a physical " + "GPU adapter's memory."); + + MOZ_COLLECT_REPORT("gpu-shared", KIND_OTHER, UNITS_BYTES, sharedBytesUsed, + "In-process memory that is shared with the GPU."); + + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(GPUAdapterReporter, nsIMemoryReporter) + +Atomic<size_t> gfxWindowsPlatform::sD3D11SharedTextures; +Atomic<size_t> gfxWindowsPlatform::sD3D9SharedTextures; + +class D3DSharedTexturesReporter final : public nsIMemoryReporter { + ~D3DSharedTexturesReporter() {} + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + if (gfxWindowsPlatform::sD3D11SharedTextures > 0) { + MOZ_COLLECT_REPORT("d3d11-shared-textures", KIND_OTHER, UNITS_BYTES, + gfxWindowsPlatform::sD3D11SharedTextures, + "D3D11 shared textures."); + } + + if (gfxWindowsPlatform::sD3D9SharedTextures > 0) { + MOZ_COLLECT_REPORT("d3d9-shared-textures", KIND_OTHER, UNITS_BYTES, + gfxWindowsPlatform::sD3D9SharedTextures, + "D3D9 shared textures."); + } + + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(D3DSharedTexturesReporter, nsIMemoryReporter) + +gfxWindowsPlatform::gfxWindowsPlatform() + : mRenderMode(RENDER_GDI), + mDwmCompositionStatus(DwmCompositionStatus::Unknown) { + /* + * Initialize COM + */ + CoInitialize(nullptr); + + RegisterStrongMemoryReporter(new GfxD2DVramReporter()); + RegisterStrongMemoryReporter(new GPUAdapterReporter()); + RegisterStrongMemoryReporter(new D3DSharedTexturesReporter()); +} + +gfxWindowsPlatform::~gfxWindowsPlatform() { + mozilla::gfx::Factory::D2DCleanup(); + + DeviceManagerDx::Shutdown(); + + /* + * Uninitialize COM + */ + CoUninitialize(); +} + +/* static */ +void gfxWindowsPlatform::InitMemoryReportersForGPUProcess() { + MOZ_RELEASE_ASSERT(XRE_IsGPUProcess()); + + RegisterStrongMemoryReporter(new GfxD2DVramReporter()); + RegisterStrongMemoryReporter(new GPUAdapterReporter()); + RegisterStrongMemoryReporter(new D3DSharedTexturesReporter()); +} + +static void UpdateANGLEConfig() { + if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + gfxConfig::Disable(Feature::D3D11_HW_ANGLE, FeatureStatus::Disabled, + "D3D11 compositing is disabled", + "FEATURE_FAILURE_HW_ANGLE_D3D11_DISABLED"_ns); + } +} + +void gfxWindowsPlatform::InitAcceleration() { + gfxPlatform::InitAcceleration(); + + DeviceManagerDx::Init(); + + InitializeConfig(); + InitGPUProcessSupport(); + // Ensure devices initialization. SharedSurfaceANGLE and + // SharedSurfaceD3D11Interop use them. The devices are lazily initialized + // with WebRender to reduce memory usage. + // Initialize them now when running non-e10s. + if (!BrowserTabsRemoteAutostart()) { + EnsureDevicesInitialized(); + } + UpdateANGLEConfig(); + UpdateRenderMode(); + + // If we have Skia and we didn't init dwrite already, do it now. + if (!DWriteEnabled() && GetDefaultContentBackend() == BackendType::SKIA) { + InitDWriteSupport(); + } + // We need to listen for font setting changes even if DWrite is not used. + Factory::SetSystemTextQuality(gfxVars::SystemTextQuality()); + gfxVars::SetSystemTextQualityListener( + gfxDWriteFont::SystemTextQualityChanged); + + if (XRE_IsParentProcess()) { + BOOL dwmEnabled = FALSE; + if (FAILED(::DwmIsCompositionEnabled(&dwmEnabled)) || !dwmEnabled) { + gfxVars::SetDwmCompositionEnabled(false); + } else { + gfxVars::SetDwmCompositionEnabled(true); + } + } + + // gfxVars are not atomic, but multiple threads can query DWM status + // Therefore, mirror value into an atomic + mDwmCompositionStatus = gfxVars::DwmCompositionEnabled() + ? DwmCompositionStatus::Enabled + : DwmCompositionStatus::Disabled; + + gfxVars::SetDwmCompositionEnabledListener([this] { + this->mDwmCompositionStatus = gfxVars::DwmCompositionEnabled() + ? DwmCompositionStatus::Enabled + : DwmCompositionStatus::Disabled; + }); + + // CanUseHardwareVideoDecoding depends on DeviceManagerDx state, + // so update the cached value now. + UpdateCanUseHardwareVideoDecoding(); + + RecordStartupTelemetry(); +} + +void gfxWindowsPlatform::InitWebRenderConfig() { + gfxPlatform::InitWebRenderConfig(); + if (XRE_IsParentProcess()) { + bool prev = + Preferences::GetBool("sanity-test.webrender.force-disabled", false); + bool current = Preferences::GetBool("gfx.webrender.force-disabled", false); + // When "gfx.webrender.force-disabled" pref is changed from false to true, + // set "layers.mlgpu.sanity-test-failed" pref to false. + // "layers.mlgpu.sanity-test-failed" pref is re-tested by SanityTest.jsm. + bool doRetest = !prev && current; + if (doRetest) { + Preferences::SetBool("layers.mlgpu.sanity-test-failed", false); + } + // Need to be called after gfxPlatform::InitWebRenderConfig(). + InitializeAdvancedLayersConfig(); + } + + if (gfxVars::UseWebRender()) { + UpdateBackendPrefs(); + } +} + +bool gfxWindowsPlatform::CanUseHardwareVideoDecoding() { + DeviceManagerDx* dm = DeviceManagerDx::Get(); + if (!dm) { + return false; + } + if (!dm->TextureSharingWorks()) { + return false; + } + return !dm->IsWARP() && gfxPlatform::CanUseHardwareVideoDecoding(); +} + +bool gfxWindowsPlatform::InitDWriteSupport() { + mozilla::ScopedGfxFeatureReporter reporter("DWrite"); + if (!Factory::EnsureDWriteFactory()) { + return false; + } + + SetupClearTypeParams(); + reporter.SetSuccessful(); + return true; +} + +bool gfxWindowsPlatform::HandleDeviceReset() { + DeviceResetReason resetReason = DeviceResetReason::OK; + if (!DidRenderingDeviceReset(&resetReason)) { + return false; + } + + if (resetReason != DeviceResetReason::FORCED_RESET) { + Telemetry::Accumulate(Telemetry::DEVICE_RESET_REASON, + uint32_t(resetReason)); + } + + // Remove devices and adapters. + DeviceManagerDx::Get()->ResetDevices(); + + imgLoader::NormalLoader()->ClearCache(true); + imgLoader::NormalLoader()->ClearCache(false); + imgLoader::PrivateBrowsingLoader()->ClearCache(true); + imgLoader::PrivateBrowsingLoader()->ClearCache(false); + gfxAlphaBoxBlur::ShutdownBlurCache(); + + gfxConfig::Reset(Feature::D3D11_COMPOSITING); + gfxConfig::Reset(Feature::ADVANCED_LAYERS); + gfxConfig::Reset(Feature::D3D11_HW_ANGLE); + gfxConfig::Reset(Feature::DIRECT2D); + + InitializeConfig(); + // XXX Add InitWebRenderConfig() calling. + InitializeAdvancedLayersConfig(); + if (mInitializedDevices) { + InitGPUProcessSupport(); + InitializeDevices(); + } + UpdateANGLEConfig(); + return true; +} + +BackendPrefsData gfxWindowsPlatform::GetBackendPrefs() const { + BackendPrefsData data; + + data.mCanvasBitmask = BackendTypeBit(BackendType::SKIA); + data.mContentBitmask = BackendTypeBit(BackendType::SKIA); + data.mCanvasDefault = BackendType::SKIA; + data.mContentDefault = BackendType::SKIA; + + if (gfxConfig::IsEnabled(Feature::DIRECT2D)) { + data.mCanvasBitmask |= BackendTypeBit(BackendType::DIRECT2D1_1); + data.mCanvasDefault = BackendType::DIRECT2D1_1; + // We do not use d2d for content when WebRender is used. + if (!gfxVars::UseWebRender()) { + data.mContentBitmask |= BackendTypeBit(BackendType::DIRECT2D1_1); + data.mContentDefault = BackendType::DIRECT2D1_1; + } + } + return data; +} + +void gfxWindowsPlatform::UpdateBackendPrefs() { + BackendPrefsData data = GetBackendPrefs(); + // Remove DIRECT2D1 preference if D2D1Device does not exist. + if (!Factory::HasD2D1Device()) { + data.mCanvasBitmask &= ~BackendTypeBit(BackendType::DIRECT2D1_1); + data.mContentBitmask &= ~BackendTypeBit(BackendType::DIRECT2D1_1); + if (data.mCanvasDefault == BackendType::DIRECT2D1_1) { + data.mCanvasDefault = BackendType::SKIA; + } + if (data.mContentDefault == BackendType::DIRECT2D1_1) { + data.mContentDefault = BackendType::SKIA; + } + } + InitBackendPrefs(std::move(data)); +} + +bool gfxWindowsPlatform::IsDirect2DBackend() { + return GetDefaultContentBackend() == BackendType::DIRECT2D1_1; +} + +void gfxWindowsPlatform::UpdateRenderMode() { + bool didReset = HandleDeviceReset(); + + UpdateBackendPrefs(); + + if (PaintThread::Get()) { + PaintThread::Get()->UpdateRenderMode(); + } + + if (didReset) { + mScreenReferenceDrawTarget = CreateOffscreenContentDrawTarget( + IntSize(1, 1), SurfaceFormat::B8G8R8A8); + if (!mScreenReferenceDrawTarget) { + gfxCriticalNote + << "Failed to update reference draw target after device reset" + << ", D3D11 device:" << hexa(Factory::GetDirect3D11Device().get()) + << ", D3D11 status:" + << FeatureStatusToString( + gfxConfig::GetValue(Feature::D3D11_COMPOSITING)) + << ", D2D1 device:" << hexa(Factory::GetD2D1Device().get()) + << ", D2D1 status:" + << FeatureStatusToString(gfxConfig::GetValue(Feature::DIRECT2D)) + << ", content:" << int(GetDefaultContentBackend()) + << ", compositor:" << int(GetCompositorBackend()); + MOZ_CRASH( + "GFX: Failed to update reference draw target after device reset"); + } + } +} + +mozilla::gfx::BackendType gfxWindowsPlatform::GetContentBackendFor( + mozilla::layers::LayersBackend aLayers) { + mozilla::gfx::BackendType defaultBackend = + gfxPlatform::GetDefaultContentBackend(); + if (aLayers == LayersBackend::LAYERS_D3D11) { + return defaultBackend; + } + + if (aLayers == LayersBackend::LAYERS_WR && + gfx::gfxVars::UseWebRenderANGLE()) { + return defaultBackend; + } + + if (defaultBackend == BackendType::DIRECT2D1_1) { + // We can't have D2D without D3D11 layers, so fallback to Skia. + return BackendType::SKIA; + } + + // Otherwise we have some non-accelerated backend and that's ok. + return defaultBackend; +} + +mozilla::gfx::BackendType gfxWindowsPlatform::GetPreferredCanvasBackend() { + mozilla::gfx::BackendType backend = gfxPlatform::GetPreferredCanvasBackend(); + + if (backend == BackendType::DIRECT2D1_1) { + if (gfx::gfxVars::UseWebRender() && !gfx::gfxVars::UseWebRenderANGLE()) { + // We can't have D2D without ANGLE when WebRender is enabled, so fallback + // to Skia. + return BackendType::SKIA; + } + + // Fall back to software when remote canvas has been deactivated. + if (CanvasChild::Deactivated()) { + return BackendType::SKIA; + } + } + return backend; +} + +gfxPlatformFontList* gfxWindowsPlatform::CreatePlatformFontList() { + gfxPlatformFontList* pfl; + + // bug 630201 - older pre-RTM versions of Direct2D/DirectWrite cause odd + // crashers so block them altogether + if (IsNotWin7PreRTM() && DWriteEnabled()) { + pfl = new gfxDWriteFontList(); + if (NS_SUCCEEDED(pfl->InitFontList())) { + return pfl; + } + // DWrite font initialization failed! Don't know why this would happen, + // but apparently it can - see bug 594865. + // So we're going to fall back to GDI fonts & rendering. + gfxPlatformFontList::Shutdown(); + DisableD2D(FeatureStatus::Failed, "Failed to initialize fonts", + "FEATURE_FAILURE_FONT_FAIL"_ns); + } + + // Ensure this is false, even if the Windows version was recent enough to + // permit it, as we're using GDI fonts. + mHasVariationFontSupport = false; + + pfl = new gfxGDIFontList(); + + if (NS_SUCCEEDED(pfl->InitFontList())) { + return pfl; + } + + gfxPlatformFontList::Shutdown(); + return nullptr; +} + +// This function will permanently disable D2D for the session. It's intended to +// be used when, after initially chosing to use Direct2D, we encounter a +// scenario we can't support. +// +// This is called during gfxPlatform::Init() so at this point there should be no +// DrawTargetD2D/1 instances. +void gfxWindowsPlatform::DisableD2D(FeatureStatus aStatus, const char* aMessage, + const nsACString& aFailureId) { + gfxConfig::SetFailed(Feature::DIRECT2D, aStatus, aMessage, aFailureId); + Factory::SetDirect3D11Device(nullptr); + UpdateBackendPrefs(); +} + +already_AddRefed<gfxASurface> gfxWindowsPlatform::CreateOffscreenSurface( + const IntSize& aSize, gfxImageFormat aFormat) { + if (!Factory::AllowedSurfaceSize(aSize)) { + return nullptr; + } + + RefPtr<gfxASurface> surf = nullptr; + +#ifdef CAIRO_HAS_WIN32_SURFACE + if (!XRE_IsContentProcess()) { + if (mRenderMode == RENDER_GDI || mRenderMode == RENDER_DIRECT2D) { + surf = new gfxWindowsSurface(aSize, aFormat); + } + } +#endif + + if (!surf || surf->CairoStatus()) { + surf = new gfxImageSurface(aSize, aFormat); + } + + return surf.forget(); +} + +static const char kFontAparajita[] = "Aparajita"; +static const char kFontArabicTypesetting[] = "Arabic Typesetting"; +static const char kFontArial[] = "Arial"; +static const char kFontArialUnicodeMS[] = "Arial Unicode MS"; +static const char kFontCambria[] = "Cambria"; +static const char kFontCambriaMath[] = "Cambria Math"; +static const char kFontEbrima[] = "Ebrima"; +static const char kFontEstrangeloEdessa[] = "Estrangelo Edessa"; +static const char kFontEuphemia[] = "Euphemia"; +static const char kFontGabriola[] = "Gabriola"; +static const char kFontJavaneseText[] = "Javanese Text"; +static const char kFontKhmerUI[] = "Khmer UI"; +static const char kFontLaoUI[] = "Lao UI"; +static const char kFontLeelawadeeUI[] = "Leelawadee UI"; +static const char kFontLucidaSansUnicode[] = "Lucida Sans Unicode"; +static const char kFontMVBoli[] = "MV Boli"; +static const char kFontMalgunGothic[] = "Malgun Gothic"; +static const char kFontMicrosoftJhengHei[] = "Microsoft JhengHei"; +static const char kFontMicrosoftNewTaiLue[] = "Microsoft New Tai Lue"; +static const char kFontMicrosoftPhagsPa[] = "Microsoft PhagsPa"; +static const char kFontMicrosoftTaiLe[] = "Microsoft Tai Le"; +static const char kFontMicrosoftUighur[] = "Microsoft Uighur"; +static const char kFontMicrosoftYaHei[] = "Microsoft YaHei"; +static const char kFontMicrosoftYiBaiti[] = "Microsoft Yi Baiti"; +static const char kFontMeiryo[] = "Meiryo"; +static const char kFontMongolianBaiti[] = "Mongolian Baiti"; +static const char kFontMyanmarText[] = "Myanmar Text"; +static const char kFontNirmalaUI[] = "Nirmala UI"; +static const char kFontNyala[] = "Nyala"; +static const char kFontPlantagenetCherokee[] = "Plantagenet Cherokee"; +static const char kFontSegoeUI[] = "Segoe UI"; +static const char kFontSegoeUIEmoji[] = "Segoe UI Emoji"; +static const char kFontSegoeUISymbol[] = "Segoe UI Symbol"; +static const char kFontSylfaen[] = "Sylfaen"; +static const char kFontTraditionalArabic[] = "Traditional Arabic"; +static const char kFontTwemojiMozilla[] = "Twemoji Mozilla"; +static const char kFontUtsaah[] = "Utsaah"; +static const char kFontYuGothic[] = "Yu Gothic"; + +void gfxWindowsPlatform::GetCommonFallbackFonts( + uint32_t aCh, Script aRunScript, eFontPresentation aPresentation, + nsTArray<const char*>& aFontList) { + if (PrefersColor(aPresentation)) { + aFontList.AppendElement(kFontSegoeUIEmoji); + aFontList.AppendElement(kFontTwemojiMozilla); + } + + // Arial is used as the default fallback for system fallback + aFontList.AppendElement(kFontArial); + + if (!IS_IN_BMP(aCh)) { + uint32_t p = aCh >> 16; + if (p == 1) { // SMP plane + aFontList.AppendElement(kFontSegoeUISymbol); + aFontList.AppendElement(kFontEbrima); + aFontList.AppendElement(kFontNirmalaUI); + aFontList.AppendElement(kFontCambriaMath); + } + } else { + uint32_t b = (aCh >> 8) & 0xff; + + switch (b) { + case 0x05: + aFontList.AppendElement(kFontEstrangeloEdessa); + aFontList.AppendElement(kFontCambria); + break; + case 0x06: + aFontList.AppendElement(kFontMicrosoftUighur); + break; + case 0x07: + aFontList.AppendElement(kFontEstrangeloEdessa); + aFontList.AppendElement(kFontMVBoli); + aFontList.AppendElement(kFontEbrima); + break; + case 0x09: + aFontList.AppendElement(kFontNirmalaUI); + aFontList.AppendElement(kFontUtsaah); + aFontList.AppendElement(kFontAparajita); + break; + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + aFontList.AppendElement(kFontNirmalaUI); + break; + case 0x0e: + aFontList.AppendElement(kFontLaoUI); + aFontList.AppendElement(kFontLeelawadeeUI); + break; + case 0x10: + aFontList.AppendElement(kFontMyanmarText); + break; + case 0x11: + aFontList.AppendElement(kFontMalgunGothic); + break; + case 0x12: + case 0x13: + aFontList.AppendElement(kFontNyala); + aFontList.AppendElement(kFontPlantagenetCherokee); + break; + case 0x14: + case 0x15: + case 0x16: + aFontList.AppendElement(kFontEuphemia); + aFontList.AppendElement(kFontSegoeUISymbol); + break; + case 0x17: + aFontList.AppendElement(kFontKhmerUI); + aFontList.AppendElement(kFontLeelawadeeUI); + break; + case 0x18: // Mongolian + aFontList.AppendElement(kFontMongolianBaiti); + aFontList.AppendElement(kFontEuphemia); + break; + case 0x19: + aFontList.AppendElement(kFontMicrosoftTaiLe); + aFontList.AppendElement(kFontMicrosoftNewTaiLue); + aFontList.AppendElement(kFontKhmerUI); + aFontList.AppendElement(kFontLeelawadeeUI); + break; + case 0x1a: + aFontList.AppendElement(kFontLeelawadeeUI); + break; + case 0x1c: + aFontList.AppendElement(kFontNirmalaUI); + break; + case 0x20: // Symbol ranges + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x29: + case 0x2a: + case 0x2b: + case 0x2c: + aFontList.AppendElement(kFontSegoeUI); + aFontList.AppendElement(kFontSegoeUISymbol); + aFontList.AppendElement(kFontCambria); + aFontList.AppendElement(kFontMeiryo); + aFontList.AppendElement(kFontArial); + aFontList.AppendElement(kFontLucidaSansUnicode); + aFontList.AppendElement(kFontEbrima); + break; + case 0x2d: + case 0x2e: + case 0x2f: + aFontList.AppendElement(kFontEbrima); + aFontList.AppendElement(kFontNyala); + aFontList.AppendElement(kFontSegoeUI); + aFontList.AppendElement(kFontSegoeUISymbol); + aFontList.AppendElement(kFontMeiryo); + break; + case 0x28: // Braille + aFontList.AppendElement(kFontSegoeUISymbol); + break; + case 0x30: + case 0x31: + aFontList.AppendElement(kFontMicrosoftYaHei); + break; + case 0x32: + aFontList.AppendElement(kFontMalgunGothic); + break; + case 0x4d: + aFontList.AppendElement(kFontSegoeUISymbol); + break; + case 0x9f: + aFontList.AppendElement(kFontMicrosoftYaHei); + aFontList.AppendElement(kFontYuGothic); + break; + case 0xa0: // Yi + case 0xa1: + case 0xa2: + case 0xa3: + case 0xa4: + aFontList.AppendElement(kFontMicrosoftYiBaiti); + aFontList.AppendElement(kFontSegoeUI); + break; + case 0xa5: + case 0xa6: + case 0xa7: + aFontList.AppendElement(kFontEbrima); + aFontList.AppendElement(kFontSegoeUI); + aFontList.AppendElement(kFontCambriaMath); + break; + case 0xa8: + aFontList.AppendElement(kFontMicrosoftPhagsPa); + aFontList.AppendElement(kFontNirmalaUI); + break; + case 0xa9: + aFontList.AppendElement(kFontMalgunGothic); + aFontList.AppendElement(kFontJavaneseText); + aFontList.AppendElement(kFontLeelawadeeUI); + break; + case 0xaa: + aFontList.AppendElement(kFontMyanmarText); + break; + case 0xab: + aFontList.AppendElement(kFontEbrima); + aFontList.AppendElement(kFontNyala); + break; + case 0xd7: + aFontList.AppendElement(kFontMalgunGothic); + break; + case 0xfb: + aFontList.AppendElement(kFontMicrosoftUighur); + aFontList.AppendElement(kFontGabriola); + aFontList.AppendElement(kFontSylfaen); + break; + case 0xfc: + case 0xfd: + aFontList.AppendElement(kFontTraditionalArabic); + aFontList.AppendElement(kFontArabicTypesetting); + break; + case 0xfe: + aFontList.AppendElement(kFontTraditionalArabic); + aFontList.AppendElement(kFontMicrosoftJhengHei); + break; + case 0xff: + aFontList.AppendElement(kFontMicrosoftJhengHei); + break; + default: + break; + } + } + + // Arial Unicode MS has lots of glyphs for obscure characters, + // use it as a last resort + aFontList.AppendElement(kFontArialUnicodeMS); +} + +bool gfxWindowsPlatform::DidRenderingDeviceReset( + DeviceResetReason* aResetReason) { + DeviceManagerDx* dm = DeviceManagerDx::Get(); + if (!dm) { + return false; + } + return dm->HasDeviceReset(aResetReason); +} + +void gfxWindowsPlatform::CompositorUpdated() { + DeviceManagerDx::Get()->ForceDeviceReset( + ForcedDeviceResetReason::COMPOSITOR_UPDATED); + UpdateRenderMode(); +} + +BOOL CALLBACK InvalidateWindowForDeviceReset(HWND aWnd, LPARAM aMsg) { + RedrawWindow(aWnd, nullptr, nullptr, + RDW_INVALIDATE | RDW_INTERNALPAINT | RDW_FRAME); + return TRUE; +} + +void gfxWindowsPlatform::SchedulePaintIfDeviceReset() { + AUTO_PROFILER_LABEL("gfxWindowsPlatform::SchedulePaintIfDeviceReset", OTHER); + + DeviceResetReason resetReason = DeviceResetReason::OK; + if (!DidRenderingDeviceReset(&resetReason)) { + return; + } + + gfxCriticalNote << "(gfxWindowsPlatform) Detected device reset: " + << (int)resetReason; + + if (XRE_IsParentProcess()) { + // Trigger an ::OnPaint for each window. + ::EnumThreadWindows(GetCurrentThreadId(), InvalidateWindowForDeviceReset, + 0); + } else { + NS_DispatchToMainThread(NS_NewRunnableFunction( + "gfx::gfxWindowsPlatform::SchedulePaintIfDeviceReset", []() -> void { + gfxWindowsPlatform::GetPlatform()->CheckForContentOnlyDeviceReset(); + })); + } + + gfxCriticalNote << "(gfxWindowsPlatform) scheduled device update."; +} + +void gfxWindowsPlatform::CheckForContentOnlyDeviceReset() { + if (!DidRenderingDeviceReset()) { + return; + } + + bool isContentOnlyTDR; + D3D11DeviceStatus status; + + DeviceManagerDx::Get()->ExportDeviceInfo(&status); + CompositorBridgeChild::Get()->SendCheckContentOnlyTDR(status.sequenceNumber(), + &isContentOnlyTDR); + + // The parent process doesn't know about the reset yet, or the reset is + // local to our device. + if (isContentOnlyTDR) { + gfxCriticalNote << "A content-only TDR is detected."; + dom::ContentChild* cc = dom::ContentChild::GetSingleton(); + cc->RecvReinitRenderingForDeviceReset(); + } +} + +nsTArray<uint8_t> gfxWindowsPlatform::GetPlatformCMSOutputProfileData() { + if (XRE_IsContentProcess()) { + // This will be passed in during InitChild so we can avoid sending a + // sync message back to the parent during init. + const mozilla::gfx::ContentDeviceData* contentDeviceData = + GetInitContentDeviceData(); + if (contentDeviceData) { + MOZ_ASSERT(!contentDeviceData->cmsOutputProfileData().IsEmpty()); + return contentDeviceData->cmsOutputProfileData().Clone(); + } + + // Otherwise we need to ask the parent for the updated color profile + mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton(); + nsTArray<uint8_t> result; + Unused << cc->SendGetOutputColorProfileData(&result); + return result; + } + + if (!mCachedOutputColorProfile.IsEmpty()) { + return mCachedOutputColorProfile.Clone(); + } + + mCachedOutputColorProfile = [&] { + nsTArray<uint8_t> prefProfileData = GetPrefCMSOutputProfileData(); + if (!prefProfileData.IsEmpty()) { + return prefProfileData; + } + + HDC dc = ::GetDC(nullptr); + if (!dc) { + return nsTArray<uint8_t>(); + } + + WCHAR profilePath[MAX_PATH]; + DWORD profilePathLen = MAX_PATH; + + bool getProfileResult = ::GetICMProfileW(dc, &profilePathLen, profilePath); + + ::ReleaseDC(nullptr, dc); + + if (!getProfileResult) { + return nsTArray<uint8_t>(); + } + + void* mem = nullptr; + size_t size = 0; + + qcms_data_from_unicode_path(profilePath, &mem, &size); + if (!mem) { + return nsTArray<uint8_t>(); + } + + nsTArray<uint8_t> result; + result.AppendElements(static_cast<uint8_t*>(mem), size); + + free(mem); + + return result; + }(); + + return mCachedOutputColorProfile.Clone(); +} + +void gfxWindowsPlatform::GetDLLVersion(char16ptr_t aDLLPath, + nsAString& aVersion) { + DWORD versInfoSize, vers[4] = {0}; + // version info not available case + aVersion.AssignLiteral(u"0.0.0.0"); + versInfoSize = GetFileVersionInfoSizeW(aDLLPath, nullptr); + AutoTArray<BYTE, 512> versionInfo; + + if (versInfoSize == 0) { + return; + } + + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + versionInfo.AppendElements(uint32_t(versInfoSize)); + + if (!GetFileVersionInfoW(aDLLPath, 0, versInfoSize, + LPBYTE(versionInfo.Elements()))) { + return; + } + + UINT len = 0; + VS_FIXEDFILEINFO* fileInfo = nullptr; + if (!VerQueryValue(LPBYTE(versionInfo.Elements()), TEXT("\\"), + (LPVOID*)&fileInfo, &len) || + len == 0 || fileInfo == nullptr) { + return; + } + + DWORD fileVersMS = fileInfo->dwFileVersionMS; + DWORD fileVersLS = fileInfo->dwFileVersionLS; + + vers[0] = HIWORD(fileVersMS); + vers[1] = LOWORD(fileVersMS); + vers[2] = HIWORD(fileVersLS); + vers[3] = LOWORD(fileVersLS); + + char buf[256]; + SprintfLiteral(buf, "%u.%u.%u.%u", vers[0], vers[1], vers[2], vers[3]); + aVersion.Assign(NS_ConvertUTF8toUTF16(buf)); +} + +static BOOL CALLBACK AppendClearTypeParams(HMONITOR aMonitor, HDC, LPRECT, + LPARAM aContext) { + MONITORINFOEXW monitorInfo; + monitorInfo.cbSize = sizeof(MONITORINFOEXW); + if (!GetMonitorInfoW(aMonitor, &monitorInfo)) { + return TRUE; + } + + ClearTypeParameterInfo ctinfo; + ctinfo.displayName.Assign(monitorInfo.szDevice); + + RefPtr<IDWriteRenderingParams> renderingParams; + HRESULT hr = Factory::GetDWriteFactory()->CreateMonitorRenderingParams( + aMonitor, getter_AddRefs(renderingParams)); + if (FAILED(hr)) { + return TRUE; + } + + ctinfo.gamma = renderingParams->GetGamma() * 1000; + ctinfo.pixelStructure = renderingParams->GetPixelGeometry(); + ctinfo.clearTypeLevel = renderingParams->GetClearTypeLevel() * 100; + ctinfo.enhancedContrast = renderingParams->GetEnhancedContrast() * 100; + + auto* params = reinterpret_cast<nsTArray<ClearTypeParameterInfo>*>(aContext); + params->AppendElement(ctinfo); + return TRUE; +} + +void gfxWindowsPlatform::GetCleartypeParams( + nsTArray<ClearTypeParameterInfo>& aParams) { + aParams.Clear(); + if (!DWriteEnabled()) { + return; + } + EnumDisplayMonitors(nullptr, nullptr, AppendClearTypeParams, + reinterpret_cast<LPARAM>(&aParams)); +} + +void gfxWindowsPlatform::FontsPrefsChanged(const char* aPref) { + bool clearTextFontCaches = true; + + gfxPlatform::FontsPrefsChanged(aPref); + + if (aPref && + !strncmp(GFX_CLEARTYPE_PARAMS, aPref, strlen(GFX_CLEARTYPE_PARAMS))) { + SetupClearTypeParams(); + } else { + clearTextFontCaches = false; + } + + if (clearTextFontCaches) { + gfxFontCache* fc = gfxFontCache::GetCache(); + if (fc) { + fc->Flush(); + } + } +} + +#define DISPLAY1_REGISTRY_KEY \ + HKEY_CURRENT_USER, L"Software\\Microsoft\\Avalon.Graphics\\DISPLAY1" + +#define ENHANCED_CONTRAST_VALUE_NAME L"EnhancedContrastLevel" + +void gfxWindowsPlatform::SetupClearTypeParams() { + if (DWriteEnabled()) { + // any missing prefs will default to invalid (-1) and be ignored; + // out-of-range values will also be ignored + FLOAT gamma = -1.0; + FLOAT contrast = -1.0; + FLOAT level = -1.0; + int geometry = -1; + int mode = -1; + int32_t value; + if (NS_SUCCEEDED(Preferences::GetInt(GFX_CLEARTYPE_PARAMS_GAMMA, &value))) { + if (value >= 1000 && value <= 2200) { + gamma = FLOAT(value / 1000.0); + } + } + + if (NS_SUCCEEDED( + Preferences::GetInt(GFX_CLEARTYPE_PARAMS_CONTRAST, &value))) { + if (value >= 0 && value <= 1000) { + contrast = FLOAT(value / 100.0); + } + } + + if (NS_SUCCEEDED(Preferences::GetInt(GFX_CLEARTYPE_PARAMS_LEVEL, &value))) { + if (value >= 0 && value <= 100) { + level = FLOAT(value / 100.0); + } + } + + if (NS_SUCCEEDED( + Preferences::GetInt(GFX_CLEARTYPE_PARAMS_STRUCTURE, &value))) { + if (value >= 0 && value <= 2) { + geometry = value; + } + } + + if (NS_SUCCEEDED(Preferences::GetInt(GFX_CLEARTYPE_PARAMS_MODE, &value))) { + if (value >= 0 && value <= 5) { + mode = value; + } + } + + cairo_dwrite_set_cleartype_params(gamma, contrast, level, geometry, mode); + + switch (mode) { + case DWRITE_RENDERING_MODE_ALIASED: + case DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC: + mMeasuringMode = DWRITE_MEASURING_MODE_GDI_CLASSIC; + break; + case DWRITE_RENDERING_MODE_CLEARTYPE_GDI_NATURAL: + mMeasuringMode = DWRITE_MEASURING_MODE_GDI_NATURAL; + break; + default: + mMeasuringMode = DWRITE_MEASURING_MODE_NATURAL; + break; + } + + RefPtr<IDWriteRenderingParams> defaultRenderingParams; + HRESULT hr = Factory::GetDWriteFactory()->CreateRenderingParams( + getter_AddRefs(defaultRenderingParams)); + if (FAILED(hr)) { + gfxWarning() << "Failed to create default rendering params"; + } + // For EnhancedContrast, we override the default if the user has not set it + // in the registry (by using the ClearType Tuner). + if (contrast < 0.0 || contrast > 10.0) { + if (defaultRenderingParams) { + HKEY hKey; + LONG res = RegOpenKeyExW(DISPLAY1_REGISTRY_KEY, 0, KEY_READ, &hKey); + if (res == ERROR_SUCCESS) { + res = RegQueryValueExW(hKey, ENHANCED_CONTRAST_VALUE_NAME, nullptr, + nullptr, nullptr, nullptr); + if (res == ERROR_SUCCESS) { + contrast = defaultRenderingParams->GetEnhancedContrast(); + } + RegCloseKey(hKey); + } + } + + if (contrast < 0.0 || contrast > 10.0) { + contrast = 1.0; + } + } + + // For parameters that have not been explicitly set, + // we copy values from default params (or our overridden value for contrast) + if (gamma < 1.0 || gamma > 2.2) { + gamma = defaultRenderingParams ? defaultRenderingParams->GetGamma() : 2.2; + } + + if (level < 0.0 || level > 1.0) { + level = defaultRenderingParams + ? defaultRenderingParams->GetClearTypeLevel() + : 1.0; + } + + DWRITE_PIXEL_GEOMETRY dwriteGeometry = + static_cast<DWRITE_PIXEL_GEOMETRY>(geometry); + DWRITE_RENDERING_MODE renderMode = static_cast<DWRITE_RENDERING_MODE>(mode); + + if (dwriteGeometry < DWRITE_PIXEL_GEOMETRY_FLAT || + dwriteGeometry > DWRITE_PIXEL_GEOMETRY_BGR) { + dwriteGeometry = defaultRenderingParams + ? defaultRenderingParams->GetPixelGeometry() + : DWRITE_PIXEL_GEOMETRY_FLAT; + } + + Factory::SetBGRSubpixelOrder(dwriteGeometry == DWRITE_PIXEL_GEOMETRY_BGR); + + if (renderMode < DWRITE_RENDERING_MODE_DEFAULT || + renderMode > DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC) { + renderMode = defaultRenderingParams + ? defaultRenderingParams->GetRenderingMode() + : DWRITE_RENDERING_MODE_DEFAULT; + } + + mRenderingParams[TEXT_RENDERING_NO_CLEARTYPE] = defaultRenderingParams; + + hr = Factory::GetDWriteFactory()->CreateCustomRenderingParams( + gamma, contrast, level, dwriteGeometry, renderMode, + getter_AddRefs(mRenderingParams[TEXT_RENDERING_NORMAL])); + if (FAILED(hr) || !mRenderingParams[TEXT_RENDERING_NORMAL]) { + mRenderingParams[TEXT_RENDERING_NORMAL] = defaultRenderingParams; + } + + hr = Factory::GetDWriteFactory()->CreateCustomRenderingParams( + gamma, contrast, level, dwriteGeometry, + DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC, + getter_AddRefs(mRenderingParams[TEXT_RENDERING_GDI_CLASSIC])); + if (FAILED(hr) || !mRenderingParams[TEXT_RENDERING_GDI_CLASSIC]) { + mRenderingParams[TEXT_RENDERING_GDI_CLASSIC] = defaultRenderingParams; + } + } +} + +ReadbackManagerD3D11* gfxWindowsPlatform::GetReadbackManager() { + if (!mD3D11ReadbackManager) { + mD3D11ReadbackManager = new ReadbackManagerD3D11(); + } + + return mD3D11ReadbackManager; +} + +bool gfxWindowsPlatform::IsOptimus() { + static int knowIsOptimus = -1; + if (knowIsOptimus == -1) { + // other potential optimus -- nvd3d9wrapx.dll & nvdxgiwrap.dll + if (GetModuleHandleA("nvumdshim.dll") || + GetModuleHandleA("nvumdshimx.dll")) { + knowIsOptimus = 1; + } else { + knowIsOptimus = 0; + } + } + return knowIsOptimus; +} +/* +static inline bool +IsWARPStable() +{ + // It seems like nvdxgiwrap makes a mess of WARP. See bug 1154703. + if (!IsWin8OrLater() || GetModuleHandleA("nvdxgiwrap.dll")) { + return false; + } + return true; +} +*/ +static void InitializeANGLEConfig() { + FeatureState& d3d11ANGLE = gfxConfig::GetFeature(Feature::D3D11_HW_ANGLE); + + if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + d3d11ANGLE.DisableByDefault(FeatureStatus::Unavailable, + "D3D11 compositing is disabled", + "FEATURE_FAILURE_HW_ANGLE_D3D11_DISABLED"_ns); + return; + } + + d3d11ANGLE.EnableByDefault(); + + nsCString message; + nsCString failureId; + if (!gfxPlatform::IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE, + &message, failureId)) { + d3d11ANGLE.Disable(FeatureStatus::Blocklisted, message.get(), failureId); + } +} + +void gfxWindowsPlatform::InitializeDirectDrawConfig() { + MOZ_ASSERT(XRE_IsParentProcess()); + + FeatureState& ddraw = gfxConfig::GetFeature(Feature::DIRECT_DRAW); + ddraw.EnableByDefault(); +} + +void gfxWindowsPlatform::InitializeConfig() { + if (XRE_IsParentProcess()) { + // The parent process first determines which features can be attempted. + // This information is relayed to content processes and the GPU process. + InitializeD3D11Config(); + InitializeANGLEConfig(); + InitializeD2DConfig(); + } else { + FetchAndImportContentDeviceData(); + InitializeANGLEConfig(); + } +} + +void gfxWindowsPlatform::InitializeD3D11Config() { + MOZ_ASSERT(XRE_IsParentProcess()); + + FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING); + + if (!gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) { + d3d11.DisableByDefault(FeatureStatus::Unavailable, + "Hardware compositing is disabled", + "FEATURE_FAILURE_D3D11_NEED_HWCOMP"_ns); + return; + } + + d3d11.EnableByDefault(); + + // Check if the user really, really wants WARP. + if (StaticPrefs::layers_d3d11_force_warp_AtStartup()) { + // Force D3D11 on even if we disabled it. + d3d11.UserForceEnable("User force-enabled WARP"); + } + + if (!IsWin8OrLater() && + !DeviceManagerDx::Get()->CheckRemotePresentSupport()) { + nsCOMPtr<nsIGfxInfo> gfxInfo; + gfxInfo = services::GetGfxInfo(); + nsAutoString adaptorId; + gfxInfo->GetAdapterDeviceID(adaptorId); + // Blocklist Intel HD Graphics 510/520/530 on Windows 7 without platform + // update due to the crashes in Bug 1351349. + if (adaptorId.EqualsLiteral("0x1912") || + adaptorId.EqualsLiteral("0x1916") || + adaptorId.EqualsLiteral("0x1902")) { +#ifdef RELEASE_OR_BETA + d3d11.Disable(FeatureStatus::Blocklisted, "Blocklisted, see bug 1351349", + "FEATURE_FAILURE_BUG_1351349"_ns); +#else + Preferences::SetBool("gfx.compositor.clearstate", true); +#endif + } + } + + nsCString message; + nsCString failureId; + if (StaticPrefs::layers_d3d11_enable_blacklist_AtStartup() && + !gfxPlatform::IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, + &message, failureId)) { + d3d11.Disable(FeatureStatus::Blocklisted, message.get(), failureId); + } +} + +/* static */ +void gfxWindowsPlatform::InitializeAdvancedLayersConfig() { + // Only enable Advanced Layers if D3D11 succeeded. + if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + return; + } + + FeatureState& al = gfxConfig::GetFeature(Feature::ADVANCED_LAYERS); + al.SetDefaultFromPref(StaticPrefs::GetPrefName_layers_mlgpu_enabled(), + true /* aIsEnablePref */, + StaticPrefs::GetPrefDefault_layers_mlgpu_enabled()); + + // Windows 7 has an extra pref since it uses totally different buffer paths + // that haven't been performance tested yet. + if (al.IsEnabled() && !IsWin8OrLater()) { + if (StaticPrefs::layers_mlgpu_enable_on_windows7_AtStartup()) { + al.UserEnable("Enabled for Windows 7 via user-preference"); + } else { + al.Disable(FeatureStatus::Disabled, + "Advanced Layers is disabled on Windows 7 by default", + "FEATURE_FAILURE_DISABLED_ON_WIN7"_ns); + } + } + + nsCString message, failureId; + if (!IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_ADVANCED_LAYERS, &message, + failureId)) { + al.Disable(FeatureStatus::Blocklisted, message.get(), failureId); + } else if (gfxVars::UseWebRender()) { + al.Disable(FeatureStatus::Blocked, + "Blocked from fallback candidate by WebRender usage", + "FEATURE_BLOCKED_BY_WEBRENDER_USAGE"_ns); + } else if (Preferences::GetBool("layers.mlgpu.sanity-test-failed", false)) { + al.Disable(FeatureStatus::Broken, "Failed to render sanity test", + "FEATURE_FAILURE_FAILED_TO_RENDER"_ns); + } +} + +/* static */ +void gfxWindowsPlatform::RecordContentDeviceFailure( + TelemetryDeviceCode aDevice) { + // If the parent process fails to acquire a device, we record this + // normally as part of the environment. The exceptional case we're + // looking for here is when the parent process successfully acquires + // a device, but the content process fails to acquire the same device. + // This would not normally be displayed in about:support. + if (!XRE_IsContentProcess()) { + return; + } + Telemetry::Accumulate(Telemetry::GFX_CONTENT_FAILED_TO_ACQUIRE_DEVICE, + uint32_t(aDevice)); +} + +void gfxWindowsPlatform::RecordStartupTelemetry() { + if (!XRE_IsParentProcess()) { + return; + } + + DeviceManagerDx* dx = DeviceManagerDx::Get(); + nsTArray<DXGI_OUTPUT_DESC1> outputs = dx->EnumerateOutputs(); + + uint32_t allSupportedColorSpaces = 0; + for (auto& output : outputs) { + uint32_t colorSpace = 1 << output.ColorSpace; + allSupportedColorSpaces |= colorSpace; + } + + Telemetry::ScalarSet( + Telemetry::ScalarID::GFX_HDR_WINDOWS_DISPLAY_COLORSPACE_BITFIELD, + allSupportedColorSpaces); +} + +// Supports lazy device initialization on Windows, so that WebRender can avoid +// initializing GPU state and allocating swap chains for most non-GPU processes. +void gfxWindowsPlatform::EnsureDevicesInitialized() { + if (!mInitializedDevices) { + mInitializedDevices = true; + InitializeDevices(); + UpdateBackendPrefs(); + } +} + +bool gfxWindowsPlatform::DevicesInitialized() { return mInitializedDevices; } + +void gfxWindowsPlatform::InitializeDevices() { + MOZ_ASSERT(NS_IsMainThread()); + + if (XRE_IsParentProcess()) { + // If we're the UI process, and the GPU process is enabled, then we don't + // initialize any DirectX devices. We do leave them enabled in gfxConfig + // though. If the GPU process fails to create these devices it will send + // a message back and we'll update their status. + if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { + return; + } + + // No GPU process, continue initializing devices as normal. + } + + // If acceleration is disabled, we refuse to initialize anything. + if (!gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) { + return; + } + + // If we previously crashed initializing devices, bail out now. + D3D11LayersCrashGuard detectCrashes; + if (detectCrashes.Crashed()) { + gfxConfig::SetFailed(Feature::HW_COMPOSITING, + FeatureStatus::CrashedOnStartup, + "Crashed during startup in a previous session"); + gfxConfig::SetFailed( + Feature::D3D11_COMPOSITING, FeatureStatus::CrashedOnStartup, + "Harware acceleration crashed during startup in a previous session"); + gfxConfig::SetFailed( + Feature::DIRECT2D, FeatureStatus::CrashedOnStartup, + "Harware acceleration crashed during startup in a previous session"); + return; + } + + bool shouldUseD2D = gfxConfig::IsEnabled(Feature::DIRECT2D); + + // First, initialize D3D11. If this succeeds we attempt to use Direct2D. + InitializeD3D11(); + InitializeD2D(); + + if (!gfxConfig::IsEnabled(Feature::DIRECT2D) && XRE_IsContentProcess() && + shouldUseD2D) { + RecordContentDeviceFailure(TelemetryDeviceCode::D2D1); + } +} + +void gfxWindowsPlatform::InitializeD3D11() { + // This function attempts to initialize our D3D11 devices, if the hardware + // is not blocklisted for D3D11 layers. This first attempt will try to create + // a hardware accelerated device. If this creation fails or the hardware is + // blocklisted, then this function will abort if WARP is disabled, causing us + // to fallback to Basic layers. If WARP is not disabled it will use a WARP + // device which should always be available on Windows 7 and higher. + if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + return; + } + + DeviceManagerDx* dm = DeviceManagerDx::Get(); + if (XRE_IsParentProcess()) { + if (!dm->CreateCompositorDevices()) { + return; + } + } + + dm->CreateContentDevices(); + + // Content process failed to create the d3d11 device while parent process + // succeed. + if (XRE_IsContentProcess() && + !gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + gfxCriticalError() + << "[D3D11] Failed to create the D3D11 device in content \ + process."; + } +} + +void gfxWindowsPlatform::InitializeD2DConfig() { + FeatureState& d2d1 = gfxConfig::GetFeature(Feature::DIRECT2D); + + if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + d2d1.DisableByDefault(FeatureStatus::Unavailable, + "Direct2D requires Direct3D 11 compositing", + "FEATURE_FAILURE_D2D_D3D11_COMP"_ns); + return; + } + + d2d1.SetDefaultFromPref(StaticPrefs::GetPrefName_gfx_direct2d_disabled(), + false, + StaticPrefs::GetPrefDefault_gfx_direct2d_disabled()); + + nsCString message; + nsCString failureId; + if (!gfxPlatform::IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_DIRECT2D, &message, + failureId)) { + d2d1.Disable(FeatureStatus::Blocklisted, message.get(), failureId); + } + + if (!d2d1.IsEnabled() && + StaticPrefs::gfx_direct2d_force_enabled_AtStartup()) { + d2d1.UserForceEnable("Force-enabled via user-preference"); + } +} + +void gfxWindowsPlatform::InitializeD2D() { + ScopedGfxFeatureReporter d2d1_1("D2D1.1"); + + FeatureState& d2d1 = gfxConfig::GetFeature(Feature::DIRECT2D); + + DeviceManagerDx* dm = DeviceManagerDx::Get(); + + // We don't know this value ahead of time, but the user can force-override + // it, so we use Disable instead of SetFailed. + if (dm->IsWARP()) { + d2d1.Disable(FeatureStatus::Blocked, + "Direct2D is not compatible with Direct3D11 WARP", + "FEATURE_FAILURE_D2D_WARP_BLOCK"_ns); + } + + // If we pass all the initial checks, we can proceed to runtime decisions. + if (!d2d1.IsEnabled()) { + return; + } + + if (!Factory::SupportsD2D1()) { + d2d1.SetFailed(FeatureStatus::Unavailable, + "Failed to acquire a Direct2D 1.1 factory", + "FEATURE_FAILURE_D2D_FACTORY"_ns); + return; + } + + if (!dm->GetContentDevice()) { + d2d1.SetFailed(FeatureStatus::Failed, + "Failed to acquire a Direct3D 11 content device", + "FEATURE_FAILURE_D2D_DEVICE"_ns); + return; + } + + if (!dm->TextureSharingWorks()) { + d2d1.SetFailed(FeatureStatus::Failed, + "Direct3D11 device does not support texture sharing", + "FEATURE_FAILURE_D2D_TXT_SHARING"_ns); + return; + } + + // Using Direct2D depends on DWrite support. + if (!DWriteEnabled() && !InitDWriteSupport()) { + d2d1.SetFailed(FeatureStatus::Failed, + "Failed to initialize DirectWrite support", + "FEATURE_FAILURE_D2D_DWRITE"_ns); + return; + } + + // Verify that Direct2D device creation succeeded. + RefPtr<ID3D11Device> contentDevice = dm->GetContentDevice(); + if (!Factory::SetDirect3D11Device(contentDevice)) { + d2d1.SetFailed(FeatureStatus::Failed, "Failed to create a Direct2D device", + "FEATURE_FAILURE_D2D_CREATE_FAILED"_ns); + return; + } + + MOZ_ASSERT(d2d1.IsEnabled()); + d2d1_1.SetSuccessful(); +} + +void gfxWindowsPlatform::InitGPUProcessSupport() { + FeatureState& gpuProc = gfxConfig::GetFeature(Feature::GPU_PROCESS); + + if (!gpuProc.IsEnabled()) { + return; + } + + nsCString message; + nsCString failureId; + if (!gfxPlatform::IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_GPU_PROCESS, + &message, failureId)) { + gpuProc.Disable(FeatureStatus::Blocklisted, message.get(), failureId); + return; + } + + if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + // Don't use the GPU process if not using D3D11, unless software + // compositor is allowed + if (StaticPrefs::layers_gpu_process_allow_software_AtStartup()) { + return; + } + gpuProc.Disable(FeatureStatus::Unavailable, + "Not using GPU Process since D3D11 is unavailable", + "FEATURE_FAILURE_NO_D3D11"_ns); + } else if (!IsWin7SP1OrLater()) { + // On Windows 7 Pre-SP1, DXGI 1.2 is not available and remote presentation + // for D3D11 will not work. Rather than take a regression we revert back + // to in-process rendering. + gpuProc.Disable(FeatureStatus::Unavailable, + "Windows 7 Pre-SP1 cannot use the GPU process", + "FEATURE_FAILURE_OLD_WINDOWS"_ns); + } else if (!IsWin8OrLater()) { + // Windows 7 SP1 can have DXGI 1.2 only via the Platform Update, so we + // explicitly check for that here. + if (!DeviceManagerDx::Get()->CheckRemotePresentSupport()) { + gpuProc.Disable(FeatureStatus::Unavailable, + "GPU Process requires the Windows 7 Platform Update", + "FEATURE_FAILURE_PLATFORM_UPDATE"_ns); + } else { + // Clear anything cached by the above call since we don't need it. + DeviceManagerDx::Get()->ResetDevices(); + } + } + + // If we're still enabled at this point, the user set the force-enabled pref. +} + +bool gfxWindowsPlatform::DwmCompositionEnabled() { + MOZ_RELEASE_ASSERT(mDwmCompositionStatus != DwmCompositionStatus::Unknown); + + return mDwmCompositionStatus == DwmCompositionStatus::Enabled; +} + +class D3DVsyncSource final : public VsyncSource { + public: + class D3DVsyncDisplay final : public VsyncSource::Display { + public: + D3DVsyncDisplay() + : mPrevVsync(TimeStamp::Now()), + mVsyncEnabledLock("D3DVsyncEnabledLock"), + mVsyncEnabled(false), + mWaitVBlankMonitor(NULL), + mIsWindows8OrLater(false) { + mVsyncThread = new base::Thread("WindowsVsyncThread"); + MOZ_RELEASE_ASSERT(mVsyncThread->Start(), + "GFX: Could not start Windows vsync thread"); + SetVsyncRate(); + + mIsWindows8OrLater = IsWin8OrLater(); + } + + void SetVsyncRate() { + if (!gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) { + mVsyncRate = TimeDuration::FromMilliseconds(1000.0 / 60.0); + return; + } + + DWM_TIMING_INFO vblankTime; + // Make sure to init the cbSize, otherwise GetCompositionTiming will fail + vblankTime.cbSize = sizeof(DWM_TIMING_INFO); + HRESULT hr = DwmGetCompositionTimingInfo(0, &vblankTime); + if (SUCCEEDED(hr)) { + UNSIGNED_RATIO refreshRate = vblankTime.rateRefresh; + // We get the rate in hertz / time, but we want the rate in ms. + float rate = ((float)refreshRate.uiDenominator / + (float)refreshRate.uiNumerator) * + 1000; + mVsyncRate = TimeDuration::FromMilliseconds(rate); + } else { + mVsyncRate = TimeDuration::FromMilliseconds(1000.0 / 60.0); + } + } + + virtual void Shutdown() override { + MOZ_ASSERT(NS_IsMainThread()); + DisableVsync(); + mVsyncThread->Stop(); + delete mVsyncThread; + } + + virtual void EnableVsync() override { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mVsyncThread->IsRunning()); + { // scope lock + MonitorAutoLock lock(mVsyncEnabledLock); + if (mVsyncEnabled) { + return; + } + mVsyncEnabled = true; + } + + mVsyncThread->message_loop()->PostTask(NewRunnableMethod( + "D3DVsyncDisplay::VBlankLoop", this, &D3DVsyncDisplay::VBlankLoop)); + } + + virtual void DisableVsync() override { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mVsyncThread->IsRunning()); + MonitorAutoLock lock(mVsyncEnabledLock); + if (!mVsyncEnabled) { + return; + } + mVsyncEnabled = false; + } + + virtual bool IsVsyncEnabled() override { + MOZ_ASSERT(NS_IsMainThread()); + MonitorAutoLock lock(mVsyncEnabledLock); + return mVsyncEnabled; + } + + virtual TimeDuration GetVsyncRate() override { return mVsyncRate; } + + void ScheduleSoftwareVsync(TimeStamp aVsyncTimestamp) { + MOZ_ASSERT(IsInVsyncThread()); + NS_WARNING( + "DwmComposition dynamically disabled, falling back to software " + "timers"); + + TimeStamp nextVsync = aVsyncTimestamp + mVsyncRate; + TimeDuration delay = nextVsync - TimeStamp::Now(); + if (delay.ToMilliseconds() < 0) { + delay = mozilla::TimeDuration::FromMilliseconds(0); + } + + mVsyncThread->message_loop()->PostDelayedTask( + NewRunnableMethod("D3DVsyncDisplay::VBlankLoop", this, + &D3DVsyncDisplay::VBlankLoop), + delay.ToMilliseconds()); + } + + // Returns the timestamp for the just happened vsync + TimeStamp GetVBlankTime() { + TimeStamp vsync = TimeStamp::Now(); + TimeStamp now = vsync; + + DWM_TIMING_INFO vblankTime; + // Make sure to init the cbSize, otherwise + // GetCompositionTiming will fail + vblankTime.cbSize = sizeof(DWM_TIMING_INFO); + HRESULT hr = DwmGetCompositionTimingInfo(0, &vblankTime); + if (!SUCCEEDED(hr)) { + return vsync; + } + + LARGE_INTEGER frequency; + QueryPerformanceFrequency(&frequency); + + LARGE_INTEGER qpcNow; + QueryPerformanceCounter(&qpcNow); + + const int microseconds = 1000000; + int64_t adjust = qpcNow.QuadPart - vblankTime.qpcVBlank; + int64_t usAdjust = (adjust * microseconds) / frequency.QuadPart; + vsync -= TimeDuration::FromMicroseconds((double)usAdjust); + + if (IsWin10OrLater()) { + // On Windows 10 and on, DWMGetCompositionTimingInfo, mostly + // reports the upcoming vsync time, which is in the future. + // It can also sometimes report a vblank time in the past. + // Since large parts of Gecko assume TimeStamps can't be in future, + // use the previous vsync. + + // Windows 10 and Intel HD vsync timestamps are messy and + // all over the place once in a while. Most of the time, + // it reports the upcoming vsync. Sometimes, that upcoming + // vsync is in the past. Sometimes that upcoming vsync is before + // the previously seen vsync. + // In these error cases, normalize to Now(); + if (vsync >= now) { + vsync = vsync - mVsyncRate; + } + } + + // On Windows 7 and 8, DwmFlush wakes up AFTER qpcVBlankTime + // from DWMGetCompositionTimingInfo. We can return the adjusted vsync. + if (vsync >= now) { + vsync = now; + } + + // Our vsync time is some time very far in the past, adjust to Now. + // 4 ms is arbitrary, so feel free to pick something else if this isn't + // working. See the comment above within IsWin10OrLater(). + if ((now - vsync).ToMilliseconds() > 4.0) { + vsync = now; + } + + return vsync; + } + + void VBlankLoop() { + MOZ_ASSERT(IsInVsyncThread()); + MOZ_ASSERT(sizeof(int64_t) == sizeof(QPC_TIME)); + + TimeStamp vsync = TimeStamp::Now(); + mPrevVsync = TimeStamp(); + TimeStamp flushTime = TimeStamp::Now(); + TimeDuration longVBlank = mVsyncRate * 2; + + for (;;) { + { // scope lock + MonitorAutoLock lock(mVsyncEnabledLock); + if (!mVsyncEnabled) return; + } + + // Large parts of gecko assume that the refresh driver timestamp + // must be <= Now() and cannot be in the future. + MOZ_ASSERT(vsync <= TimeStamp::Now()); + Display::NotifyVsync(vsync, vsync + mVsyncRate); + + // DwmComposition can be dynamically enabled/disabled + // so we have to check every time that it's available. + // When it is unavailable, we fallback to software but will try + // to get back to dwm rendering once it's re-enabled + if (!gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) { + ScheduleSoftwareVsync(vsync); + return; + } + + HRESULT hr = E_FAIL; + if (mIsWindows8OrLater && + !StaticPrefs::gfx_vsync_force_disable_waitforvblank()) { + UpdateVBlankOutput(); + if (mWaitVBlankOutput) { + const TimeStamp vblank_begin_wait = TimeStamp::Now(); + hr = mWaitVBlankOutput->WaitForVBlank(); + if (SUCCEEDED(hr)) { + // vblank might return instantly when running headless, + // monitor powering off, etc. Since we're on a dedicated + // thread, instant-return should not happen in the normal + // case, so catch any odd behavior with a time cutoff: + TimeDuration vblank_wait = TimeStamp::Now() - vblank_begin_wait; + if (vblank_wait.ToMilliseconds() < 1.0) { + hr = E_FAIL; // fall back on old behavior + } + } + } + } + if (!SUCCEEDED(hr)) { + hr = DwmFlush(); + } + if (!SUCCEEDED(hr)) { + // DWMFlush isn't working, fallback to software vsync. + ScheduleSoftwareVsync(TimeStamp::Now()); + return; + } + + TimeStamp now = TimeStamp::Now(); + TimeDuration flushDiff = now - flushTime; + flushTime = now; + if ((flushDiff > longVBlank) || mPrevVsync.IsNull()) { + // Our vblank took longer than 2 intervals, readjust our timestamps + vsync = GetVBlankTime(); + mPrevVsync = vsync; + } else { + // Instead of giving the actual vsync time, a constant interval + // between vblanks instead of the noise generated via hardware + // is actually what we want. Most apps just care about the diff + // between vblanks to animate, so a clean constant interval is + // smoother. + vsync = mPrevVsync + mVsyncRate; + if (vsync > now) { + // DWMFlush woke up very early, so readjust our times again + vsync = GetVBlankTime(); + } + + if (vsync <= mPrevVsync) { + vsync = TimeStamp::Now(); + } + + if ((now - vsync).ToMilliseconds() > 2.0) { + // Account for time drift here where vsync never quite catches up to + // Now and we'd fall ever so slightly further behind Now(). + vsync = GetVBlankTime(); + } + + mPrevVsync = vsync; + } + } // end for + } + virtual ~D3DVsyncDisplay() { MOZ_ASSERT(NS_IsMainThread()); } + + private: + bool IsInVsyncThread() { + return mVsyncThread->thread_id() == PlatformThread::CurrentId(); + } + + void UpdateVBlankOutput() { + HMONITOR primary_monitor = + MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY); + if (primary_monitor == mWaitVBlankMonitor && mWaitVBlankOutput) { + return; + } + + mWaitVBlankMonitor = primary_monitor; + + RefPtr<IDXGIOutput> output = nullptr; + if (DeviceManagerDx* dx = DeviceManagerDx::Get()) { + if (dx->GetOutputFromMonitor(mWaitVBlankMonitor, &output)) { + mWaitVBlankOutput = output; + return; + } + } + + // failed to convert a monitor to an output so keep trying + mWaitVBlankOutput = nullptr; + } + + TimeStamp mPrevVsync; + Monitor mVsyncEnabledLock; + base::Thread* mVsyncThread; + TimeDuration mVsyncRate; + bool mVsyncEnabled; + + HMONITOR mWaitVBlankMonitor; + RefPtr<IDXGIOutput> mWaitVBlankOutput; + bool mIsWindows8OrLater; + }; // end d3dvsyncdisplay + + D3DVsyncSource() { mPrimaryDisplay = new D3DVsyncDisplay(); } + + virtual Display& GetGlobalDisplay() override { return *mPrimaryDisplay; } + + private: + virtual ~D3DVsyncSource() = default; + RefPtr<D3DVsyncDisplay> mPrimaryDisplay; +}; // end D3DVsyncSource + +already_AddRefed<mozilla::gfx::VsyncSource> +gfxWindowsPlatform::CreateHardwareVsyncSource() { + MOZ_RELEASE_ASSERT(NS_IsMainThread(), "GFX: Not in main thread."); + + if (!DwmCompositionEnabled()) { + NS_WARNING("DWM not enabled, falling back to software vsync"); + return gfxPlatform::CreateHardwareVsyncSource(); + } + + RefPtr<VsyncSource> d3dVsyncSource = new D3DVsyncSource(); + return d3dVsyncSource.forget(); +} + +void gfxWindowsPlatform::GetAcceleratedCompositorBackends( + nsTArray<LayersBackend>& aBackends) { + if (gfxConfig::IsEnabled(Feature::OPENGL_COMPOSITING) && + StaticPrefs::layers_prefer_opengl_AtStartup()) { + aBackends.AppendElement(LayersBackend::LAYERS_OPENGL); + } + + if (gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + aBackends.AppendElement(LayersBackend::LAYERS_D3D11); + } +} + +void gfxWindowsPlatform::ImportGPUDeviceData( + const mozilla::gfx::GPUDeviceData& aData) { + MOZ_ASSERT(XRE_IsParentProcess()); + + gfxPlatform::ImportGPUDeviceData(aData); + + gfxConfig::ImportChange(Feature::D3D11_COMPOSITING, aData.d3d11Compositing()); + + DeviceManagerDx* dm = DeviceManagerDx::Get(); + if (gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + dm->ImportDeviceInfo(aData.gpuDevice().ref()); + } else { + // There should be no devices, so this just takes away the device status. + dm->ResetDevices(); + + // Make sure we disable D2D if content processes might use it. + FeatureState& d2d1 = gfxConfig::GetFeature(Feature::DIRECT2D); + if (d2d1.IsEnabled()) { + d2d1.SetFailed(FeatureStatus::Unavailable, + "Direct2D requires Direct3D 11 compositing", + "FEATURE_FAILURE_D2D_D3D11_COMP"_ns); + } + } + + // CanUseHardwareVideoDecoding depends on d3d11 state, so update + // the cached value now. + UpdateCanUseHardwareVideoDecoding(); + + // For completeness (and messaging in about:support). Content recomputes this + // on its own, and we won't use ANGLE in the UI process if we're using a GPU + // process. + UpdateANGLEConfig(); +} + +void gfxWindowsPlatform::ImportContentDeviceData( + const mozilla::gfx::ContentDeviceData& aData) { + MOZ_ASSERT(XRE_IsContentProcess()); + + gfxPlatform::ImportContentDeviceData(aData); + + const DevicePrefs& prefs = aData.prefs(); + gfxConfig::Inherit(Feature::D3D11_COMPOSITING, prefs.d3d11Compositing()); + gfxConfig::Inherit(Feature::DIRECT2D, prefs.useD2D1()); + + if (gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + DeviceManagerDx* dm = DeviceManagerDx::Get(); + dm->ImportDeviceInfo(aData.d3d11()); + } + + // aData->cmsOutputProfileData() will be read during color profile init, + // not as part of this import function +} + +void gfxWindowsPlatform::BuildContentDeviceData(ContentDeviceData* aOut) { + // Check for device resets before giving back new graphics information. + UpdateRenderMode(); + + gfxPlatform::BuildContentDeviceData(aOut); + + const FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING); + aOut->prefs().d3d11Compositing() = d3d11.GetValue(); + aOut->prefs().useD2D1() = gfxConfig::GetValue(Feature::DIRECT2D); + + if (d3d11.IsEnabled()) { + DeviceManagerDx* dm = DeviceManagerDx::Get(); + dm->ExportDeviceInfo(&aOut->d3d11()); + } + + aOut->cmsOutputProfileData() = + gfxPlatform::GetPlatform()->GetPlatformCMSOutputProfileData(); +} + +bool gfxWindowsPlatform::CheckVariationFontSupport() { + // Variation font support is only available on Fall Creators Update or later. + return IsWin10FallCreatorsUpdateOrLater(); +} + +void gfxWindowsPlatform::GetPlatformDisplayInfo( + mozilla::widget::InfoObject& aObj) { + HwStretchingSupport stretch; + DeviceManagerDx::Get()->CheckHardwareStretchingSupport(stretch); + + nsPrintfCString stretchValue( + "both=%u window-only=%u full-screen-only=%u none=%u error=%u", + stretch.mBoth, stretch.mWindowOnly, stretch.mFullScreenOnly, + stretch.mNone, stretch.mError); + aObj.DefineProperty("HardwareStretching", stretchValue.get()); + + ScaledResolutionSet scaled; + GetScaledResolutions(scaled); + if (scaled.IsEmpty()) { + return; + } + + aObj.DefineProperty("ScaledResolutionCount", scaled.Length()); + for (size_t i = 0; i < scaled.Length(); ++i) { + auto& s = scaled[i]; + nsPrintfCString name("ScaledResolution%zu", i); + nsPrintfCString value("source %dx%d, target %dx%d", s.first.width, + s.first.height, s.second.width, s.second.height); + aObj.DefineProperty(name.get(), value.get()); + } +} diff --git a/gfx/thebes/gfxWindowsPlatform.h b/gfx/thebes/gfxWindowsPlatform.h new file mode 100644 index 0000000000..79797d134d --- /dev/null +++ b/gfx/thebes/gfxWindowsPlatform.h @@ -0,0 +1,276 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_WINDOWS_PLATFORM_H +#define GFX_WINDOWS_PLATFORM_H + +/** + * XXX to get CAIRO_HAS_DWRITE_FONT + * and cairo_win32_scaled_font_select_font + */ +#include "cairo-win32.h" + +#include "gfxCrashReporterUtils.h" +#include "gfxFontUtils.h" +#include "gfxWindowsSurface.h" +#include "gfxFont.h" +#include "gfxDWriteFonts.h" +#include "gfxPlatform.h" +#include "gfxTelemetry.h" +#include "gfxTypes.h" +#include "mozilla/Attributes.h" +#include "mozilla/Atomics.h" +#include "nsTArray.h" +#include "nsDataHashtable.h" + +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" + +#include <windows.h> +#include <objbase.h> + +#include <dxgi.h> + +// This header is available in the June 2010 SDK and in the Win8 SDK +#include <d3dcommon.h> +// Win 8.0 SDK types we'll need when building using older sdks. +#if !defined(D3D_FEATURE_LEVEL_11_1) // defined in the 8.0 SDK only +# define D3D_FEATURE_LEVEL_11_1 static_cast<D3D_FEATURE_LEVEL>(0xb100) +# define D3D_FL9_1_REQ_TEXTURE2D_U_OR_V_DIMENSION 2048 +# define D3D_FL9_3_REQ_TEXTURE2D_U_OR_V_DIMENSION 4096 +#endif + +namespace mozilla { +namespace gfx { +class DrawTarget; +class FeatureState; +class DeviceManagerDx; +} // namespace gfx +namespace layers { +class ReadbackManagerD3D11; +} +} // namespace mozilla +struct IDirect3DDevice9; +struct ID3D11Device; +struct IDXGIAdapter1; + +/** + * Utility to get a Windows HDC from a Moz2D DrawTarget. If the DrawTarget is + * not backed by a HDC this will get the HDC for the screen device context + * instead. + */ +class MOZ_STACK_CLASS DCForMetrics final { + public: + explicit DCForMetrics(); + + ~DCForMetrics() { ReleaseDC(nullptr, mDC); } + + operator HDC() { return mDC; } + + private: + HDC mDC; +}; + +// ClearType parameters set by running ClearType tuner +struct ClearTypeParameterInfo { + ClearTypeParameterInfo() + : gamma(-1), + pixelStructure(-1), + clearTypeLevel(-1), + enhancedContrast(-1) {} + + nsString displayName; // typically just 'DISPLAY1' + int32_t gamma; + int32_t pixelStructure; + int32_t clearTypeLevel; + int32_t enhancedContrast; +}; + +class gfxWindowsPlatform final : public gfxPlatform { + friend class mozilla::gfx::DeviceManagerDx; + + public: + enum TextRenderingMode { + TEXT_RENDERING_NO_CLEARTYPE, + TEXT_RENDERING_NORMAL, + TEXT_RENDERING_GDI_CLASSIC, + TEXT_RENDERING_COUNT + }; + + gfxWindowsPlatform(); + virtual ~gfxWindowsPlatform(); + static gfxWindowsPlatform* GetPlatform() { + return (gfxWindowsPlatform*)gfxPlatform::GetPlatform(); + } + + void EnsureDevicesInitialized() override; + bool DevicesInitialized() override; + + gfxPlatformFontList* CreatePlatformFontList() override; + + virtual already_AddRefed<gfxASurface> CreateOffscreenSurface( + const IntSize& aSize, gfxImageFormat aFormat) override; + + enum RenderMode { + /* Use GDI and windows surfaces */ + RENDER_GDI = 0, + + /* Use 32bpp image surfaces and call StretchDIBits */ + RENDER_IMAGE_STRETCH32, + + /* Use 32bpp image surfaces, and do 32->24 conversion before calling + StretchDIBits */ + RENDER_IMAGE_STRETCH24, + + /* Use Direct2D rendering */ + RENDER_DIRECT2D, + + /* max */ + RENDER_MODE_MAX + }; + + bool IsDirect2DBackend(); + + /** + * Updates render mode with relation to the current preferences and + * available devices. + */ + void UpdateRenderMode(); + + /** + * Verifies a D2D device is present and working, will attempt to create one + * it is non-functional or non-existant. + * + * \param aAttemptForce Attempt to force D2D cairo device creation by using + * cairo device creation routines. + */ + void VerifyD2DDevice(bool aAttemptForce); + + void GetCommonFallbackFonts(uint32_t aCh, Script aRunScript, + eFontPresentation aPresentation, + nsTArray<const char*>& aFontList) override; + + bool CanUseHardwareVideoDecoding() override; + + void CompositorUpdated() override; + + bool DidRenderingDeviceReset( + DeviceResetReason* aResetReason = nullptr) override; + void SchedulePaintIfDeviceReset() override; + void CheckForContentOnlyDeviceReset(); + + mozilla::gfx::BackendType GetContentBackendFor( + mozilla::layers::LayersBackend aLayers) override; + + mozilla::gfx::BackendType GetPreferredCanvasBackend() override; + + static void GetDLLVersion(char16ptr_t aDLLPath, nsAString& aVersion); + + // returns ClearType tuning information for each display + static void GetCleartypeParams(nsTArray<ClearTypeParameterInfo>& aParams); + + void FontsPrefsChanged(const char* aPref) override; + + void SetupClearTypeParams(); + + static inline bool DWriteEnabled() { + return !!mozilla::gfx::Factory::GetDWriteFactory(); + } + inline DWRITE_MEASURING_MODE DWriteMeasuringMode() { return mMeasuringMode; } + + // Note that this may return nullptr, if we encountered an error initializing + // the default rendering params. + IDWriteRenderingParams* GetRenderingParams(TextRenderingMode aRenderMode) { + return mRenderingParams[aRenderMode]; + } + + public: + bool DwmCompositionEnabled(); + + mozilla::layers::ReadbackManagerD3D11* GetReadbackManager(); + + static bool IsOptimus(); + + bool SupportsApzWheelInput() const override { return true; } + + // Recreate devices as needed for a device reset. Returns true if a device + // reset occurred. + bool HandleDeviceReset(); + void UpdateBackendPrefs(); + + already_AddRefed<mozilla::gfx::VsyncSource> CreateHardwareVsyncSource() + override; + static mozilla::Atomic<size_t> sD3D11SharedTextures; + static mozilla::Atomic<size_t> sD3D9SharedTextures; + + bool SupportsPluginDirectBitmapDrawing() override { return true; } + + static void RecordContentDeviceFailure( + mozilla::gfx::TelemetryDeviceCode aDevice); + + static void InitMemoryReportersForGPUProcess(); + + protected: + bool AccelerateLayersByDefault() override { return true; } + void GetAcceleratedCompositorBackends( + nsTArray<mozilla::layers::LayersBackend>& aBackends) override; + nsTArray<uint8_t> GetPlatformCMSOutputProfileData() override; + void GetPlatformDisplayInfo(mozilla::widget::InfoObject& aObj) override; + + void ImportGPUDeviceData(const mozilla::gfx::GPUDeviceData& aData) override; + void ImportContentDeviceData( + const mozilla::gfx::ContentDeviceData& aData) override; + void BuildContentDeviceData(mozilla::gfx::ContentDeviceData* aOut) override; + + BackendPrefsData GetBackendPrefs() const override; + + bool CheckVariationFontSupport() override; + + protected: + RenderMode mRenderMode; + + private: + enum class DwmCompositionStatus : uint32_t { + Unknown, + Disabled, + Enabled, + }; + + void Init(); + void InitAcceleration() override; + void InitWebRenderConfig() override; + + void InitializeDevices(); + void InitializeD3D11(); + void InitializeD2D(); + bool InitDWriteSupport(); + void InitGPUProcessSupport(); + + void DisableD2D(mozilla::gfx::FeatureStatus aStatus, const char* aMessage, + const nsACString& aFailureId); + + void InitializeConfig(); + void InitializeD3D9Config(); + void InitializeD3D11Config(); + void InitializeD2DConfig(); + void InitializeDirectDrawConfig(); + void InitializeAdvancedLayersConfig(); + + void RecordStartupTelemetry(); + + RefPtr<IDWriteRenderingParams> mRenderingParams[TEXT_RENDERING_COUNT]; + DWRITE_MEASURING_MODE mMeasuringMode; + + RefPtr<mozilla::layers::ReadbackManagerD3D11> mD3D11ReadbackManager; + bool mInitializedDevices = false; + + mozilla::Atomic<DwmCompositionStatus, mozilla::ReleaseAcquire> + mDwmCompositionStatus; + + // Cached contents of the output color profile file + nsTArray<uint8_t> mCachedOutputColorProfile; +}; + +#endif /* GFX_WINDOWS_PLATFORM_H */ diff --git a/gfx/thebes/gfxWindowsSurface.cpp b/gfx/thebes/gfxWindowsSurface.cpp new file mode 100644 index 0000000000..b57d4fdb8c --- /dev/null +++ b/gfx/thebes/gfxWindowsSurface.cpp @@ -0,0 +1,159 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxWindowsSurface.h" +#include "gfxContext.h" +#include "gfxPlatform.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/HelpersCairo.h" +#include "mozilla/gfx/Logging.h" + +#include "cairo.h" +#include "cairo-win32.h" + +#include "nsString.h" + +gfxWindowsSurface::gfxWindowsSurface(HDC dc, uint32_t flags) + : mOwnsDC(false), mDC(dc), mWnd(nullptr) { + InitWithDC(flags); +} + +gfxWindowsSurface::gfxWindowsSurface(IDirect3DSurface9* surface, uint32_t flags) + : mOwnsDC(false), mDC(0), mWnd(nullptr) { + cairo_surface_t* surf = cairo_win32_surface_create_with_d3dsurface9(surface); + Init(surf); +} + +void gfxWindowsSurface::MakeInvalid(mozilla::gfx::IntSize& size) { + size = mozilla::gfx::IntSize(-1, -1); +} + +gfxWindowsSurface::gfxWindowsSurface(const mozilla::gfx::IntSize& realSize, + gfxImageFormat imageFormat) + : mOwnsDC(false), mWnd(nullptr) { + mozilla::gfx::IntSize size(realSize); + if (!mozilla::gfx::Factory::CheckSurfaceSize(size)) MakeInvalid(size); + + cairo_format_t cformat = GfxFormatToCairoFormat(imageFormat); + cairo_surface_t* surf = + cairo_win32_surface_create_with_dib(cformat, size.width, size.height); + + Init(surf); + + if (CairoStatus() == CAIRO_STATUS_SUCCESS) { + mDC = cairo_win32_surface_get_dc(CairoSurface()); + RecordMemoryUsed(size.width * size.height * 4 + sizeof(gfxWindowsSurface)); + } else { + mDC = nullptr; + } +} + +gfxWindowsSurface::gfxWindowsSurface(cairo_surface_t* csurf) + : mOwnsDC(false), mWnd(nullptr) { + if (cairo_surface_status(csurf) == 0) + mDC = cairo_win32_surface_get_dc(csurf); + else + mDC = nullptr; + + MOZ_ASSERT(cairo_surface_get_type(csurf) != + CAIRO_SURFACE_TYPE_WIN32_PRINTING); + + Init(csurf, true); +} + +void gfxWindowsSurface::InitWithDC(uint32_t flags) { + if (flags & FLAG_IS_TRANSPARENT) { + Init(cairo_win32_surface_create_with_alpha(mDC)); + } else { + Init(cairo_win32_surface_create(mDC)); + } +} + +already_AddRefed<gfxASurface> gfxWindowsSurface::CreateSimilarSurface( + gfxContentType aContent, const mozilla::gfx::IntSize& aSize) { + if (!mSurface || !mSurfaceValid) { + return nullptr; + } + + cairo_surface_t* surface; + if (GetContentType() == gfxContentType::COLOR_ALPHA) { + // When creating a similar surface to a transparent surface, ensure + // the new surface uses a DIB. cairo_surface_create_similar won't + // use a DIB for a gfxContentType::COLOR surface if this surface doesn't + // have a DIB (e.g. if we're a transparent window surface). But + // we need a DIB to perform well if the new surface is composited into + // a surface that's the result of + // create_similar(gfxContentType::COLOR_ALPHA) (e.g. a backbuffer for the + // window) --- that new surface *would* have a DIB. + gfxImageFormat gformat = + gfxPlatform::GetPlatform()->OptimalFormatForContent(aContent); + cairo_format_t cformat = GfxFormatToCairoFormat(gformat); + surface = + cairo_win32_surface_create_with_dib(cformat, aSize.width, aSize.height); + } else { + surface = cairo_surface_create_similar( + mSurface, (cairo_content_t)(int)aContent, aSize.width, aSize.height); + } + + if (cairo_surface_status(surface)) { + cairo_surface_destroy(surface); + return nullptr; + } + + RefPtr<gfxASurface> result = Wrap(surface, aSize); + cairo_surface_destroy(surface); + return result.forget(); +} + +gfxWindowsSurface::~gfxWindowsSurface() { + if (mOwnsDC) { + if (mWnd) + ::ReleaseDC(mWnd, mDC); + else + ::DeleteDC(mDC); + } +} + +HDC gfxWindowsSurface::GetDC() { + return cairo_win32_surface_get_dc(CairoSurface()); +} + +already_AddRefed<gfxImageSurface> gfxWindowsSurface::GetAsImageSurface() { + if (!mSurfaceValid) { + NS_WARNING( + "GetImageSurface on an invalid (null) surface; who's calling this " + "without checking for surface errors?"); + return nullptr; + } + + NS_ASSERTION( + CairoSurface() != nullptr, + "CairoSurface() shouldn't be nullptr when mSurfaceValid is TRUE!"); + + cairo_surface_t* isurf = cairo_win32_surface_get_image(CairoSurface()); + if (!isurf) return nullptr; + + RefPtr<gfxImageSurface> result = + gfxASurface::Wrap(isurf).downcast<gfxImageSurface>(); + result->SetOpaqueRect(GetOpaqueRect()); + + return result.forget(); +} + +const mozilla::gfx::IntSize gfxWindowsSurface::GetSize() const { + if (!mSurfaceValid) { + NS_WARNING( + "GetImageSurface on an invalid (null) surface; who's calling this " + "without checking for surface errors?"); + return mozilla::gfx::IntSize(-1, -1); + } + + NS_ASSERTION( + mSurface != nullptr, + "CairoSurface() shouldn't be nullptr when mSurfaceValid is TRUE!"); + + return mozilla::gfx::IntSize(cairo_win32_surface_get_width(mSurface), + cairo_win32_surface_get_height(mSurface)); +} diff --git a/gfx/thebes/gfxWindowsSurface.h b/gfx/thebes/gfxWindowsSurface.h new file mode 100644 index 0000000000..1194a5b170 --- /dev/null +++ b/gfx/thebes/gfxWindowsSurface.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_WINDOWSSURFACE_H +#define GFX_WINDOWSSURFACE_H + +#include "gfxASurface.h" +#include "gfxImageSurface.h" + +/* include windows.h for the HWND and HDC definitions that we need. */ +#include <windows.h> + +struct IDirect3DSurface9; + +/* undefine LoadImage because our code uses that name */ +#undef LoadImage + +class gfxContext; + +class gfxWindowsSurface : public gfxASurface { + public: + enum { FLAG_IS_TRANSPARENT = (1 << 2) }; + + explicit gfxWindowsSurface(HDC dc, uint32_t flags = 0); + + // Create from a shared d3d9surface + explicit gfxWindowsSurface(IDirect3DSurface9* surface, uint32_t flags = 0); + + // Create a DIB surface + explicit gfxWindowsSurface(const mozilla::gfx::IntSize& size, + gfxImageFormat imageFormat = + mozilla::gfx::SurfaceFormat::X8R8G8B8_UINT32); + + explicit gfxWindowsSurface(cairo_surface_t* csurf); + + virtual already_AddRefed<gfxASurface> CreateSimilarSurface( + gfxContentType aType, const mozilla::gfx::IntSize& aSize); + + void InitWithDC(uint32_t flags); + + virtual ~gfxWindowsSurface(); + + HDC GetDC(); + + already_AddRefed<gfxImageSurface> GetAsImageSurface(); + + const mozilla::gfx::IntSize GetSize() const; + + private: + void MakeInvalid(mozilla::gfx::IntSize& size); + + bool mOwnsDC; + + HDC mDC; + HWND mWnd; +}; + +#endif /* GFX_WINDOWSSURFACE_H */ diff --git a/gfx/thebes/gfxXlibNativeRenderer.cpp b/gfx/thebes/gfxXlibNativeRenderer.cpp new file mode 100644 index 0000000000..0bf34e359a --- /dev/null +++ b/gfx/thebes/gfxXlibNativeRenderer.cpp @@ -0,0 +1,588 @@ +/* -*- 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 "gfxXlibNativeRenderer.h" + +#include "gfxXlibSurface.h" +#include "gfxImageSurface.h" +#include "gfxContext.h" +#include "gfxPlatform.h" +#include "gfxAlphaRecovery.h" +#include "cairo-xlib.h" +#include "cairo-xlib-xrender.h" +#include "mozilla/gfx/BorrowedContext.h" +#include "mozilla/gfx/HelpersCairo.h" +#include "gfx2DGlue.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +#if 0 +# include <stdio.h> +# define NATIVE_DRAWING_NOTE(m) fprintf(stderr, m) +#else +# define NATIVE_DRAWING_NOTE(m) \ + do { \ + } while (0) +#endif + +/* We have four basic strategies available: + + 1) 'direct': If the target is an xlib surface, and other conditions are met, + we can pass the underlying drawable directly to the callback. + + 2) 'simple': If the drawing is opaque, or we can draw to a surface with an + alpha channel, then we can create a temporary xlib surface, pass its + underlying drawable to the callback, and composite the result using + cairo. + + 3) 'copy-background': If the drawing is not opaque but the target is + opaque, and we can draw to a surface with format such that pixel + conversion to and from the target format is exact, we can create a + temporary xlib surface, copy the background from the target, pass the + underlying drawable to the callback, and copy back to the target. + + This strategy is not used if the pixel format conversion is not exact, + because that would mean that drawing intended to be very transparent + messes with other content. + + The strategy is prefered over simple for non-opaque drawing and opaque + targets on the same screen as compositing without alpha is a simpler + operation. + + 4) 'alpha-extraction': create a temporary xlib surface, fill with black, + pass its underlying drawable to the callback, copy the results to a + cairo image surface, repeat with a white background, update the on-black + image alpha values by comparing the two images, then paint the on-black + image using cairo. + + Sure would be nice to have an X extension or GL to do this for us on the + server... +*/ + +static cairo_bool_t _convert_coord_to_int(double coord, int32_t* v) { + *v = (int32_t)coord; + /* XXX allow some tolerance here? */ + return *v == coord; +} + +static bool _get_rectangular_clip(cairo_t* cr, const IntRect& bounds, + bool* need_clip, IntRect* rectangles, + int max_rectangles, int* num_rectangles) { + cairo_rectangle_list_t* cliplist; + cairo_rectangle_t* clips; + int i; + bool retval = true; + + cliplist = cairo_copy_clip_rectangle_list(cr); + if (cliplist->status != CAIRO_STATUS_SUCCESS) { + retval = false; + NATIVE_DRAWING_NOTE("FALLBACK: non-rectangular clip"); + goto FINISH; + } + + /* the clip is always in surface backend coordinates (i.e. native backend + * coords) */ + clips = cliplist->rectangles; + + for (i = 0; i < cliplist->num_rectangles; ++i) { + IntRect rect; + if (!_convert_coord_to_int(clips[i].x, &rect.x) || + !_convert_coord_to_int(clips[i].y, &rect.y) || + !_convert_coord_to_int(clips[i].width, &rect.width) || + !_convert_coord_to_int(clips[i].height, &rect.height)) { + retval = false; + NATIVE_DRAWING_NOTE("FALLBACK: non-integer clip"); + goto FINISH; + } + + if (rect.IsEqualInterior(bounds)) { + /* the bounds are entirely inside the clip region so we don't need to + * clip. */ + *need_clip = false; + goto FINISH; + } + + NS_ASSERTION(bounds.Contains(rect), + "Was expecting to be clipped to bounds"); + + if (i >= max_rectangles) { + retval = false; + NATIVE_DRAWING_NOTE("FALLBACK: unsupported clip rectangle count"); + goto FINISH; + } + + rectangles[i] = rect; + } + + *need_clip = true; + *num_rectangles = cliplist->num_rectangles; + +FINISH: + cairo_rectangle_list_destroy(cliplist); + + return retval; +} + +#define MAX_STATIC_CLIP_RECTANGLES 50 + +/** + * Try the direct path. + * @return True if we took the direct path + */ +bool gfxXlibNativeRenderer::DrawDirect(DrawTarget* aDT, IntSize size, + uint32_t flags, Screen* screen, + Visual* visual) { + // We need to actually borrow the context because we want to read out the + // clip rectangles. + BorrowedCairoContext borrowed(aDT); + if (!borrowed.mCairo) { + return false; + } + + bool direct = DrawCairo(borrowed.mCairo, size, flags, screen, visual); + borrowed.Finish(); + + return direct; +} + +bool gfxXlibNativeRenderer::DrawCairo(cairo_t* cr, IntSize size, uint32_t flags, + Screen* screen, Visual* visual) { + /* Check that the target surface is an xlib surface. */ + cairo_surface_t* target = cairo_get_group_target(cr); + if (cairo_surface_get_type(target) != CAIRO_SURFACE_TYPE_XLIB) { + NATIVE_DRAWING_NOTE("FALLBACK: non-X surface"); + return false; + } + + cairo_matrix_t matrix; + cairo_get_matrix(cr, &matrix); + double device_offset_x, device_offset_y; + cairo_surface_get_device_offset(target, &device_offset_x, &device_offset_y); + + /* Draw() checked that the matrix contained only a very-close-to-integer + translation. Here (and in several other places and thebes) device + offsets are assumed to be integer. */ + NS_ASSERTION(int32_t(device_offset_x) == device_offset_x && + int32_t(device_offset_y) == device_offset_y, + "Expected integer device offsets"); + IntPoint offset(NS_lroundf(matrix.x0 + device_offset_x), + NS_lroundf(matrix.y0 + device_offset_y)); + + int max_rectangles = 0; + if (flags & DRAW_SUPPORTS_CLIP_RECT) { + max_rectangles = 1; + } + if (flags & DRAW_SUPPORTS_CLIP_LIST) { + max_rectangles = MAX_STATIC_CLIP_RECTANGLES; + } + + /* The client won't draw outside the surface so consider this when + analysing clip rectangles. */ + IntRect bounds(offset, size); + bounds.IntersectRect(bounds, + IntRect(0, 0, cairo_xlib_surface_get_width(target), + cairo_xlib_surface_get_height(target))); + + bool needs_clip = true; + IntRect rectangles[MAX_STATIC_CLIP_RECTANGLES]; + int rect_count = 0; + + /* Check that the clip is rectangular and aligned on unit boundaries. */ + /* Temporarily set the matrix for _get_rectangular_clip. It's basically + the identity matrix, but we must adjust for the fact that our + offset-rect is in device coordinates. */ + cairo_identity_matrix(cr); + cairo_translate(cr, -device_offset_x, -device_offset_y); + bool have_rectangular_clip = _get_rectangular_clip( + cr, bounds, &needs_clip, rectangles, max_rectangles, &rect_count); + cairo_set_matrix(cr, &matrix); + if (!have_rectangular_clip) return false; + + /* Stop now if everything is clipped out */ + if (needs_clip && rect_count == 0) return true; + + /* Check that the screen is supported. + Visuals belong to screens, so, if alternate visuals are not supported, + then alternate screens cannot be supported. */ + bool supports_alternate_visual = + (flags & DRAW_SUPPORTS_ALTERNATE_VISUAL) != 0; + bool supports_alternate_screen = + supports_alternate_visual && (flags & DRAW_SUPPORTS_ALTERNATE_SCREEN); + if (!supports_alternate_screen && + cairo_xlib_surface_get_screen(target) != screen) { + NATIVE_DRAWING_NOTE("FALLBACK: non-default screen"); + return false; + } + + /* Check that there is a visual */ + Visual* target_visual = cairo_xlib_surface_get_visual(target); + if (!target_visual) { + NATIVE_DRAWING_NOTE("FALLBACK: no Visual for surface"); + return false; + } + /* Check that the visual is supported */ + if (!supports_alternate_visual && target_visual != visual) { + // Only the format of the visual is important (not the GLX properties) + // for Xlib or XRender drawing. + XRenderPictFormat* target_format = + cairo_xlib_surface_get_xrender_format(target); + if (!target_format || + (target_format != + XRenderFindVisualFormat(DisplayOfScreen(screen), visual))) { + NATIVE_DRAWING_NOTE("FALLBACK: unsupported Visual"); + return false; + } + } + + /* we're good to go! */ + NATIVE_DRAWING_NOTE("TAKING FAST PATH\n"); + cairo_surface_flush(target); + nsresult rv = + DrawWithXlib(target, offset, rectangles, needs_clip ? rect_count : 0); + if (NS_SUCCEEDED(rv)) { + cairo_surface_mark_dirty(target); + return true; + } + return false; +} + +static bool VisualHasAlpha(Screen* screen, Visual* visual) { + // There may be some other visuals format with alpha but usually this is + // the only one we care about. + return visual->c_class == TrueColor && visual->bits_per_rgb == 8 && + visual->red_mask == 0xff0000 && visual->green_mask == 0xff00 && + visual->blue_mask == 0xff && + gfxXlibSurface::DepthOfVisual(screen, visual) == 32; +} + +// Returns whether pixel conversion between visual and format is exact (in +// both directions). +static bool FormatConversionIsExact(Screen* screen, Visual* visual, + XRenderPictFormat* format) { + if (!format || visual->c_class != TrueColor || + format->type != PictTypeDirect || + gfxXlibSurface::DepthOfVisual(screen, visual) != format->depth) + return false; + + XRenderPictFormat* visualFormat = + XRenderFindVisualFormat(DisplayOfScreen(screen), visual); + + if (visualFormat->type != PictTypeDirect) return false; + + const XRenderDirectFormat& a = visualFormat->direct; + const XRenderDirectFormat& b = format->direct; + return a.redMask == b.redMask && a.greenMask == b.greenMask && + a.blueMask == b.blueMask; +} + +// The 3 non-direct strategies described above. +// The surface format and strategy are inter-dependent. +enum DrawingMethod { eSimple, eCopyBackground, eAlphaExtraction }; + +static cairo_surface_t* CreateTempXlibSurface( + cairo_surface_t* cairoTarget, DrawTarget* drawTarget, IntSize size, + bool canDrawOverBackground, uint32_t flags, Screen* screen, Visual* visual, + DrawingMethod* method) { + NS_ASSERTION(cairoTarget || drawTarget, "Must have some type"); + + bool drawIsOpaque = (flags & gfxXlibNativeRenderer::DRAW_IS_OPAQUE) != 0; + bool supportsAlternateVisual = + (flags & gfxXlibNativeRenderer::DRAW_SUPPORTS_ALTERNATE_VISUAL) != 0; + bool supportsAlternateScreen = + supportsAlternateVisual && + (flags & gfxXlibNativeRenderer::DRAW_SUPPORTS_ALTERNATE_SCREEN); + + cairo_surface_type_t cairoTargetType = + cairoTarget ? cairo_surface_get_type(cairoTarget) + : (cairo_surface_type_t)0xFF; + + Screen* target_screen = cairoTargetType == CAIRO_SURFACE_TYPE_XLIB + ? cairo_xlib_surface_get_screen(cairoTarget) + : screen; + + // When the background has an alpha channel, we need to draw with an alpha + // channel anyway, so there is no need to copy the background. If + // doCopyBackground is set here, we'll also need to check below that the + // background can copied without any loss in format conversions. + bool doCopyBackground = + !drawIsOpaque && canDrawOverBackground && cairoTarget && + cairo_surface_get_content(cairoTarget) == CAIRO_CONTENT_COLOR; + + if (supportsAlternateScreen && screen != target_screen && drawIsOpaque) { + // Prefer a visual on the target screen. + // (If !drawIsOpaque, we'll need doCopyBackground or an alpha channel.) + visual = DefaultVisualOfScreen(target_screen); + screen = target_screen; + + } else if (doCopyBackground || (supportsAlternateVisual && drawIsOpaque)) { + // Analyse the pixel formats either to check whether we can + // doCopyBackground or to see if we can find a better visual for + // opaque drawing. + Visual* target_visual = nullptr; + XRenderPictFormat* target_format = nullptr; + if (cairoTargetType == CAIRO_SURFACE_TYPE_XLIB) { + target_visual = cairo_xlib_surface_get_visual(cairoTarget); + target_format = cairo_xlib_surface_get_xrender_format(cairoTarget); + } else if (cairoTargetType == CAIRO_SURFACE_TYPE_IMAGE || drawTarget) { + gfxImageFormat imageFormat = + drawTarget ? SurfaceFormatToImageFormat(drawTarget->GetFormat()) + : CairoFormatToGfxFormat( + cairo_image_surface_get_format(cairoTarget)); + target_visual = gfxXlibSurface::FindVisual(screen, imageFormat); + Display* dpy = DisplayOfScreen(screen); + if (target_visual) { + target_format = XRenderFindVisualFormat(dpy, target_visual); + } else { + target_format = gfxXlibSurface::FindRenderFormat(dpy, imageFormat); + } + } + + if (supportsAlternateVisual && + (supportsAlternateScreen || screen == target_screen)) { + if (target_visual) { + visual = target_visual; + screen = target_screen; + } + } + // Could try harder to match formats across screens for background + // copying when !supportsAlternateScreen, if we cared. Preferably + // we'll find a visual below with an alpha channel anyway; if so, the + // background won't need to be copied. + + if (doCopyBackground && visual != target_visual && + !FormatConversionIsExact(screen, visual, target_format)) { + doCopyBackground = false; + } + } + + if (supportsAlternateVisual && !drawIsOpaque && + (screen != target_screen || + !(doCopyBackground || VisualHasAlpha(screen, visual)))) { + // Try to find a visual with an alpha channel. + Screen* visualScreen = supportsAlternateScreen ? target_screen : screen; + Visual* argbVisual = gfxXlibSurface::FindVisual( + visualScreen, SurfaceFormat::A8R8G8B8_UINT32); + if (argbVisual) { + visual = argbVisual; + screen = visualScreen; + } else if (!doCopyBackground && + gfxXlibSurface::DepthOfVisual(screen, visual) != 24) { + // Will need to do alpha extraction; prefer a 24-bit visual. + // No advantage in using the target screen. + Visual* rgb24Visual = + gfxXlibSurface::FindVisual(screen, SurfaceFormat::X8R8G8B8_UINT32); + if (rgb24Visual) { + visual = rgb24Visual; + } + } + } + + Drawable drawable = + (screen == target_screen && cairoTargetType == CAIRO_SURFACE_TYPE_XLIB) + ? cairo_xlib_surface_get_drawable(cairoTarget) + : RootWindowOfScreen(screen); + + cairo_surface_t* surface = gfxXlibSurface::CreateCairoSurface( + screen, visual, IntSize(size.width, size.height), drawable); + if (!surface) { + return nullptr; + } + + if (drawIsOpaque || + cairo_surface_get_content(surface) == CAIRO_CONTENT_COLOR_ALPHA) { + NATIVE_DRAWING_NOTE(drawIsOpaque ? ", SIMPLE OPAQUE\n" + : ", SIMPLE WITH ALPHA"); + *method = eSimple; + } else if (doCopyBackground) { + NATIVE_DRAWING_NOTE(", COPY BACKGROUND\n"); + *method = eCopyBackground; + } else { + NATIVE_DRAWING_NOTE(", SLOW ALPHA EXTRACTION\n"); + *method = eAlphaExtraction; + } + + return surface; +} + +bool gfxXlibNativeRenderer::DrawOntoTempSurface( + cairo_surface_t* tempXlibSurface, IntPoint offset) { + cairo_surface_flush(tempXlibSurface); + /* no clipping is needed because the callback can't draw outside the native + surface anyway */ + nsresult rv = DrawWithXlib(tempXlibSurface, offset, nullptr, 0); + cairo_surface_mark_dirty(tempXlibSurface); + return NS_SUCCEEDED(rv); +} + +static already_AddRefed<gfxImageSurface> CopyXlibSurfaceToImage( + cairo_surface_t* tempXlibSurface, IntSize size, gfxImageFormat format) { + RefPtr<gfxImageSurface> result = new gfxImageSurface(size, format); + + cairo_t* copyCtx = cairo_create(result->CairoSurface()); + cairo_set_source_surface(copyCtx, tempXlibSurface, 0, 0); + cairo_set_operator(copyCtx, CAIRO_OPERATOR_SOURCE); + cairo_paint(copyCtx); + cairo_destroy(copyCtx); + + return result.forget(); +} + +void gfxXlibNativeRenderer::Draw(gfxContext* ctx, IntSize size, uint32_t flags, + Screen* screen, Visual* visual) { + Matrix matrix = ctx->CurrentMatrix(); + + // We can only draw direct or onto a copied background if pixels align and + // native drawing is compatible with the current operator. (The matrix is + // actually also pixel-exact for flips and right-angle rotations, which + // would permit copying the background but not drawing direct.) + bool matrixIsIntegerTranslation = !matrix.HasNonIntegerTranslation(); + bool canDrawOverBackground = + matrixIsIntegerTranslation && ctx->CurrentOp() == CompositionOp::OP_OVER; + + // The padding of 0.5 for non-pixel-exact transformations used here is + // the same as what _cairo_pattern_analyze_filter uses. + const gfxFloat filterRadius = 0.5; + gfxRect affectedRect(0.0, 0.0, size.width, size.height); + if (!matrixIsIntegerTranslation) { + // The filter footprint means that the affected rectangle is a + // little larger than the drawingRect; + affectedRect.Inflate(filterRadius); + + NATIVE_DRAWING_NOTE("FALLBACK: matrix not integer translation"); + } else if (!canDrawOverBackground) { + NATIVE_DRAWING_NOTE("FALLBACK: unsupported operator"); + } + + DrawTarget* drawTarget = ctx->GetDrawTarget(); + if (!drawTarget) { + gfxCriticalError() << "gfxContext without a DrawTarget"; + return; + } + + // Clipping to the region affected by drawing allows us to consider only + // the portions of the clip region that will be affected by drawing. + gfxRect clipExtents; + { + gfxContextAutoSaveRestore autoSR(ctx); + ctx->Clip(affectedRect); + + clipExtents = ctx->GetClipExtents(); + if (clipExtents.IsEmpty()) { + return; // nothing to do + } + if (canDrawOverBackground && + DrawDirect(drawTarget, size, flags, screen, visual)) { + return; + } + } + + IntRect drawingRect(IntPoint(0, 0), size); + // Drawing need only be performed within the clip extents + // (and padding for the filter). + if (!matrixIsIntegerTranslation) { + // The source surface may need to be a little larger than the clip + // extents due to the filter footprint. + clipExtents.Inflate(filterRadius); + } + clipExtents.RoundOut(); + + IntRect intExtents(int32_t(clipExtents.X()), int32_t(clipExtents.Y()), + int32_t(clipExtents.Width()), + int32_t(clipExtents.Height())); + drawingRect.IntersectRect(drawingRect, intExtents); + + gfxPoint offset(drawingRect.x, drawingRect.y); + + DrawingMethod method; + Matrix dtTransform = drawTarget->GetTransform(); + gfxPoint deviceTranslation = gfxPoint(dtTransform._31, dtTransform._32); + cairo_t* cairo = static_cast<cairo_t*>( + drawTarget->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT)); + cairo_surface_t* cairoTarget = + cairo ? cairo_get_group_target(cairo) : nullptr; + cairo_surface_t* tempXlibSurface = CreateTempXlibSurface( + cairoTarget, drawTarget, size, canDrawOverBackground, flags, screen, + visual, &method); + if (!tempXlibSurface) { + return; + } + + bool drawIsOpaque = (flags & DRAW_IS_OPAQUE) != 0; + if (!drawIsOpaque) { + cairo_t* tmpCtx = cairo_create(tempXlibSurface); + if (method == eCopyBackground) { + NS_ASSERTION(cairoTarget, + "eCopyBackground only used when there's a cairoTarget"); + cairo_set_operator(tmpCtx, CAIRO_OPERATOR_SOURCE); + gfxPoint pt = -(offset + deviceTranslation); + cairo_set_source_surface(tmpCtx, cairoTarget, pt.x, pt.y); + // The copy from the tempXlibSurface to the target context should + // use operator SOURCE, but that would need a mask to bound the + // operation. Here we only copy opaque backgrounds so operator + // OVER will behave like SOURCE masked by the surface. + NS_ASSERTION( + cairo_surface_get_content(tempXlibSurface) == CAIRO_CONTENT_COLOR, + "Don't copy background with a transparent surface"); + } else { + cairo_set_operator(tmpCtx, CAIRO_OPERATOR_CLEAR); + } + cairo_paint(tmpCtx); + cairo_destroy(tmpCtx); + } + + if (!DrawOntoTempSurface(tempXlibSurface, -drawingRect.TopLeft())) { + cairo_surface_destroy(tempXlibSurface); + return; + } + + SurfaceFormat moz2DFormat = + cairo_surface_get_content(tempXlibSurface) == CAIRO_CONTENT_COLOR + ? SurfaceFormat::B8G8R8A8 + : SurfaceFormat::B8G8R8X8; + if (method != eAlphaExtraction) { + RefPtr<SourceSurface> sourceSurface = + Factory::CreateSourceSurfaceForCairoSurface(tempXlibSurface, size, + moz2DFormat); + if (sourceSurface) { + drawTarget->DrawSurface(sourceSurface, + Rect(offset.x, offset.y, size.width, size.height), + Rect(0, 0, size.width, size.height)); + } + cairo_surface_destroy(tempXlibSurface); + return; + } + + RefPtr<gfxImageSurface> blackImage = CopyXlibSurfaceToImage( + tempXlibSurface, size, SurfaceFormat::A8R8G8B8_UINT32); + + cairo_t* tmpCtx = cairo_create(tempXlibSurface); + cairo_set_source_rgba(tmpCtx, 1.0, 1.0, 1.0, 1.0); + cairo_set_operator(tmpCtx, CAIRO_OPERATOR_SOURCE); + cairo_paint(tmpCtx); + cairo_destroy(tmpCtx); + DrawOntoTempSurface(tempXlibSurface, -drawingRect.TopLeft()); + RefPtr<gfxImageSurface> whiteImage = CopyXlibSurfaceToImage( + tempXlibSurface, size, SurfaceFormat::X8R8G8B8_UINT32); + + if (blackImage->CairoStatus() == CAIRO_STATUS_SUCCESS && + whiteImage->CairoStatus() == CAIRO_STATUS_SUCCESS) { + if (!gfxAlphaRecovery::RecoverAlpha(blackImage, whiteImage)) { + cairo_surface_destroy(tempXlibSurface); + return; + } + + gfxASurface* paintSurface = blackImage; + RefPtr<SourceSurface> sourceSurface = + Factory::CreateSourceSurfaceForCairoSurface( + paintSurface->CairoSurface(), size, moz2DFormat); + if (sourceSurface) { + drawTarget->DrawSurface(sourceSurface, + Rect(offset.x, offset.y, size.width, size.height), + Rect(0, 0, size.width, size.height)); + } + } + cairo_surface_destroy(tempXlibSurface); +} diff --git a/gfx/thebes/gfxXlibNativeRenderer.h b/gfx/thebes/gfxXlibNativeRenderer.h new file mode 100644 index 0000000000..3845627c80 --- /dev/null +++ b/gfx/thebes/gfxXlibNativeRenderer.h @@ -0,0 +1,105 @@ +/* -*- 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 GFXXLIBNATIVERENDER_H_ +#define GFXXLIBNATIVERENDER_H_ + +#include "nsPoint.h" +#include "nsRect.h" +#include "mozilla/gfx/Rect.h" +#include "mozilla/gfx/Point.h" +#include <X11/Xlib.h> + +namespace mozilla { +namespace gfx { +class DrawTarget; +} +} // namespace mozilla + +class gfxASurface; +class gfxContext; +typedef struct _cairo cairo_t; +typedef struct _cairo_surface cairo_surface_t; + +/** + * This class lets us take code that draws into an X drawable and lets us + * use it to draw into any Thebes context. The user should subclass this class, + * override DrawWithXib, and then call Draw(). The drawing will be subjected + * to all Thebes transformations, clipping etc. + */ +class gfxXlibNativeRenderer { + public: + /** + * Perform the native drawing. + * @param surface the cairo_surface_t for drawing. Must be a + * cairo_xlib_surface_t. The extents of this surface do not necessarily cover + * the entire rectangle with size provided to Draw(). + * @param offset draw at this offset into the given drawable + * @param clipRects an array of rectangles; clip to the union. + * Any rectangles provided will be contained by the + * rectangle with size provided to Draw and by the + * surface extents. + * @param numClipRects the number of rects in the array, or zero if + * no clipping is required. + */ + virtual nsresult DrawWithXlib(cairo_surface_t* surface, + mozilla::gfx::IntPoint offset, + mozilla::gfx::IntRect* clipRects, + uint32_t numClipRects) = 0; + + enum { + // If set, then Draw() is opaque, i.e., every pixel in the intersection + // of the clipRect and (offset.x,offset.y,bounds.width,bounds.height) + // will be set and there is no dependence on what the existing pixels + // in the drawable are set to. + DRAW_IS_OPAQUE = 0x01, + // If set, then numClipRects can be zero or one + DRAW_SUPPORTS_CLIP_RECT = 0x04, + // If set, then numClipRects can be any value. If neither this + // nor CLIP_RECT are set, then numClipRects will be zero + DRAW_SUPPORTS_CLIP_LIST = 0x08, + // If set, then the surface in the callback may have any visual; + // otherwise the pixels will have the same format as the visual + // passed to 'Draw'. + DRAW_SUPPORTS_ALTERNATE_VISUAL = 0x10, + // If set, then the Screen 'screen' in the callback can be different + // from the default Screen of the display passed to 'Draw' and can be + // on a different display. + DRAW_SUPPORTS_ALTERNATE_SCREEN = 0x20 + }; + + /** + * @param flags see above + * @param size the size of the rectangle being drawn; + * the caller guarantees that drawing will not extend beyond the rectangle + * (0,0,size.width,size.height). + * @param screen a Screen to use for the drawing if ctx doesn't have one. + * @param visual a Visual to use for the drawing if ctx doesn't have one. + * @param result if non-null, we will try to capture a copy of the + * rendered image into a surface similar to the surface of ctx; if + * successful, a pointer to the new gfxASurface is stored in *resultSurface, + * otherwise *resultSurface is set to nullptr. + */ + void Draw(gfxContext* ctx, mozilla::gfx::IntSize size, uint32_t flags, + Screen* screen, Visual* visual); + + private: + bool DrawDirect(mozilla::gfx::DrawTarget* aDT, mozilla::gfx::IntSize bounds, + uint32_t flags, Screen* screen, Visual* visual); + + bool DrawCairo(cairo_t* cr, mozilla::gfx::IntSize size, uint32_t flags, + Screen* screen, Visual* visual); + + void DrawFallback(mozilla::gfx::DrawTarget* dt, gfxContext* ctx, + gfxASurface* aSurface, mozilla::gfx::IntSize& size, + mozilla::gfx::IntRect& drawingRect, + bool canDrawOverBackground, uint32_t flags, Screen* screen, + Visual* visual); + + bool DrawOntoTempSurface(cairo_surface_t* tempXlibSurface, + mozilla::gfx::IntPoint offset); +}; + +#endif /*GFXXLIBNATIVERENDER_H_*/ diff --git a/gfx/thebes/gfxXlibSurface.cpp b/gfx/thebes/gfxXlibSurface.cpp new file mode 100644 index 0000000000..de6eb0779e --- /dev/null +++ b/gfx/thebes/gfxXlibSurface.cpp @@ -0,0 +1,542 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxXlibSurface.h" + +#include "cairo.h" +#include "cairo-xlib.h" +#include "cairo-xlib-xrender.h" +#include <X11/Xlibint.h> /* For XESetCloseDisplay */ +#undef max // Xlibint.h defines this and it breaks std::max +#undef min // Xlibint.h defines this and it breaks std::min +#undef Data + +#include "nsTArray.h" +#include "nsAlgorithm.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/Preferences.h" +#include <algorithm> +#include "mozilla/CheckedInt.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +gfxXlibSurface::gfxXlibSurface(Display* dpy, Drawable drawable, Visual* visual) + : mPixmapTaken(false), + mDisplay(dpy), + mDrawable(drawable), + mGLXPixmap(X11None) { + const gfx::IntSize size = DoSizeQuery(); + cairo_surface_t* surf = + cairo_xlib_surface_create(dpy, drawable, visual, size.width, size.height); + Init(surf); +} + +gfxXlibSurface::gfxXlibSurface(Display* dpy, Drawable drawable, Visual* visual, + const gfx::IntSize& size) + : mPixmapTaken(false), + mDisplay(dpy), + mDrawable(drawable), + mGLXPixmap(X11None) { + NS_ASSERTION(Factory::CheckSurfaceSize(size, XLIB_IMAGE_SIDE_SIZE_LIMIT), + "Bad size"); + + cairo_surface_t* surf = + cairo_xlib_surface_create(dpy, drawable, visual, size.width, size.height); + Init(surf); +} + +gfxXlibSurface::gfxXlibSurface(Screen* screen, Drawable drawable, + XRenderPictFormat* format, + const gfx::IntSize& size) + : mPixmapTaken(false), + mDisplay(DisplayOfScreen(screen)), + mDrawable(drawable), + mGLXPixmap(X11None) { + NS_ASSERTION(Factory::CheckSurfaceSize(size, XLIB_IMAGE_SIDE_SIZE_LIMIT), + "Bad Size"); + + cairo_surface_t* surf = cairo_xlib_surface_create_with_xrender_format( + mDisplay, drawable, screen, format, size.width, size.height); + Init(surf); +} + +gfxXlibSurface::gfxXlibSurface(cairo_surface_t* csurf) + : mPixmapTaken(false), mGLXPixmap(X11None) { + MOZ_ASSERT(cairo_surface_status(csurf) == 0, + "Not expecting an error surface"); + + mDrawable = cairo_xlib_surface_get_drawable(csurf); + mDisplay = cairo_xlib_surface_get_display(csurf); + + Init(csurf, true); +} + +gfxXlibSurface::~gfxXlibSurface() { + // gfxASurface's destructor calls RecordMemoryFreed(). + if (mPixmapTaken) { + if (mGLXPixmap) { + gl::sGLXLibrary.DestroyPixmap(mDisplay, mGLXPixmap); + } + XFreePixmap(mDisplay, mDrawable); + } +} + +static Drawable CreatePixmap(Screen* screen, const gfx::IntSize& size, + unsigned int depth, Drawable relatedDrawable) { + if (!Factory::CheckSurfaceSize(size, XLIB_IMAGE_SIDE_SIZE_LIMIT)) + return X11None; + + if (relatedDrawable == X11None) { + relatedDrawable = RootWindowOfScreen(screen); + } + Display* dpy = DisplayOfScreen(screen); + // X gives us a fatal error if we try to create a pixmap of width + // or height 0 + return XCreatePixmap(dpy, relatedDrawable, std::max(1, size.width), + std::max(1, size.height), depth); +} + +void gfxXlibSurface::TakePixmap() { + NS_ASSERTION(!mPixmapTaken, "I already own the Pixmap!"); + mPixmapTaken = true; + + // The bit depth returned from Cairo is technically int, but this is + // the last place we'd be worried about that scenario. + unsigned int bitDepth = cairo_xlib_surface_get_depth(CairoSurface()); + MOZ_ASSERT((bitDepth % 8) == 0, "Memory used not recorded correctly"); + + // Divide by 8 because surface_get_depth gives us the number of *bits* per + // pixel. + gfx::IntSize size = GetSize(); + CheckedInt32 totalBytes = + CheckedInt32(size.width) * CheckedInt32(size.height) * (bitDepth / 8); + + // Don't do anything in the "else" case. We could add INT32_MAX, but that + // would overflow the memory used counter. It would also mean we tried for + // a 2G image. For now, we'll just assert, + MOZ_ASSERT(totalBytes.isValid(), "Did not expect to exceed 2Gb image"); + if (totalBytes.isValid()) { + RecordMemoryUsed(totalBytes.value()); + } +} + +Drawable gfxXlibSurface::ReleasePixmap() { + NS_ASSERTION(mPixmapTaken, "I don't own the Pixmap!"); + mPixmapTaken = false; + RecordMemoryFreed(); + return mDrawable; +} + +static cairo_user_data_key_t gDestroyPixmapKey; + +struct DestroyPixmapClosure { + DestroyPixmapClosure(Drawable d, Screen* s) : mPixmap(d), mScreen(s) {} + Drawable mPixmap; + Screen* mScreen; +}; + +static void DestroyPixmap(void* data) { + DestroyPixmapClosure* closure = static_cast<DestroyPixmapClosure*>(data); + XFreePixmap(DisplayOfScreen(closure->mScreen), closure->mPixmap); + delete closure; +} + +/* static */ +cairo_surface_t* gfxXlibSurface::CreateCairoSurface(Screen* screen, + Visual* visual, + const gfx::IntSize& size, + Drawable relatedDrawable) { + Drawable drawable = CreatePixmap(screen, size, DepthOfVisual(screen, visual), + relatedDrawable); + if (!drawable) return nullptr; + + cairo_surface_t* surface = cairo_xlib_surface_create( + DisplayOfScreen(screen), drawable, visual, size.width, size.height); + if (cairo_surface_status(surface)) { + cairo_surface_destroy(surface); + XFreePixmap(DisplayOfScreen(screen), drawable); + return nullptr; + } + + DestroyPixmapClosure* closure = new DestroyPixmapClosure(drawable, screen); + cairo_surface_set_user_data(surface, &gDestroyPixmapKey, closure, + DestroyPixmap); + return surface; +} + +/* static */ +already_AddRefed<gfxXlibSurface> gfxXlibSurface::Create( + Screen* screen, Visual* visual, const gfx::IntSize& size, + Drawable relatedDrawable) { + Drawable drawable = CreatePixmap(screen, size, DepthOfVisual(screen, visual), + relatedDrawable); + if (!drawable) return nullptr; + + RefPtr<gfxXlibSurface> result = + new gfxXlibSurface(DisplayOfScreen(screen), drawable, visual, size); + result->TakePixmap(); + + if (result->CairoStatus() != 0) return nullptr; + + return result.forget(); +} + +/* static */ +already_AddRefed<gfxXlibSurface> gfxXlibSurface::Create( + Screen* screen, XRenderPictFormat* format, const gfx::IntSize& size, + Drawable relatedDrawable) { + Drawable drawable = + CreatePixmap(screen, size, format->depth, relatedDrawable); + if (!drawable) return nullptr; + + RefPtr<gfxXlibSurface> result = + new gfxXlibSurface(screen, drawable, format, size); + result->TakePixmap(); + + if (result->CairoStatus() != 0) return nullptr; + + return result.forget(); +} + +static bool GetForce24bppPref() { + return Preferences::GetBool("mozilla.widget.force-24bpp", false); +} + +already_AddRefed<gfxASurface> gfxXlibSurface::CreateSimilarSurface( + gfxContentType aContent, const gfx::IntSize& aSize) { + if (!mSurface || !mSurfaceValid) { + return nullptr; + } + + if (aContent == gfxContentType::COLOR) { + // cairo_surface_create_similar will use a matching visual if it can. + // However, systems with 16-bit or indexed default visuals may benefit + // from rendering with 24-bit formats. + static bool force24bpp = GetForce24bppPref(); + if (force24bpp && cairo_xlib_surface_get_depth(CairoSurface()) != 24) { + XRenderPictFormat* format = + XRenderFindStandardFormat(mDisplay, PictStandardRGB24); + if (format) { + // Cairo only performs simple self-copies as desired if it + // knows that this is a Pixmap surface. It only knows that + // surfaces are pixmap surfaces if it creates the Pixmap + // itself, so we use cairo_surface_create_similar with a + // temporary reference surface to indicate the format. + Screen* screen = cairo_xlib_surface_get_screen(CairoSurface()); + RefPtr<gfxXlibSurface> depth24reference = gfxXlibSurface::Create( + screen, format, gfx::IntSize(1, 1), mDrawable); + if (depth24reference) + return depth24reference->gfxASurface::CreateSimilarSurface(aContent, + aSize); + } + } + } + + return gfxASurface::CreateSimilarSurface(aContent, aSize); +} + +void gfxXlibSurface::Finish() { + if (mPixmapTaken && mGLXPixmap) { + gl::sGLXLibrary.DestroyPixmap(mDisplay, mGLXPixmap); + mGLXPixmap = X11None; + } + gfxASurface::Finish(); +} + +const gfx::IntSize gfxXlibSurface::GetSize() const { + if (!mSurfaceValid) return gfx::IntSize(0, 0); + + return gfx::IntSize(cairo_xlib_surface_get_width(mSurface), + cairo_xlib_surface_get_height(mSurface)); +} + +const gfx::IntSize gfxXlibSurface::DoSizeQuery() { + // figure out width/height/depth + Window root_ignore; + int x_ignore, y_ignore; + unsigned int bwidth_ignore, width, height, depth; + + XGetGeometry(mDisplay, mDrawable, &root_ignore, &x_ignore, &y_ignore, &width, + &height, &bwidth_ignore, &depth); + + return gfx::IntSize(width, height); +} + +class DisplayTable { + public: + static bool GetColormapAndVisual(Screen* screen, XRenderPictFormat* format, + Visual* visual, Colormap* colormap, + Visual** visualForColormap); + + private: + struct ColormapEntry { + XRenderPictFormat* mFormat; + // The Screen is needed here because colormaps (and their visuals) may + // only be used on one Screen, but XRenderPictFormats are not unique + // to any one Screen. + Screen* mScreen; + Visual* mVisual; + Colormap mColormap; + }; + + class DisplayInfo { + public: + explicit DisplayInfo(Display* display) : mDisplay(display) {} + Display* mDisplay; + nsTArray<ColormapEntry> mColormapEntries; + }; + + // Comparator for finding the DisplayInfo + class FindDisplay { + public: + bool Equals(const DisplayInfo& info, const Display* display) const { + return info.mDisplay == display; + } + }; + + static int DisplayClosing(Display* display, XExtCodes* codes); + + nsTArray<DisplayInfo> mDisplays; + static DisplayTable* sDisplayTable; +}; + +DisplayTable* DisplayTable::sDisplayTable; + +// Pixmaps don't have a particular associated visual but the pixel values are +// interpreted according to a visual/colormap pairs. +// +// cairo is designed for surfaces with either TrueColor visuals or the +// default visual (which may not be true color). TrueColor visuals don't +// really need a colormap because the visual indicates the pixel format, +// and cairo uses the default visual with the default colormap, so cairo +// surfaces don't need an explicit colormap. +// +// However, some toolkits (e.g. GDK) need a colormap even with TrueColor +// visuals. We can create a colormap for these visuals, but it will use about +// 20kB of memory in the server, so we use the default colormap when +// suitable and share colormaps between surfaces. Another reason for +// minimizing colormap turnover is that the plugin process must leak resources +// for each new colormap id when using older GDK libraries (bug 569775). +// +// Only the format of the pixels is important for rendering to Pixmaps, so if +// the format of a visual matches that of the surface, then that visual can be +// used for rendering to the surface. Multiple visuals can match the same +// format (but have different GLX properties), so the visual returned may +// differ from the visual passed in. Colormaps are tied to a visual, so +// should only be used with their visual. + +/* static */ +bool DisplayTable::GetColormapAndVisual(Screen* aScreen, + XRenderPictFormat* aFormat, + Visual* aVisual, Colormap* aColormap, + Visual** aVisualForColormap) + +{ + Display* display = DisplayOfScreen(aScreen); + + // Use the default colormap if the default visual matches. + Visual* defaultVisual = DefaultVisualOfScreen(aScreen); + if (aVisual == defaultVisual || + (aFormat && aFormat == XRenderFindVisualFormat(display, defaultVisual))) { + *aColormap = DefaultColormapOfScreen(aScreen); + *aVisualForColormap = defaultVisual; + return true; + } + + // Only supporting TrueColor non-default visuals + if (!aVisual || aVisual->c_class != TrueColor) return false; + + if (!sDisplayTable) { + sDisplayTable = new DisplayTable(); + } + + nsTArray<DisplayInfo>* displays = &sDisplayTable->mDisplays; + size_t d = displays->IndexOf(display, 0, FindDisplay()); + + if (d == displays->NoIndex) { + d = displays->Length(); + // Register for notification of display closing, when this info + // becomes invalid. + XExtCodes* codes = XAddExtension(display); + if (!codes) return false; + + XESetCloseDisplay(display, codes->extension, DisplayClosing); + // Add a new DisplayInfo. + displays->AppendElement(display); + } + + nsTArray<ColormapEntry>* entries = &displays->ElementAt(d).mColormapEntries; + + // Only a small number of formats are expected to be used, so just do a + // simple linear search. + for (uint32_t i = 0; i < entries->Length(); ++i) { + const ColormapEntry& entry = entries->ElementAt(i); + // Only the format and screen need to match. (The visual may differ.) + // If there is no format (e.g. no RENDER extension) then just compare + // the visual. + if ((aFormat && entry.mFormat == aFormat && entry.mScreen == aScreen) || + aVisual == entry.mVisual) { + *aColormap = entry.mColormap; + *aVisualForColormap = entry.mVisual; + return true; + } + } + + // No existing entry. Create a colormap and add an entry. + Colormap colormap = + XCreateColormap(display, RootWindowOfScreen(aScreen), aVisual, AllocNone); + ColormapEntry* newEntry = entries->AppendElement(); + newEntry->mFormat = aFormat; + newEntry->mScreen = aScreen; + newEntry->mVisual = aVisual; + newEntry->mColormap = colormap; + + *aColormap = colormap; + *aVisualForColormap = aVisual; + return true; +} + +/* static */ +int DisplayTable::DisplayClosing(Display* display, XExtCodes* codes) { + // No need to free the colormaps explicitly as they will be released when + // the connection is closed. + sDisplayTable->mDisplays.RemoveElement(display, FindDisplay()); + if (sDisplayTable->mDisplays.Length() == 0) { + delete sDisplayTable; + sDisplayTable = nullptr; + } + return 0; +} + +/* static */ +bool gfxXlibSurface::GetColormapAndVisual(cairo_surface_t* aXlibSurface, + Colormap* aColormap, + Visual** aVisual) { + XRenderPictFormat* format = + cairo_xlib_surface_get_xrender_format(aXlibSurface); + Screen* screen = cairo_xlib_surface_get_screen(aXlibSurface); + Visual* visual = cairo_xlib_surface_get_visual(aXlibSurface); + + return DisplayTable::GetColormapAndVisual(screen, format, visual, aColormap, + aVisual); +} + +bool gfxXlibSurface::GetColormapAndVisual(Colormap* aColormap, + Visual** aVisual) { + if (!mSurfaceValid) return false; + + return GetColormapAndVisual(CairoSurface(), aColormap, aVisual); +} + +/* static */ +int gfxXlibSurface::DepthOfVisual(const Screen* screen, const Visual* visual) { + for (int d = 0; d < screen->ndepths; d++) { + const Depth& d_info = screen->depths[d]; + if (visual >= &d_info.visuals[0] && + visual < &d_info.visuals[d_info.nvisuals]) + return d_info.depth; + } + + NS_ERROR("Visual not on Screen."); + return 0; +} + +/* static */ +Visual* gfxXlibSurface::FindVisual(Screen* screen, gfxImageFormat format) { + int depth; + unsigned long red_mask, green_mask, blue_mask; + switch (format) { + case gfx::SurfaceFormat::A8R8G8B8_UINT32: + depth = 32; + red_mask = 0xff0000; + green_mask = 0xff00; + blue_mask = 0xff; + break; + case gfx::SurfaceFormat::X8R8G8B8_UINT32: + depth = 24; + red_mask = 0xff0000; + green_mask = 0xff00; + blue_mask = 0xff; + break; + case gfx::SurfaceFormat::R5G6B5_UINT16: + depth = 16; + red_mask = 0xf800; + green_mask = 0x7e0; + blue_mask = 0x1f; + break; + case gfx::SurfaceFormat::A8: + default: + return nullptr; + } + + for (int d = 0; d < screen->ndepths; d++) { + const Depth& d_info = screen->depths[d]; + if (d_info.depth != depth) continue; + + for (int v = 0; v < d_info.nvisuals; v++) { + Visual* visual = &d_info.visuals[v]; + + if (visual->c_class == TrueColor && visual->red_mask == red_mask && + visual->green_mask == green_mask && visual->blue_mask == blue_mask) + return visual; + } + } + + return nullptr; +} + +/* static */ +XRenderPictFormat* gfxXlibSurface::FindRenderFormat(Display* dpy, + gfxImageFormat format) { + switch (format) { + case gfx::SurfaceFormat::A8R8G8B8_UINT32: + return XRenderFindStandardFormat(dpy, PictStandardARGB32); + case gfx::SurfaceFormat::X8R8G8B8_UINT32: + return XRenderFindStandardFormat(dpy, PictStandardRGB24); + case gfx::SurfaceFormat::R5G6B5_UINT16: { + // PictStandardRGB16_565 is not standard Xrender format + // we should try to find related visual + // and find xrender format by visual + Visual* visual = FindVisual(DefaultScreenOfDisplay(dpy), format); + if (!visual) return nullptr; + return XRenderFindVisualFormat(dpy, visual); + } + case gfx::SurfaceFormat::A8: + return XRenderFindStandardFormat(dpy, PictStandardA8); + default: + break; + } + + return nullptr; +} + +Screen* gfxXlibSurface::XScreen() { + return cairo_xlib_surface_get_screen(CairoSurface()); +} + +XRenderPictFormat* gfxXlibSurface::XRenderFormat() { + return cairo_xlib_surface_get_xrender_format(CairoSurface()); +} + +GLXPixmap gfxXlibSurface::GetGLXPixmap() { + if (!mGLXPixmap) { +#ifdef DEBUG + // cairo_surface_has_show_text_glyphs is used solely for the + // side-effect of setting the error on surface if + // cairo_surface_finish() has been called. + cairo_surface_has_show_text_glyphs(CairoSurface()); + NS_ASSERTION(CairoStatus() != CAIRO_STATUS_SURFACE_FINISHED, + "GetGLXPixmap called after surface finished"); +#endif + mGLXPixmap = gl::sGLXLibrary.CreatePixmap(this); + } + return mGLXPixmap; +} + +void gfxXlibSurface::BindGLXPixmap(GLXPixmap aPixmap) { + MOZ_ASSERT(!mGLXPixmap, "A GLXPixmap is already bound!"); + mGLXPixmap = aPixmap; +} diff --git a/gfx/thebes/gfxXlibSurface.h b/gfx/thebes/gfxXlibSurface.h new file mode 100644 index 0000000000..4b0ed92615 --- /dev/null +++ b/gfx/thebes/gfxXlibSurface.h @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_XLIBSURFACE_H +#define GFX_XLIBSURFACE_H + +#include "gfxASurface.h" + +#include <X11/extensions/Xrender.h> +#include <X11/Xlib.h> +#include "X11UndefineNone.h" + +#include "GLXLibrary.h" + +#include "nsSize.h" + +// Although the dimension parameters in the xCreatePixmapReq wire protocol are +// 16-bit unsigned integers, the server's CreatePixmap returns BadAlloc if +// either dimension cannot be represented by a 16-bit *signed* integer. +#define XLIB_IMAGE_SIDE_SIZE_LIMIT 0x7fff + +class gfxXlibSurface final : public gfxASurface { + public: + // construct a wrapper around the specified drawable with dpy/visual. + // Will use XGetGeometry to query the window/pixmap size. + gfxXlibSurface(Display* dpy, Drawable drawable, Visual* visual); + + // construct a wrapper around the specified drawable with dpy/visual, + // and known width/height. + gfxXlibSurface(Display* dpy, Drawable drawable, Visual* visual, + const mozilla::gfx::IntSize& size); + + // construct a wrapper around the specified drawable with dpy/format, + // and known width/height. + gfxXlibSurface(::Screen* screen, Drawable drawable, XRenderPictFormat* format, + const mozilla::gfx::IntSize& size); + + explicit gfxXlibSurface(cairo_surface_t* csurf); + + // create a new Pixmap and wrapper surface. + // |relatedDrawable| provides a hint to the server for determining whether + // the pixmap should be in video or system memory. It must be on + // |screen| (if specified). + static already_AddRefed<gfxXlibSurface> Create( + ::Screen* screen, Visual* visual, const mozilla::gfx::IntSize& size, + Drawable relatedDrawable = X11None); + static cairo_surface_t* CreateCairoSurface( + ::Screen* screen, Visual* visual, const mozilla::gfx::IntSize& size, + Drawable relatedDrawable = X11None); + static already_AddRefed<gfxXlibSurface> Create( + ::Screen* screen, XRenderPictFormat* format, + const mozilla::gfx::IntSize& size, Drawable relatedDrawable = X11None); + + virtual ~gfxXlibSurface(); + + already_AddRefed<gfxASurface> CreateSimilarSurface( + gfxContentType aType, const mozilla::gfx::IntSize& aSize) override; + void Finish() override; + + const mozilla::gfx::IntSize GetSize() const override; + + Display* XDisplay() { return mDisplay; } + ::Screen* XScreen(); + Drawable XDrawable() { return mDrawable; } + XRenderPictFormat* XRenderFormat(); + + static int DepthOfVisual(const ::Screen* screen, const Visual* visual); + static Visual* FindVisual(::Screen* screen, gfxImageFormat format); + static XRenderPictFormat* FindRenderFormat(Display* dpy, + gfxImageFormat format); + static bool GetColormapAndVisual(cairo_surface_t* aXlibSurface, + Colormap* colormap, Visual** visual); + + // take ownership of a passed-in Pixmap, calling XFreePixmap on it + // when the gfxXlibSurface is destroyed. + void TakePixmap(); + + // Release ownership of this surface's Pixmap. This is only valid + // on gfxXlibSurfaces for which the user called TakePixmap(), or + // on those created by a Create() factory method. + Drawable ReleasePixmap(); + + // Find a visual and colormap pair suitable for rendering to this surface. + bool GetColormapAndVisual(Colormap* colormap, Visual** visual); + + GLXPixmap GetGLXPixmap(); + // Binds a GLXPixmap backed by this context's surface. + // Primarily for use in sharing surfaces. + void BindGLXPixmap(GLXPixmap aPixmap); + + // Return true if cairo will take its slow path when this surface is used + // in a pattern with EXTEND_PAD. As a workaround for XRender's RepeatPad + // not being implemented correctly on old X servers, cairo avoids XRender + // and instead reads back to perform EXTEND_PAD with pixman. Cairo does + // this for servers older than xorg-server 1.7. + bool IsPadSlow() { + // The test here matches that for buggy_pad_reflect in + // _cairo_xlib_device_create. + return VendorRelease(mDisplay) >= 60700000 || + VendorRelease(mDisplay) < 10699000; + } + + protected: + // if TakePixmap() has been called on this + bool mPixmapTaken; + + Display* mDisplay; + Drawable mDrawable; + + const mozilla::gfx::IntSize DoSizeQuery(); + + GLXPixmap mGLXPixmap; +}; + +#endif /* GFX_XLIBSURFACE_H */ diff --git a/gfx/thebes/moz.build b/gfx/thebes/moz.build new file mode 100644 index 0000000000..d317214a44 --- /dev/null +++ b/gfx/thebes/moz.build @@ -0,0 +1,294 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("*Text*"): + BUG_COMPONENT = ("Core", "Graphics: Text") + +with Files("*DWrite*"): + BUG_COMPONENT = ("Core", "Graphics: Text") + +XPIDL_SOURCES += [ + "nsIFontLoadCompleteCallback.idl", +] + +XPIDL_MODULE = "gfx" + +EXPORTS += [ + "DrawMode.h", + "gfx2DGlue.h", + "gfxAlphaRecovery.h", + "gfxASurface.h", + "gfxBaseSharedMemorySurface.h", + "gfxBlur.h", + "gfxColor.h", + "gfxContext.h", + "gfxDrawable.h", + "gfxEnv.h", + "gfxFailure.h", + "gfxFont.h", + "gfxFontConstants.h", + "gfxFontEntry.h", + "gfxFontFamilyList.h", + "gfxFontFeatures.h", + "gfxFontInfoLoader.h", + "gfxFontPrefLangList.h", + "gfxFontSrcPrincipal.h", + "gfxFontSrcURI.h", + "gfxFontUtils.h", + "gfxFontVariations.h", + "gfxGradientCache.h", + "gfxImageSurface.h", + "gfxLineSegment.h", + "gfxMathTable.h", + "gfxMatrix.h", + "gfxOTSUtils.h", + "gfxPattern.h", + "gfxPlatform.h", + "gfxPlatformFontList.h", + "gfxPoint.h", + "gfxQuad.h", + "gfxQuaternion.h", + "gfxRect.h", + "gfxSharedImageSurface.h", + "gfxSkipChars.h", + "gfxSVGGlyphs.h", + "gfxTextRun.h", + "gfxTypes.h", + "gfxUserFontSet.h", + "gfxUtils.h", + "SharedFontList.h", + "SoftwareVsyncSource.h", + "ThebesRLBoxTypes.h", + "VsyncSource.h", +] + +EXPORTS.mozilla.gfx += [ + "D3D11Checks.h", + "DeviceManagerDx.h", + "DisplayConfigWindows.h", + "PrintTarget.h", + "PrintTargetThebes.h", + "ThebesRLBox.h", +] + +if CONFIG["MOZ_ENABLE_SKIA"]: + EXPORTS.mozilla.gfx += ["SkMemoryReporter.h"] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android": + EXPORTS += [ + "gfxAndroidPlatform.h", + "gfxFT2FontBase.h", + "gfxFT2Fonts.h", + ] + EXPORTS.mozilla.gfx += [ + "PrintTargetPDF.h", + ] + SOURCES += [ + "gfxAndroidPlatform.cpp", + "gfxFT2FontBase.cpp", + "gfxFT2FontList.cpp", + "gfxFT2Fonts.cpp", + "gfxFT2Utils.cpp", + "PrintTargetPDF.cpp", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + EXPORTS += [ + "gfxPlatformMac.h", + "gfxQuartzNativeDrawing.h", + "gfxQuartzSurface.h", + ] + EXPORTS.mozilla.gfx += [ + "PrintTargetCG.h", + ] + SOURCES += [ + "gfxCoreTextShaper.cpp", + "gfxMacFont.cpp", + "gfxPlatformMac.cpp", + "gfxQuartzNativeDrawing.cpp", + "gfxQuartzSurface.cpp", + "PrintTargetCG.mm", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + EXPORTS += [ + "gfxFT2FontBase.h", + "gfxGdkNativeRenderer.h", + "gfxPlatformGtk.h", + ] + EXPORTS.mozilla.gfx += [ + "PrintTargetPDF.h", + "PrintTargetPS.h", + ] + SOURCES += [ + "gfxFcPlatformFontList.cpp", + "gfxFT2FontBase.cpp", + "gfxFT2Utils.cpp", + "gfxGdkNativeRenderer.cpp", + "gfxPlatformGtk.cpp", + "PrintTargetPDF.cpp", + "PrintTargetPS.cpp", + ] + + if CONFIG["MOZ_X11"]: + EXPORTS += [ + "gfxXlibNativeRenderer.h", + "gfxXlibSurface.h", + ] + SOURCES += [ + "gfxXlibNativeRenderer.cpp", + "gfxXlibSurface.cpp", + ] + +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": + EXPORTS += [ + "gfxDWriteFonts.h", + "gfxGDIFont.h", + "gfxGDIFontList.h", + "gfxWindowsNativeDrawing.h", + "gfxWindowsPlatform.h", + "gfxWindowsSurface.h", + ] + EXPORTS.mozilla.gfx += [ + "PrintTargetPDF.h", + "PrintTargetWindows.h", + ] + SOURCES += [ + "DisplayConfigWindows.cpp", + "gfxDWriteCommon.cpp", + "gfxDWriteFonts.cpp", + "gfxGDIFont.cpp", + "gfxGDIFontList.cpp", + "gfxWindowsNativeDrawing.cpp", + "gfxWindowsPlatform.cpp", + "gfxWindowsSurface.cpp", + "PrintTargetPDF.cpp", + "PrintTargetWindows.cpp", + ] + UNIFIED_SOURCES += [ + "gfxDWriteFontList.cpp", + ] + +# Are we targeting x86 or x64? If so, build gfxAlphaRecoverySSE2.cpp. +if CONFIG["INTEL_ARCHITECTURE"]: + SOURCES += ["gfxAlphaRecoverySSE2.cpp"] + # The file uses SSE2 intrinsics, so it needs special compile flags on some + # compilers. + SOURCES["gfxAlphaRecoverySSE2.cpp"].flags += CONFIG["SSE2_FLAGS"] + +SOURCES += [ + # Includes mac system header conflicting with point/size, + # and includes glxXlibSurface.h which drags in Xrender.h + "gfxASurface.cpp", + # on X11, gfxDrawable.cpp includes X headers for an old workaround which + # we could consider removing soon (affects Ubuntus older than 10.04 LTS) + # which currently prevent it from joining UNIFIED_SOURCES. + "gfxDrawable.cpp", + # gfxFontUtils.cpp and gfxPlatform.cpp include mac system header conflicting with point/size + "gfxFontUtils.cpp", + "gfxPlatform.cpp", + "PrintTarget.cpp", + "PrintTargetThebes.cpp", +] + +UNIFIED_SOURCES += [ + "CJKCompatSVS.cpp", + "gfxAlphaRecovery.cpp", + "gfxBaseSharedMemorySurface.cpp", + "gfxBlur.cpp", + "gfxContext.cpp", + "gfxFont.cpp", + "gfxFontEntry.cpp", + "gfxFontFeatures.cpp", + "gfxFontInfoLoader.cpp", + "gfxFontMissingGlyphs.cpp", + "gfxFontSrcPrincipal.cpp", + "gfxFontSrcURI.cpp", + "gfxGlyphExtents.cpp", + "gfxGradientCache.cpp", + "gfxGraphiteShaper.cpp", + "gfxHarfBuzzShaper.cpp", + "gfxImageSurface.cpp", + "gfxMathTable.cpp", + "gfxPattern.cpp", + "gfxPlatformFontList.cpp", + "gfxScriptItemizer.cpp", + "gfxSkipChars.cpp", + "gfxSVGGlyphs.cpp", + "gfxTextRun.cpp", + "gfxUserFontSet.cpp", + "gfxUtils.cpp", + "SharedFontList.cpp", + "SoftwareVsyncSource.cpp", + "VsyncSource.cpp", +] + +if CONFIG["MOZ_ENABLE_SKIA"]: + UNIFIED_SOURCES += [ + "SkMemoryReporter.cpp", + ] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + UNIFIED_SOURCES += [ + "gfxMacPlatformFontList.mm", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": + UNIFIED_SOURCES += [ + "D3D11Checks.cpp", + ] + SOURCES += [ + "DeviceManagerDx.cpp", + ] + +if CONFIG["MOZ_ENABLE_SKIA_PDF"]: + EXPORTS.mozilla.gfx += [ + "PrintTargetSkPDF.h", + ] + SOURCES += [ + "PrintTargetSkPDF.cpp", + ] + +# We use ICU for normalization functions: +USE_LIBS += [ + "icu", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "/dom/base", + "/dom/xml", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("android", "gtk"): + DEFINES["MOZ_ENABLE_FREETYPE"] = True + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": + for var in ("MOZ_ENABLE_D3D10_LAYER",): + if CONFIG[var]: + DEFINES[var] = True + +CXXFLAGS += CONFIG["MOZ_CAIRO_CFLAGS"] +CXXFLAGS += CONFIG["TK_CFLAGS"] +CFLAGS += CONFIG["MOZ_CAIRO_CFLAGS"] +CFLAGS += CONFIG["TK_CFLAGS"] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("android"): + CXXFLAGS += CONFIG["CAIRO_FT_CFLAGS"] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + CXXFLAGS += CONFIG["MOZ_PANGO_CFLAGS"] + +if CONFIG["MOZ_WAYLAND"]: + CXXFLAGS += CONFIG["MOZ_WAYLAND_CFLAGS"] + +LOCAL_INCLUDES += CONFIG["SKIA_INCLUDES"] + +DEFINES["GRAPHITE2_STATIC"] = True + +if CONFIG["CC_TYPE"] == "clang": + # Suppress warnings from Skia header files. + SOURCES["gfxPlatform.cpp"].flags += ["-Wno-implicit-fallthrough"] diff --git a/gfx/thebes/nsIFontLoadCompleteCallback.idl b/gfx/thebes/nsIFontLoadCompleteCallback.idl new file mode 100644 index 0000000000..4f1df74a79 --- /dev/null +++ b/gfx/thebes/nsIFontLoadCompleteCallback.idl @@ -0,0 +1,13 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[uuid(302dbf09-079b-4648-8a06-a0486c1749c0)] +interface nsIFontLoadCompleteCallback : nsISupports +{ + void fontLoadComplete(); +}; |