diff options
Diffstat (limited to 'gfx/thebes')
178 files changed, 92738 insertions, 0 deletions
diff --git a/gfx/thebes/CJKCompatSVS.cpp b/gfx/thebes/CJKCompatSVS.cpp new file mode 100644 index 0000000000..3774c2b11e --- /dev/null +++ b/gfx/thebes/CJKCompatSVS.cpp @@ -0,0 +1,2048 @@ +// Generated by gencjkcisvs.py. Do not edit. + +#include <stdint.h> + +#define U16(v) (((v) >> 8) & 0xFF), ((v) & 0xFF) +#define U24(v) (((v) >> 16) & 0xFF), (((v) >> 8) & 0xFF), ((v) & 0xFF) +#define U32(v) \ + (((v) >> 24) & 0xFF), (((v) >> 16) & 0xFF), (((v) >> 8) & 0xFF), ((v) & 0xFF) +#define GLYPH(v) U16(v >= 0x2F800 ? (v) - (0x2F800 - 0xFB00) : (v)) + +// Fallback mappings for CJK Compatibility Ideographs Standardized Variants +// taken from StandardizedVariants-6.3.0.txt. +// Using OpenType format 14 cmap subtable structure to reuse the lookup code +// for fonts. The glyphID field is used to store the corresponding codepoints +// CJK Compatibility Ideographs. To fit codepoints into the 16-bit glyphID +// field, CJK Compatibility Ideographs Supplement (U+2F800..U+2FA1F) will be +// mapped to 0xFB00..0xFD1F. +extern const uint8_t sCJKCompatSVSTable[] = { + U16(14), // format + U32(5065), // length + U32(3), // numVarSelectorRecords + U24(0xFE00), + U32(0), + U32(43), // varSelectorRecord[0] + U24(0xFE01), + U32(0), + U32(4557), // varSelectorRecord[1] + U24(0xFE02), + U32(0), + U32(5001), // varSelectorRecord[2] + // 0xFE00 + U32(902), // numUVSMappings + U24(0x349E), + GLYPH(0x2F80C), + U24(0x34B9), + GLYPH(0x2F813), + U24(0x34BB), + GLYPH(0x2F9CA), + U24(0x34DF), + GLYPH(0x2F81F), + U24(0x3515), + GLYPH(0x2F824), + U24(0x36EE), + GLYPH(0x2F867), + U24(0x36FC), + GLYPH(0x2F868), + U24(0x3781), + GLYPH(0x2F876), + U24(0x382F), + GLYPH(0x2F883), + U24(0x3862), + GLYPH(0x2F888), + U24(0x387C), + GLYPH(0x2F88A), + U24(0x38C7), + GLYPH(0x2F896), + U24(0x38E3), + GLYPH(0x2F89B), + U24(0x391C), + GLYPH(0x2F8A2), + U24(0x393A), + GLYPH(0x2F8A1), + U24(0x3A2E), + GLYPH(0x2F8C2), + U24(0x3A6C), + GLYPH(0x2F8C7), + U24(0x3AE4), + GLYPH(0x2F8D1), + U24(0x3B08), + GLYPH(0x2F8D0), + U24(0x3B19), + GLYPH(0x2F8CE), + U24(0x3B49), + GLYPH(0x2F8DE), + U24(0x3B9D), + GLYPH(0xFAD2), + U24(0x3C18), + GLYPH(0x2F8EE), + U24(0x3C4E), + GLYPH(0x2F8F2), + U24(0x3D33), + GLYPH(0x2F90A), + U24(0x3D96), + GLYPH(0x2F916), + U24(0x3EAC), + GLYPH(0x2F92A), + U24(0x3EB8), + GLYPH(0x2F92C), + U24(0x3F1B), + GLYPH(0x2F933), + U24(0x3FFC), + GLYPH(0x2F93E), + U24(0x4008), + GLYPH(0x2F93F), + U24(0x4018), + GLYPH(0xFAD3), + U24(0x4039), + GLYPH(0xFAD4), + U24(0x4046), + GLYPH(0x2F94B), + U24(0x4096), + GLYPH(0x2F94C), + U24(0x40E3), + GLYPH(0x2F951), + U24(0x412F), + GLYPH(0x2F958), + U24(0x4202), + GLYPH(0x2F960), + U24(0x4227), + GLYPH(0x2F964), + U24(0x42A0), + GLYPH(0x2F967), + U24(0x4301), + GLYPH(0x2F96D), + U24(0x4334), + GLYPH(0x2F971), + U24(0x4359), + GLYPH(0x2F974), + U24(0x43D5), + GLYPH(0x2F981), + U24(0x43D9), + GLYPH(0x2F8D7), + U24(0x440B), + GLYPH(0x2F984), + U24(0x446B), + GLYPH(0x2F98E), + U24(0x452B), + GLYPH(0x2F9A7), + U24(0x455D), + GLYPH(0x2F9AE), + U24(0x4561), + GLYPH(0x2F9AF), + U24(0x456B), + GLYPH(0x2F9B2), + U24(0x45D7), + GLYPH(0x2F9BF), + U24(0x45F9), + GLYPH(0x2F9C2), + U24(0x4635), + GLYPH(0x2F9C8), + U24(0x46BE), + GLYPH(0x2F9CD), + U24(0x46C7), + GLYPH(0x2F9CE), + U24(0x4995), + GLYPH(0x2F9EF), + U24(0x49E6), + GLYPH(0x2F9F2), + U24(0x4A6E), + GLYPH(0x2F9F8), + U24(0x4A76), + GLYPH(0x2F9F9), + U24(0x4AB2), + GLYPH(0x2F9FC), + U24(0x4B33), + GLYPH(0x2FA03), + U24(0x4BCE), + GLYPH(0x2FA08), + U24(0x4CCE), + GLYPH(0x2FA0D), + U24(0x4CED), + GLYPH(0x2FA0E), + U24(0x4CF8), + GLYPH(0x2FA11), + U24(0x4D56), + GLYPH(0x2FA16), + U24(0x4E0D), + GLYPH(0xF967), + U24(0x4E26), + GLYPH(0xFA70), + U24(0x4E32), + GLYPH(0xF905), + U24(0x4E38), + GLYPH(0x2F801), + U24(0x4E39), + GLYPH(0xF95E), + U24(0x4E3D), + GLYPH(0x2F800), + U24(0x4E41), + GLYPH(0x2F802), + U24(0x4E82), + GLYPH(0xF91B), + U24(0x4E86), + GLYPH(0xF9BA), + U24(0x4EAE), + GLYPH(0xF977), + U24(0x4EC0), + GLYPH(0xF9FD), + U24(0x4ECC), + GLYPH(0x2F819), + U24(0x4EE4), + GLYPH(0xF9A8), + U24(0x4F60), + GLYPH(0x2F804), + U24(0x4F80), + GLYPH(0xFA73), + U24(0x4F86), + GLYPH(0xF92D), + U24(0x4F8B), + GLYPH(0xF9B5), + U24(0x4FAE), + GLYPH(0xFA30), + U24(0x4FBB), + GLYPH(0x2F806), + U24(0x4FBF), + GLYPH(0xF965), + U24(0x5002), + GLYPH(0x2F807), + U24(0x502B), + GLYPH(0xF9D4), + U24(0x507A), + GLYPH(0x2F808), + U24(0x5099), + GLYPH(0x2F809), + U24(0x50CF), + GLYPH(0x2F80B), + U24(0x50DA), + GLYPH(0xF9BB), + U24(0x50E7), + GLYPH(0xFA31), + U24(0x5140), + GLYPH(0xFA0C), + U24(0x5145), + GLYPH(0xFA74), + U24(0x514D), + GLYPH(0xFA32), + U24(0x5154), + GLYPH(0x2F80F), + U24(0x5164), + GLYPH(0x2F810), + U24(0x5167), + GLYPH(0x2F814), + U24(0x5168), + GLYPH(0xFA72), + U24(0x5169), + GLYPH(0xF978), + U24(0x516D), + GLYPH(0xF9D1), + U24(0x5177), + GLYPH(0x2F811), + U24(0x5180), + GLYPH(0xFA75), + U24(0x518D), + GLYPH(0x2F815), + U24(0x5192), + GLYPH(0x2F8D2), + U24(0x5195), + GLYPH(0x2F8D3), + U24(0x5197), + GLYPH(0x2F817), + U24(0x51A4), + GLYPH(0x2F818), + U24(0x51AC), + GLYPH(0x2F81A), + U24(0x51B5), + GLYPH(0xFA71), + U24(0x51B7), + GLYPH(0xF92E), + U24(0x51C9), + GLYPH(0xF979), + U24(0x51CC), + GLYPH(0xF955), + U24(0x51DC), + GLYPH(0xF954), + U24(0x51DE), + GLYPH(0xFA15), + U24(0x51F5), + GLYPH(0x2F81D), + U24(0x5203), + GLYPH(0x2F81E), + U24(0x5207), + GLYPH(0xFA00), + U24(0x5217), + GLYPH(0xF99C), + U24(0x5229), + GLYPH(0xF9DD), + U24(0x523A), + GLYPH(0xF9FF), + U24(0x523B), + GLYPH(0x2F820), + U24(0x5246), + GLYPH(0x2F821), + U24(0x5272), + GLYPH(0x2F822), + U24(0x5277), + GLYPH(0x2F823), + U24(0x5289), + GLYPH(0xF9C7), + U24(0x529B), + GLYPH(0xF98A), + U24(0x52A3), + GLYPH(0xF99D), + U24(0x52B3), + GLYPH(0x2F992), + U24(0x52C7), + GLYPH(0xFA76), + U24(0x52C9), + GLYPH(0xFA33), + U24(0x52D2), + GLYPH(0xF952), + U24(0x52DE), + GLYPH(0xF92F), + U24(0x52E4), + GLYPH(0xFA34), + U24(0x52F5), + GLYPH(0xF97F), + U24(0x52FA), + GLYPH(0xFA77), + U24(0x5305), + GLYPH(0x2F829), + U24(0x5306), + GLYPH(0x2F82A), + U24(0x5317), + GLYPH(0xF963), + U24(0x533F), + GLYPH(0xF9EB), + U24(0x5349), + GLYPH(0x2F82C), + U24(0x5351), + GLYPH(0xFA35), + U24(0x535A), + GLYPH(0x2F82E), + U24(0x5373), + GLYPH(0x2F82F), + U24(0x5375), + GLYPH(0xF91C), + U24(0x537D), + GLYPH(0x2F830), + U24(0x537F), + GLYPH(0x2F831), + U24(0x53C3), + GLYPH(0xF96B), + U24(0x53CA), + GLYPH(0x2F836), + U24(0x53DF), + GLYPH(0x2F837), + U24(0x53E5), + GLYPH(0xF906), + U24(0x53EB), + GLYPH(0x2F839), + U24(0x53F1), + GLYPH(0x2F83A), + U24(0x5406), + GLYPH(0x2F83B), + U24(0x540F), + GLYPH(0xF9DE), + U24(0x541D), + GLYPH(0xF9ED), + U24(0x5438), + GLYPH(0x2F83D), + U24(0x5442), + GLYPH(0xF980), + U24(0x5448), + GLYPH(0x2F83E), + U24(0x5468), + GLYPH(0x2F83F), + U24(0x549E), + GLYPH(0x2F83C), + U24(0x54A2), + GLYPH(0x2F840), + U24(0x54BD), + GLYPH(0xF99E), + U24(0x54F6), + GLYPH(0x2F841), + U24(0x5510), + GLYPH(0x2F842), + U24(0x5553), + GLYPH(0x2F843), + U24(0x5555), + GLYPH(0xFA79), + U24(0x5563), + GLYPH(0x2F844), + U24(0x5584), + GLYPH(0x2F845), + U24(0x5587), + GLYPH(0xF90B), + U24(0x5599), + GLYPH(0xFA7A), + U24(0x559D), + GLYPH(0xFA36), + U24(0x55AB), + GLYPH(0x2F848), + U24(0x55B3), + GLYPH(0x2F849), + U24(0x55C0), + GLYPH(0xFA0D), + U24(0x55C2), + GLYPH(0x2F84A), + U24(0x55E2), + GLYPH(0xFA7B), + U24(0x5606), + GLYPH(0xFA37), + U24(0x5651), + GLYPH(0x2F84E), + U24(0x5668), + GLYPH(0xFA38), + U24(0x5674), + GLYPH(0x2F84F), + U24(0x56F9), + GLYPH(0xF9A9), + U24(0x5716), + GLYPH(0x2F84B), + U24(0x5717), + GLYPH(0x2F84D), + U24(0x578B), + GLYPH(0x2F855), + U24(0x57CE), + GLYPH(0x2F852), + U24(0x57F4), + GLYPH(0x2F853), + U24(0x580D), + GLYPH(0x2F854), + U24(0x5831), + GLYPH(0x2F857), + U24(0x5832), + GLYPH(0x2F856), + U24(0x5840), + GLYPH(0xFA39), + U24(0x585A), + GLYPH(0xFA10), + U24(0x585E), + GLYPH(0xF96C), + U24(0x58A8), + GLYPH(0xFA3A), + U24(0x58AC), + GLYPH(0x2F858), + U24(0x58B3), + GLYPH(0xFA7D), + U24(0x58D8), + GLYPH(0xF94A), + U24(0x58DF), + GLYPH(0xF942), + U24(0x58EE), + GLYPH(0x2F851), + U24(0x58F2), + GLYPH(0x2F85A), + U24(0x58F7), + GLYPH(0x2F85B), + U24(0x5906), + GLYPH(0x2F85C), + U24(0x591A), + GLYPH(0x2F85D), + U24(0x5922), + GLYPH(0x2F85E), + U24(0x5944), + GLYPH(0xFA7E), + U24(0x5948), + GLYPH(0xF90C), + U24(0x5951), + GLYPH(0xF909), + U24(0x5954), + GLYPH(0xFA7F), + U24(0x5962), + GLYPH(0x2F85F), + U24(0x5973), + GLYPH(0xF981), + U24(0x59D8), + GLYPH(0x2F865), + U24(0x59EC), + GLYPH(0x2F862), + U24(0x5A1B), + GLYPH(0x2F863), + U24(0x5A27), + GLYPH(0x2F864), + U24(0x5A62), + GLYPH(0xFA80), + U24(0x5A66), + GLYPH(0x2F866), + U24(0x5AB5), + GLYPH(0x2F986), + U24(0x5B08), + GLYPH(0x2F869), + U24(0x5B28), + GLYPH(0xFA81), + U24(0x5B3E), + GLYPH(0x2F86A), + U24(0x5B85), + GLYPH(0xFA04), + U24(0x5BC3), + GLYPH(0x2F86D), + U24(0x5BD8), + GLYPH(0x2F86E), + U24(0x5BE7), + GLYPH(0xF95F), + U24(0x5BEE), + GLYPH(0xF9BC), + U24(0x5BF3), + GLYPH(0x2F870), + U24(0x5BFF), + GLYPH(0x2F872), + U24(0x5C06), + GLYPH(0x2F873), + U24(0x5C22), + GLYPH(0x2F875), + U24(0x5C3F), + GLYPH(0xF9BD), + U24(0x5C60), + GLYPH(0x2F877), + U24(0x5C62), + GLYPH(0xF94B), + U24(0x5C64), + GLYPH(0xFA3B), + U24(0x5C65), + GLYPH(0xF9DF), + U24(0x5C6E), + GLYPH(0xFA3C), + U24(0x5C8D), + GLYPH(0x2F87A), + U24(0x5CC0), + GLYPH(0x2F879), + U24(0x5D19), + GLYPH(0xF9D5), + U24(0x5D43), + GLYPH(0x2F87C), + U24(0x5D50), + GLYPH(0xF921), + U24(0x5D6B), + GLYPH(0x2F87F), + U24(0x5D6E), + GLYPH(0x2F87E), + U24(0x5D7C), + GLYPH(0x2F880), + U24(0x5DB2), + GLYPH(0x2F9F4), + U24(0x5DBA), + GLYPH(0xF9AB), + U24(0x5DE1), + GLYPH(0x2F881), + U24(0x5DE2), + GLYPH(0x2F882), + U24(0x5DFD), + GLYPH(0x2F884), + U24(0x5E28), + GLYPH(0x2F885), + U24(0x5E3D), + GLYPH(0x2F886), + U24(0x5E69), + GLYPH(0x2F887), + U24(0x5E74), + GLYPH(0xF98E), + U24(0x5EA6), + GLYPH(0xFA01), + U24(0x5EB0), + GLYPH(0x2F88B), + U24(0x5EB3), + GLYPH(0x2F88C), + U24(0x5EB6), + GLYPH(0x2F88D), + U24(0x5EC9), + GLYPH(0xF9A2), + U24(0x5ECA), + GLYPH(0xF928), + U24(0x5ED2), + GLYPH(0xFA82), + U24(0x5ED3), + GLYPH(0xFA0B), + U24(0x5ED9), + GLYPH(0xFA83), + U24(0x5EEC), + GLYPH(0xF982), + U24(0x5EFE), + GLYPH(0x2F890), + U24(0x5F04), + GLYPH(0xF943), + U24(0x5F22), + GLYPH(0x2F894), + U24(0x5F53), + GLYPH(0x2F874), + U24(0x5F62), + GLYPH(0x2F899), + U24(0x5F69), + GLYPH(0xFA84), + U24(0x5F6B), + GLYPH(0x2F89A), + U24(0x5F8B), + GLYPH(0xF9D8), + U24(0x5F9A), + GLYPH(0x2F89C), + U24(0x5FA9), + GLYPH(0xF966), + U24(0x5FAD), + GLYPH(0xFA85), + U24(0x5FCD), + GLYPH(0x2F89D), + U24(0x5FD7), + GLYPH(0x2F89E), + U24(0x5FF5), + GLYPH(0xF9A3), + U24(0x5FF9), + GLYPH(0x2F89F), + U24(0x6012), + GLYPH(0xF960), + U24(0x601C), + GLYPH(0xF9AC), + U24(0x6075), + GLYPH(0xFA6B), + U24(0x6081), + GLYPH(0x2F8A0), + U24(0x6094), + GLYPH(0xFA3D), + U24(0x60C7), + GLYPH(0x2F8A5), + U24(0x60D8), + GLYPH(0xFA86), + U24(0x60E1), + GLYPH(0xF9B9), + U24(0x6108), + GLYPH(0xFA88), + U24(0x6144), + GLYPH(0xF9D9), + U24(0x6148), + GLYPH(0x2F8A6), + U24(0x614C), + GLYPH(0x2F8A7), + U24(0x614E), + GLYPH(0xFA87), + U24(0x6160), + GLYPH(0xFA8A), + U24(0x6168), + GLYPH(0xFA3E), + U24(0x617A), + GLYPH(0x2F8AA), + U24(0x618E), + GLYPH(0xFA3F), + U24(0x6190), + GLYPH(0xF98F), + U24(0x61A4), + GLYPH(0x2F8AD), + U24(0x61AF), + GLYPH(0x2F8AE), + U24(0x61B2), + GLYPH(0x2F8AC), + U24(0x61DE), + GLYPH(0x2F8AF), + U24(0x61F2), + GLYPH(0xFA40), + U24(0x61F6), + GLYPH(0xF90D), + U24(0x6200), + GLYPH(0xF990), + U24(0x6210), + GLYPH(0x2F8B2), + U24(0x621B), + GLYPH(0x2F8B3), + U24(0x622E), + GLYPH(0xF9D2), + U24(0x6234), + GLYPH(0xFA8C), + U24(0x625D), + GLYPH(0x2F8B4), + U24(0x62B1), + GLYPH(0x2F8B5), + U24(0x62C9), + GLYPH(0xF925), + U24(0x62CF), + GLYPH(0xF95B), + U24(0x62D3), + GLYPH(0xFA02), + U24(0x62D4), + GLYPH(0x2F8B6), + U24(0x62FC), + GLYPH(0x2F8BA), + U24(0x62FE), + GLYPH(0xF973), + U24(0x633D), + GLYPH(0x2F8B9), + U24(0x6350), + GLYPH(0x2F8B7), + U24(0x6368), + GLYPH(0x2F8BB), + U24(0x637B), + GLYPH(0xF9A4), + U24(0x6383), + GLYPH(0x2F8BC), + U24(0x63A0), + GLYPH(0xF975), + U24(0x63A9), + GLYPH(0x2F8C1), + U24(0x63C4), + GLYPH(0xFA8D), + U24(0x63C5), + GLYPH(0x2F8C0), + U24(0x63E4), + GLYPH(0x2F8BD), + U24(0x641C), + GLYPH(0xFA8E), + U24(0x6422), + GLYPH(0x2F8BF), + U24(0x6452), + GLYPH(0xFA8F), + U24(0x6469), + GLYPH(0x2F8C3), + U24(0x6477), + GLYPH(0x2F8C6), + U24(0x647E), + GLYPH(0x2F8C4), + U24(0x649A), + GLYPH(0xF991), + U24(0x649D), + GLYPH(0x2F8C5), + U24(0x64C4), + GLYPH(0xF930), + U24(0x654F), + GLYPH(0xFA41), + U24(0x6556), + GLYPH(0xFA90), + U24(0x656C), + GLYPH(0x2F8C9), + U24(0x6578), + GLYPH(0xF969), + U24(0x6599), + GLYPH(0xF9BE), + U24(0x65C5), + GLYPH(0xF983), + U24(0x65E2), + GLYPH(0xFA42), + U24(0x65E3), + GLYPH(0x2F8CB), + U24(0x6613), + GLYPH(0xF9E0), + U24(0x6649), + GLYPH(0x2F8CD), + U24(0x6674), + GLYPH(0xFA12), + U24(0x6688), + GLYPH(0xF9C5), + U24(0x6691), + GLYPH(0xFA43), + U24(0x669C), + GLYPH(0x2F8D5), + U24(0x66B4), + GLYPH(0xFA06), + U24(0x66C6), + GLYPH(0xF98B), + U24(0x66F4), + GLYPH(0xF901), + U24(0x66F8), + GLYPH(0x2F8CC), + U24(0x6700), + GLYPH(0x2F8D4), + U24(0x6717), + GLYPH(0xF929), + U24(0x671B), + GLYPH(0xFA93), + U24(0x6721), + GLYPH(0x2F8DA), + U24(0x674E), + GLYPH(0xF9E1), + U24(0x6753), + GLYPH(0x2F8DC), + U24(0x6756), + GLYPH(0xFA94), + U24(0x675E), + GLYPH(0x2F8DB), + U24(0x677B), + GLYPH(0xF9C8), + U24(0x6785), + GLYPH(0x2F8E0), + U24(0x6797), + GLYPH(0xF9F4), + U24(0x67F3), + GLYPH(0xF9C9), + U24(0x67FA), + GLYPH(0x2F8DF), + U24(0x6817), + GLYPH(0xF9DA), + U24(0x681F), + GLYPH(0x2F8E5), + U24(0x6852), + GLYPH(0x2F8E1), + U24(0x6881), + GLYPH(0xF97A), + U24(0x6885), + GLYPH(0xFA44), + U24(0x688E), + GLYPH(0x2F8E4), + U24(0x68A8), + GLYPH(0xF9E2), + U24(0x6914), + GLYPH(0x2F8E6), + U24(0x6942), + GLYPH(0x2F8E8), + U24(0x69A3), + GLYPH(0x2F8E9), + U24(0x69EA), + GLYPH(0x2F8EA), + U24(0x6A02), + GLYPH(0xF914), + U24(0x6A13), + GLYPH(0xF94C), + U24(0x6AA8), + GLYPH(0x2F8EB), + U24(0x6AD3), + GLYPH(0xF931), + U24(0x6ADB), + GLYPH(0x2F8ED), + U24(0x6B04), + GLYPH(0xF91D), + U24(0x6B21), + GLYPH(0x2F8EF), + U24(0x6B54), + GLYPH(0x2F8F1), + U24(0x6B72), + GLYPH(0x2F8F3), + U24(0x6B77), + GLYPH(0xF98C), + U24(0x6B79), + GLYPH(0xFA95), + U24(0x6B9F), + GLYPH(0x2F8F4), + U24(0x6BAE), + GLYPH(0xF9A5), + U24(0x6BBA), + GLYPH(0xF970), + U24(0x6BBB), + GLYPH(0x2F8F6), + U24(0x6C4E), + GLYPH(0x2F8FA), + U24(0x6C67), + GLYPH(0x2F8FE), + U24(0x6C88), + GLYPH(0xF972), + U24(0x6CBF), + GLYPH(0x2F8FC), + U24(0x6CCC), + GLYPH(0xF968), + U24(0x6CCD), + GLYPH(0x2F8FD), + U24(0x6CE5), + GLYPH(0xF9E3), + U24(0x6D16), + GLYPH(0x2F8FF), + U24(0x6D1B), + GLYPH(0xF915), + U24(0x6D1E), + GLYPH(0xFA05), + U24(0x6D34), + GLYPH(0x2F907), + U24(0x6D3E), + GLYPH(0x2F900), + U24(0x6D41), + GLYPH(0xF9CA), + U24(0x6D69), + GLYPH(0x2F903), + U24(0x6D6A), + GLYPH(0xF92A), + U24(0x6D77), + GLYPH(0xFA45), + U24(0x6D78), + GLYPH(0x2F904), + U24(0x6D85), + GLYPH(0x2F905), + U24(0x6DCB), + GLYPH(0xF9F5), + U24(0x6DDA), + GLYPH(0xF94D), + U24(0x6DEA), + GLYPH(0xF9D6), + U24(0x6DF9), + GLYPH(0x2F90E), + U24(0x6E1A), + GLYPH(0xFA46), + U24(0x6E2F), + GLYPH(0x2F908), + U24(0x6E6E), + GLYPH(0x2F909), + U24(0x6E9C), + GLYPH(0xF9CB), + U24(0x6EBA), + GLYPH(0xF9EC), + U24(0x6EC7), + GLYPH(0x2F90C), + U24(0x6ECB), + GLYPH(0xFA99), + U24(0x6ED1), + GLYPH(0xF904), + U24(0x6EDB), + GLYPH(0xFA98), + U24(0x6F0F), + GLYPH(0xF94E), + U24(0x6F22), + GLYPH(0xFA47), + U24(0x6F23), + GLYPH(0xF992), + U24(0x6F6E), + GLYPH(0x2F90F), + U24(0x6FC6), + GLYPH(0x2F912), + U24(0x6FEB), + GLYPH(0xF922), + U24(0x6FFE), + GLYPH(0xF984), + U24(0x701B), + GLYPH(0x2F915), + U24(0x701E), + GLYPH(0xFA9B), + U24(0x7039), + GLYPH(0x2F913), + U24(0x704A), + GLYPH(0x2F917), + U24(0x7070), + GLYPH(0x2F835), + U24(0x7077), + GLYPH(0x2F919), + U24(0x707D), + GLYPH(0x2F918), + U24(0x7099), + GLYPH(0xF9FB), + U24(0x70AD), + GLYPH(0x2F91A), + U24(0x70C8), + GLYPH(0xF99F), + U24(0x70D9), + GLYPH(0xF916), + U24(0x7145), + GLYPH(0x2F91C), + U24(0x7149), + GLYPH(0xF993), + U24(0x716E), + GLYPH(0xFA48), + U24(0x719C), + GLYPH(0x2F91E), + U24(0x71CE), + GLYPH(0xF9C0), + U24(0x71D0), + GLYPH(0xF9EE), + U24(0x7210), + GLYPH(0xF932), + U24(0x721B), + GLYPH(0xF91E), + U24(0x7228), + GLYPH(0x2F920), + U24(0x722B), + GLYPH(0xFA49), + U24(0x7235), + GLYPH(0xFA9E), + U24(0x7250), + GLYPH(0x2F922), + U24(0x7262), + GLYPH(0xF946), + U24(0x7280), + GLYPH(0x2F924), + U24(0x7295), + GLYPH(0x2F925), + U24(0x72AF), + GLYPH(0xFA9F), + U24(0x72C0), + GLYPH(0xF9FA), + U24(0x72FC), + GLYPH(0xF92B), + U24(0x732A), + GLYPH(0xFA16), + U24(0x7375), + GLYPH(0xF9A7), + U24(0x737A), + GLYPH(0x2F928), + U24(0x7387), + GLYPH(0xF961), + U24(0x738B), + GLYPH(0x2F929), + U24(0x73A5), + GLYPH(0x2F92B), + U24(0x73B2), + GLYPH(0xF9AD), + U24(0x73DE), + GLYPH(0xF917), + U24(0x7406), + GLYPH(0xF9E4), + U24(0x7409), + GLYPH(0xF9CC), + U24(0x7422), + GLYPH(0xFA4A), + U24(0x7447), + GLYPH(0x2F92E), + U24(0x745C), + GLYPH(0x2F92F), + U24(0x7469), + GLYPH(0xF9AE), + U24(0x7471), + GLYPH(0xFAA1), + U24(0x7485), + GLYPH(0x2F931), + U24(0x7489), + GLYPH(0xF994), + U24(0x7498), + GLYPH(0xF9EF), + U24(0x74CA), + GLYPH(0x2F932), + U24(0x7506), + GLYPH(0xFAA2), + U24(0x7524), + GLYPH(0x2F934), + U24(0x753B), + GLYPH(0xFAA3), + U24(0x753E), + GLYPH(0x2F936), + U24(0x7559), + GLYPH(0xF9CD), + U24(0x7565), + GLYPH(0xF976), + U24(0x7570), + GLYPH(0xF962), + U24(0x75E2), + GLYPH(0xF9E5), + U24(0x7610), + GLYPH(0x2F93A), + U24(0x761D), + GLYPH(0xFAA4), + U24(0x761F), + GLYPH(0xFAA5), + U24(0x7642), + GLYPH(0xF9C1), + U24(0x7669), + GLYPH(0xF90E), + U24(0x76CA), + GLYPH(0xFA17), + U24(0x76DB), + GLYPH(0xFAA7), + U24(0x76E7), + GLYPH(0xF933), + U24(0x76F4), + GLYPH(0xFAA8), + U24(0x7701), + GLYPH(0xF96D), + U24(0x771E), + GLYPH(0x2F945), + U24(0x771F), + GLYPH(0x2F946), + U24(0x7740), + GLYPH(0xFAAA), + U24(0x774A), + GLYPH(0xFAA9), + U24(0x778B), + GLYPH(0x2F94A), + U24(0x77A7), + GLYPH(0xFA9D), + U24(0x784E), + GLYPH(0x2F94E), + U24(0x786B), + GLYPH(0xF9CE), + U24(0x788C), + GLYPH(0xF93B), + U24(0x7891), + GLYPH(0xFA4B), + U24(0x78CA), + GLYPH(0xF947), + U24(0x78CC), + GLYPH(0xFAAB), + U24(0x78FB), + GLYPH(0xF964), + U24(0x792A), + GLYPH(0xF985), + U24(0x793C), + GLYPH(0xFA18), + U24(0x793E), + GLYPH(0xFA4C), + U24(0x7948), + GLYPH(0xFA4E), + U24(0x7949), + GLYPH(0xFA4D), + U24(0x7950), + GLYPH(0xFA4F), + U24(0x7956), + GLYPH(0xFA50), + U24(0x795D), + GLYPH(0xFA51), + U24(0x795E), + GLYPH(0xFA19), + U24(0x7965), + GLYPH(0xFA1A), + U24(0x797F), + GLYPH(0xF93C), + U24(0x798D), + GLYPH(0xFA52), + U24(0x798E), + GLYPH(0xFA53), + U24(0x798F), + GLYPH(0xFA1B), + U24(0x79AE), + GLYPH(0xF9B6), + U24(0x79CA), + GLYPH(0xF995), + U24(0x79EB), + GLYPH(0x2F957), + U24(0x7A1C), + GLYPH(0xF956), + U24(0x7A40), + GLYPH(0xFA54), + U24(0x7A4A), + GLYPH(0x2F95A), + U24(0x7A4F), + GLYPH(0x2F95B), + U24(0x7A81), + GLYPH(0xFA55), + U24(0x7AB1), + GLYPH(0xFAAC), + U24(0x7ACB), + GLYPH(0xF9F7), + U24(0x7AEE), + GLYPH(0x2F95F), + U24(0x7B20), + GLYPH(0xF9F8), + U24(0x7BC0), + GLYPH(0xFA56), + U24(0x7BC6), + GLYPH(0x2F962), + U24(0x7BC9), + GLYPH(0x2F963), + U24(0x7C3E), + GLYPH(0xF9A6), + U24(0x7C60), + GLYPH(0xF944), + U24(0x7C7B), + GLYPH(0xFAAE), + U24(0x7C92), + GLYPH(0xF9F9), + U24(0x7CBE), + GLYPH(0xFA1D), + U24(0x7CD2), + GLYPH(0x2F966), + U24(0x7CD6), + GLYPH(0xFA03), + U24(0x7CE3), + GLYPH(0x2F969), + U24(0x7CE7), + GLYPH(0xF97B), + U24(0x7CE8), + GLYPH(0x2F968), + U24(0x7D00), + GLYPH(0x2F96A), + U24(0x7D10), + GLYPH(0xF9CF), + U24(0x7D22), + GLYPH(0xF96A), + U24(0x7D2F), + GLYPH(0xF94F), + U24(0x7D5B), + GLYPH(0xFAAF), + U24(0x7D63), + GLYPH(0x2F96C), + U24(0x7DA0), + GLYPH(0xF93D), + U24(0x7DBE), + GLYPH(0xF957), + U24(0x7DC7), + GLYPH(0x2F96E), + U24(0x7DF4), + GLYPH(0xF996), + U24(0x7E02), + GLYPH(0x2F96F), + U24(0x7E09), + GLYPH(0xFA58), + U24(0x7E37), + GLYPH(0xF950), + U24(0x7E41), + GLYPH(0xFA59), + U24(0x7E45), + GLYPH(0x2F970), + U24(0x7F3E), + GLYPH(0xFAB1), + U24(0x7F72), + GLYPH(0xFA5A), + U24(0x7F79), + GLYPH(0xF9E6), + U24(0x7F7A), + GLYPH(0x2F976), + U24(0x7F85), + GLYPH(0xF90F), + U24(0x7F95), + GLYPH(0x2F978), + U24(0x7F9A), + GLYPH(0xF9AF), + U24(0x7FBD), + GLYPH(0xFA1E), + U24(0x7FFA), + GLYPH(0x2F979), + U24(0x8001), + GLYPH(0xF934), + U24(0x8005), + GLYPH(0xFA5B), + U24(0x8046), + GLYPH(0xF9B0), + U24(0x8060), + GLYPH(0x2F97D), + U24(0x806F), + GLYPH(0xF997), + U24(0x8070), + GLYPH(0x2F97F), + U24(0x807E), + GLYPH(0xF945), + U24(0x808B), + GLYPH(0xF953), + U24(0x80AD), + GLYPH(0x2F8D6), + U24(0x80B2), + GLYPH(0x2F982), + U24(0x8103), + GLYPH(0x2F983), + U24(0x813E), + GLYPH(0x2F985), + U24(0x81D8), + GLYPH(0xF926), + U24(0x81E8), + GLYPH(0xF9F6), + U24(0x81ED), + GLYPH(0xFA5C), + U24(0x8201), + GLYPH(0x2F893), + U24(0x8204), + GLYPH(0x2F98C), + U24(0x8218), + GLYPH(0xFA6D), + U24(0x826F), + GLYPH(0xF97C), + U24(0x8279), + GLYPH(0xFA5D), + U24(0x828B), + GLYPH(0x2F990), + U24(0x8291), + GLYPH(0x2F98F), + U24(0x829D), + GLYPH(0x2F991), + U24(0x82B1), + GLYPH(0x2F993), + U24(0x82B3), + GLYPH(0x2F994), + U24(0x82BD), + GLYPH(0x2F995), + U24(0x82E5), + GLYPH(0xF974), + U24(0x82E6), + GLYPH(0x2F996), + U24(0x831D), + GLYPH(0x2F999), + U24(0x8323), + GLYPH(0x2F99C), + U24(0x8336), + GLYPH(0xF9FE), + U24(0x8352), + GLYPH(0xFAB3), + U24(0x8353), + GLYPH(0x2F9A0), + U24(0x8363), + GLYPH(0x2F99A), + U24(0x83AD), + GLYPH(0x2F99B), + U24(0x83BD), + GLYPH(0x2F99D), + U24(0x83C9), + GLYPH(0xF93E), + U24(0x83CA), + GLYPH(0x2F9A1), + U24(0x83CC), + GLYPH(0x2F9A2), + U24(0x83DC), + GLYPH(0x2F9A3), + U24(0x83E7), + GLYPH(0x2F99E), + U24(0x83EF), + GLYPH(0xFAB4), + U24(0x83F1), + GLYPH(0xF958), + U24(0x843D), + GLYPH(0xF918), + U24(0x8449), + GLYPH(0xF96E), + U24(0x8457), + GLYPH(0xFA5F), + U24(0x84EE), + GLYPH(0xF999), + U24(0x84F1), + GLYPH(0x2F9A8), + U24(0x84F3), + GLYPH(0x2F9A9), + U24(0x84FC), + GLYPH(0xF9C2), + U24(0x8516), + GLYPH(0x2F9AA), + U24(0x8564), + GLYPH(0x2F9AC), + U24(0x85CD), + GLYPH(0xF923), + U24(0x85FA), + GLYPH(0xF9F0), + U24(0x8606), + GLYPH(0xF935), + U24(0x8612), + GLYPH(0xFA20), + U24(0x862D), + GLYPH(0xF91F), + U24(0x863F), + GLYPH(0xF910), + U24(0x8650), + GLYPH(0x2F9B3), + U24(0x865C), + GLYPH(0xF936), + U24(0x8667), + GLYPH(0x2F9B5), + U24(0x8669), + GLYPH(0x2F9B6), + U24(0x8688), + GLYPH(0x2F9B8), + U24(0x86A9), + GLYPH(0x2F9B7), + U24(0x86E2), + GLYPH(0x2F9BA), + U24(0x870E), + GLYPH(0x2F9B9), + U24(0x8728), + GLYPH(0x2F9BC), + U24(0x876B), + GLYPH(0x2F9BD), + U24(0x8779), + GLYPH(0xFAB5), + U24(0x8786), + GLYPH(0x2F9BE), + U24(0x87BA), + GLYPH(0xF911), + U24(0x87E1), + GLYPH(0x2F9C0), + U24(0x8801), + GLYPH(0x2F9C1), + U24(0x881F), + GLYPH(0xF927), + U24(0x884C), + GLYPH(0xFA08), + U24(0x8860), + GLYPH(0x2F9C3), + U24(0x8863), + GLYPH(0x2F9C4), + U24(0x88C2), + GLYPH(0xF9A0), + U24(0x88CF), + GLYPH(0xF9E7), + U24(0x88D7), + GLYPH(0x2F9C6), + U24(0x88DE), + GLYPH(0x2F9C7), + U24(0x88E1), + GLYPH(0xF9E8), + U24(0x88F8), + GLYPH(0xF912), + U24(0x88FA), + GLYPH(0x2F9C9), + U24(0x8910), + GLYPH(0xFA60), + U24(0x8941), + GLYPH(0xFAB6), + U24(0x8964), + GLYPH(0xF924), + U24(0x8986), + GLYPH(0xFAB7), + U24(0x898B), + GLYPH(0xFA0A), + U24(0x8996), + GLYPH(0xFA61), + U24(0x8AA0), + GLYPH(0x2F9CF), + U24(0x8AAA), + GLYPH(0xF96F), + U24(0x8ABF), + GLYPH(0xFAB9), + U24(0x8ACB), + GLYPH(0xFABB), + U24(0x8AD2), + GLYPH(0xF97D), + U24(0x8AD6), + GLYPH(0xF941), + U24(0x8AED), + GLYPH(0xFABE), + U24(0x8AF8), + GLYPH(0xFA22), + U24(0x8AFE), + GLYPH(0xF95D), + U24(0x8B01), + GLYPH(0xFA62), + U24(0x8B39), + GLYPH(0xFA63), + U24(0x8B58), + GLYPH(0xF9FC), + U24(0x8B80), + GLYPH(0xF95A), + U24(0x8B8A), + GLYPH(0xFAC0), + U24(0x8C48), + GLYPH(0xF900), + U24(0x8C55), + GLYPH(0x2F9D2), + U24(0x8CAB), + GLYPH(0x2F9D4), + U24(0x8CC1), + GLYPH(0x2F9D5), + U24(0x8CC2), + GLYPH(0xF948), + U24(0x8CC8), + GLYPH(0xF903), + U24(0x8CD3), + GLYPH(0xFA64), + U24(0x8D08), + GLYPH(0xFA65), + U24(0x8D1B), + GLYPH(0x2F9D6), + U24(0x8D77), + GLYPH(0x2F9D7), + U24(0x8DBC), + GLYPH(0x2F9DB), + U24(0x8DCB), + GLYPH(0x2F9DA), + U24(0x8DEF), + GLYPH(0xF937), + U24(0x8DF0), + GLYPH(0x2F9DC), + U24(0x8ECA), + GLYPH(0xF902), + U24(0x8ED4), + GLYPH(0x2F9DE), + U24(0x8F26), + GLYPH(0xF998), + U24(0x8F2A), + GLYPH(0xF9D7), + U24(0x8F38), + GLYPH(0xFAC2), + U24(0x8F3B), + GLYPH(0xFA07), + U24(0x8F62), + GLYPH(0xF98D), + U24(0x8F9E), + GLYPH(0x2F98D), + U24(0x8FB0), + GLYPH(0xF971), + U24(0x8FB6), + GLYPH(0xFA66), + U24(0x9023), + GLYPH(0xF99A), + U24(0x9038), + GLYPH(0xFA25), + U24(0x9072), + GLYPH(0xFAC3), + U24(0x907C), + GLYPH(0xF9C3), + U24(0x908F), + GLYPH(0xF913), + U24(0x9094), + GLYPH(0x2F9E2), + U24(0x90CE), + GLYPH(0xF92C), + U24(0x90DE), + GLYPH(0xFA2E), + U24(0x90F1), + GLYPH(0x2F9E3), + U24(0x90FD), + GLYPH(0xFA26), + U24(0x9111), + GLYPH(0x2F9E4), + U24(0x911B), + GLYPH(0x2F9E6), + U24(0x916A), + GLYPH(0xF919), + U24(0x9199), + GLYPH(0xFAC4), + U24(0x91B4), + GLYPH(0xF9B7), + U24(0x91CC), + GLYPH(0xF9E9), + U24(0x91CF), + GLYPH(0xF97E), + U24(0x91D1), + GLYPH(0xF90A), + U24(0x9234), + GLYPH(0xF9B1), + U24(0x9238), + GLYPH(0x2F9E7), + U24(0x9276), + GLYPH(0xFAC5), + U24(0x927C), + GLYPH(0x2F9EA), + U24(0x92D7), + GLYPH(0x2F9E8), + U24(0x92D8), + GLYPH(0x2F9E9), + U24(0x9304), + GLYPH(0xF93F), + U24(0x934A), + GLYPH(0xF99B), + U24(0x93F9), + GLYPH(0x2F9EB), + U24(0x9415), + GLYPH(0x2F9EC), + U24(0x958B), + GLYPH(0x2F9EE), + U24(0x95AD), + GLYPH(0xF986), + U24(0x95B7), + GLYPH(0x2F9F0), + U24(0x962E), + GLYPH(0xF9C6), + U24(0x964B), + GLYPH(0xF951), + U24(0x964D), + GLYPH(0xFA09), + U24(0x9675), + GLYPH(0xF959), + U24(0x9678), + GLYPH(0xF9D3), + U24(0x967C), + GLYPH(0xFAC6), + U24(0x9686), + GLYPH(0xF9DC), + U24(0x96A3), + GLYPH(0xF9F1), + U24(0x96B7), + GLYPH(0xFA2F), + U24(0x96B8), + GLYPH(0xF9B8), + U24(0x96C3), + GLYPH(0x2F9F3), + U24(0x96E2), + GLYPH(0xF9EA), + U24(0x96E3), + GLYPH(0xFA68), + U24(0x96F6), + GLYPH(0xF9B2), + U24(0x96F7), + GLYPH(0xF949), + U24(0x9723), + GLYPH(0x2F9F5), + U24(0x9732), + GLYPH(0xF938), + U24(0x9748), + GLYPH(0xF9B3), + U24(0x9756), + GLYPH(0xFA1C), + U24(0x97DB), + GLYPH(0xFAC9), + U24(0x97E0), + GLYPH(0x2F9FA), + U24(0x97FF), + GLYPH(0xFA69), + U24(0x980B), + GLYPH(0xFACB), + U24(0x9818), + GLYPH(0xF9B4), + U24(0x9829), + GLYPH(0x2FA00), + U24(0x983B), + GLYPH(0xFA6A), + U24(0x985E), + GLYPH(0xF9D0), + U24(0x98E2), + GLYPH(0x2FA02), + U24(0x98EF), + GLYPH(0xFA2A), + U24(0x98FC), + GLYPH(0xFA2B), + U24(0x9928), + GLYPH(0xFA2C), + U24(0x9929), + GLYPH(0x2FA04), + U24(0x99A7), + GLYPH(0x2FA05), + U24(0x99C2), + GLYPH(0x2FA06), + U24(0x99F1), + GLYPH(0xF91A), + U24(0x99FE), + GLYPH(0x2FA07), + U24(0x9A6A), + GLYPH(0xF987), + U24(0x9B12), + GLYPH(0xFACD), + U24(0x9B6F), + GLYPH(0xF939), + U24(0x9C40), + GLYPH(0x2FA0B), + U24(0x9C57), + GLYPH(0xF9F2), + U24(0x9CFD), + GLYPH(0x2FA0C), + U24(0x9D67), + GLYPH(0x2FA0F), + U24(0x9DB4), + GLYPH(0xFA2D), + U24(0x9DFA), + GLYPH(0xF93A), + U24(0x9E1E), + GLYPH(0xF920), + U24(0x9E7F), + GLYPH(0xF940), + U24(0x9E97), + GLYPH(0xF988), + U24(0x9E9F), + GLYPH(0xF9F3), + U24(0x9EBB), + GLYPH(0x2FA15), + U24(0x9ECE), + GLYPH(0xF989), + U24(0x9EF9), + GLYPH(0x2FA17), + U24(0x9EFE), + GLYPH(0x2FA18), + U24(0x9F05), + GLYPH(0x2FA19), + U24(0x9F0F), + GLYPH(0x2FA1A), + U24(0x9F16), + GLYPH(0x2FA1B), + U24(0x9F3B), + GLYPH(0x2FA1C), + U24(0x9F43), + GLYPH(0xFAD8), + U24(0x9F8D), + GLYPH(0xF9C4), + U24(0x9F8E), + GLYPH(0xFAD9), + U24(0x9F9C), + GLYPH(0xF907), + U24(0x20122), + GLYPH(0x2F803), + U24(0x2051C), + GLYPH(0x2F812), + U24(0x20525), + GLYPH(0x2F91B), + U24(0x2054B), + GLYPH(0x2F816), + U24(0x2063A), + GLYPH(0x2F80D), + U24(0x20804), + GLYPH(0x2F9D9), + U24(0x208DE), + GLYPH(0x2F9DD), + U24(0x20A2C), + GLYPH(0x2F834), + U24(0x20B63), + GLYPH(0x2F838), + U24(0x214E4), + GLYPH(0x2F859), + U24(0x216A8), + GLYPH(0x2F860), + U24(0x216EA), + GLYPH(0x2F861), + U24(0x219C8), + GLYPH(0x2F86C), + U24(0x21B18), + GLYPH(0x2F871), + U24(0x21D0B), + GLYPH(0x2F8F8), + U24(0x21DE4), + GLYPH(0x2F87B), + U24(0x21DE6), + GLYPH(0x2F87D), + U24(0x22183), + GLYPH(0x2F889), + U24(0x2219F), + GLYPH(0x2F939), + U24(0x22331), + GLYPH(0x2F891), + U24(0x226D4), + GLYPH(0x2F8A4), + U24(0x22844), + GLYPH(0xFAD0), + U24(0x2284A), + GLYPH(0xFACF), + U24(0x22B0C), + GLYPH(0x2F8B8), + U24(0x22BF1), + GLYPH(0x2F8BE), + U24(0x2300A), + GLYPH(0x2F8CA), + U24(0x232B8), + GLYPH(0x2F897), + U24(0x2335F), + GLYPH(0x2F980), + U24(0x23393), + GLYPH(0x2F989), + U24(0x2339C), + GLYPH(0x2F98A), + U24(0x233C3), + GLYPH(0x2F8DD), + U24(0x233D5), + GLYPH(0xFAD1), + U24(0x2346D), + GLYPH(0x2F8E3), + U24(0x236A3), + GLYPH(0x2F8EC), + U24(0x238A7), + GLYPH(0x2F8F0), + U24(0x23A8D), + GLYPH(0x2F8F7), + U24(0x23AFA), + GLYPH(0x2F8F9), + U24(0x23CBC), + GLYPH(0x2F8FB), + U24(0x23D1E), + GLYPH(0x2F906), + U24(0x23ED1), + GLYPH(0x2F90D), + U24(0x23F5E), + GLYPH(0x2F910), + U24(0x23F8E), + GLYPH(0x2F911), + U24(0x24263), + GLYPH(0x2F91D), + U24(0x242EE), + GLYPH(0xFA6C), + U24(0x243AB), + GLYPH(0x2F91F), + U24(0x24608), + GLYPH(0x2F923), + U24(0x24735), + GLYPH(0x2F926), + U24(0x24814), + GLYPH(0x2F927), + U24(0x24C36), + GLYPH(0x2F935), + U24(0x24C92), + GLYPH(0x2F937), + U24(0x24FA1), + GLYPH(0x2F93B), + U24(0x24FB8), + GLYPH(0x2F93C), + U24(0x25044), + GLYPH(0x2F93D), + U24(0x250F2), + GLYPH(0x2F942), + U24(0x250F3), + GLYPH(0x2F941), + U24(0x25119), + GLYPH(0x2F943), + U24(0x25133), + GLYPH(0x2F944), + U24(0x25249), + GLYPH(0xFAD5), + U24(0x2541D), + GLYPH(0x2F94D), + U24(0x25626), + GLYPH(0x2F952), + U24(0x2569A), + GLYPH(0x2F954), + U24(0x256C5), + GLYPH(0x2F955), + U24(0x2597C), + GLYPH(0x2F95C), + U24(0x25AA7), + GLYPH(0x2F95D), + U24(0x25BAB), + GLYPH(0x2F961), + U24(0x25C80), + GLYPH(0x2F965), + U24(0x25CD0), + GLYPH(0xFAD6), + U24(0x25F86), + GLYPH(0x2F96B), + U24(0x261DA), + GLYPH(0x2F898), + U24(0x26228), + GLYPH(0x2F972), + U24(0x26247), + GLYPH(0x2F973), + U24(0x262D9), + GLYPH(0x2F975), + U24(0x2633E), + GLYPH(0x2F977), + U24(0x264DA), + GLYPH(0x2F97B), + U24(0x26523), + GLYPH(0x2F97C), + U24(0x265A8), + GLYPH(0x2F97E), + U24(0x267A7), + GLYPH(0x2F987), + U24(0x267B5), + GLYPH(0x2F988), + U24(0x26B3C), + GLYPH(0x2F997), + U24(0x26C36), + GLYPH(0x2F9A4), + U24(0x26CD5), + GLYPH(0x2F9A6), + U24(0x26D6B), + GLYPH(0x2F9A5), + U24(0x26F2C), + GLYPH(0x2F9AD), + U24(0x26FB1), + GLYPH(0x2F9B0), + U24(0x270D2), + GLYPH(0x2F9B1), + U24(0x273CA), + GLYPH(0x2F9AB), + U24(0x27667), + GLYPH(0x2F9C5), + U24(0x278AE), + GLYPH(0x2F9CB), + U24(0x27966), + GLYPH(0x2F9CC), + U24(0x27CA8), + GLYPH(0x2F9D3), + U24(0x27ED3), + GLYPH(0xFAD7), + U24(0x27F2F), + GLYPH(0x2F9D8), + U24(0x285D2), + GLYPH(0x2F9E0), + U24(0x285ED), + GLYPH(0x2F9E1), + U24(0x2872E), + GLYPH(0x2F9E5), + U24(0x28BFA), + GLYPH(0x2F9ED), + U24(0x28D77), + GLYPH(0x2F9F1), + U24(0x29145), + GLYPH(0x2F9F6), + U24(0x291DF), + GLYPH(0x2F81C), + U24(0x2921A), + GLYPH(0x2F9F7), + U24(0x2940A), + GLYPH(0x2F9FB), + U24(0x29496), + GLYPH(0x2F9FD), + U24(0x295B6), + GLYPH(0x2FA01), + U24(0x29B30), + GLYPH(0x2FA09), + U24(0x2A0CE), + GLYPH(0x2FA10), + U24(0x2A105), + GLYPH(0x2FA12), + U24(0x2A20E), + GLYPH(0x2FA13), + U24(0x2A291), + GLYPH(0x2FA14), + U24(0x2A392), + GLYPH(0x2F88F), + U24(0x2A600), + GLYPH(0x2FA1D), + // 0xFE01 + U32(88), // numUVSMappings + U24(0x3B9D), + GLYPH(0x2F8E7), + U24(0x3EB8), + GLYPH(0x2F92D), + U24(0x4039), + GLYPH(0x2F949), + U24(0x4FAE), + GLYPH(0x2F805), + U24(0x50E7), + GLYPH(0x2F80A), + U24(0x514D), + GLYPH(0x2F80E), + U24(0x51B5), + GLYPH(0x2F81B), + U24(0x5207), + GLYPH(0x2F850), + U24(0x52C7), + GLYPH(0x2F825), + U24(0x52C9), + GLYPH(0x2F826), + U24(0x52E4), + GLYPH(0x2F827), + U24(0x52FA), + GLYPH(0x2F828), + U24(0x5317), + GLYPH(0x2F82B), + U24(0x5351), + GLYPH(0x2F82D), + U24(0x537F), + GLYPH(0x2F832), + U24(0x5584), + GLYPH(0x2F846), + U24(0x5599), + GLYPH(0x2F847), + U24(0x559D), + GLYPH(0xFA78), + U24(0x5606), + GLYPH(0x2F84C), + U24(0x585A), + GLYPH(0xFA7C), + U24(0x5B3E), + GLYPH(0x2F86B), + U24(0x5BE7), + GLYPH(0xF9AA), + U24(0x5C6E), + GLYPH(0x2F878), + U24(0x5ECA), + GLYPH(0x2F88E), + U24(0x5F22), + GLYPH(0x2F895), + U24(0x6094), + GLYPH(0x2F8A3), + U24(0x614C), + GLYPH(0x2F8A9), + U24(0x614E), + GLYPH(0x2F8A8), + U24(0x618E), + GLYPH(0xFA89), + U24(0x61F2), + GLYPH(0xFA8B), + U24(0x61F6), + GLYPH(0x2F8B1), + U24(0x654F), + GLYPH(0x2F8C8), + U24(0x6674), + GLYPH(0xFA91), + U24(0x6691), + GLYPH(0x2F8CF), + U24(0x6717), + GLYPH(0xFA92), + U24(0x671B), + GLYPH(0x2F8D9), + U24(0x6885), + GLYPH(0x2F8E2), + U24(0x6A02), + GLYPH(0xF95C), + U24(0x6BBA), + GLYPH(0xFA96), + U24(0x6D41), + GLYPH(0xFA97), + U24(0x6D77), + GLYPH(0x2F901), + U24(0x6ECB), + GLYPH(0x2F90B), + U24(0x6F22), + GLYPH(0xFA9A), + U24(0x701E), + GLYPH(0x2F914), + U24(0x716E), + GLYPH(0xFA9C), + U24(0x7235), + GLYPH(0x2F921), + U24(0x732A), + GLYPH(0xFAA0), + U24(0x7387), + GLYPH(0xF9DB), + U24(0x7471), + GLYPH(0x2F930), + U24(0x7570), + GLYPH(0x2F938), + U24(0x76CA), + GLYPH(0xFAA6), + U24(0x76F4), + GLYPH(0x2F940), + U24(0x771F), + GLYPH(0x2F947), + U24(0x774A), + GLYPH(0x2F948), + U24(0x788C), + GLYPH(0x2F94F), + U24(0x78CC), + GLYPH(0x2F950), + U24(0x7956), + GLYPH(0x2F953), + U24(0x798F), + GLYPH(0x2F956), + U24(0x7A40), + GLYPH(0x2F959), + U24(0x7BC0), + GLYPH(0xFAAD), + U24(0x7DF4), + GLYPH(0xFA57), + U24(0x8005), + GLYPH(0xFAB2), + U24(0x8201), + GLYPH(0x2F98B), + U24(0x8279), + GLYPH(0xFA5E), + U24(0x82E5), + GLYPH(0x2F998), + U24(0x8457), + GLYPH(0x2F99F), + U24(0x865C), + GLYPH(0x2F9B4), + U24(0x8779), + GLYPH(0x2F9BB), + U24(0x8996), + GLYPH(0xFAB8), + U24(0x8AAA), + GLYPH(0xF9A1), + U24(0x8AED), + GLYPH(0x2F9D0), + U24(0x8AF8), + GLYPH(0xFABA), + U24(0x8AFE), + GLYPH(0xFABD), + U24(0x8B01), + GLYPH(0xFABC), + U24(0x8B39), + GLYPH(0xFABF), + U24(0x8B8A), + GLYPH(0x2F9D1), + U24(0x8D08), + GLYPH(0xFAC1), + U24(0x8F38), + GLYPH(0x2F9DF), + U24(0x9038), + GLYPH(0xFA67), + U24(0x96E3), + GLYPH(0xFAC7), + U24(0x9756), + GLYPH(0xFAC8), + U24(0x97FF), + GLYPH(0xFACA), + U24(0x980B), + GLYPH(0x2F9FE), + U24(0x983B), + GLYPH(0xFACC), + U24(0x9B12), + GLYPH(0x2FA0A), + U24(0x9F9C), + GLYPH(0xF908), + U24(0x22331), + GLYPH(0x2F892), + U24(0x25AA7), + GLYPH(0x2F95E), + // 0xFE02 + U32(12), // numUVSMappings + U24(0x537F), + GLYPH(0x2F833), + U24(0x5BE7), + GLYPH(0x2F86F), + U24(0x618E), + GLYPH(0x2F8AB), + U24(0x61F2), + GLYPH(0x2F8B0), + U24(0x6717), + GLYPH(0x2F8D8), + U24(0x6A02), + GLYPH(0xF9BF), + U24(0x6BBA), + GLYPH(0x2F8F5), + U24(0x6D41), + GLYPH(0x2F902), + U24(0x7DF4), + GLYPH(0xFAB0), + U24(0x8005), + GLYPH(0x2F97A), + U24(0x980B), + GLYPH(0x2F9FF), + U24(0x9F9C), + GLYPH(0xFACE), +}; + +#undef U16 +#undef U24 +#undef U32 +#undef GLYPH + +static_assert(sizeof sCJKCompatSVSTable == 5065, "Table generator has a bug."); diff --git a/gfx/thebes/COLRFonts.cpp b/gfx/thebes/COLRFonts.cpp new file mode 100644 index 0000000000..6369bf191d --- /dev/null +++ b/gfx/thebes/COLRFonts.cpp @@ -0,0 +1,2673 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "COLRFonts.h" +#include "gfxFontUtils.h" +#include "gfxUtils.h" +#include "harfbuzz/hb.h" +#include "harfbuzz/hb-ot.h" +#include "mozilla/gfx/Helpers.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "TextDrawTarget.h" + +#include <limits> + +using namespace mozilla; +using namespace mozilla::gfx; + +namespace { // anonymous namespace for implementation internals + +#pragma pack(1) // ensure no padding is added to the COLR structs + +// Alias bigendian-reading types from gfxFontUtils to names used in the spec. +using int16 = AutoSwap_PRInt16; +using uint16 = AutoSwap_PRUint16; +using int32 = AutoSwap_PRInt32; +using uint32 = AutoSwap_PRUint32; +using FWORD = AutoSwap_PRInt16; +using UFWORD = AutoSwap_PRUint16; +using Offset16 = AutoSwap_PRUint16; +using Offset24 = AutoSwap_PRUint24; +using Offset32 = AutoSwap_PRUint32; + +struct COLRv1Header; +struct ClipList; +struct LayerRecord; +struct BaseGlyphRecord; +struct DeltaSetIndexMap; +struct ItemVariationStore; + +struct COLRHeader { + uint16 version; + uint16 numBaseGlyphRecords; + Offset32 baseGlyphRecordsOffset; + Offset32 layerRecordsOffset; + uint16 numLayerRecords; + + const BaseGlyphRecord* GetBaseGlyphRecords() const { + return reinterpret_cast<const BaseGlyphRecord*>( + reinterpret_cast<const char*>(this) + baseGlyphRecordsOffset); + } + + const LayerRecord* GetLayerRecords() const { + return reinterpret_cast<const LayerRecord*>( + reinterpret_cast<const char*>(this) + layerRecordsOffset); + } + + bool Validate(uint64_t aLength) const; +}; + +struct BaseGlyphPaintRecord { + uint16 glyphID; + Offset32 paintOffset; +}; + +struct BaseGlyphList { + uint32 numBaseGlyphPaintRecords; + // BaseGlyphPaintRecord baseGlyphPaintRecords[numBaseGlyphPaintRecords]; + const BaseGlyphPaintRecord* baseGlyphPaintRecords() const { + return reinterpret_cast<const BaseGlyphPaintRecord*>(this + 1); + } + bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const; +}; + +struct LayerList { + uint32 numLayers; + // uint32 paintOffsets[numLayers]; + const uint32* paintOffsets() const { + return reinterpret_cast<const uint32*>(this + 1); + } + bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const; +}; + +struct COLRv1Header { + COLRHeader base; + Offset32 baseGlyphListOffset; + Offset32 layerListOffset; + Offset32 clipListOffset; + Offset32 varIndexMapOffset; + Offset32 itemVariationStoreOffset; + + bool Validate(uint64_t aLength) const; + + const BaseGlyphList* baseGlyphList() const { + uint32_t offset = baseGlyphListOffset; + if (!offset) { + return nullptr; + } + const char* ptr = reinterpret_cast<const char*>(this) + offset; + return reinterpret_cast<const struct BaseGlyphList*>(ptr); + } + + const LayerList* layerList() const { + uint32_t offset = layerListOffset; + if (!offset) { + return nullptr; + } + const char* ptr = reinterpret_cast<const char*>(this) + offset; + return reinterpret_cast<const LayerList*>(ptr); + } + + const struct ClipList* clipList() const { + uint32_t offset = clipListOffset; + if (!offset) { + return nullptr; + } + const char* ptr = reinterpret_cast<const char*>(this) + offset; + return reinterpret_cast<const ClipList*>(ptr); + } + + const struct DeltaSetIndexMap* varIndexMap() const { + uint32_t offset = varIndexMapOffset; + if (!offset) { + return nullptr; + } + const char* ptr = reinterpret_cast<const char*>(this) + offset; + return reinterpret_cast<const DeltaSetIndexMap*>(ptr); + } + + const struct ItemVariationStore* itemVariationStore() const { + uint32_t offset = itemVariationStoreOffset; + if (!offset) { + return nullptr; + } + const char* ptr = reinterpret_cast<const char*>(this) + offset; + return reinterpret_cast<const ItemVariationStore*>(ptr); + } + + const BaseGlyphPaintRecord* GetBaseGlyphPaint(uint32_t aGlyphId) const; +}; + +struct PaintState { + union { + const COLRHeader* v0; + const COLRv1Header* v1; + } mHeader; + const sRGBColor* mPalette; + DrawTarget* mDrawTarget; + ScaledFont* mScaledFont; + const int* mCoords; + DrawOptions mDrawOptions; + uint32_t mCOLRLength; + sRGBColor mCurrentColor; + float mFontUnitsToPixels; + uint16_t mNumColors; + uint16_t mCoordCount; + nsTArray<uint32_t>* mVisited; + + const char* COLRv1BaseAddr() const { + return reinterpret_cast<const char*>(mHeader.v1); + } + + DeviceColor GetColor(uint16_t aPaletteIndex, float aAlpha) const; + + // Convert from FUnits (either integer or Fixed 16.16) to device pixels. + template <typename T> + float F2P(T aPixels) const { + return mFontUnitsToPixels * float(aPixels); + } +}; + +DeviceColor PaintState::GetColor(uint16_t aPaletteIndex, float aAlpha) const { + sRGBColor color; + if (aPaletteIndex < mNumColors) { + color = mPalette[uint16_t(aPaletteIndex)]; + } else if (aPaletteIndex == 0xffff) { + color = mCurrentColor; + } else { // Palette index out of range! Return transparent black. + color = sRGBColor(); + } + color.a *= aAlpha; + return ToDeviceColor(color); +} + +static bool DispatchPaint(const PaintState& aState, uint32_t aOffset, + const Rect* aBounds /* may be nullptr if unknown */); +static UniquePtr<Pattern> DispatchMakePattern(const PaintState& aState, + uint32_t aOffset); +static Rect DispatchGetBounds(const PaintState& aState, uint32_t aOffset); +static Matrix DispatchGetMatrix(const PaintState& aState, uint32_t aOffset); + +// Variation-data types +struct Fixed { + enum { kFractionBits = 16 }; + operator float() const { + return float(int32_t(value)) / float(1 << kFractionBits); + } + int32_t intRepr() const { return int32_t(value); } + + private: + int32 value; +}; + +struct F2DOT14 { + enum { kFractionBits = 14 }; + operator float() const { + return float(int16_t(value)) / float(1 << kFractionBits); + } + int32_t intRepr() const { return int16_t(value); } + + private: + int16 value; +}; + +// Saturating addition used for variation indexes to avoid wrap-around. +static uint32_t SatAdd(uint32_t a, uint32_t b) { + if (a <= std::numeric_limits<uint32_t>::max() - b) { + return a + b; + } + return std::numeric_limits<uint32_t>::max(); +} + +struct RegionAxisCoordinates { + F2DOT14 startCoord; + F2DOT14 peakCoord; + F2DOT14 endCoord; +}; + +struct VariationRegion { + // RegionAxisCoordinates regionAxes[axisCount]; + const RegionAxisCoordinates* regionAxes() const { + return reinterpret_cast<const RegionAxisCoordinates*>(this); + } +}; + +struct VariationRegionList { + uint16 axisCount; + uint16 regionCount; + // VariationRegion variationRegions[regionCount]; + const char* variationRegionsBase() const { + return reinterpret_cast<const char*>(this + 1); + } + size_t regionSize() const { + return uint16_t(axisCount) * sizeof(RegionAxisCoordinates); + } + const VariationRegion* getRegion(uint16_t i) const { + if (i >= uint16_t(regionCount)) { + return nullptr; + } + return reinterpret_cast<const VariationRegion*>(variationRegionsBase() + + i * regionSize()); + } + bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const { + if (variationRegionsBase() - reinterpret_cast<const char*>(aHeader) + + uint16_t(regionCount) * regionSize() > + aLength) { + return false; + } + return true; + } +}; + +struct DeltaSet { + // (int16 and int8) + // *or* + // (int32 and int16) deltaData[regionIndexCount]; +}; + +struct DeltaSetIndexMap { + enum { INNER_INDEX_BIT_COUNT_MASK = 0x0f, MAP_ENTRY_SIZE_MASK = 0x30 }; + uint8_t format; + uint8_t entryFormat; + union { + struct { + uint16 mapCount; + // uint8 mapData[variable]; + } v0; + struct { + uint32 mapCount; + // uint8 mapData[variable]; + } v1; + }; + uint32_t entrySize() const { + return (((entryFormat & MAP_ENTRY_SIZE_MASK) >> 4) + 1); + } + uint32_t map(uint32_t aIndex) const { + uint32_t mapCount; + const uint8_t* mapData; + switch (format) { + case 0: + mapCount = uint32_t(v0.mapCount); + mapData = reinterpret_cast<const uint8_t*>(&v0.mapCount) + + sizeof(v0.mapCount); + break; + case 1: + mapCount = uint32_t(v1.mapCount); + mapData = reinterpret_cast<const uint8_t*>(&v1.mapCount) + + sizeof(v1.mapCount); + break; + default: + // unknown DeltaSetIndexMap format + return aIndex; + } + if (!mapCount) { + return aIndex; + } + if (aIndex >= mapCount) { + aIndex = mapCount - 1; + } + const uint8_t* entryData = mapData + aIndex * entrySize(); + uint32_t entry = 0; + for (uint32_t i = 0; i < entrySize(); ++i) { + entry = (entry << 8) + entryData[i]; + } + uint16_t outerIndex = + entry >> ((entryFormat & INNER_INDEX_BIT_COUNT_MASK) + 1); + uint16_t innerIndex = + entry & ((1 << ((entryFormat & INNER_INDEX_BIT_COUNT_MASK) + 1)) - 1); + return (uint32_t(outerIndex) << 16) + innerIndex; + } + bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const; +}; + +enum EntryFormatMasks { + INNER_INDEX_BIT_COUNT_MASK = 0x0f, + MAP_ENTRY_SIZE_MASK = 0x30 +}; + +struct ItemVariationData { + enum { WORD_DELTA_COUNT_MASK = 0x7FFF, LONG_WORDS = 0x8000 }; + uint16 itemCount; + uint16 wordDeltaCount; + uint16 regionIndexCount; + // uint16 regionIndexes[regionIndexCount]; + const uint16* regionIndexes() const { + return reinterpret_cast<const uint16*>( + reinterpret_cast<const char*>(this + 1)); + } + // DeltaSet deltaSets[itemCount]; + const DeltaSet* deltaSets() const { + return reinterpret_cast<const DeltaSet*>( + reinterpret_cast<const char*>(this + 1) + + uint16_t(regionIndexCount) * sizeof(uint16)); + } + bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const; +}; + +struct ItemVariationStore { + uint16 format; + Offset32 variationRegionListOffset; + uint16 itemVariationDataCount; + // Offset32 itemVariationDataOffsets[itemVariationDataCount]; + const Offset32* itemVariationDataOffsets() const { + return reinterpret_cast<const Offset32*>( + reinterpret_cast<const char*>(this + 1)); + } + const VariationRegionList* variationRegionList() const { + return reinterpret_cast<const VariationRegionList*>( + reinterpret_cast<const char*>(this) + variationRegionListOffset); + } + bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const; +}; + +static int32_t ApplyVariation(const PaintState& aState, int32_t aValue, + uint32_t aIndex) { + if (aIndex == 0xffffffff) { + return aValue; + } + const auto* store = aState.mHeader.v1->itemVariationStore(); + if (!store || uint16_t(store->format) != 1) { + return aValue; + } + const DeltaSetIndexMap* map = aState.mHeader.v1->varIndexMap(); + uint32_t mappedIndex = map ? map->map(aIndex) : aIndex; + uint16_t outerIndex = mappedIndex >> 16; + uint16_t innerIndex = mappedIndex & 0xffff; + const auto* itemVariationDataOffsets = store->itemVariationDataOffsets(); + if (mappedIndex == 0xffffffff || + outerIndex >= uint16_t(store->itemVariationDataCount) || + !itemVariationDataOffsets[outerIndex]) { + return aValue; + } + const auto* regionList = store->variationRegionList(); + if (outerIndex >= uint16_t(store->itemVariationDataCount)) { + return aValue; + } + const auto* variationData = reinterpret_cast<const ItemVariationData*>( + reinterpret_cast<const char*>(store) + + itemVariationDataOffsets[outerIndex]); + if (innerIndex >= uint16_t(variationData->itemCount)) { + return aValue; + } + const auto* regionIndexes = variationData->regionIndexes(); + uint16_t regionIndexCount = variationData->regionIndexCount; + const DeltaSet* deltaSets = variationData->deltaSets(); + uint16_t wordDeltaCount = variationData->wordDeltaCount; + bool longWords = wordDeltaCount & ItemVariationData::LONG_WORDS; + wordDeltaCount &= ItemVariationData::WORD_DELTA_COUNT_MASK; + uint32_t deltaSetSize = (regionIndexCount + wordDeltaCount) << longWords; + const uint8_t* deltaData = + reinterpret_cast<const uint8_t*>(deltaSets) + deltaSetSize * innerIndex; + uint16_t deltaSize = longWords ? 4 : 2; + int32_t result = aValue; + for (uint16_t i = 0; i < regionIndexCount; ++i, deltaData += deltaSize) { + if (i == wordDeltaCount) { + deltaSize >>= 1; + } + const auto* region = regionList->getRegion(uint16_t(regionIndexes[i])); + if (!region) { + return aValue; + } + // XXX Should we do the calculations here in fixed-point? Check spec. + float scalar = -1.0; + for (uint16_t axisIndex = 0; axisIndex < uint16_t(regionList->axisCount); + ++axisIndex) { + const auto& axis = region->regionAxes()[axisIndex]; + float peak = axis.peakCoord; + if (peak == 0.0) { + // This axis cannot contribute to scalar. + continue; + } + float start = axis.startCoord; + float end = axis.endCoord; + float value = axisIndex < aState.mCoordCount + ? float(aState.mCoords[axisIndex]) / 16384.0f + : 0.0; + if (value < start || value > end) { + // Out of range: this region is not applicable. + scalar = -1.0; + break; + } + if (scalar < 0.0) { + scalar = 1.0; + } + if (value == peak) { + continue; + } + if (value < peak && peak > start) { + scalar *= (value - start) / (peak - start); + } else if (value > peak && peak < end) { + scalar *= (end - value) / (end - peak); + } + } + if (scalar <= 0.0) { + continue; + } + int32_t delta = *reinterpret_cast<const int8_t*>(deltaData); // sign-extend + for (uint16_t j = 1; j < deltaSize; ++j) { + delta = (delta << 8) | deltaData[j]; + } + delta = int32_t(floorf((float(delta) * scalar) + 0.5f)); + result += delta; + } + return result; +}; + +static float ApplyVariation(const PaintState& aState, Fixed aValue, + uint32_t aIndex) { + return float(ApplyVariation(aState, aValue.intRepr(), aIndex)) / + (1 << Fixed::kFractionBits); +} + +static float ApplyVariation(const PaintState& aState, F2DOT14 aValue, + uint32_t aIndex) { + return float(ApplyVariation(aState, aValue.intRepr(), aIndex)) / + (1 << F2DOT14::kFractionBits); +} + +struct ClipBoxFormat1 { + enum { kFormat = 1 }; + uint8_t format; + FWORD xMin; + FWORD yMin; + FWORD xMax; + FWORD yMax; + + Rect GetRect(const PaintState& aState) const { + MOZ_ASSERT(format == kFormat); + int32_t x0 = int16_t(xMin); + int32_t y0 = int16_t(yMin); + int32_t x1 = int16_t(xMax); + int32_t y1 = int16_t(yMax); + // Flip the y-coordinates to map from OpenType to Moz2d space. + return Rect(aState.F2P(x0), -aState.F2P(y1), aState.F2P(x1 - x0), + aState.F2P(y1 - y0)); + } +}; + +struct ClipBoxFormat2 : public ClipBoxFormat1 { + enum { kFormat = 2 }; + uint32 varIndexBase; + + Rect GetRect(const PaintState& aState) const { + MOZ_ASSERT(format == kFormat); + int32_t x0 = ApplyVariation(aState, int16_t(xMin), varIndexBase); + int32_t y0 = ApplyVariation(aState, int16_t(yMin), SatAdd(varIndexBase, 1)); + int32_t x1 = ApplyVariation(aState, int16_t(xMax), SatAdd(varIndexBase, 2)); + int32_t y1 = ApplyVariation(aState, int16_t(yMax), SatAdd(varIndexBase, 3)); + return Rect(aState.F2P(x0), -aState.F2P(y1), aState.F2P(x1 - x0), + aState.F2P(y1 - y0)); + } +}; + +struct Clip { + uint16 startGlyphID; + uint16 endGlyphID; + Offset24 clipBoxOffset; + + Rect GetRect(const PaintState& aState) const { + uint32_t offset = aState.mHeader.v1->clipListOffset + clipBoxOffset; + const auto* box = aState.COLRv1BaseAddr() + offset; + switch (*box) { + case 1: + return reinterpret_cast<const ClipBoxFormat1*>(box)->GetRect(aState); + case 2: + return reinterpret_cast<const ClipBoxFormat2*>(box)->GetRect(aState); + default: + // unknown ClipBoxFormat + break; + } + return Rect(); + } + bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const; +}; + +struct ClipList { + uint8_t format; + uint32 numClips; + // Clip clips[numClips] + const Clip* clips() const { return reinterpret_cast<const Clip*>(this + 1); } + const Clip* GetClip(uint32_t aGlyphId) const { + auto compare = [](const void* key, const void* data) -> int { + uint32_t glyphId = (uint32_t)(uintptr_t)key; + const auto* clip = reinterpret_cast<const Clip*>(data); + uint32_t start = uint16_t(clip->startGlyphID); + uint32_t end = uint16_t(clip->endGlyphID); + if (start <= glyphId && end >= glyphId) { + return 0; + } + return start > glyphId ? -1 : 1; + }; + return reinterpret_cast<const Clip*>(bsearch((void*)(uintptr_t)aGlyphId, + clips(), uint32_t(numClips), + sizeof(Clip), compare)); + } + bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const; +}; + +struct LayerRecord { + uint16 glyphId; + uint16 paletteEntryIndex; + + bool Paint(const PaintState& aState, float aAlpha, + const Point& aPoint) const { + Glyph glyph{uint16_t(glyphId), aPoint}; + GlyphBuffer buffer{&glyph, 1}; + aState.mDrawTarget->FillGlyphs( + aState.mScaledFont, buffer, + ColorPattern(aState.GetColor(paletteEntryIndex, aAlpha)), + aState.mDrawOptions); + return true; + } +}; + +struct BaseGlyphRecord { + uint16 glyphId; + uint16 firstLayerIndex; + uint16 numLayers; + + bool Paint(const PaintState& aState, float aAlpha, + const Point& aPoint) const { + uint32_t layerIndex = uint16_t(firstLayerIndex); + uint32_t end = layerIndex + uint16_t(numLayers); + if (end > uint16_t(aState.mHeader.v0->numLayerRecords)) { + MOZ_ASSERT_UNREACHABLE("bad COLRv0 table"); + return false; + } + const auto* layers = aState.mHeader.v0->GetLayerRecords(); + while (layerIndex < end) { + if (!layers[layerIndex].Paint(aState, aAlpha, aPoint)) { + return false; + } + ++layerIndex; + } + return true; + } +}; + +struct ColorStop { + F2DOT14 stopOffset; + uint16 paletteIndex; + F2DOT14 alpha; + + float GetStopOffset(const PaintState& aState) const { return stopOffset; } + uint16_t GetPaletteIndex() const { return paletteIndex; } + float GetAlpha(const PaintState& aState) const { return alpha; } +}; + +struct VarColorStop : public ColorStop { + uint32 varIndexBase; + + float GetStopOffset(const PaintState& aState) const { + return ApplyVariation(aState, stopOffset, varIndexBase); + } + float GetAlpha(const PaintState& aState) const { + return ApplyVariation(aState, alpha, SatAdd(varIndexBase, 1)); + } +}; + +template <typename T> +struct ColorLineT { + enum { EXTEND_PAD = 0, EXTEND_REPEAT = 1, EXTEND_REFLECT = 2 }; + uint8_t extend; + uint16 numStops; + const T* colorStops() const { return reinterpret_cast<const T*>(this + 1); } + + // If the color line has only one stop, return it as a simple ColorPattern. + UniquePtr<Pattern> AsSolidColor(const PaintState& aState) const { + if (uint16_t(numStops) != 1) { + return nullptr; + } + const auto* stop = colorStops(); + return MakeUnique<ColorPattern>( + aState.GetColor(stop->GetPaletteIndex(), stop->GetAlpha(aState))); + } + + // Retrieve the color stops into an array of GradientStop records. The stops + // are normalized to the range [0 .. 1], and the original offsets of the + // first and last stops are returned. + // If aReverse is true, the color line is reversed. + void CollectGradientStops(const PaintState& aState, + nsTArray<GradientStop>& aStops, float* aFirstStop, + float* aLastStop, bool aReverse = false) const { + MOZ_ASSERT(aStops.IsEmpty()); + uint16_t count = numStops; + if (!count) { + return; + } + const auto* stop = colorStops(); + if (reinterpret_cast<const char*>(stop) + count * sizeof(T) > + aState.COLRv1BaseAddr() + aState.mCOLRLength) { + return; + } + aStops.SetCapacity(count); + for (uint16_t i = 0; i < count; ++i, ++stop) { + DeviceColor color = + aState.GetColor(stop->GetPaletteIndex(), stop->GetAlpha(aState)); + aStops.AppendElement(GradientStop{stop->GetStopOffset(aState), color}); + } + if (count == 1) { + *aFirstStop = *aLastStop = aStops[0].offset; + return; + } + aStops.StableSort(); + if (aReverse) { + float a = aStops[0].offset; + float b = aStops.LastElement().offset; + aStops.Reverse(); + for (auto& gs : aStops) { + gs.offset = a + b - gs.offset; + } + } + // Normalize stops to the range 0.0 .. 1.0, and return the original + // start & end. + // Note that if all stops are at the same offset, no normalization + // will be done. + *aFirstStop = aStops[0].offset; + *aLastStop = aStops.LastElement().offset; + if ((*aLastStop > *aFirstStop) && + (*aLastStop != 1.0f || *aFirstStop != 0.0f)) { + float f = 1.0f / (*aLastStop - *aFirstStop); + for (auto& gs : aStops) { + gs.offset = (gs.offset - *aFirstStop) * f; + } + } + } + + // Create a gfx::GradientStops representing the given color line stops, + // applying our extend mode. + already_AddRefed<GradientStops> MakeGradientStops( + const PaintState& aState, nsTArray<GradientStop>& aStops) const { + auto mapExtendMode = [](uint8_t aExtend) -> ExtendMode { + switch (aExtend) { + case EXTEND_REPEAT: + return ExtendMode::REPEAT; + case EXTEND_REFLECT: + return ExtendMode::REFLECT; + case EXTEND_PAD: + default: + return ExtendMode::CLAMP; + } + }; + return aState.mDrawTarget->CreateGradientStops( + aStops.Elements(), aStops.Length(), mapExtendMode(extend)); + } + + already_AddRefed<GradientStops> MakeGradientStops( + const PaintState& aState, float* aFirstStop, float* aLastStop, + bool aReverse = false) const { + AutoTArray<GradientStop, 8> stops; + CollectGradientStops(aState, stops, aFirstStop, aLastStop, aReverse); + if (stops.IsEmpty()) { + return nullptr; + } + return MakeGradientStops(aState, stops); + } +}; + +using ColorLine = ColorLineT<ColorStop>; +using VarColorLine = ColorLineT<VarColorStop>; + +// Used to check for cycles in the paint graph, and bail out to avoid infinite +// recursion when traversing the graph in Paint() or GetBoundingRect(). (Only +// PaintColrLayers and PaintColrGlyph can cause cycles; all other paint types +// have only forward references within the table.) +#define IF_CYCLE_RETURN(retval) \ + if (aState.mVisited->Contains(aOffset)) { \ + return retval; \ + } \ + aState.mVisited->AppendElement(aOffset); \ + ScopeExit e([aState]() { aState.mVisited->RemoveLastElement(); }) + +struct PaintColrLayers { + enum { kFormat = 1 }; + uint8_t format; + uint8_t numLayers; + uint32 firstLayerIndex; + + bool Paint(const PaintState& aState, uint32_t aOffset, + const Rect* aBounds) const { + MOZ_ASSERT(format == kFormat); + IF_CYCLE_RETURN(true); + const auto* layerList = aState.mHeader.v1->layerList(); + if (!layerList) { + return false; + } + if (uint64_t(firstLayerIndex) + numLayers > layerList->numLayers) { + return false; + } + const auto* paintOffsets = layerList->paintOffsets() + firstLayerIndex; + for (uint32_t i = 0; i < numLayers; i++) { + if (!DispatchPaint(aState, + aState.mHeader.v1->layerListOffset + paintOffsets[i], + aBounds)) { + return false; + } + } + return true; + } + + Rect GetBoundingRect(const PaintState& aState, uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + IF_CYCLE_RETURN(Rect()); + const auto* layerList = aState.mHeader.v1->layerList(); + if (!layerList) { + return Rect(); + } + if (uint64_t(firstLayerIndex) + numLayers > layerList->numLayers) { + return Rect(); + } + Rect result; + const auto* paintOffsets = layerList->paintOffsets() + firstLayerIndex; + for (uint32_t i = 0; i < numLayers; i++) { + result = result.Union(DispatchGetBounds( + aState, aState.mHeader.v1->layerListOffset + paintOffsets[i])); + } + return result; + } +}; + +struct PaintPatternBase { + bool Paint(const PaintState& aState, uint32_t aOffset, + const Rect* aBounds) const { + Matrix m = aState.mDrawTarget->GetTransform(); + if (m.Invert()) { + if (auto pattern = DispatchMakePattern(aState, aOffset)) { + aState.mDrawTarget->FillRect( + m.TransformBounds(IntRectToRect(aState.mDrawTarget->GetRect())), + *pattern, aState.mDrawOptions); + return true; + } + } + return false; + } + + Rect GetBoundingRect(const PaintState& aState, uint32_t aOffset) const { + return Rect(); + } +}; + +struct PaintSolid : public PaintPatternBase { + enum { kFormat = 2 }; + uint8_t format; + uint16 paletteIndex; + F2DOT14 alpha; + + UniquePtr<Pattern> MakePattern(const PaintState& aState, + uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + return MakeUnique<ColorPattern>(aState.GetColor(paletteIndex, alpha)); + } +}; + +struct PaintVarSolid : public PaintSolid { + enum { kFormat = 3 }; + uint32 varIndexBase; + + UniquePtr<Pattern> MakePattern(const PaintState& aState, + uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + return MakeUnique<ColorPattern>(aState.GetColor( + paletteIndex, ApplyVariation(aState, alpha, varIndexBase))); + } +}; + +struct PaintLinearGradient : public PaintPatternBase { + enum { kFormat = 4 }; + uint8_t format; + Offset24 colorLineOffset; + FWORD x0; + FWORD y0; + FWORD x1; + FWORD y1; + FWORD x2; + FWORD y2; + + UniquePtr<Pattern> MakePattern(const PaintState& aState, + uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + uint32_t clOffset = aOffset + colorLineOffset; + if (clOffset + sizeof(ColorLine) + sizeof(ColorStop) > aState.mCOLRLength) { + return nullptr; + } + const auto* colorLine = + reinterpret_cast<const ColorLine*>(aState.COLRv1BaseAddr() + clOffset); + Point p0(aState.F2P(int16_t(x0)), aState.F2P(int16_t(y0))); + Point p1(aState.F2P(int16_t(x1)), aState.F2P(int16_t(y1))); + Point p2(aState.F2P(int16_t(x2)), aState.F2P(int16_t(y2))); + return NormalizeAndMakeGradient(aState, colorLine, p0, p1, p2); + } + + template <typename T> + UniquePtr<Pattern> NormalizeAndMakeGradient(const PaintState& aState, + const T* aColorLine, Point p0, + Point p1, Point p2) const { + // Ill-formed gradient should not be rendered. + if (p1 == p0 || p2 == p0) { + return MakeUnique<ColorPattern>(DeviceColor()); + } + UniquePtr<Pattern> solidColor = aColorLine->AsSolidColor(aState); + if (solidColor) { + return solidColor; + } + float firstStop, lastStop; + AutoTArray<GradientStop, 8> stopArray; + aColorLine->CollectGradientStops(aState, stopArray, &firstStop, &lastStop); + if (stopArray.IsEmpty()) { + return MakeUnique<ColorPattern>(DeviceColor()); + } + if (firstStop != 0.0 || lastStop != 1.0) { + if (firstStop == lastStop) { + if (aColorLine->extend != T::EXTEND_PAD) { + return MakeUnique<ColorPattern>(DeviceColor()); + } + // For extend-pad, when the color line is zero-length, we add a "fake" + // color stop to create a [0.0..1.0]-normalized color line, so that the + // projection of points below works as expected. + for (auto& gs : stopArray) { + gs.offset = 0.0f; + } + stopArray.AppendElement( + GradientStop{1.0f, stopArray.LastElement().color}); + lastStop += 1.0f; + } + // Adjust positions of the points to account for normalization of the + // color line stop offsets. + Point v = p1 - p0; + p0 += v * firstStop; + p1 -= v * (1.0f - lastStop); + // Move the rotation vector to maintain the same direction from p0. + p2 += v * firstStop; + } + Point p3; + if (FuzzyEqualsMultiplicative(p2.y, p0.y)) { + // rotation vector is horizontal + p3 = Point(p0.x, p1.y); + } else if (FuzzyEqualsMultiplicative(p2.x, p0.x)) { + // rotation vector is vertical + p3 = Point(p1.x, p0.y); + } else { + float m = (p2.y - p0.y) / (p2.x - p0.x); // slope of line p0->p2 + float mInv = -1.0f / m; // slope of desired perpendicular p0->p3 + float c1 = p0.y - mInv * p0.x; // line p0->p3 is m * x - y + c1 = 0 + float c2 = p1.y - m * p1.x; // line p1->p3 is mInv * x - y + c2 = 0 + float x3 = (c1 - c2) / (m - mInv); + float y3 = (c1 * m - c2 * mInv) / (m - mInv); + p3 = Point(x3, y3); + } + RefPtr stops = aColorLine->MakeGradientStops(aState, stopArray); + return MakeUnique<LinearGradientPattern>(p0, p3, std::move(stops), + Matrix::Scaling(1.0, -1.0)); + } +}; + +struct PaintVarLinearGradient : public PaintLinearGradient { + enum { kFormat = 5 }; + uint32 varIndexBase; + + UniquePtr<Pattern> MakePattern(const PaintState& aState, + uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + uint32_t clOffset = aOffset + colorLineOffset; + if (clOffset + sizeof(VarColorLine) + sizeof(VarColorStop) > + aState.mCOLRLength) { + return nullptr; + } + const auto* colorLine = reinterpret_cast<const VarColorLine*>( + aState.COLRv1BaseAddr() + clOffset); + Point p0(aState.F2P(ApplyVariation(aState, int16_t(x0), varIndexBase)), + aState.F2P( + ApplyVariation(aState, int16_t(y0), SatAdd(varIndexBase, 1)))); + Point p1(aState.F2P( + ApplyVariation(aState, int16_t(x1), SatAdd(varIndexBase, 2))), + aState.F2P( + ApplyVariation(aState, int16_t(y1), SatAdd(varIndexBase, 3)))); + Point p2(aState.F2P( + ApplyVariation(aState, int16_t(x2), SatAdd(varIndexBase, 4))), + aState.F2P( + ApplyVariation(aState, int16_t(y2), SatAdd(varIndexBase, 5)))); + return NormalizeAndMakeGradient(aState, colorLine, p0, p1, p2); + } +}; + +struct PaintRadialGradient : public PaintPatternBase { + enum { kFormat = 6 }; + uint8_t format; + Offset24 colorLineOffset; + FWORD x0; + FWORD y0; + UFWORD radius0; + FWORD x1; + FWORD y1; + UFWORD radius1; + + UniquePtr<Pattern> MakePattern(const PaintState& aState, + uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + uint32_t clOffset = aOffset + colorLineOffset; + if (clOffset + sizeof(ColorLine) + sizeof(ColorStop) > aState.mCOLRLength) { + return nullptr; + } + const auto* colorLine = + reinterpret_cast<const ColorLine*>(aState.COLRv1BaseAddr() + clOffset); + Point c1(aState.F2P(int16_t(x0)), aState.F2P(int16_t(y0))); + Point c2(aState.F2P(int16_t(x1)), aState.F2P(int16_t(y1))); + float r1 = aState.F2P(uint16_t(radius0)); + float r2 = aState.F2P(uint16_t(radius1)); + return NormalizeAndMakeGradient(aState, colorLine, c1, c2, r1, r2); + } + + // Helper function to trim the gradient stops array at the start or end. + void TruncateGradientStops(nsTArray<GradientStop>& aStops, float aStart, + float aEnd) const { + // For pad mode, we may need a sub-range of the line: figure out which + // stops to trim, and interpolate as needed at truncation point(s). + // (Currently this is only ever used to trim one end of the color line, + // so edge cases that may occur when trimming both ends are untested.) + MOZ_ASSERT(aStart == 0.0f || aEnd == 1.0f, + "Trimming both ends of color-line is untested!"); + + // Create a color that is |r| of the way from c1 to c2. + auto interpolateColor = [](DeviceColor c1, DeviceColor c2, float r) { + return DeviceColor( + c2.r * r + c1.r * (1.0f - r), c2.g * r + c1.g * (1.0f - r), + c2.b * r + c1.b * (1.0f - r), c2.a * r + c1.a * (1.0f - r)); + }; + + size_t count = aStops.Length(); + MOZ_ASSERT(count > 1); + + // Truncate at the start of the color line? + if (aStart > 0.0f) { + // Skip forward past any stops that can be dropped. + size_t i = 0; + while (i < count - 1 && aStops[i].offset < aStart) { + ++i; + } + // If we're not truncating exactly at a color-stop offset, shift the + // preceding stop to the truncation offset and interpolate its color. + if (i && aStops[i].offset > aStart) { + auto& prev = aStops[i - 1]; + auto& curr = aStops[i]; + float ratio = (aStart - prev.offset) / (curr.offset - prev.offset); + prev.color = interpolateColor(prev.color, curr.color, ratio); + prev.offset = aStart; + --i; // We don't want to remove this stop, as we adjusted it. + } + aStops.RemoveElementsAt(0, i); + // Re-normalize the remaining stops to the [0, 1] range. + if (aStart < 1.0f) { + float r = 1.0f / (1.0f - aStart); + for (auto& gs : aStops) { + gs.offset = r * (gs.offset - aStart); + } + } + } + + // Truncate at the end of the color line? + if (aEnd < 1.0f) { + // Skip back over any stops that can be dropped. + size_t i = count - 1; + while (i && aStops[i].offset > aEnd) { + --i; + } + // If we're not truncating exactly at a color-stop offset, shift the + // following stop to the truncation offset and interpolate its color. + if (i + 1 < count && aStops[i].offset < aEnd) { + auto& next = aStops[i + 1]; + auto& curr = aStops[i]; + float ratio = (aEnd - curr.offset) / (next.offset - curr.offset); + next.color = interpolateColor(curr.color, next.color, ratio); + next.offset = aEnd; + ++i; + } + aStops.RemoveElementsAt(i + 1, count - i - 1); + // Re-normalize the remaining stops to the [0, 1] range. + if (aEnd > 0.0f) { + float r = 1.0f / aEnd; + for (auto& gs : aStops) { + gs.offset = r * gs.offset; + } + } + } + } + + template <typename T> + UniquePtr<Pattern> NormalizeAndMakeGradient(const PaintState& aState, + const T* aColorLine, Point c1, + Point c2, float r1, + float r2) const { + if ((c1 == c2 && r1 == r2) || (r1 == 0.0 && r2 == 0.0)) { + return MakeUnique<ColorPattern>(DeviceColor()); + } + UniquePtr<Pattern> solidColor = aColorLine->AsSolidColor(aState); + if (solidColor) { + return solidColor; + } + float firstStop, lastStop; + AutoTArray<GradientStop, 8> stopArray; + aColorLine->CollectGradientStops(aState, stopArray, &firstStop, &lastStop); + if (stopArray.IsEmpty()) { + return MakeUnique<ColorPattern>(DeviceColor()); + } + // If the color stop offsets had to be normalized to the [0, 1] range, + // adjust the circle positions and radii to match. + if (firstStop != 0.0f || lastStop != 1.0f) { + if (firstStop == lastStop) { + if (aColorLine->extend != T::EXTEND_PAD) { + return MakeUnique<ColorPattern>(DeviceColor()); + } + // For extend-pad, when the color line is zero-length, we add a "fake" + // color stop to ensure we'll maintain the orientation of the cone, + // otherwise when we adjust circles to account for the normalized color + // stops, the centers will coalesce and the cone or cylinder collapses. + for (auto& gs : stopArray) { + gs.offset = 0.0f; + } + stopArray.AppendElement( + GradientStop{1.0f, stopArray.LastElement().color}); + lastStop += 1.0f; + } + // Adjust centers along the vector between them, and scale radii for + // gradient line defined from 0.0 to 1.0. + Point vec = c2 - c1; + c1 += vec * firstStop; + c2 -= vec * (1.0f - lastStop); + float deltaR = r2 - r1; + r1 = r1 + deltaR * firstStop; + r2 = r2 - deltaR * (1.0f - lastStop); + } + if ((r1 < 0.0f || r2 < 0.0f) && aColorLine->extend == T::EXTEND_PAD) { + // For EXTEND_PAD, we can restrict the gradient definition to just its + // visible portion because the shader doesn't need to see any part of the + // color line that extends into the negative-radius "virtual cone". + if (r1 < 0.0f && r2 < 0.0f) { + // If both radii are negative, then only the color at the closer circle + // will appear in the projected positive cone (or if they're equal, + // nothing will be visible at all). + if (r1 == r2) { + return MakeUnique<ColorPattern>(DeviceColor()); + } + // The defined range of the color line is entirely in the invisible + // cone; all that will project into visible space is a single color. + if (r1 < r2) { + // Keep only the last color stop. + stopArray.RemoveElementsAt(0, stopArray.Length() - 1); + } else { + // Keep only the first color stop. + stopArray.RemoveElementsAt(1, stopArray.Length() - 1); + } + } else { + // Truncate the gradient at the tip of the visible cone: find the color + // stops closest to that point and interpolate between them. + if (r1 < r2) { + float start = r1 / (r1 - r2); + TruncateGradientStops(stopArray, start, 1.0f); + r1 = 0.0f; + c1 = c1 * (1.0f - start) + c2 * start; + } else if (r2 < r1) { + float end = 1.0f - r2 / (r2 - r1); + TruncateGradientStops(stopArray, 0.0f, end); + r2 = 0.0f; + c2 = c1 * (1.0f - end) + c2 * end; + } + } + } + // Handle negative radii, which the shader won't understand directly, by + // projecting the circles along the cones such that both radii are positive. + if (r1 < 0.0f || r2 < 0.0f) { + float deltaR = r2 - r1; + // If deltaR is zero, then nothing is visible because the cone has + // degenerated into a negative-radius cylinder, and does not project + // into visible space at all. + if (deltaR == 0.0f) { + return MakeUnique<ColorPattern>(DeviceColor()); + } + Point vec = c2 - c1; + if (aColorLine->extend == T::EXTEND_REFLECT) { + deltaR *= 2.0f; + vec = vec * 2.0f; + } + if (r2 < r1) { + vec = -vec; + deltaR = -deltaR; + } + // Number of repeats by which we need to shift. + float n = std::ceil(std::max(-r1, -r2) / deltaR); + deltaR *= n; + r1 += deltaR; + r2 += deltaR; + vec = vec * n; + c1 += vec; + c2 += vec; + } + RefPtr stops = aColorLine->MakeGradientStops(aState, stopArray); + if (!stops) { + return MakeUnique<ColorPattern>(DeviceColor()); + } + return MakeUnique<RadialGradientPattern>(c1, c2, r1, r2, std::move(stops), + Matrix::Scaling(1.0, -1.0)); + } +}; + +struct PaintVarRadialGradient : public PaintRadialGradient { + enum { kFormat = 7 }; + uint32 varIndexBase; + + UniquePtr<Pattern> MakePattern(const PaintState& aState, + uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + uint32_t clOffset = aOffset + colorLineOffset; + if (clOffset + sizeof(VarColorLine) + sizeof(VarColorStop) > + aState.mCOLRLength) { + return nullptr; + } + const auto* colorLine = reinterpret_cast<const VarColorLine*>( + aState.COLRv1BaseAddr() + clOffset); + Point c1(aState.F2P(ApplyVariation(aState, int16_t(x0), varIndexBase)), + aState.F2P( + ApplyVariation(aState, int16_t(y0), SatAdd(varIndexBase, 1)))); + float r1 = aState.F2P( + ApplyVariation(aState, uint16_t(radius0), SatAdd(varIndexBase, 2))); + Point c2(aState.F2P( + ApplyVariation(aState, int16_t(x1), SatAdd(varIndexBase, 3))), + aState.F2P( + ApplyVariation(aState, int16_t(y1), SatAdd(varIndexBase, 4)))); + float r2 = aState.F2P( + ApplyVariation(aState, uint16_t(radius1), SatAdd(varIndexBase, 5))); + return NormalizeAndMakeGradient(aState, colorLine, c1, c2, r1, r2); + } +}; + +struct PaintSweepGradient : public PaintPatternBase { + enum { kFormat = 8 }; + uint8_t format; + Offset24 colorLineOffset; + FWORD centerX; + FWORD centerY; + F2DOT14 startAngle; + F2DOT14 endAngle; + + UniquePtr<Pattern> MakePattern(const PaintState& aState, + uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + uint32_t clOffset = aOffset + colorLineOffset; + if (clOffset + sizeof(ColorLine) + sizeof(ColorStop) > aState.mCOLRLength) { + return nullptr; + } + const auto* colorLine = + reinterpret_cast<const ColorLine*>(aState.COLRv1BaseAddr() + clOffset); + float start = float(startAngle) + 1.0f; + float end = float(endAngle) + 1.0f; + Point center(aState.F2P(int16_t(centerX)), aState.F2P(int16_t(centerY))); + return NormalizeAndMakeGradient(aState, colorLine, center, start, end); + } + + template <typename T> + UniquePtr<Pattern> NormalizeAndMakeGradient(const PaintState& aState, + const T* aColorLine, + Point aCenter, float aStart, + float aEnd) const { + if (aStart == aEnd && aColorLine->extend != T::EXTEND_PAD) { + return MakeUnique<ColorPattern>(DeviceColor()); + } + UniquePtr<Pattern> solidColor = aColorLine->AsSolidColor(aState); + if (solidColor) { + return solidColor; + } + // ConicGradientPattern works counterclockwise. If the gradient is defined + // clockwise (with aStart greater than aEnd), we'll reverse the color line + // and swap the start and end angles. + bool reverse = aEnd < aStart; + float firstStop, lastStop; + RefPtr stops = + aColorLine->MakeGradientStops(aState, &firstStop, &lastStop, reverse); + if (!stops) { + return nullptr; + } + if (firstStop != 0.0 || lastStop != 1.0) { + if (firstStop == lastStop) { + if (aColorLine->extend != T::EXTEND_PAD) { + return MakeUnique<ColorPattern>(DeviceColor()); + } + } else { + float sweep = aEnd - aStart; + aStart = aStart + sweep * firstStop; + aEnd = aStart + sweep * (lastStop - firstStop); + } + } + if (reverse) { + std::swap(aStart, aEnd); + } + return MakeUnique<ConicGradientPattern>(aCenter, M_PI / 2.0, aStart / 2.0, + aEnd / 2.0, std::move(stops), + Matrix::Scaling(1.0, -1.0)); + } +}; + +struct PaintVarSweepGradient : public PaintSweepGradient { + enum { kFormat = 9 }; + uint32 varIndexBase; + + UniquePtr<Pattern> MakePattern(const PaintState& aState, + uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + uint32_t clOffset = aOffset + colorLineOffset; + if (clOffset + sizeof(VarColorLine) + sizeof(VarColorStop) > + aState.mCOLRLength) { + return nullptr; + } + const auto* colorLine = reinterpret_cast<const VarColorLine*>( + aState.COLRv1BaseAddr() + clOffset); + float start = + ApplyVariation(aState, startAngle, SatAdd(varIndexBase, 2)) + 1.0f; + float end = + ApplyVariation(aState, endAngle, SatAdd(varIndexBase, 3)) + 1.0f; + Point center( + aState.F2P(ApplyVariation(aState, int16_t(centerX), varIndexBase)), + aState.F2P( + ApplyVariation(aState, int16_t(centerY), SatAdd(varIndexBase, 1)))); + return NormalizeAndMakeGradient(aState, colorLine, center, start, end); + } +}; + +struct PaintGlyph { + enum { kFormat = 10 }; + uint8_t format; + Offset24 paintOffset; + uint16 glyphID; + + bool Paint(const PaintState& aState, uint32_t aOffset, + const Rect* aBounds) const { + MOZ_ASSERT(format == kFormat); + if (!paintOffset) { + return true; + } + Glyph g{uint16_t(glyphID), Point()}; + GlyphBuffer buffer{&g, 1}; + // If the paint is a simple fill (rather than a sub-graph of further paint + // records), we can just use FillGlyphs to render it instead of setting up + // a clip. + UniquePtr<Pattern> fillPattern = + DispatchMakePattern(aState, aOffset + paintOffset); + if (fillPattern) { + // On macOS we can't use FillGlyphs because when we render the glyph, + // Core Text's own color font support may step in and ignore the + // pattern. So to avoid this, fill the glyph as a path instead. +#if XP_MACOSX + RefPtr<Path> path = + aState.mScaledFont->GetPathForGlyphs(buffer, aState.mDrawTarget); + aState.mDrawTarget->Fill(path, *fillPattern, aState.mDrawOptions); +#else + aState.mDrawTarget->FillGlyphs(aState.mScaledFont, buffer, *fillPattern, + aState.mDrawOptions); +#endif + return true; + } + RefPtr<Path> path = + aState.mScaledFont->GetPathForGlyphs(buffer, aState.mDrawTarget); + aState.mDrawTarget->PushClip(path); + bool ok = DispatchPaint(aState, aOffset + paintOffset, aBounds); + aState.mDrawTarget->PopClip(); + return ok; + } + + Rect GetBoundingRect(const PaintState& aState, uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + Glyph g{uint16_t(glyphID), Point()}; + GlyphBuffer buffer{&g, 1}; + RefPtr<Path> path = + aState.mScaledFont->GetPathForGlyphs(buffer, aState.mDrawTarget); + return path->GetFastBounds(); + } +}; + +struct PaintColrGlyph { + enum { kFormat = 11 }; + uint8_t format; + uint16 glyphID; + + // Factored out as a helper because this is also used by the top-level + // PaintGlyphGraph function. + static bool DoPaint(const PaintState& aState, + const BaseGlyphPaintRecord* aBaseGlyphPaint, + uint32_t aGlyphId, const Rect* aBounds) { + AutoPopClips clips(aState.mDrawTarget); + Rect clipRect; + if (const auto* clipList = aState.mHeader.v1->clipList()) { + if (const auto* clip = clipList->GetClip(aGlyphId)) { + clipRect = clip->GetRect(aState); + clips.PushClipRect(clipRect); + if (!aBounds) { + aBounds = &clipRect; + } + } + } + return DispatchPaint( + aState, + aState.mHeader.v1->baseGlyphListOffset + aBaseGlyphPaint->paintOffset, + aBounds); + } + + bool Paint(const PaintState& aState, uint32_t aOffset, + const Rect* aBounds) const { + MOZ_ASSERT(format == kFormat); + IF_CYCLE_RETURN(true); + const auto* base = aState.mHeader.v1->GetBaseGlyphPaint(glyphID); + return base ? DoPaint(aState, base, uint16_t(glyphID), aBounds) : false; + } + + Rect GetBoundingRect(const PaintState& aState, uint32_t aOffset) const { + IF_CYCLE_RETURN(Rect()); + if (const auto* clipList = aState.mHeader.v1->clipList()) { + if (const auto* clip = clipList->GetClip(uint16_t(glyphID))) { + return clip->GetRect(aState); + } + } + if (const auto* base = + aState.mHeader.v1->GetBaseGlyphPaint(uint16_t(glyphID))) { + return DispatchGetBounds( + aState, aState.mHeader.v1->baseGlyphListOffset + base->paintOffset); + } + return Rect(); + } +}; + +#undef IF_CYCLE_RETURN + +struct Affine2x3 { + Fixed xx; + Fixed yx; + Fixed xy; + Fixed yy; + Fixed dx; + Fixed dy; + + Matrix AsMatrix(const PaintState& aState) const { + // Flip signs because of opposite y-axis direction in moz2d vs opentype. + return Matrix(float(xx), -float(yx), -float(xy), float(yy), + aState.F2P(float(dx)), -aState.F2P(float(dy))); + } +}; + +struct VarAffine2x3 : public Affine2x3 { + uint32 varIndexBase; + + Matrix AsMatrix(const PaintState& aState) const { + return Matrix( + ApplyVariation(aState, xx, varIndexBase), + -ApplyVariation(aState, yx, SatAdd(varIndexBase, 1)), + -ApplyVariation(aState, xy, SatAdd(varIndexBase, 2)), + ApplyVariation(aState, yy, SatAdd(varIndexBase, 3)), + aState.F2P(ApplyVariation(aState, dx, SatAdd(varIndexBase, 4))), + -aState.F2P(ApplyVariation(aState, dy, SatAdd(varIndexBase, 5)))); + }; +}; + +struct PaintTransformBase { + uint8_t format; + Offset24 paintOffset; + + bool Paint(const PaintState& aState, uint32_t aOffset, + const Rect* aBounds) const { + if (!paintOffset) { + return true; + } + AutoRestoreTransform saveTransform(aState.mDrawTarget); + aState.mDrawTarget->ConcatTransform(DispatchGetMatrix(aState, aOffset)); + return DispatchPaint(aState, aOffset + paintOffset, aBounds); + } + + Rect GetBoundingRect(const PaintState& aState, uint32_t aOffset) const { + if (!paintOffset) { + return Rect(); + } + Rect bounds = DispatchGetBounds(aState, aOffset + paintOffset); + bounds = DispatchGetMatrix(aState, aOffset).TransformBounds(bounds); + return bounds; + } +}; + +struct PaintTransform : public PaintTransformBase { + enum { kFormat = 12 }; + Offset24 transformOffset; + + Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + if (aOffset + transformOffset + sizeof(Affine2x3) > aState.mCOLRLength) { + return Matrix(); + } + const auto* t = reinterpret_cast<const Affine2x3*>( + aState.COLRv1BaseAddr() + aOffset + transformOffset); + return t->AsMatrix(aState); + } +}; + +struct PaintVarTransform : public PaintTransformBase { + enum { kFormat = 13 }; + Offset24 transformOffset; + + Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + if (aOffset + transformOffset + sizeof(VarAffine2x3) > aState.mCOLRLength) { + return Matrix(); + } + const auto* t = reinterpret_cast<const VarAffine2x3*>( + aState.COLRv1BaseAddr() + aOffset + transformOffset); + return t->AsMatrix(aState); + } +}; + +struct PaintTranslate : public PaintTransformBase { + enum { kFormat = 14 }; + FWORD dx; + FWORD dy; + + Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + return Matrix::Translation(aState.F2P(int16_t(dx)), + -aState.F2P(int16_t(dy))); + } +}; + +struct PaintVarTranslate : public PaintTranslate { + enum { kFormat = 15 }; + uint32 varIndexBase; + + Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + return Matrix::Translation( + aState.F2P(ApplyVariation(aState, int16_t(dx), varIndexBase)), + -aState.F2P( + ApplyVariation(aState, int16_t(dy), SatAdd(varIndexBase, 1)))); + } +}; + +struct PaintScale : public PaintTransformBase { + enum { kFormat = 16 }; + F2DOT14 scaleX; + F2DOT14 scaleY; + + Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + return Matrix::Scaling(float(scaleX), float(scaleY)); + } +}; + +struct PaintVarScale : public PaintScale { + enum { kFormat = 17 }; + uint32 varIndexBase; + + Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + return Matrix::Scaling( + ApplyVariation(aState, scaleX, varIndexBase), + ApplyVariation(aState, scaleY, SatAdd(varIndexBase, 1))); + } +}; + +struct PaintScaleAroundCenter : public PaintTransformBase { + enum { kFormat = 18 }; + F2DOT14 scaleX; + F2DOT14 scaleY; + FWORD centerX; + FWORD centerY; + + Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + Point center(aState.F2P(int16_t(centerX)), -aState.F2P(int16_t(centerY))); + return Matrix::Translation(center) + .PreScale(float(scaleX), float(scaleY)) + .PreTranslate(-center); + } +}; + +struct PaintVarScaleAroundCenter : public PaintScaleAroundCenter { + enum { kFormat = 19 }; + uint32 varIndexBase; + + Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + Point center(aState.F2P(ApplyVariation(aState, int16_t(centerX), + SatAdd(varIndexBase, 2))), + -aState.F2P(ApplyVariation(aState, int16_t(centerY), + SatAdd(varIndexBase, 3)))); + return Matrix::Translation(center) + .PreScale(ApplyVariation(aState, scaleX, varIndexBase), + ApplyVariation(aState, scaleY, SatAdd(varIndexBase, 1))) + .PreTranslate(-center); + } +}; + +struct PaintScaleUniform : public PaintTransformBase { + enum { kFormat = 20 }; + F2DOT14 scale; + + Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + return Matrix::Scaling(float(scale), float(scale)); + } +}; + +struct PaintVarScaleUniform : public PaintScaleUniform { + enum { kFormat = 21 }; + uint32 varIndexBase; + + Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + float sc = ApplyVariation(aState, scale, varIndexBase); + return Matrix::Scaling(sc, sc); + } +}; + +struct PaintScaleUniformAroundCenter : public PaintTransformBase { + enum { kFormat = 22 }; + F2DOT14 scale; + FWORD centerX; + FWORD centerY; + + Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + Point center(aState.F2P(int16_t(centerX)), -aState.F2P(int16_t(centerY))); + return Matrix::Translation(center) + .PreScale(float(scale), float(scale)) + .PreTranslate(-center); + } +}; + +struct PaintVarScaleUniformAroundCenter : public PaintScaleUniformAroundCenter { + enum { kFormat = 23 }; + uint32 varIndexBase; + + Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + float sc = ApplyVariation(aState, scale, varIndexBase); + Point center(aState.F2P(ApplyVariation(aState, int16_t(centerX), + SatAdd(varIndexBase, 1))), + -aState.F2P(ApplyVariation(aState, int16_t(centerY), + SatAdd(varIndexBase, 2)))); + return Matrix::Translation(center).PreScale(sc, sc).PreTranslate(-center); + } +}; + +struct PaintRotate : public PaintTransformBase { + enum { kFormat = 24 }; + F2DOT14 angle; + + Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + return Matrix::Rotation(-float(angle) * float(M_PI)); + } +}; + +struct PaintVarRotate : public PaintRotate { + enum { kFormat = 25 }; + uint32 varIndexBase; + + Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + float ang = ApplyVariation(aState, angle, varIndexBase); + return Matrix::Rotation(-ang * float(M_PI)); + } +}; + +struct PaintRotateAroundCenter : public PaintTransformBase { + enum { kFormat = 26 }; + F2DOT14 angle; + FWORD centerX; + FWORD centerY; + + Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + Point center(aState.F2P(int16_t(centerX)), -aState.F2P(int16_t(centerY))); + return Matrix::Translation(center) + .PreRotate(-float(angle) * float(M_PI)) + .PreTranslate(-center); + } +}; + +struct PaintVarRotateAroundCenter : public PaintRotateAroundCenter { + enum { kFormat = 27 }; + uint32 varIndexBase; + + Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + float ang = ApplyVariation(aState, angle, varIndexBase); + Point center(aState.F2P(ApplyVariation(aState, int16_t(centerX), + SatAdd(varIndexBase, 1))), + -aState.F2P(ApplyVariation(aState, int16_t(centerY), + SatAdd(varIndexBase, 2)))); + return Matrix::Translation(center) + .PreRotate(-ang * float(M_PI)) + .PreTranslate(-center); + } +}; + +static inline Matrix SkewMatrix(float aSkewX, float aSkewY) { + float xy = tanf(aSkewX * float(M_PI)); + float yx = tanf(aSkewY * float(M_PI)); + return std::isnan(xy) || std::isnan(yx) ? Matrix() + : Matrix(1.0, -yx, xy, 1.0, 0.0, 0.0); +} + +struct PaintSkew : public PaintTransformBase { + enum { kFormat = 28 }; + F2DOT14 xSkewAngle; + F2DOT14 ySkewAngle; + + Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + return SkewMatrix(float(xSkewAngle), float(ySkewAngle)); + } +}; + +struct PaintVarSkew : public PaintSkew { + enum { kFormat = 29 }; + uint32 varIndexBase; + + Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + return SkewMatrix( + float(ApplyVariation(aState, xSkewAngle, varIndexBase)), + float(ApplyVariation(aState, ySkewAngle, SatAdd(varIndexBase, 1)))); + } +}; + +struct PaintSkewAroundCenter : public PaintTransformBase { + enum { kFormat = 30 }; + F2DOT14 xSkewAngle; + F2DOT14 ySkewAngle; + FWORD centerX; + FWORD centerY; + + Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + Point center(aState.F2P(int16_t(centerX)), -aState.F2P(int16_t(centerY))); + return Matrix::Translation(center) + .PreMultiply(SkewMatrix(float(xSkewAngle), float(ySkewAngle))) + .PreTranslate(-center); + } +}; + +struct PaintVarSkewAroundCenter : public PaintSkewAroundCenter { + enum { kFormat = 31 }; + uint32 varIndexBase; + + Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const { + MOZ_ASSERT(format == kFormat); + Point center(aState.F2P(ApplyVariation(aState, int16_t(centerX), + SatAdd(varIndexBase, 2))), + -aState.F2P(ApplyVariation(aState, int16_t(centerY), + SatAdd(varIndexBase, 3)))); + return Matrix::Translation(center) + .PreMultiply(SkewMatrix( + ApplyVariation(aState, xSkewAngle, varIndexBase), + ApplyVariation(aState, ySkewAngle, SatAdd(varIndexBase, 1)))) + .PreTranslate(-center); + } +}; + +struct PaintComposite { + enum { kFormat = 32 }; + uint8_t format; + Offset24 sourcePaintOffset; + uint8_t compositeMode; + Offset24 backdropPaintOffset; + + enum { + COMPOSITE_CLEAR = 0, + COMPOSITE_SRC = 1, + COMPOSITE_DEST = 2, + COMPOSITE_SRC_OVER = 3, + COMPOSITE_DEST_OVER = 4, + COMPOSITE_SRC_IN = 5, + COMPOSITE_DEST_IN = 6, + COMPOSITE_SRC_OUT = 7, + COMPOSITE_DEST_OUT = 8, + COMPOSITE_SRC_ATOP = 9, + COMPOSITE_DEST_ATOP = 10, + COMPOSITE_XOR = 11, + COMPOSITE_PLUS = 12, + COMPOSITE_SCREEN = 13, + COMPOSITE_OVERLAY = 14, + COMPOSITE_DARKEN = 15, + COMPOSITE_LIGHTEN = 16, + COMPOSITE_COLOR_DODGE = 17, + COMPOSITE_COLOR_BURN = 18, + COMPOSITE_HARD_LIGHT = 19, + COMPOSITE_SOFT_LIGHT = 20, + COMPOSITE_DIFFERENCE = 21, + COMPOSITE_EXCLUSION = 22, + COMPOSITE_MULTIPLY = 23, + COMPOSITE_HSL_HUE = 24, + COMPOSITE_HSL_SATURATION = 25, + COMPOSITE_HSL_COLOR = 26, + COMPOSITE_HSL_LUMINOSITY = 27 + }; + + bool Paint(const PaintState& aState, uint32_t aOffset, + const Rect* aBounds) const { + MOZ_ASSERT(format == kFormat); + if (!backdropPaintOffset || !sourcePaintOffset) { + return true; + } + auto mapCompositionMode = [](uint8_t aMode) -> CompositionOp { + switch (aMode) { + default: + return CompositionOp::OP_SOURCE; + case COMPOSITE_CLEAR: + case COMPOSITE_SRC: + case COMPOSITE_DEST: + MOZ_ASSERT_UNREACHABLE("should have short-circuited"); + return CompositionOp::OP_SOURCE; + case COMPOSITE_SRC_OVER: + return CompositionOp::OP_OVER; + case COMPOSITE_DEST_OVER: + return CompositionOp::OP_DEST_OVER; + case COMPOSITE_SRC_IN: + return CompositionOp::OP_IN; + case COMPOSITE_DEST_IN: + return CompositionOp::OP_DEST_IN; + case COMPOSITE_SRC_OUT: + return CompositionOp::OP_OUT; + case COMPOSITE_DEST_OUT: + return CompositionOp::OP_DEST_OUT; + case COMPOSITE_SRC_ATOP: + return CompositionOp::OP_ATOP; + case COMPOSITE_DEST_ATOP: + return CompositionOp::OP_DEST_ATOP; + case COMPOSITE_XOR: + return CompositionOp::OP_XOR; + case COMPOSITE_PLUS: + return CompositionOp::OP_ADD; + case COMPOSITE_SCREEN: + return CompositionOp::OP_SCREEN; + case COMPOSITE_OVERLAY: + return CompositionOp::OP_OVERLAY; + case COMPOSITE_DARKEN: + return CompositionOp::OP_DARKEN; + case COMPOSITE_LIGHTEN: + return CompositionOp::OP_LIGHTEN; + case COMPOSITE_COLOR_DODGE: + return CompositionOp::OP_COLOR_DODGE; + case COMPOSITE_COLOR_BURN: + return CompositionOp::OP_COLOR_BURN; + case COMPOSITE_HARD_LIGHT: + return CompositionOp::OP_HARD_LIGHT; + case COMPOSITE_SOFT_LIGHT: + return CompositionOp::OP_SOFT_LIGHT; + case COMPOSITE_DIFFERENCE: + return CompositionOp::OP_DIFFERENCE; + case COMPOSITE_EXCLUSION: + return CompositionOp::OP_EXCLUSION; + case COMPOSITE_MULTIPLY: + return CompositionOp::OP_MULTIPLY; + case COMPOSITE_HSL_HUE: + return CompositionOp::OP_HUE; + case COMPOSITE_HSL_SATURATION: + return CompositionOp::OP_SATURATION; + case COMPOSITE_HSL_COLOR: + return CompositionOp::OP_COLOR; + case COMPOSITE_HSL_LUMINOSITY: + return CompositionOp::OP_LUMINOSITY; + } + }; + // Short-circuit cases: + if (compositeMode == COMPOSITE_CLEAR) { + return true; + } + if (compositeMode == COMPOSITE_SRC) { + return DispatchPaint(aState, aOffset + sourcePaintOffset, aBounds); + } + if (compositeMode == COMPOSITE_DEST) { + return DispatchPaint(aState, aOffset + backdropPaintOffset, aBounds); + } + + // We need bounds for the temporary surfaces; so if we didn't have + // explicitly-provided bounds from a clipList entry for the top-level + // glyph, then we need to determine the bounding rect here. + Rect bounds = aBounds ? *aBounds : GetBoundingRect(aState, aOffset); + if (bounds.IsEmpty()) { + return true; + } + bounds.RoundOut(); + + PaintState state = aState; + state.mDrawOptions.mCompositionOp = CompositionOp::OP_OVER; + IntSize intSize(int(bounds.width), int(bounds.height)); + + if (!aState.mDrawTarget->CanCreateSimilarDrawTarget( + intSize, SurfaceFormat::B8G8R8A8)) { + // We're not going to be able to render this, so just bail out. + // (Returning true rather than false means we'll just not paint this + // part of the glyph, but won't return an error and likely fall back + // to an ugly black blob.) + return true; + } + + // Draw the backdrop paint graph to a temporary surface. + RefPtr backdrop = aState.mDrawTarget->CreateSimilarDrawTarget( + intSize, SurfaceFormat::B8G8R8A8); + if (!backdrop) { + return true; + } + backdrop->SetTransform(Matrix::Translation(-bounds.TopLeft())); + state.mDrawTarget = backdrop; + if (!DispatchPaint(state, aOffset + backdropPaintOffset, &bounds)) { + return false; + } + + // Draw the source paint graph to another temp surface. + RefPtr source = aState.mDrawTarget->CreateSimilarDrawTarget( + intSize, SurfaceFormat::B8G8R8A8); + if (!source) { + return true; + } + source->SetTransform(Matrix::Translation(-bounds.TopLeft())); + state.mDrawTarget = source; + if (!DispatchPaint(state, aOffset + sourcePaintOffset, &bounds)) { + return false; + } + + // Composite the source onto the backdrop using the specified operation. + Rect localBounds(Point(), bounds.Size()); + RefPtr snapshot = source->Snapshot(); + backdrop->SetTransform(Matrix()); + backdrop->DrawSurface(snapshot, localBounds, localBounds, + DrawSurfaceOptions(), + DrawOptions(1.0, mapCompositionMode(compositeMode))); + + // And copy the composited result to our final destination. + snapshot = backdrop->Snapshot(); + aState.mDrawTarget->DrawSurface(snapshot, bounds, localBounds); + + return true; + } + + Rect GetBoundingRect(const PaintState& aState, uint32_t aOffset) const { + if (!backdropPaintOffset || !sourcePaintOffset) { + return Rect(); + } + // For now, just return the maximal bounds that could result; this could be + // smarter, returning just one of the rects or their intersection when + // appropriate for the composite mode in effect. + return DispatchGetBounds(aState, aOffset + backdropPaintOffset) + .Union(DispatchGetBounds(aState, aOffset + sourcePaintOffset)); + } +}; + +#pragma pack() + +const BaseGlyphPaintRecord* COLRv1Header::GetBaseGlyphPaint( + uint32_t aGlyphId) const { + const auto* list = baseGlyphList(); + if (!list) { + return nullptr; + } + auto compare = [](const void* key, const void* data) -> int { + uint32_t glyphId = (uint32_t)(uintptr_t)key; + const auto* paintRecord = + reinterpret_cast<const BaseGlyphPaintRecord*>(data); + uint32_t baseGlyphId = uint16_t(paintRecord->glyphID); + if (baseGlyphId == glyphId) { + return 0; + } + return baseGlyphId > glyphId ? -1 : 1; + }; + return reinterpret_cast<const BaseGlyphPaintRecord*>( + bsearch((void*)(uintptr_t)aGlyphId, list + 1, + uint32_t(list->numBaseGlyphPaintRecords), + sizeof(BaseGlyphPaintRecord), compare)); +} + +#define DO_CASE_VAR(T) \ + DO_CASE(Paint##T); \ + DO_CASE(PaintVar##T) + +// Process paint table at aOffset from start of COLRv1 table. +static bool DispatchPaint(const PaintState& aState, uint32_t aOffset, + const Rect* aBounds) { + if (aOffset >= aState.mCOLRLength) { + return false; + } + + const char* paint = aState.COLRv1BaseAddr() + aOffset; + // All paint table formats start with an 8-bit 'format' field. + uint8_t format = uint8_t(*paint); + +#define DO_CASE(T) \ + case T::kFormat: \ + return aOffset + sizeof(T) <= aState.mCOLRLength \ + ? reinterpret_cast<const T*>(paint)->Paint(aState, aOffset, \ + aBounds) \ + : false + + switch (format) { + DO_CASE(PaintColrLayers); + DO_CASE_VAR(Solid); + DO_CASE_VAR(LinearGradient); + DO_CASE_VAR(RadialGradient); + DO_CASE_VAR(SweepGradient); + DO_CASE(PaintGlyph); + DO_CASE(PaintColrGlyph); + DO_CASE_VAR(Transform); + DO_CASE_VAR(Translate); + DO_CASE_VAR(Scale); + DO_CASE_VAR(ScaleAroundCenter); + DO_CASE_VAR(ScaleUniform); + DO_CASE_VAR(ScaleUniformAroundCenter); + DO_CASE_VAR(Rotate); + DO_CASE_VAR(RotateAroundCenter); + DO_CASE_VAR(Skew); + DO_CASE_VAR(SkewAroundCenter); + DO_CASE(PaintComposite); + default: + break; + } + +#undef DO_CASE + + return false; +} + +// Get a gfx::Pattern corresponding to the given paint table, if it is a +// simple format that can be used as a fill (not a sub-graph). +static UniquePtr<Pattern> DispatchMakePattern(const PaintState& aState, + uint32_t aOffset) { + if (aOffset >= aState.mCOLRLength) { + return nullptr; + } + + const char* paint = aState.COLRv1BaseAddr() + aOffset; + // All paint table formats start with an 8-bit 'format' field. + uint8_t format = uint8_t(*paint); + +#define DO_CASE(T) \ + case T::kFormat: \ + return aOffset + sizeof(T) <= aState.mCOLRLength \ + ? reinterpret_cast<const T*>(paint)->MakePattern(aState, \ + aOffset) \ + : nullptr; + + switch (format) { + DO_CASE_VAR(Solid); + DO_CASE_VAR(LinearGradient); + DO_CASE_VAR(RadialGradient); + DO_CASE_VAR(SweepGradient); + default: + break; + } + +#undef DO_CASE + + return nullptr; +} + +static Matrix DispatchGetMatrix(const PaintState& aState, uint32_t aOffset) { + if (aOffset >= aState.mCOLRLength) { + return Matrix(); + } + + const char* paint = aState.COLRv1BaseAddr() + aOffset; + // All paint table formats start with an 8-bit 'format' field. + uint8_t format = uint8_t(*paint); + +#define DO_CASE(T) \ + case T::kFormat: \ + return aOffset + sizeof(T) <= aState.mCOLRLength \ + ? reinterpret_cast<const T*>(paint)->GetMatrix(aState, aOffset) \ + : Matrix(); + + switch (format) { + DO_CASE_VAR(Transform); + DO_CASE_VAR(Translate); + DO_CASE_VAR(Scale); + DO_CASE_VAR(ScaleAroundCenter); + DO_CASE_VAR(ScaleUniform); + DO_CASE_VAR(ScaleUniformAroundCenter); + DO_CASE_VAR(Rotate); + DO_CASE_VAR(RotateAroundCenter); + DO_CASE_VAR(Skew); + DO_CASE_VAR(SkewAroundCenter); + default: + break; + } + +#undef DO_CASE + + return Matrix(); +} + +static Rect DispatchGetBounds(const PaintState& aState, uint32_t aOffset) { + if (aOffset >= aState.mCOLRLength) { + return Rect(); + } + + const char* paint = aState.COLRv1BaseAddr() + aOffset; + // All paint table formats start with an 8-bit 'format' field. + uint8_t format = uint8_t(*paint); + +#define DO_CASE(T) \ + case T::kFormat: \ + return aOffset + sizeof(T) <= aState.mCOLRLength \ + ? reinterpret_cast<const T*>(paint)->GetBoundingRect(aState, \ + aOffset) \ + : Rect(); + + switch (format) { + DO_CASE(PaintColrLayers); + DO_CASE_VAR(Solid); + DO_CASE_VAR(LinearGradient); + DO_CASE_VAR(RadialGradient); + DO_CASE_VAR(SweepGradient); + DO_CASE(PaintGlyph); + DO_CASE(PaintColrGlyph); + DO_CASE_VAR(Transform); + DO_CASE_VAR(Translate); + DO_CASE_VAR(Scale); + DO_CASE_VAR(ScaleAroundCenter); + DO_CASE_VAR(ScaleUniform); + DO_CASE_VAR(ScaleUniformAroundCenter); + DO_CASE_VAR(Rotate); + DO_CASE_VAR(RotateAroundCenter); + DO_CASE_VAR(Skew); + DO_CASE_VAR(SkewAroundCenter); + DO_CASE(PaintComposite); + default: + break; + } + +#undef DO_CASE + + return Rect(); +} + +#undef DO_CASE_VAR + +bool COLRHeader::Validate(uint64_t aLength) const { + uint64_t count; + if ((count = numBaseGlyphRecords)) { + if (baseGlyphRecordsOffset + count * sizeof(BaseGlyphRecord) > aLength) { + return false; + } + } + if ((count = numLayerRecords)) { + if (layerRecordsOffset + count * sizeof(LayerRecord) > aLength) { + return false; + } + } + // Check ordering of baseGlyphRecords, and that layer indices are in bounds. + int32_t lastGlyphId = -1; + const auto* baseGlyph = reinterpret_cast<const BaseGlyphRecord*>( + reinterpret_cast<const char*>(this) + baseGlyphRecordsOffset); + for (uint16_t i = 0; i < uint16_t(numBaseGlyphRecords); i++, baseGlyph++) { + uint16_t glyphId = baseGlyph->glyphId; + if (lastGlyphId >= int32_t(glyphId)) { + return false; + } + if (uint32_t(baseGlyph->firstLayerIndex) + uint32_t(baseGlyph->numLayers) > + uint32_t(numLayerRecords)) { + return false; + } + lastGlyphId = glyphId; + } + // We don't need to validate all the layer paletteEntryIndex fields here, + // because PaintState.GetColor will range-check them at paint time. + return true; +} + +bool COLRv1Header::Validate(uint64_t aLength) const { + if (!base.Validate(aLength)) { + return false; + } + if (baseGlyphListOffset + sizeof(BaseGlyphList) > aLength || + layerListOffset + sizeof(LayerList) > aLength || + clipListOffset + sizeof(ClipList) > aLength || + varIndexMapOffset + sizeof(DeltaSetIndexMap) > aLength || + itemVariationStoreOffset + sizeof(ItemVariationStore) > aLength) { + return false; + } + const auto* b = baseGlyphList(); + if (b && !b->Validate(this, aLength)) { + return false; + } + const auto* l = layerList(); + if (l && !l->Validate(this, aLength)) { + return false; + } + const auto* c = clipList(); + if (c && !c->Validate(this, aLength)) { + return false; + } + const auto* v = varIndexMap(); + if (v && !v->Validate(this, aLength)) { + return false; + } + const auto* i = itemVariationStore(); + if (i && !i->Validate(this, aLength)) { + return false; + } + return true; +} + +bool BaseGlyphList::Validate(const COLRv1Header* aHeader, + uint64_t aLength) const { + uint64_t count = numBaseGlyphPaintRecords; + if (aHeader->baseGlyphListOffset + sizeof(BaseGlyphList) + + count * sizeof(BaseGlyphPaintRecord) > + aLength) { + return false; + } + // Check ordering of baseGlyphPaint records. + const auto* records = baseGlyphPaintRecords(); + int32_t prevGlyphID = -1; + for (uint32_t i = 0; i < numBaseGlyphPaintRecords; ++i) { + const auto& base = records[i]; + if (int32_t(uint16_t(base.glyphID)) <= prevGlyphID) { + return false; + } + prevGlyphID = base.glyphID; + } + return true; +} + +bool LayerList::Validate(const COLRv1Header* aHeader, uint64_t aLength) const { + // Check that paintOffsets array fits. + uint64_t count = numLayers; + uint32_t listOffset = aHeader->layerListOffset; + if (listOffset + sizeof(LayerList) + count * sizeof(uint32) > aLength) { + return false; + } + // Check that values in paintOffsets are within bounds. + const auto* offsets = paintOffsets(); + for (uint32_t i = 0; i < count; i++) { + if (listOffset + offsets[i] >= aLength) { + return false; + } + } + return true; +} + +bool Clip::Validate(const COLRv1Header* aHeader, uint64_t aLength) const { + uint32_t offset = aHeader->clipListOffset + clipBoxOffset; + if (offset >= aLength) { + return false; + } + // ClipBox format begins with a 1-byte format field. + const uint8_t* box = reinterpret_cast<const uint8_t*>(aHeader) + offset; + switch (*box) { + case 1: + if (offset <= aLength - sizeof(ClipBoxFormat1)) { + return true; + } + break; + case 2: + if (offset <= aLength - sizeof(ClipBoxFormat2)) { + return true; + } + break; + default: + // unknown ClipBoxFormat + break; + } + return false; +} + +bool ClipList::Validate(const COLRv1Header* aHeader, uint64_t aLength) const { + uint64_t count = numClips; + if (aHeader->clipListOffset + sizeof(ClipList) + count * sizeof(Clip) > + aLength) { + return false; + } + // Check ordering of clip records, and that they are within bounds. + const auto* clipArray = clips(); + int32_t prevEnd = -1; + for (uint32_t i = 0; i < count; ++i) { + const auto& clip = clipArray[i]; + if (int32_t(uint16_t(clip.startGlyphID)) <= prevEnd) { + return false; + } + if (!clip.Validate(aHeader, aLength)) { + return false; + } + prevEnd = uint16_t(clip.endGlyphID); + } + return true; +} + +bool DeltaSetIndexMap::Validate(const COLRv1Header* aHeader, + uint64_t aLength) const { + uint64_t entrySize = ((entryFormat & MAP_ENTRY_SIZE_MASK) >> 4) + 1; + uint64_t mapCount; + uint64_t baseSize; + switch (format) { + case 0: + mapCount = uint32_t(v0.mapCount); + baseSize = 4; + break; + case 1: + mapCount = uint32_t(v1.mapCount); + baseSize = 6; + break; + default: + return false; + } + if (aHeader->varIndexMapOffset + baseSize + mapCount * entrySize > aLength) { + return false; + } + return true; +} + +bool ItemVariationStore::Validate(const COLRv1Header* aHeader, + uint64_t aLength) const { + uint64_t offset = reinterpret_cast<const char*>(this) - + reinterpret_cast<const char*>(aHeader); + if (offset + variationRegionListOffset + sizeof(VariationRegionList) > + aLength) { + return false; + } + if (!variationRegionList()->Validate(aHeader, aLength)) { + return false; + } + uint16_t count = itemVariationDataCount; + if (offset + sizeof(ItemVariationStore) + count * sizeof(Offset32) > + aLength) { + return false; + } + const auto* ivdOffsets = itemVariationDataOffsets(); + for (uint16_t i = 0; i < count; ++i) { + uint32_t o = ivdOffsets[i]; + if (offset + o + sizeof(ItemVariationData) > aLength) { + return false; + } + const auto* variationData = reinterpret_cast<const ItemVariationData*>( + reinterpret_cast<const char*>(this) + ivdOffsets[i]); + if (!variationData->Validate(aHeader, aLength)) { + return false; + } + } + return true; +} + +bool ItemVariationData::Validate(const COLRv1Header* aHeader, + uint64_t aLength) const { + if (reinterpret_cast<const char*>(regionIndexes() + + uint16_t(regionIndexCount)) > + reinterpret_cast<const char*>(aHeader) + aLength) { + return false; + } + uint16_t wordDeltaCount = this->wordDeltaCount; + bool longWords = wordDeltaCount & LONG_WORDS; + wordDeltaCount &= WORD_DELTA_COUNT_MASK; + uint32_t deltaSetSize = + (uint16_t(regionIndexCount) + uint16_t(wordDeltaCount)) << longWords; + if (reinterpret_cast<const char*>(deltaSets()) + + uint16_t(itemCount) * deltaSetSize > + reinterpret_cast<const char*>(aHeader) + aLength) { + return false; + } + return true; +} + +} // end anonymous namespace + +namespace mozilla::gfx { + +bool COLRFonts::ValidateColorGlyphs(hb_blob_t* aCOLR, hb_blob_t* aCPAL) { + struct ColorRecord { + uint8_t blue; + uint8_t green; + uint8_t red; + uint8_t alpha; + }; + + struct CPALHeaderVersion0 { + uint16 version; + uint16 numPaletteEntries; + uint16 numPalettes; + uint16 numColorRecords; + Offset32 colorRecordsArrayOffset; + // uint16 colorRecordIndices[numPalettes]; + const uint16* colorRecordIndices() const { + return reinterpret_cast<const uint16*>(this + 1); + } + }; + + unsigned int cpalLength; + const CPALHeaderVersion0* cpal = reinterpret_cast<const CPALHeaderVersion0*>( + hb_blob_get_data(aCPAL, &cpalLength)); + if (!cpal || cpalLength < sizeof(CPALHeaderVersion0)) { + return false; + } + + // We can handle either version 0 or 1. + if (uint16_t(cpal->version) > 1) { + return false; + } + + uint16_t numPaletteEntries = cpal->numPaletteEntries; + uint16_t numPalettes = cpal->numPalettes; + uint16_t numColorRecords = cpal->numColorRecords; + uint32_t colorRecordsArrayOffset = cpal->colorRecordsArrayOffset; + const auto* indices = cpal->colorRecordIndices(); + if (colorRecordsArrayOffset >= cpalLength) { + return false; + } + if (!numPaletteEntries || !numPalettes || + numColorRecords < numPaletteEntries) { + return false; + } + if (sizeof(ColorRecord) * numColorRecords > + cpalLength - colorRecordsArrayOffset) { + return false; + } + if (sizeof(uint16) * numPalettes > cpalLength - sizeof(CPALHeaderVersion0)) { + return false; + } + for (uint16_t i = 0; i < numPalettes; ++i) { + uint32_t index = indices[i]; + if (index + numPaletteEntries > numColorRecords) { + return false; + } + } + + // The additional fields in CPALv1 are not checked here; the harfbuzz code + // handles reading them safely. + + unsigned int colrLength; + const COLRHeader* colr = + reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR, &colrLength)); + if (!colr || colrLength < sizeof(COLRHeader)) { + return false; + } + + if (uint16_t(colr->version) == 1) { + return StaticPrefs::gfx_font_rendering_colr_v1_enabled() && + colrLength >= sizeof(COLRv1Header) && + reinterpret_cast<const COLRv1Header*>(colr)->Validate(colrLength); + } + + if (uint16_t(colr->version) != 0) { + // We only support version 1 (above) or version 0 headers. + return false; + } + + return colr->Validate(colrLength); +} + +const COLRFonts::GlyphLayers* COLRFonts::GetGlyphLayers(hb_blob_t* aCOLR, + uint32_t aGlyphId) { + unsigned int length; + const COLRHeader* colr = + reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR, &length)); + // This should never be called unless we have checked that the COLR table is + // structurally valid, so it will be safe to read the header fields. + MOZ_RELEASE_ASSERT(colr && length >= sizeof(COLRHeader), "bad COLR table!"); + auto compareBaseGlyph = [](const void* key, const void* data) -> int { + uint32_t glyphId = (uint32_t)(uintptr_t)key; + const auto* baseGlyph = reinterpret_cast<const BaseGlyphRecord*>(data); + uint32_t baseGlyphId = uint16_t(baseGlyph->glyphId); + if (baseGlyphId == glyphId) { + return 0; + } + return baseGlyphId > glyphId ? -1 : 1; + }; + return reinterpret_cast<const GlyphLayers*>( + bsearch((void*)(uintptr_t)aGlyphId, colr->GetBaseGlyphRecords(), + uint16_t(colr->numBaseGlyphRecords), sizeof(BaseGlyphRecord), + compareBaseGlyph)); +} + +bool COLRFonts::PaintGlyphLayers( + hb_blob_t* aCOLR, hb_face_t* aFace, const GlyphLayers* aLayers, + DrawTarget* aDrawTarget, layout::TextDrawTarget* aTextDrawer, + ScaledFont* aScaledFont, DrawOptions aDrawOptions, const Point& aPoint, + const sRGBColor& aCurrentColor, const nsTArray<sRGBColor>* aColors) { + const auto* glyphRecord = reinterpret_cast<const BaseGlyphRecord*>(aLayers); + // Default to opaque rendering (non-webrender applies alpha with a layer) + float alpha = 1.0; + if (aTextDrawer) { + // defaultColor is the one that comes from CSS, so it has transparency info. + bool hasComplexTransparency = + 0.0 < aCurrentColor.a && aCurrentColor.a < 1.0; + if (hasComplexTransparency && uint16_t(glyphRecord->numLayers) > 1) { + // WebRender doesn't support drawing multi-layer transparent color-glyphs, + // as it requires compositing all the layers before applying transparency. + // (pretend to succeed, output doesn't matter, we will emit a blob) + aTextDrawer->FoundUnsupportedFeature(); + return true; + } + + // If we get here, then either alpha is 0 or 1, or there's only one layer + // which shouldn't have composition issues. In all of these cases, applying + // transparency directly to the glyph should work perfectly fine. + // + // Note that we must still emit completely transparent emoji, because they + // might be wrapped in a shadow that uses the text run's glyphs. + alpha = aCurrentColor.a; + } + + unsigned int length; + const COLRHeader* colr = + reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR, &length)); + PaintState state{{colr}, + aColors->Elements(), + aDrawTarget, + aScaledFont, + nullptr, // variations not needed + aDrawOptions, + length, + aCurrentColor, + 0.0, // fontUnitsToPixels not needed + uint16_t(aColors->Length()), + 0, + nullptr}; + return glyphRecord->Paint(state, alpha, aPoint); +} + +const COLRFonts::GlyphPaintGraph* COLRFonts::GetGlyphPaintGraph( + hb_blob_t* aCOLR, uint32_t aGlyphId) { + if (!StaticPrefs::gfx_font_rendering_colr_v1_enabled()) { + return nullptr; + } + unsigned int blobLength; + const auto* colr = + reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR, &blobLength)); + MOZ_ASSERT(colr, "Cannot get COLR raw data"); + MOZ_ASSERT(blobLength >= sizeof(COLRHeader), "COLR data too small"); + + uint16_t version = colr->version; + if (version == 1) { + MOZ_ASSERT(blobLength >= sizeof(COLRv1Header), "COLRv1 data too small"); + const auto* colrv1 = reinterpret_cast<const COLRv1Header*>(colr); + return reinterpret_cast<const GlyphPaintGraph*>( + colrv1->GetBaseGlyphPaint(aGlyphId)); + } + + return nullptr; +} + +bool COLRFonts::PaintGlyphGraph( + hb_blob_t* aCOLR, hb_font_t* aFont, const GlyphPaintGraph* aPaintGraph, + DrawTarget* aDrawTarget, layout::TextDrawTarget* aTextDrawer, + ScaledFont* aScaledFont, DrawOptions aDrawOptions, const Point& aPoint, + const sRGBColor& aCurrentColor, const nsTArray<sRGBColor>* aColors, + uint32_t aGlyphId, float aFontUnitsToPixels) { + if (aTextDrawer) { + // Currently we always punt to a blob for COLRv1 glyphs. + aTextDrawer->FoundUnsupportedFeature(); + return true; + } + + unsigned int coordCount; + const int* coords = hb_font_get_var_coords_normalized(aFont, &coordCount); + + AutoTArray<uint32_t, 32> visitedOffsets; + PaintState state{{nullptr}, + aColors->Elements(), + aDrawTarget, + aScaledFont, + coords, + aDrawOptions, + hb_blob_get_length(aCOLR), + aCurrentColor, + aFontUnitsToPixels, + uint16_t(aColors->Length()), + uint16_t(coordCount), + &visitedOffsets}; + state.mHeader.v1 = + reinterpret_cast<const COLRv1Header*>(hb_blob_get_data(aCOLR, nullptr)); + AutoRestoreTransform saveTransform(aDrawTarget); + aDrawTarget->ConcatTransform(Matrix::Translation(aPoint)); + return PaintColrGlyph::DoPaint( + state, reinterpret_cast<const BaseGlyphPaintRecord*>(aPaintGraph), + aGlyphId, nullptr); +} + +Rect COLRFonts::GetColorGlyphBounds(hb_blob_t* aCOLR, hb_font_t* aFont, + uint32_t aGlyphId, DrawTarget* aDrawTarget, + ScaledFont* aScaledFont, + float aFontUnitsToPixels) { + unsigned int coordCount; + const int* coords = hb_font_get_var_coords_normalized(aFont, &coordCount); + + AutoTArray<uint32_t, 32> visitedOffsets; + PaintState state{{nullptr}, + nullptr, // palette is not needed + aDrawTarget, + aScaledFont, + coords, + DrawOptions(), + hb_blob_get_length(aCOLR), + sRGBColor(), + aFontUnitsToPixels, + 0, // numPaletteEntries + uint16_t(coordCount), + &visitedOffsets}; + state.mHeader.v1 = + reinterpret_cast<const COLRv1Header*>(hb_blob_get_data(aCOLR, nullptr)); + MOZ_ASSERT(uint16_t(state.mHeader.v1->base.version) == 1); + // If a clip rect is provided, return this as the glyph bounds. + const auto* clipList = state.mHeader.v1->clipList(); + if (clipList) { + const auto* clip = clipList->GetClip(aGlyphId); + if (clip) { + return clip->GetRect(state); + } + } + // Otherwise, compute bounds by walking the paint graph. + const auto* base = state.mHeader.v1->GetBaseGlyphPaint(aGlyphId); + if (base) { + return DispatchGetBounds( + state, state.mHeader.v1->baseGlyphListOffset + base->paintOffset); + } + return Rect(); +} + +uint16_t COLRFonts::GetColrTableVersion(hb_blob_t* aCOLR) { + unsigned int blobLength; + const auto* colr = + reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR, &blobLength)); + MOZ_ASSERT(colr, "Cannot get COLR raw data"); + MOZ_ASSERT(blobLength >= sizeof(COLRHeader), "COLR data too small"); + return colr->version; +} + +nsTArray<sRGBColor> COLRFonts::CreateColorPalette( + hb_face_t* aFace, const FontPaletteValueSet* aPaletteValueSet, + nsAtom* aFontPalette, const nsACString& aFamilyName) { + // Find the base color palette to use, if there are multiple available; + // default to first in the font, if nothing matches what is requested. + unsigned int paletteIndex = 0; + unsigned int count = hb_ot_color_palette_get_count(aFace); + MOZ_ASSERT(count > 0, "No palettes? Font should have been rejected!"); + + const FontPaletteValueSet::PaletteValues* fpv = nullptr; + if (aFontPalette && aFontPalette != nsGkAtoms::normal && + (count > 1 || aPaletteValueSet)) { + auto findPalette = [&](hb_ot_color_palette_flags_t flag) -> unsigned int { + MOZ_ASSERT(flag != HB_OT_COLOR_PALETTE_FLAG_DEFAULT); + for (unsigned int i = 0; i < count; ++i) { + if (hb_ot_color_palette_get_flags(aFace, i) & flag) { + return i; + } + } + return 0; + }; + + if (aFontPalette == nsGkAtoms::light) { + paletteIndex = + findPalette(HB_OT_COLOR_PALETTE_FLAG_USABLE_WITH_LIGHT_BACKGROUND); + } else if (aFontPalette == nsGkAtoms::dark) { + paletteIndex = + findPalette(HB_OT_COLOR_PALETTE_FLAG_USABLE_WITH_DARK_BACKGROUND); + } else { + if (aPaletteValueSet) { + if ((fpv = aPaletteValueSet->Lookup(aFontPalette, aFamilyName))) { + if (fpv->mBasePalette >= 0 && fpv->mBasePalette < int32_t(count)) { + paletteIndex = fpv->mBasePalette; + } else if (fpv->mBasePalette == + FontPaletteValueSet::PaletteValues::kLight) { + paletteIndex = findPalette( + HB_OT_COLOR_PALETTE_FLAG_USABLE_WITH_LIGHT_BACKGROUND); + } else if (fpv->mBasePalette == + FontPaletteValueSet::PaletteValues::kDark) { + paletteIndex = findPalette( + HB_OT_COLOR_PALETTE_FLAG_USABLE_WITH_DARK_BACKGROUND); + } + } + } + } + } + + // Collect the palette colors and convert them to sRGBColor values. + count = + hb_ot_color_palette_get_colors(aFace, paletteIndex, 0, nullptr, nullptr); + nsTArray<hb_color_t> colors; + colors.SetLength(count); + hb_ot_color_palette_get_colors(aFace, paletteIndex, 0, &count, + colors.Elements()); + + nsTArray<sRGBColor> palette; + palette.SetCapacity(count); + for (const auto c : colors) { + palette.AppendElement( + sRGBColor(hb_color_get_red(c) / 255.0, hb_color_get_green(c) / 255.0, + hb_color_get_blue(c) / 255.0, hb_color_get_alpha(c) / 255.0)); + } + + // Apply @font-palette-values overrides, if present. + if (fpv) { + for (const auto overrideColor : fpv->mOverrides) { + if (overrideColor.mIndex < palette.Length()) { + palette[overrideColor.mIndex] = overrideColor.mColor; + } + } + } + + return palette; +} + +const FontPaletteValueSet::PaletteValues* FontPaletteValueSet::Lookup( + nsAtom* aName, const nsACString& aFamily) const { + nsAutoCString family(aFamily); + ToLowerCase(family); + if (const HashEntry* entry = + mValues.GetEntry(PaletteHashKey(aName, family))) { + return &entry->mValue; + } + return nullptr; +} + +FontPaletteValueSet::PaletteValues* FontPaletteValueSet::Insert( + nsAtom* aName, const nsACString& aFamily) { + nsAutoCString family(aFamily); + ToLowerCase(family); + HashEntry* entry = mValues.PutEntry(PaletteHashKey(aName, family)); + return &entry->mValue; +} + +} // end namespace mozilla::gfx diff --git a/gfx/thebes/COLRFonts.h b/gfx/thebes/COLRFonts.h new file mode 100644 index 0000000000..57129f1364 --- /dev/null +++ b/gfx/thebes/COLRFonts.h @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef COLR_FONTS_H +#define COLR_FONTS_H + +#include "mozilla/gfx/2D.h" +#include "mozilla/UniquePtr.h" +#include "nsAtom.h" +#include "nsTArray.h" +#include "nsTHashtable.h" + +struct hb_blob_t; +struct hb_face_t; +struct hb_font_t; + +namespace mozilla { + +namespace layout { +class TextDrawTarget; +} + +namespace gfx { + +class FontPaletteValueSet { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FontPaletteValueSet) + + struct OverrideColor { + uint32_t mIndex = 0; + sRGBColor mColor; + }; + + struct PaletteValues { + enum { kLight = -1, kDark = -2 }; + int32_t mBasePalette = 0; // 0-based index, or kLight/kDark constants + nsTArray<OverrideColor> mOverrides; + }; + + const PaletteValues* Lookup(nsAtom* aName, const nsACString& aFamily) const; + + PaletteValues* Insert(nsAtom* aName, const nsACString& aFamily); + + private: + ~FontPaletteValueSet() = default; + + struct PaletteHashKey { + RefPtr<nsAtom> mName; + nsCString mFamily; + + PaletteHashKey() = delete; + PaletteHashKey(nsAtom* aName, const nsACString& aFamily) + : mName(aName), mFamily(aFamily) {} + PaletteHashKey(const PaletteHashKey& aKey) = default; + }; + + class HashEntry : public PLDHashEntryHdr { + public: + using KeyType = const PaletteHashKey&; + using KeyTypePointer = const PaletteHashKey*; + + HashEntry() = delete; + explicit HashEntry(KeyTypePointer aKey) : mKey(*aKey) {} + HashEntry(HashEntry&& other) noexcept + : PLDHashEntryHdr(std::move(other)), + mKey(other.mKey), + mValue(std::move(other.mValue)) { + NS_ERROR("Should not be called"); + } + ~HashEntry() = default; + + bool KeyEquals(const KeyTypePointer aKey) const { + return mKey.mName == aKey->mName && mKey.mFamily.Equals(aKey->mFamily); + } + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(const KeyTypePointer aKey) { + return aKey->mName->hash() + + HashString(aKey->mFamily.get(), aKey->mFamily.Length()); + } + enum { ALLOW_MEMMOVE = true }; + + PaletteHashKey mKey; + PaletteValues mValue; + }; + + nsTHashtable<HashEntry> mValues; +}; + +class COLRFonts { + public: + static bool ValidateColorGlyphs(hb_blob_t* aCOLR, hb_blob_t* aCPAL); + + // COLRv0: color glyph is represented as a simple list of colored layers. + // (This is used only as an opaque pointer; the internal type is private.) + struct GlyphLayers; + + static const GlyphLayers* GetGlyphLayers(hb_blob_t* aCOLR, uint32_t aGlyphId); + + static bool PaintGlyphLayers( + hb_blob_t* aCOLR, hb_face_t* aFace, const GlyphLayers* aLayers, + DrawTarget* aDrawTarget, layout::TextDrawTarget* aTextDrawer, + ScaledFont* aScaledFont, DrawOptions aDrawOptions, const Point& aPoint, + const sRGBColor& aCurrentColor, const nsTArray<sRGBColor>* aColors); + + // COLRv1 support: color glyph is represented by a directed acyclic graph of + // paint records. + // (This is used only as an opaque pointer; the internal type is private.) + struct GlyphPaintGraph; + + static const GlyphPaintGraph* GetGlyphPaintGraph(hb_blob_t* aCOLR, + uint32_t aGlyphId); + + static bool PaintGlyphGraph( + hb_blob_t* aCOLR, hb_font_t* aFont, const GlyphPaintGraph* aPaintGraph, + DrawTarget* aDrawTarget, layout::TextDrawTarget* aTextDrawer, + ScaledFont* aScaledFont, DrawOptions aDrawOptions, const Point& aPoint, + const sRGBColor& aCurrentColor, const nsTArray<sRGBColor>* aColors, + uint32_t aGlyphId, float aFontUnitsToPixels); + + static Rect GetColorGlyphBounds(hb_blob_t* aCOLR, hb_font_t* aFont, + uint32_t aGlyphId, DrawTarget* aDrawTarget, + ScaledFont* aScaledFont, + float aFontUnitsToPixels); + + static uint16_t GetColrTableVersion(hb_blob_t* aCOLR); + + static nsTArray<sRGBColor> CreateColorPalette( + hb_face_t* aFace, const FontPaletteValueSet* aPaletteValueSet, + nsAtom* aFontPalette, const nsACString& aFamilyName); +}; + +} // namespace gfx + +} // namespace mozilla + +#endif // COLR_FONTS_H diff --git a/gfx/thebes/CoreTextFontList.cpp b/gfx/thebes/CoreTextFontList.cpp new file mode 100644 index 0000000000..0c8c179e8e --- /dev/null +++ b/gfx/thebes/CoreTextFontList.cpp @@ -0,0 +1,1823 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AppleUtils.h" +#include "CoreTextFontList.h" +#include "gfxFontConstants.h" +#include "gfxMacFont.h" +#include "gfxUserFontSet.h" + +#include "harfbuzz/hb.h" + +#include "MainThreadUtils.h" + +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/Logging.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/Telemetry.h" + +#include "nsAppDirectoryServiceDefs.h" +#include "nsCharTraits.h" +#include "nsComponentManagerUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsIDirectoryEnumerator.h" +#include "nsServiceManagerUtils.h" +#include "SharedFontList-impl.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +// List generated by diffing the arrays returned by +// CTFontManagerCopyAvailableFontFamilyNames() when built with +// MACOSX_DEPLOYMENT_TARGET=10.12 vs 11.0, to identify the font family names +// that Core Text is treating as "deprecated" and hiding from the app on newer +// systems. +constexpr nsLiteralCString kDeprecatedFontFamilies[] = { + // Dot-prefixed font families are supposed to be hidden from the + // user-visible + // font list anyhow, so we don't need to add them here. + // ".Al Bayan PUA"_ns, + // ".Al Nile PUA"_ns, + // ".Al Tarikh PUA"_ns, + // ".Apple Color Emoji UI"_ns, + // ".Apple SD Gothic NeoI"_ns, + // ".Aqua Kana"_ns, + // ".Arial Hebrew Desk Interface"_ns, + // ".Baghdad PUA"_ns, + // ".Beirut PUA"_ns, + // ".Damascus PUA"_ns, + // ".DecoType Naskh PUA"_ns, + // ".Diwan Kufi PUA"_ns, + // ".Farah PUA"_ns, + // ".Geeza Pro Interface"_ns, + // ".Geeza Pro PUA"_ns, + // ".Helvetica LT MM"_ns, + // ".Hiragino Kaku Gothic Interface"_ns, + // ".Hiragino Sans GB Interface"_ns, + // ".Keyboard"_ns, + // ".KufiStandardGK PUA"_ns, + // ".LastResort"_ns, + // ".Lucida Grande UI"_ns, + // ".Muna PUA"_ns, + // ".Nadeem PUA"_ns, + // ".New York"_ns, + // ".Noto Nastaliq Urdu UI"_ns, + // ".PingFang HK"_ns, + // ".PingFang SC"_ns, + // ".PingFang TC"_ns, + // ".Sana PUA"_ns, + // ".Savoye LET CC."_ns, + // ".SF Arabic"_ns, + // ".SF Compact Rounded"_ns, + // ".SF Compact"_ns, + // ".SF NS Mono"_ns, + // ".SF NS Rounded"_ns, + // ".SF NS"_ns, + // ".Times LT MM"_ns, + "Hiragino Kaku Gothic Pro"_ns, + "Hiragino Kaku Gothic ProN"_ns, + "Hiragino Kaku Gothic Std"_ns, + "Hiragino Kaku Gothic StdN"_ns, + "Hiragino Maru Gothic Pro"_ns, + "Hiragino Mincho Pro"_ns, + "Iowan Old Style"_ns, + "Noto Sans Adlam"_ns, + "Noto Sans Armenian"_ns, + "Noto Sans Avestan"_ns, + "Noto Sans Bamum"_ns, + "Noto Sans Bassa Vah"_ns, + "Noto Sans Batak"_ns, + "Noto Sans Bhaiksuki"_ns, + "Noto Sans Brahmi"_ns, + "Noto Sans Buginese"_ns, + "Noto Sans Buhid"_ns, + "Noto Sans Carian"_ns, + "Noto Sans Caucasian Albanian"_ns, + "Noto Sans Chakma"_ns, + "Noto Sans Cham"_ns, + "Noto Sans Coptic"_ns, + "Noto Sans Cuneiform"_ns, + "Noto Sans Cypriot"_ns, + "Noto Sans Duployan"_ns, + "Noto Sans Egyptian Hieroglyphs"_ns, + "Noto Sans Elbasan"_ns, + "Noto Sans Glagolitic"_ns, + "Noto Sans Gothic"_ns, + "Noto Sans Gunjala Gondi"_ns, + "Noto Sans Hanifi Rohingya"_ns, + "Noto Sans Hanunoo"_ns, + "Noto Sans Hatran"_ns, + "Noto Sans Imperial Aramaic"_ns, + "Noto Sans Inscriptional Pahlavi"_ns, + "Noto Sans Inscriptional Parthian"_ns, + "Noto Sans Javanese"_ns, + "Noto Sans Kaithi"_ns, + "Noto Sans Kayah Li"_ns, + "Noto Sans Kharoshthi"_ns, + "Noto Sans Khojki"_ns, + "Noto Sans Khudawadi"_ns, + "Noto Sans Lepcha"_ns, + "Noto Sans Limbu"_ns, + "Noto Sans Linear A"_ns, + "Noto Sans Linear B"_ns, + "Noto Sans Lisu"_ns, + "Noto Sans Lycian"_ns, + "Noto Sans Lydian"_ns, + "Noto Sans Mahajani"_ns, + "Noto Sans Mandaic"_ns, + "Noto Sans Manichaean"_ns, + "Noto Sans Marchen"_ns, + "Noto Sans Masaram Gondi"_ns, + "Noto Sans Meetei Mayek"_ns, + "Noto Sans Mende Kikakui"_ns, + "Noto Sans Meroitic"_ns, + "Noto Sans Miao"_ns, + "Noto Sans Modi"_ns, + "Noto Sans Mongolian"_ns, + "Noto Sans Mro"_ns, + "Noto Sans Multani"_ns, + "Noto Sans Nabataean"_ns, + "Noto Sans New Tai Lue"_ns, + "Noto Sans Newa"_ns, + "Noto Sans NKo"_ns, + "Noto Sans Ol Chiki"_ns, + "Noto Sans Old Hungarian"_ns, + "Noto Sans Old Italic"_ns, + "Noto Sans Old North Arabian"_ns, + "Noto Sans Old Permic"_ns, + "Noto Sans Old Persian"_ns, + "Noto Sans Old South Arabian"_ns, + "Noto Sans Old Turkic"_ns, + "Noto Sans Osage"_ns, + "Noto Sans Osmanya"_ns, + "Noto Sans Pahawh Hmong"_ns, + "Noto Sans Palmyrene"_ns, + "Noto Sans Pau Cin Hau"_ns, + "Noto Sans PhagsPa"_ns, + "Noto Sans Phoenician"_ns, + "Noto Sans Psalter Pahlavi"_ns, + "Noto Sans Rejang"_ns, + "Noto Sans Samaritan"_ns, + "Noto Sans Saurashtra"_ns, + "Noto Sans Sharada"_ns, + "Noto Sans Siddham"_ns, + "Noto Sans Sora Sompeng"_ns, + "Noto Sans Sundanese"_ns, + "Noto Sans Syloti Nagri"_ns, + "Noto Sans Syriac"_ns, + "Noto Sans Tagalog"_ns, + "Noto Sans Tagbanwa"_ns, + "Noto Sans Tai Le"_ns, + "Noto Sans Tai Tham"_ns, + "Noto Sans Tai Viet"_ns, + "Noto Sans Takri"_ns, + "Noto Sans Thaana"_ns, + "Noto Sans Tifinagh"_ns, + "Noto Sans Tirhuta"_ns, + "Noto Sans Ugaritic"_ns, + "Noto Sans Vai"_ns, + "Noto Sans Wancho"_ns, + "Noto Sans Warang Citi"_ns, + "Noto Sans Yi"_ns, + "Noto Sans Zawgyi"_ns, + "Noto Serif Ahom"_ns, + "Noto Serif Balinese"_ns, + "Noto Serif Yezidi"_ns, + "Athelas"_ns, + "Courier"_ns, + "Marion"_ns, + "Seravek"_ns, + "Superclarendon"_ns, + "Times"_ns, +}; + +static void GetStringForCFString(CFStringRef aSrc, nsAString& aDest) { + auto len = CFStringGetLength(aSrc); + aDest.SetLength(len); + CFStringGetCharacters(aSrc, CFRangeMake(0, len), + (UniChar*)aDest.BeginWriting()); +} + +static CFStringRef CreateCFStringForString(const nsACString& aSrc) { + return CFStringCreateWithBytes(kCFAllocatorDefault, + (const UInt8*)aSrc.BeginReading(), + aSrc.Length(), kCFStringEncodingUTF8, false); +} + +#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 CTFontEntry::ReadCMAP(FontInfoData* aFontInfoData) { + // attempt this once, if errors occur leave a blank cmap + if (mCharacterMap || mShmemCharacterMap) { + return NS_OK; + } + + RefPtr<gfxCharacterMap> charmap; + nsresult rv; + + uint32_t uvsOffset = 0; + if (aFontInfoData && + (charmap = GetCMAPFromFontInfo(aFontInfoData, uvsOffset))) { + rv = NS_OK; + } else { + uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p'); + charmap = new gfxCharacterMap(); + AutoTable cmapTable(this, kCMAP); + + if (cmapTable) { + uint32_t cmapLen; + const uint8_t* cmapData = reinterpret_cast<const uint8_t*>( + hb_blob_get_data(cmapTable, &cmapLen)); + rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, *charmap, uvsOffset); + } else { + rv = NS_ERROR_NOT_AVAILABLE; + } + } + mUVSOffset.exchange(uvsOffset); + + if (NS_SUCCEEDED(rv) && !mIsDataUserFont && !HasGraphiteTables()) { + // For downloadable fonts, trust the author and don't + // try to munge the cmap based on script shaping support. + + // We also assume a Graphite font knows what it's doing, + // and provides whatever shaping is needed for the + // characters it supports, so only check/clear the + // complex-script ranges for non-Graphite fonts + + // for layout support, check for the presence of mort/morx/kerx and/or + // opentype layout tables + bool hasAATLayout = HasFontTable(TRUETYPE_TAG('m', 'o', 'r', 'x')) || + HasFontTable(TRUETYPE_TAG('m', 'o', 'r', 't')); + bool hasAppleKerning = HasFontTable(TRUETYPE_TAG('k', 'e', 'r', 'x')); + bool hasGSUB = HasFontTable(TRUETYPE_TAG('G', 'S', 'U', 'B')); + bool hasGPOS = HasFontTable(TRUETYPE_TAG('G', 'P', 'O', 'S')); + if ((hasAATLayout && !(hasGSUB || hasGPOS)) || hasAppleKerning) { + mRequiresAAT = true; // prefer CoreText if font has no OTL tables, + // or if it uses the Apple-specific 'kerx' + // variant of kerning table + } + + for (const ScriptRange* sr = gfxPlatformFontList::sComplexScriptRanges; + sr->rangeStart; sr++) { + // check to see if the cmap includes complex script codepoints + if (charmap->TestRange(sr->rangeStart, sr->rangeEnd)) { + if (hasAATLayout) { + // prefer CoreText for Apple's complex-script fonts, + // even if they also have some OpenType tables + // (e.g. Geeza Pro Bold on 10.6; see bug 614903) + mRequiresAAT = true; + // and don't mask off complex-script ranges, we assume + // the AAT tables will provide the necessary shaping + continue; + } + + // We check for GSUB here, as GPOS alone would not be ok. + if (hasGSUB && SupportsScriptInGSUB(sr->tags, sr->numTags)) { + continue; + } + + charmap->ClearRange(sr->rangeStart, sr->rangeEnd); + } + } + + // Bug 1360309, 1393624: several of Apple's Chinese fonts have spurious + // blank glyphs for obscure Tibetan and Arabic-script codepoints. + // Blocklist these so that font fallback will not use them. + if (mRequiresAAT && + (FamilyName().EqualsLiteral("Songti SC") || + FamilyName().EqualsLiteral("Songti TC") || + FamilyName().EqualsLiteral("STSong") || + // Bug 1390980: on 10.11, the Kaiti fonts are also affected. + FamilyName().EqualsLiteral("Kaiti SC") || + FamilyName().EqualsLiteral("Kaiti TC") || + FamilyName().EqualsLiteral("STKaiti"))) { + charmap->ClearRange(0x0f6b, 0x0f70); + charmap->ClearRange(0x0f8c, 0x0f8f); + charmap->clear(0x0f98); + charmap->clear(0x0fbd); + charmap->ClearRange(0x0fcd, 0x0fff); + charmap->clear(0x0620); + charmap->clear(0x065f); + charmap->ClearRange(0x06ee, 0x06ef); + charmap->clear(0x06ff); + } + } + + bool setCharMap = true; + if (NS_SUCCEEDED(rv)) { + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + fontlist::FontList* sharedFontList = pfl->SharedFontList(); + if (!IsUserFont() && mShmemFace && mShmemFamily) { + mShmemFace->SetCharacterMap(sharedFontList, charmap, mShmemFamily); + if (TrySetShmemCharacterMap()) { + setCharMap = false; + } + } else { + charmap = pfl->FindCharMap(charmap); + } + mHasCmapTable = true; + } else { + // if error occurred, initialize to null cmap + charmap = new gfxCharacterMap(); + mHasCmapTable = false; + } + if (setCharMap) { + // Temporarily retain charmap, until the shared version is + // ready for use. + if (mCharacterMap.compareExchange(nullptr, charmap.get())) { + charmap.get()->AddRef(); + } + } + + LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %zu hash: %8.8x%s\n", + mName.get(), charmap->SizeOfIncludingThis(moz_malloc_size_of), + charmap->mHash, mCharacterMap == charmap ? " new" : "")); + if (LOG_CMAPDATA_ENABLED()) { + char prefix[256]; + SprintfLiteral(prefix, "(cmapdata) name: %.220s", mName.get()); + charmap->Dump(prefix, eGfxLog_cmapdata); + } + + return rv; +} + +gfxFont* CTFontEntry::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 CTFontEntry::HasVariations() { + if (!mHasVariationsInitialized) { + mHasVariationsInitialized = true; + mHasVariations = gfxPlatform::HasVariationFontSupport() && + HasFontTable(TRUETYPE_TAG('f', 'v', 'a', 'r')); + } + + return mHasVariations; +} + +void CTFontEntry::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 CTFontEntry::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 CTFontEntry::IsCFF() { + if (!mIsCFFInitialized) { + mIsCFFInitialized = true; + mIsCFF = HasFontTable(TRUETYPE_TAG('C', 'F', 'F', ' ')); + } + + return mIsCFF; +} + +CTFontEntry::CTFontEntry(const nsACString& aPostscriptName, WeightRange aWeight, + bool aIsStandardFace, double aSizeHint) + : gfxFontEntry(aPostscriptName, aIsStandardFace), + mFontRef(NULL), + mSizeHint(aSizeHint), + mFontRefInitialized(false), + mRequiresAAT(false), + mIsCFF(false), + mIsCFFInitialized(false), + mHasVariations(false), + mHasVariationsInitialized(false), + mHasAATSmallCaps(false), + mHasAATSmallCapsInitialized(false) { + mWeightRange = aWeight; + mOpszAxis.mTag = 0; +} + +CTFontEntry::CTFontEntry(const nsACString& aPostscriptName, CGFontRef aFontRef, + WeightRange aWeight, StretchRange aStretch, + SlantStyleRange aStyle, bool aIsDataUserFont, + bool aIsLocalUserFont) + : gfxFontEntry(aPostscriptName, false), + mFontRef(NULL), + mSizeHint(0.0), + mFontRefInitialized(false), + mRequiresAAT(false), + mIsCFF(false), + mIsCFFInitialized(false), + mHasVariations(false), + mHasVariationsInitialized(false), + mHasAATSmallCaps(false), + mHasAATSmallCapsInitialized(false) { + mFontRef = aFontRef; + mFontRefInitialized = true; + CFRetain(mFontRef); + + mWeightRange = aWeight; + mStretchRange = aStretch; + mFixedPitch = false; // xxx - do we need this for downloaded fonts? + mStyleRange = aStyle; + mOpszAxis.mTag = 0; + + NS_ASSERTION(!(aIsDataUserFont && aIsLocalUserFont), + "userfont is either a data font or a local font"); + mIsDataUserFont = aIsDataUserFont; + mIsLocalUserFont = aIsLocalUserFont; +} + +gfxFontEntry* CTFontEntry::Clone() const { + MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!"); + CTFontEntry* fe = new CTFontEntry(Name(), Weight(), mStandardFace, mSizeHint); + fe->mStyleRange = mStyleRange; + fe->mStretchRange = mStretchRange; + fe->mFixedPitch = mFixedPitch; + return fe; +} + +CGFontRef CTFontEntry::GetFontRef() { + { + AutoReadLock lock(mLock); + if (mFontRefInitialized) { + return mFontRef; + } + } + AutoWriteLock lock(mLock); + 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 CTFontEntry::CreateOrCopyFontRef() { + if (mFontRef) { + // We have a cached CGFont, just add a reference. Caller must + // release, but we'll still own our reference. + ::CGFontRetain(mFontRef); + return mFontRef; + } + + CrashReporter::AutoAnnotateCrashReport autoFontName( + CrashReporter::Annotation::FontName, mName); + + // Create a new CGFont; caller will own the only reference to it. + AutoCFRelease<CFStringRef> psname = CreateCFStringForString(mName); + if (!psname) { + return nullptr; + } + + CGFontRef ref = CGFontCreateWithFontName(psname); + 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 CTFontEntry::DestroyBlobFunc(void* aUserData) { +#ifdef NS_BUILD_REFCNT_LOGGING + FontTableRec* ftr = static_cast<FontTableRec*>(aUserData); + delete ftr; +#else + CFRelease((CFDataRef)aUserData); +#endif +} + +hb_blob_t* CTFontEntry::GetFontTable(uint32_t aTag) { + mLock.ReadLock(); + AutoCFRelease<CGFontRef> fontRef = CreateOrCopyFontRef(); + mLock.ReadUnlock(); + if (!fontRef) { + return nullptr; + } + + CFDataRef dataRef = ::CGFontCopyTableForTag(fontRef, aTag); + if (dataRef) { + return hb_blob_create((const char*)CFDataGetBytePtr(dataRef), + CFDataGetLength(dataRef), HB_MEMORY_MODE_READONLY, +#ifdef NS_BUILD_REFCNT_LOGGING + new FontTableRec(dataRef), +#else + (void*)dataRef, +#endif + DestroyBlobFunc); + } + + return nullptr; +} + +bool CTFontEntry::HasFontTable(uint32_t aTableTag) { + { + // If we've already initialized mAvailableTables, we can return without + // needing to take an exclusive lock. + AutoReadLock lock(mLock); + if (mAvailableTables.Count()) { + return mAvailableTables.GetEntry(aTableTag); + } + } + + AutoWriteLock lock(mLock); + if (mAvailableTables.Count() == 0) { + AutoCFRelease<CGFontRef> fontRef = CreateOrCopyFontRef(); + if (!fontRef) { + return false; + } + AutoCFRelease<CFArrayRef> tags = ::CGFontCopyTableTags(fontRef); + if (!tags) { + return false; + } + int numTags = (int)CFArrayGetCount(tags); + for (int t = 0; t < numTags; t++) { + uint32_t tag = (uint32_t)(uintptr_t)CFArrayGetValueAtIndex(tags, t); + mAvailableTables.PutEntry(tag); + } + } + + return mAvailableTables.GetEntry(aTableTag); +} + +static bool CheckForAATSmallCaps(CFArrayRef aFeatures) { + // Walk the array of feature descriptors from the font, and see whether + // a small-caps feature setting is available. + // Just bail out (returning false) if at any point we fail to find the + // expected dictionary keys, etc; if the font has bad data, we don't even + // try to search the rest of it. + auto numFeatures = CFArrayGetCount(aFeatures); + for (auto f = 0; f < numFeatures; ++f) { + auto featureDict = (CFDictionaryRef)CFArrayGetValueAtIndex(aFeatures, f); + if (!featureDict) { + return false; + } + auto featureNum = (CFNumberRef)CFDictionaryGetValue( + featureDict, CFSTR("CTFeatureTypeIdentifier")); + if (!featureNum) { + return false; + } + int16_t featureType; + if (!CFNumberGetValue(featureNum, kCFNumberSInt16Type, &featureType)) { + return false; + } + if (featureType == kLetterCaseType || featureType == kLowerCaseType) { + // Which selector to look for, depending whether we've found the + // legacy LetterCase feature or the new LowerCase one. + const uint16_t smallCaps = (featureType == kLetterCaseType) + ? kSmallCapsSelector + : kLowerCaseSmallCapsSelector; + auto selectors = (CFArrayRef)CFDictionaryGetValue( + featureDict, CFSTR("CTFeatureTypeSelectors")); + if (!selectors) { + return false; + } + auto numSelectors = CFArrayGetCount(selectors); + for (auto s = 0; s < numSelectors; s++) { + auto selectorDict = + (CFDictionaryRef)CFArrayGetValueAtIndex(selectors, s); + if (!selectorDict) { + return false; + } + auto selectorNum = (CFNumberRef)CFDictionaryGetValue( + selectorDict, CFSTR("CTFeatureSelectorIdentifier")); + if (!selectorNum) { + return false; + } + int16_t selectorValue; + if (!CFNumberGetValue(selectorNum, kCFNumberSInt16Type, + &selectorValue)) { + return false; + } + if (selectorValue == smallCaps) { + return true; + } + } + } + } + return false; +} + +bool CTFontEntry::SupportsOpenTypeFeature(Script aScript, + uint32_t aFeatureTag) { + // If we're going to shape with Core Text, we don't support added + // OpenType features (aside from any CT applies by default), except + // for 'smcp' which we map to an AAT feature selector. + if (RequiresAATLayout()) { + if (aFeatureTag != HB_TAG('s', 'm', 'c', 'p')) { + return false; + } + if (mHasAATSmallCapsInitialized) { + return mHasAATSmallCaps; + } + mHasAATSmallCapsInitialized = true; + CGFontRef cgFont = GetFontRef(); + if (!cgFont) { + return mHasAATSmallCaps; + } + + CrashReporter::AutoAnnotateCrashReport autoFontName( + CrashReporter::Annotation::FontName, FamilyName()); + + AutoCFRelease<CTFontRef> ctFont = + CTFontCreateWithGraphicsFont(cgFont, 0.0, nullptr, nullptr); + if (ctFont) { + AutoCFRelease<CFArrayRef> features = CTFontCopyFeatures(ctFont); + if (features) { + mHasAATSmallCaps = CheckForAATSmallCaps(features); + } + } + return mHasAATSmallCaps; + } + return gfxFontEntry::SupportsOpenTypeFeature(aScript, aFeatureTag); +} + +void CTFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +static CTFontDescriptorRef CreateDescriptorForFamily( + const nsACString& aFamilyName, bool aNormalized) { + AutoCFRelease<CFStringRef> family = CreateCFStringForString(aFamilyName); + const void* values[] = {family}; + const void* keys[] = {kCTFontFamilyNameAttribute}; + AutoCFRelease<CFDictionaryRef> attributes = CFDictionaryCreate( + kCFAllocatorDefault, keys, values, 1, &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + // Not AutoCFRelease, because we might return it. + CTFontDescriptorRef descriptor = + CTFontDescriptorCreateWithAttributes(attributes); + + if (aNormalized) { + CTFontDescriptorRef normalized = + CTFontDescriptorCreateMatchingFontDescriptor(descriptor, nullptr); + if (normalized) { + CFRelease(descriptor); + return normalized; + } + } + + return descriptor; +} + +void CTFontFamily::LocalizedName(nsACString& aLocalizedName) { + AutoCFRelease<CTFontDescriptorRef> descriptor = + CreateDescriptorForFamily(mName, true); + if (descriptor) { + AutoCFRelease<CFStringRef> name = + static_cast<CFStringRef>(CTFontDescriptorCopyLocalizedAttribute( + descriptor, kCTFontFamilyNameAttribute, nullptr)); + if (name) { + nsAutoString localized; + GetStringForCFString(name, localized); + if (!localized.IsEmpty()) { + CopyUTF16toUTF8(localized, aLocalizedName); + return; + } + } + } + + // failed to get localized name, just use the canonical one + aLocalizedName = mName; +} + +// Return the CSS weight value to use for the given face, overriding what +// AppKit gives us (used to adjust families with bad weight values, see +// bug 931426). +// A return value of 0 indicates no override - use the existing weight. +static inline int GetWeightOverride(const nsAString& aPSName) { + nsAutoCString prefName("font.weight-override."); + // The PostScript name is required to be ASCII; if it's not, the font is + // broken anyway, so we really don't care that this is lossy. + LossyAppendUTF16toASCII(aPSName, prefName); + return Preferences::GetInt(prefName.get(), 0); +} + +// The Core Text weight trait is documented as +// +// ...a float value between -1.0 and 1.0 for normalized weight. +// The value of 0.0 corresponds to the regular or medium font weight. +// +// (https://developer.apple.com/documentation/coretext/kctfontweighttrait) +// +// CSS 'normal' font-weight is defined as 400, so we map 0.0 to this. +// The exact mapping to use for other values is not well defined; the table +// here is empirically determined by looking at what Core Text returns for +// the various system fonts that have a range of weights. +static inline int32_t CoreTextWeightToCSSWeight(CGFloat aCTWeight) { + using Mapping = std::pair<CGFloat, int32_t>; + constexpr Mapping kCoreTextToCSSWeights[] = { + // clang-format off + {-1.0, 1}, + {-0.8, 100}, + {-0.6, 200}, + {-0.4, 300}, + {0.0, 400}, // standard 'regular' weight + {0.23, 500}, + {0.3, 600}, + {0.4, 700}, // standard 'bold' weight + {0.56, 800}, + {0.62, 900}, // Core Text seems to return 0.62 for faces with both + // usWeightClass=800 and 900 in their OS/2 tables! + // We use 900 as there are also fonts that return 0.56, + // so we want an intermediate value for that. + {1.0, 1000}, + // clang-format on + }; + const auto* begin = &kCoreTextToCSSWeights[0]; + const auto* end = begin + ArrayLength(kCoreTextToCSSWeights); + auto m = std::upper_bound(begin, end, aCTWeight, + [](CGFloat aValue, const Mapping& aMapping) { + return aValue <= aMapping.first; + }); + if (m == end) { + NS_WARNING("Core Text weight out of range"); + return 1000; + } + if (m->first == aCTWeight || m == begin) { + return m->second; + } + // Interpolate between the preceding and found entries: + const auto* prev = m - 1; + const auto t = (aCTWeight - prev->first) / (m->first - prev->first); + return NS_round(prev->second * (1.0 - t) + m->second * t); +} + +// The Core Text width trait is documented as +// +// ...a float between -1.0 and 1.0. The value of 0.0 corresponds to regular +// glyph spacing, and negative values represent condensed glyph spacing +// +// (https://developer.apple.com/documentation/coretext/kctfontweighttrait) +// +// CSS 'normal' font-stretch is 100%; 'ultra-expanded' is 200%, and 'ultra- +// condensed' is 50%. We map the extremes of the Core Text trait to these +// values, and interpolate in between these and normal. +static inline FontStretch CoreTextWidthToCSSStretch(CGFloat aCTWidth) { + if (aCTWidth >= 0.0) { + return FontStretch::FromFloat(100.0 + aCTWidth * 100.0); + } + return FontStretch::FromFloat(100.0 + aCTWidth * 50.0); +} + +void CTFontFamily::AddFace(CTFontDescriptorRef aFace) { + AutoCFRelease<CFStringRef> psname = + (CFStringRef)CTFontDescriptorCopyAttribute(aFace, kCTFontNameAttribute); + AutoCFRelease<CFStringRef> facename = + (CFStringRef)CTFontDescriptorCopyAttribute(aFace, + kCTFontStyleNameAttribute); + + AutoCFRelease<CFDictionaryRef> traitsDict = + (CFDictionaryRef)CTFontDescriptorCopyAttribute(aFace, + kCTFontTraitsAttribute); + CFNumberRef weight = + (CFNumberRef)CFDictionaryGetValue(traitsDict, kCTFontWeightTrait); + CFNumberRef width = + (CFNumberRef)CFDictionaryGetValue(traitsDict, kCTFontWidthTrait); + CFNumberRef symbolicTraits = + (CFNumberRef)CFDictionaryGetValue(traitsDict, kCTFontSymbolicTrait); + + bool isStandardFace = false; + + // make a nsString + nsAutoString postscriptFontName; + GetStringForCFString(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)); + cssWeight *= 100; // scale up to CSS values + } else { + CGFloat weightValue; + CFNumberGetValue(weight, kCFNumberCGFloatType, &weightValue); + cssWeight = CoreTextWeightToCSSWeight(weightValue); + } + + if (kCFCompareEqualTo == CFStringCompare(facename, CFSTR("Regular"), 0) || + kCFCompareEqualTo == CFStringCompare(facename, CFSTR("Bold"), 0) || + kCFCompareEqualTo == CFStringCompare(facename, CFSTR("Italic"), 0) || + kCFCompareEqualTo == CFStringCompare(facename, CFSTR("Oblique"), 0) || + kCFCompareEqualTo == CFStringCompare(facename, CFSTR("Bold Italic"), 0) || + kCFCompareEqualTo == + CFStringCompare(facename, CFSTR("Bold Oblique"), 0)) { + isStandardFace = true; + } + + // create a font entry + CTFontEntry* fontEntry = new CTFontEntry( + NS_ConvertUTF16toUTF8(postscriptFontName), + WeightRange(FontWeight::FromInt(cssWeight)), isStandardFace); + + CGFloat widthValue; + CFNumberGetValue(width, kCFNumberCGFloatType, &widthValue); + fontEntry->mStretchRange = + StretchRange(CoreTextWidthToCSSStretch(widthValue)); + + SInt32 traitsValue; + CFNumberGetValue(symbolicTraits, kCFNumberSInt32Type, &traitsValue); + if (traitsValue & kCTFontItalicTrait) { + fontEntry->mStyleRange = SlantStyleRange(FontSlantStyle::ITALIC); + } + + if (traitsValue & kCTFontMonoSpaceTrait) { + fontEntry->mFixedPitch = true; + } + + if (gfxPlatform::HasVariationFontSupport()) { + fontEntry->SetupVariationRanges(); + } + + if (LOG_FONTLIST_ENABLED()) { + nsAutoCString weightString; + fontEntry->Weight().ToString(weightString); + nsAutoCString stretchString; + fontEntry->Stretch().ToString(stretchString); + LOG_FONTLIST( + ("(fontlist) added (%s) to family (%s)" + " with style: %s weight: %s stretch: %s", + fontEntry->Name().get(), Name().get(), + fontEntry->IsItalic() ? "italic" : "normal", weightString.get(), + stretchString.get())); + } + + // insert into font entry array of family + AddFontEntryLocked(fontEntry); +} + +void CTFontFamily::FindStyleVariationsLocked(FontInfoData* aFontInfoData) { + if (mHasStyles) { + return; + } + + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("CTFontFamily::FindStyleVariations", + LAYOUT, mName); + + struct Context { + CTFontFamily* family; + const void* prevValue = nullptr; + }; + + auto addFaceFunc = [](const void* aValue, void* aContext) -> void { + Context* context = (Context*)aContext; + if (aValue == context->prevValue) { + return; + } + context->prevValue = aValue; + CTFontFamily* family = context->family; + // Calling family->AddFace requires that family->mLock is held. We know + // this will be true because FindStyleVariationsLocked already requires it, + // but the thread-safety analysis can't track that through into the lambda + // here, so we disable the check to avoid a spurious warning. + MOZ_PUSH_IGNORE_THREAD_SAFETY; + family->AddFace((CTFontDescriptorRef)aValue); + MOZ_POP_THREAD_SAFETY; + }; + + AutoCFRelease<CTFontDescriptorRef> descriptor = + CreateDescriptorForFamily(mName, false); + AutoCFRelease<CFArrayRef> faces = + CTFontDescriptorCreateMatchingFontDescriptors(descriptor, nullptr); + + if (faces) { + Context context{this}; + CFArrayApplyFunction(faces, CFRangeMake(0, CFArrayGetCount(faces)), + addFaceFunc, &context); + } + + SortAvailableFonts(); + SetHasStyles(true); + + if (mIsBadUnderlineFamily) { + SetBadUnderlineFonts(); + } + + CheckForSimpleFamily(); +} + +/* CoreTextFontList */ +#pragma mark - + +CoreTextFontList::CoreTextFontList() + : gfxPlatformFontList(false), mDefaultFont(nullptr) { +#ifdef MOZ_BUNDLED_FONTS + // We activate bundled fonts if the pref is > 0 (on) or < 0 (auto), only an + // explicit value of 0 (off) will disable them. + if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) { + TimeStamp start = TimeStamp::Now(); + ActivateBundledFonts(); + TimeStamp end = TimeStamp::Now(); + Telemetry::Accumulate(Telemetry::FONTLIST_BUNDLEDFONTS_ACTIVATE, + (end - start).ToMilliseconds()); + } +#endif + + // Load the font-list preferences now, so that we don't have to do it from + // Init[Shared]FontListForPlatform, which may be called off-main-thread. + gfxFontUtils::GetPrefsFontList("font.preload-names-list", mPreloadFonts); +} + +CoreTextFontList::~CoreTextFontList() { + AutoLock lock(mLock); + + if (XRE_IsParentProcess()) { + CFNotificationCenterRemoveObserver( + CFNotificationCenterGetLocalCenter(), this, + kCTFontManagerRegisteredFontsChangedNotification, 0); + } + + if (mDefaultFont) { + CFRelease(mDefaultFont); + } +} + +void CoreTextFontList::AddFamily(const nsACString& aFamilyName, + FontVisibility aVisibility) { + nsAutoCString key; + ToLowerCase(aFamilyName, key); + + RefPtr<gfxFontFamily> familyEntry = + new CTFontFamily(aFamilyName, aVisibility); + mFontFamilies.InsertOrUpdate(key, RefPtr{familyEntry}); + + // check the bad underline blocklist + if (mBadUnderlineFamilyNames.ContainsSorted(key)) { + familyEntry->SetBadUnderlineFamily(); + } +} + +void CoreTextFontList::AddFamily(CFStringRef aFamily) { + // CTFontManager includes internal family names and LastResort; skip those. + if (!aFamily || + CFStringCompare(aFamily, CFSTR("LastResort"), + kCFCompareCaseInsensitive) == kCFCompareEqualTo || + CFStringCompare(aFamily, CFSTR(".LastResort"), + kCFCompareCaseInsensitive) == kCFCompareEqualTo) { + return; + } + + nsAutoString familyName; + GetStringForCFString(aFamily, familyName); + + NS_ConvertUTF16toUTF8 nameUtf8(familyName); + AddFamily(nameUtf8, GetVisibilityForFamily(nameUtf8)); +} + +/* static */ +void CoreTextFontList::ActivateFontsFromDir( + const nsACString& aDir, nsTHashSet<nsCStringHashKey>* aLoadedFamilies) { + AutoCFRelease<CFURLRef> directory = CFURLCreateFromFileSystemRepresentation( + kCFAllocatorDefault, (const UInt8*)nsPromiseFlatCString(aDir).get(), + aDir.Length(), true); + if (!directory) { + return; + } + AutoCFRelease<CFURLEnumeratorRef> enumerator = + CFURLEnumeratorCreateForDirectoryURL(kCFAllocatorDefault, directory, + kCFURLEnumeratorDefaultBehavior, + nullptr); + if (!enumerator) { + return; + } + AutoCFRelease<CFMutableArrayRef> urls = + CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + if (!urls) { + return; + } + + CFURLRef url; + CFURLEnumeratorResult result; + do { + result = CFURLEnumeratorGetNextURL(enumerator, &url, nullptr); + if (result != kCFURLEnumeratorSuccess) { + continue; + } + CFArrayAppendValue(urls, url); + + if (!aLoadedFamilies) { + continue; + } + AutoCFRelease<CFArrayRef> descriptors = + CTFontManagerCreateFontDescriptorsFromURL(url); + if (!descriptors || !CFArrayGetCount(descriptors)) { + continue; + } + CTFontDescriptorRef desc = + (CTFontDescriptorRef)CFArrayGetValueAtIndex(descriptors, 0); + AutoCFRelease<CFStringRef> name = + (CFStringRef)CTFontDescriptorCopyAttribute(desc, + kCTFontFamilyNameAttribute); + nsAutoCString key; + key.SetLength((CFStringGetLength(name) + 1) * 3); + if (CFStringGetCString(name, key.BeginWriting(), key.Length(), + kCFStringEncodingUTF8)) { + key.SetLength(strlen(key.get())); + aLoadedFamilies->Insert(key); + } + } while (result != kCFURLEnumeratorEnd); + + CTFontManagerRegisterFontURLs(urls, kCTFontManagerScopeProcess, false, + nullptr); +} + +void CoreTextFontList::ReadSystemFontList(dom::SystemFontList* aList) + MOZ_NO_THREAD_SAFETY_ANALYSIS { + // Note: We rely on the records for mSystemFontFamilyName (if present) being + // *before* the main font list, so that name is known in the content process + // by the time we add the actual family records to the font list. + aList->entries().AppendElement(FontFamilyListEntry( + mSystemFontFamilyName, FontVisibility::Unknown, kSystemFontFamily)); + + // Now collect the list of available families, with visibility attributes. + for (auto f = mFontFamilies.Iter(); !f.Done(); f.Next()) { + auto macFamily = f.Data().get(); + aList->entries().AppendElement(FontFamilyListEntry( + macFamily->Name(), macFamily->Visibility(), kStandardFontFamily)); + } +} + +void CoreTextFontList::PreloadNamesList() { + uint32_t numFonts = mPreloadFonts.Length(); + for (uint32_t i = 0; i < numFonts; i++) { + nsAutoCString key; + GenerateFontListKey(mPreloadFonts[i], key); + + // only search canonical names! + gfxFontFamily* familyEntry = mFontFamilies.GetWeak(key); + if (familyEntry) { + familyEntry->ReadOtherFamilyNames(this); + } + } +} + +nsresult CoreTextFontList::InitFontListForPlatform() { + // The font registration thread was created early in startup, to give the + // system a head start on activating all the supplemental-language fonts. + // Here, we need to wait until it has finished its work. + gfxPlatformMac::WaitForFontRegistration(); + + Telemetry::AutoTimer<Telemetry::MAC_INITFONTLIST_TOTAL> timer; + + InitSystemFontNames(); + + if (XRE_IsParentProcess()) { + static bool firstTime = true; + if (firstTime) { + CFNotificationCenterAddObserver( + CFNotificationCenterGetLocalCenter(), this, + RegisteredFontsChangedNotificationCallback, + kCTFontManagerRegisteredFontsChangedNotification, 0, + CFNotificationSuspensionBehaviorDeliverImmediately); + firstTime = false; + } + + // We're not a content process, so get the available fonts directly + // from Core Text. + AutoCFRelease<CFArrayRef> familyNames = + CTFontManagerCopyAvailableFontFamilyNames(); + for (CFIndex i = 0; i < CFArrayGetCount(familyNames); i++) { + CFStringRef familyName = + (CFStringRef)CFArrayGetValueAtIndex(familyNames, i); + AddFamily(familyName); + } + for (const auto& name : kDeprecatedFontFamilies) { + if (DeprecatedFamilyIsAvailable(name)) { + AddFamily(name, GetVisibilityForFamily(name)); + } + } + } else { + // Content process: use font list passed from the chrome process via + // the GetXPCOMProcessAttributes message, because it's much faster than + // querying Core Text again in the child. + auto& fontList = dom::ContentChild::GetSingleton()->SystemFontList(); + for (FontFamilyListEntry& ffe : fontList.entries()) { + switch (ffe.entryType()) { + case kStandardFontFamily: + if (ffe.familyName() == mSystemFontFamilyName) { + continue; + } + AddFamily(ffe.familyName(), ffe.visibility()); + break; + case kSystemFontFamily: + mSystemFontFamilyName = ffe.familyName(); + break; + } + } + fontList.entries().Clear(); + } + + InitSingleFaceList(); + + // to avoid full search of font name tables, seed the other names table with + // localized names from some of the prefs fonts which are accessed via their + // localized names. changes in the pref fonts will only cause a font lookup + // miss earlier. this is a simple optimization, it's not required for + // correctness + PreloadNamesList(); + + // start the delayed cmap loader + GetPrefsAndStartLoader(); + + return NS_OK; +} + +void CoreTextFontList::InitSharedFontListForPlatform() { + gfxPlatformMac::WaitForFontRegistration(); + + InitSystemFontNames(); + + if (XRE_IsParentProcess()) { + // Only the parent process listens for OS font-changed notifications; + // after rebuilding its list, it will update the content processes. + static bool firstTime = true; + if (firstTime) { + CFNotificationCenterAddObserver( + CFNotificationCenterGetLocalCenter(), this, + RegisteredFontsChangedNotificationCallback, + kCTFontManagerRegisteredFontsChangedNotification, 0, + CFNotificationSuspensionBehaviorDeliverImmediately); + firstTime = false; + } + + AutoCFRelease<CFArrayRef> familyNames = + CTFontManagerCopyAvailableFontFamilyNames(); + nsTArray<fontlist::Family::InitData> families; + families.SetCapacity(CFArrayGetCount(familyNames) + + ArrayLength(kDeprecatedFontFamilies)); + for (CFIndex i = 0; i < CFArrayGetCount(familyNames); ++i) { + nsAutoString name16; + CFStringRef familyName = + (CFStringRef)CFArrayGetValueAtIndex(familyNames, i); + GetStringForCFString(familyName, name16); + NS_ConvertUTF16toUTF8 name(name16); + nsAutoCString key; + GenerateFontListKey(name, key); + families.AppendElement(fontlist::Family::InitData( + key, name, fontlist::Family::kNoIndex, GetVisibilityForFamily(name))); + } + for (const nsACString& name : kDeprecatedFontFamilies) { + if (DeprecatedFamilyIsAvailable(name)) { + nsAutoCString key; + GenerateFontListKey(name, key); + families.AppendElement( + fontlist::Family::InitData(key, name, fontlist::Family::kNoIndex, + GetVisibilityForFamily(name))); + } + } + SharedFontList()->SetFamilyNames(families); + InitAliasesForSingleFaceList(); + GetPrefsAndStartLoader(); + } +} + +gfxFontFamily* CoreTextFontList::FindSystemFontFamily( + const nsACString& aFamily) { + nsAutoCString key; + GenerateFontListKey(aFamily, key); + + gfxFontFamily* familyEntry; + if ((familyEntry = mFontFamilies.GetWeak(key))) { + return CheckFamily(familyEntry); + } + + return nullptr; +} + +void CoreTextFontList::RegisteredFontsChangedNotificationCallback( + CFNotificationCenterRef center, void* observer, CFStringRef name, + const void* object, CFDictionaryRef userInfo) { + if (!CFEqual(name, kCTFontManagerRegisteredFontsChangedNotification)) { + return; + } + + CoreTextFontList* fl = static_cast<CoreTextFontList*>(observer); + if (!fl->IsInitialized()) { + return; + } + + // xxx - should be carefully pruning the list of fonts, not rebuilding it from + // scratch + fl->UpdateFontList(); + + gfxPlatform::ForceGlobalReflow(gfxPlatform::NeedsReframe::Yes); + dom::ContentParent::NotifyUpdatedFonts(true); +} + +gfxFontEntry* CoreTextFontList::PlatformGlobalFontFallback( + nsPresContext* aPresContext, const uint32_t aCh, Script aRunScript, + const gfxFontStyle* aMatchStyle, FontFamily& aMatchedFamily) { + CFStringRef str; + UniChar ch[2]; + CFIndex length = 1; + + if (IS_IN_BMP(aCh)) { + ch[0] = aCh; + str = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, ch, 1, + kCFAllocatorNull); + } else { + ch[0] = H_SURROGATE(aCh); + ch[1] = L_SURROGATE(aCh); + str = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, ch, 2, + kCFAllocatorNull); + length = 2; + } + if (!str) { + return nullptr; + } + + // use CoreText to find the fallback family + + gfxFontEntry* fontEntry = nullptr; + bool cantUseFallbackFont = false; + + if (!mDefaultFont) { + mDefaultFont = CTFontCreateWithName(CFSTR("LucidaGrande"), 12.f, NULL); + } + + AutoCFRelease<CTFontRef> fallback = + CTFontCreateForString(mDefaultFont, str, CFRangeMake(0, length)); + + if (fallback) { + AutoCFRelease<CFStringRef> familyNameRef = CTFontCopyFamilyName(fallback); + + if (familyNameRef && + CFStringCompare(familyNameRef, CFSTR("LastResort"), + kCFCompareCaseInsensitive) != kCFCompareEqualTo && + CFStringCompare(familyNameRef, CFSTR(".LastResort"), + kCFCompareCaseInsensitive) != kCFCompareEqualTo) { + AutoTArray<UniChar, 1024> buffer; + CFIndex familyNameLen = CFStringGetLength(familyNameRef); + buffer.SetLength(familyNameLen + 1); + CFStringGetCharacters(familyNameRef, CFRangeMake(0, familyNameLen), + buffer.Elements()); + buffer[familyNameLen] = 0; + NS_ConvertUTF16toUTF8 familyNameString( + reinterpret_cast<char16_t*>(buffer.Elements()), familyNameLen); + + if (SharedFontList()) { + fontlist::Family* family = + FindSharedFamily(aPresContext, familyNameString); + if (family) { + fontlist::Face* face = + family->FindFaceForStyle(SharedFontList(), *aMatchStyle); + if (face) { + fontEntry = GetOrCreateFontEntryLocked(face, family); + } + if (fontEntry) { + if (fontEntry->HasCharacter(aCh)) { + aMatchedFamily = FontFamily(family); + } else { + fontEntry = nullptr; + cantUseFallbackFont = true; + } + } + } + } + + // The macOS system font does not appear in the shared font list, so if + // we didn't find the fallback font above, we should also check for an + // unshared fontFamily in the system list. + if (!fontEntry) { + gfxFontFamily* family = FindSystemFontFamily(familyNameString); + if (family) { + fontEntry = family->FindFontForStyle(*aMatchStyle); + if (fontEntry) { + if (fontEntry->HasCharacter(aCh)) { + aMatchedFamily = FontFamily(family); + } else { + fontEntry = nullptr; + cantUseFallbackFont = true; + } + } + } + } + } + } + + if (cantUseFallbackFont) { + Telemetry::Accumulate(Telemetry::BAD_FALLBACK_FONT, cantUseFallbackFont); + } + + CFRelease(str); + + return fontEntry; +} + +gfxFontEntry* CoreTextFontList::LookupLocalFont( + nsPresContext* aPresContext, const nsACString& aFontName, + WeightRange aWeightForEntry, StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) { + if (aFontName.IsEmpty() || aFontName[0] == '.') { + return nullptr; + } + + AutoLock lock(mLock); + + CrashReporter::AutoAnnotateCrashReport autoFontName( + CrashReporter::Annotation::FontName, aFontName); + + AutoCFRelease<CFStringRef> faceName = CreateCFStringForString(aFontName); + if (!faceName) { + return nullptr; + } + + // lookup face based on postscript or full name + AutoCFRelease<CGFontRef> fontRef = CGFontCreateWithFontName(faceName); + if (!fontRef) { + return nullptr; + } + + // It's possible for CGFontCreateWithFontName to return a font that has been + // deactivated/uninstalled, or a font that is excluded from the font list due + // to CSS font-visibility restriction. So we need to check whether this font + // is allowed to be used. + + // CGFontRef doesn't offer a family-name API, so we go via a CTFontRef. + AutoCFRelease<CTFontRef> ctFont = + CTFontCreateWithGraphicsFont(fontRef, 0.0, nullptr, nullptr); + if (!ctFont) { + return nullptr; + } + AutoCFRelease<CFStringRef> name = CTFontCopyFamilyName(ctFont); + + // Convert the family name to a key suitable for font-list lookup (8-bit, + // lowercased). + nsAutoCString key; + // CFStringGetLength is in UTF-16 code units. The maximum this count can + // expand when converted to UTF-8 is 3x. We add 1 to ensure there will also be + // space for null-termination of the resulting C string. + key.SetLength((CFStringGetLength(name) + 1) * 3); + if (!CFStringGetCString(name, key.BeginWriting(), key.Length(), + kCFStringEncodingUTF8)) { + // This shouldn't ever happen, but if it does we just bail. + NS_WARNING("Failed to get family name?"); + key.Truncate(0); + } + if (key.IsEmpty()) { + return nullptr; + } + // Reset our string length to match the actual C string we got, which will + // usually be much shorter than the maximal buffer we allocated. + key.Truncate(strlen(key.get())); + ToLowerCase(key); + // If the family can't be looked up, this font is not available for use. + FontFamily family = FindFamily(aPresContext, key); + if (family.IsNull()) { + return nullptr; + } + + return new CTFontEntry(aFontName, fontRef, aWeightForEntry, aStretchForEntry, + aStyleForEntry, false, true); +} + +static void ReleaseData(void* info, const void* data, size_t size) { + free((void*)data); +} + +gfxFontEntry* CoreTextFontList::MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, + uint32_t aLength) { + NS_ASSERTION(aFontData, "MakePlatformFont called with null data"); + + // create the font entry + nsAutoString uniqueName; + + nsresult rv = gfxFontUtils::MakeUniqueUserFontName(uniqueName); + if (NS_FAILED(rv)) { + return nullptr; + } + + CrashReporter::AutoAnnotateCrashReport autoFontName( + CrashReporter::Annotation::FontName, aFontName); + + AutoCFRelease<CGDataProviderRef> provider = + ::CGDataProviderCreateWithData(nullptr, aFontData, aLength, &ReleaseData); + AutoCFRelease<CGFontRef> fontRef = ::CGFontCreateWithDataProvider(provider); + if (!fontRef) { + return nullptr; + } + + auto newFontEntry = MakeUnique<CTFontEntry>( + NS_ConvertUTF16toUTF8(uniqueName), fontRef, aWeightForEntry, + aStretchForEntry, aStyleForEntry, true, false); + return newFontEntry.release(); +} + +// Webkit code uses a system font meta name, so mimic that here +// WebCore/platform/graphics/mac/FontCacheMac.mm +static const char kSystemFont_system[] = "-apple-system"; + +bool CoreTextFontList::FindAndAddFamiliesLocked( + nsPresContext* aPresContext, StyleGenericFontFamily aGeneric, + const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput, + FindFamiliesFlags aFlags, gfxFontStyle* aStyle, nsAtom* aLanguage, + gfxFloat aDevToCssSize) { + if (aFamily.EqualsLiteral(kSystemFont_system)) { + // Search for special system font name, -apple-system. This is not done via + // the shared fontlist because the hidden system font may not be included + // there; we create a separate gfxFontFamily to manage this family. + if (auto* fam = FindSystemFontFamily(mSystemFontFamilyName)) { + aOutput->AppendElement(fam); + return true; + } + return false; + } + + return gfxPlatformFontList::FindAndAddFamiliesLocked( + aPresContext, aGeneric, aFamily, aOutput, aFlags, aStyle, aLanguage, + aDevToCssSize); +} + +// used to load system-wide font info on off-main thread +class CTFontInfo final : public FontInfoData { + public: + CTFontInfo(bool aLoadOtherNames, bool aLoadFaceNames, bool aLoadCmaps, + RecursiveMutex& aLock) + : FontInfoData(aLoadOtherNames, aLoadFaceNames, aLoadCmaps), + mLock(aLock) {} + + virtual ~CTFontInfo() = default; + + virtual void Load() { FontInfoData::Load(); } + + // loads font data for all members of a given family + virtual void LoadFontFamilyData(const nsACString& aFamilyName); + + RecursiveMutex& mLock; +}; + +void CTFontInfo::LoadFontFamilyData(const nsACString& aFamilyName) { + CrashReporter::AutoAnnotateCrashReport autoFontName( + CrashReporter::Annotation::FontName, aFamilyName); + // Prevent this from running concurrently with CGFont operations on the main + // thread, because the macOS font cache is fragile with concurrent access. + // This appears to be a vulnerability within CoreText in versions of macOS + // before macOS 13. In time, we can remove this lock. + RecursiveMutexAutoLock lock(mLock); + + // family name ==> CTFontDescriptor + AutoCFRelease<CFStringRef> family = CreateCFStringForString(aFamilyName); + + AutoCFRelease<CFMutableDictionaryRef> attr = + CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CFDictionaryAddValue(attr, kCTFontFamilyNameAttribute, family); + AutoCFRelease<CTFontDescriptorRef> fd = + CTFontDescriptorCreateWithAttributes(attr); + AutoCFRelease<CFArrayRef> matchingFonts = + CTFontDescriptorCreateMatchingFontDescriptors(fd, NULL); + if (!matchingFonts) { + return; + } + + nsTArray<nsCString> otherFamilyNames; + bool hasOtherFamilyNames = true; + + // iterate over faces in the family + int f, numFaces = (int)CFArrayGetCount(matchingFonts); + CTFontDescriptorRef prevFace = nullptr; + for (f = 0; f < numFaces; f++) { + mLoadStats.fonts++; + + CTFontDescriptorRef faceDesc = + (CTFontDescriptorRef)CFArrayGetValueAtIndex(matchingFonts, f); + if (!faceDesc) { + continue; + } + + if (faceDesc == prevFace) { + continue; + } + prevFace = faceDesc; + + AutoCFRelease<CTFontRef> fontRef = + CTFontCreateWithFontDescriptor(faceDesc, 0.0, nullptr); + if (!fontRef) { + NS_WARNING("failed to create a CTFontRef"); + continue; + } + + if (mLoadCmaps) { + // face name + AutoCFRelease<CFStringRef> faceName = + (CFStringRef)CTFontDescriptorCopyAttribute(faceDesc, + kCTFontNameAttribute); + + AutoTArray<UniChar, 1024> buffer; + CFIndex len = CFStringGetLength(faceName); + buffer.SetLength(len + 1); + CFStringGetCharacters(faceName, CFRangeMake(0, len), buffer.Elements()); + buffer[len] = 0; + NS_ConvertUTF16toUTF8 fontName( + reinterpret_cast<char16_t*>(buffer.Elements()), len); + + // load the cmap data + FontFaceData fontData; + AutoCFRelease<CFDataRef> cmapTable = CTFontCopyTable( + fontRef, kCTFontTableCmap, kCTFontTableOptionNoOptions); + + if (cmapTable) { + const uint8_t* cmapData = (const uint8_t*)CFDataGetBytePtr(cmapTable); + uint32_t cmapLen = CFDataGetLength(cmapTable); + RefPtr<gfxCharacterMap> charmap = new gfxCharacterMap(); + uint32_t offset; + nsresult rv; + + rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, *charmap, offset); + if (NS_SUCCEEDED(rv)) { + fontData.mCharacterMap = charmap; + fontData.mUVSOffset = offset; + mLoadStats.cmaps++; + } + } + + mFontFaceData.InsertOrUpdate(fontName, fontData); + } + + if (mLoadOtherNames && hasOtherFamilyNames) { + AutoCFRelease<CFDataRef> nameTable = CTFontCopyTable( + fontRef, kCTFontTableName, kCTFontTableOptionNoOptions); + + if (nameTable) { + const char* nameData = (const char*)CFDataGetBytePtr(nameTable); + uint32_t nameLen = CFDataGetLength(nameTable); + gfxFontUtils::ReadOtherFamilyNamesForFace( + aFamilyName, nameData, nameLen, otherFamilyNames, false); + hasOtherFamilyNames = otherFamilyNames.Length() != 0; + } + } + } + + // if found other names, insert them in the hash table + if (otherFamilyNames.Length() != 0) { + mOtherFamilyNames.InsertOrUpdate(aFamilyName, otherFamilyNames); + mLoadStats.othernames += otherFamilyNames.Length(); + } +} + +already_AddRefed<FontInfoData> CoreTextFontList::CreateFontInfoData() { + bool loadCmaps = !UsesSystemFallback() || + gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback(); + + mLock.AssertCurrentThreadIn(); + RefPtr<CTFontInfo> fi = + new CTFontInfo(true, NeedFullnamePostscriptNames(), loadCmaps, mLock); + return fi.forget(); +} + +gfxFontFamily* CoreTextFontList::CreateFontFamily( + const nsACString& aName, FontVisibility aVisibility) const { + return new CTFontFamily(aName, aVisibility); +} + +gfxFontEntry* CoreTextFontList::CreateFontEntry( + fontlist::Face* aFace, const fontlist::Family* aFamily) { + CTFontEntry* fe = new CTFontEntry( + aFace->mDescriptor.AsString(SharedFontList()), aFace->mWeight, false, + 0.0); // XXX standardFace, sizeHint + fe->InitializeFrom(aFace, aFamily); + return fe; +} + +void CoreTextFontList::AddFaceInitData( + CTFontDescriptorRef aFontDesc, nsTArray<fontlist::Face::InitData>& aFaces, + bool aLoadCmaps) { + AutoCFRelease<CFStringRef> psname = + (CFStringRef)CTFontDescriptorCopyAttribute(aFontDesc, + kCTFontNameAttribute); + AutoCFRelease<CFStringRef> facename = + (CFStringRef)CTFontDescriptorCopyAttribute(aFontDesc, + kCTFontStyleNameAttribute); + AutoCFRelease<CFDictionaryRef> traitsDict = + (CFDictionaryRef)CTFontDescriptorCopyAttribute(aFontDesc, + kCTFontTraitsAttribute); + + CFNumberRef weight = + (CFNumberRef)CFDictionaryGetValue(traitsDict, kCTFontWeightTrait); + CFNumberRef width = + (CFNumberRef)CFDictionaryGetValue(traitsDict, kCTFontWidthTrait); + CFNumberRef symbolicTraits = + (CFNumberRef)CFDictionaryGetValue(traitsDict, kCTFontSymbolicTrait); + + // make a nsString + nsAutoString postscriptFontName; + GetStringForCFString(psname, postscriptFontName); + + int32_t cssWeight = PR_GetCurrentThread() == sInitFontListThread + ? 0 + : 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)); + cssWeight *= 100; // scale up to CSS values + } else { + CGFloat weightValue; + CFNumberGetValue(weight, kCFNumberCGFloatType, &weightValue); + cssWeight = CoreTextWeightToCSSWeight(weightValue); + } + + CGFloat widthValue; + CFNumberGetValue(width, kCFNumberCGFloatType, &widthValue); + StretchRange stretch(CoreTextWidthToCSSStretch(widthValue)); + + SlantStyleRange slantStyle(FontSlantStyle::NORMAL); + SInt32 traitsValue; + CFNumberGetValue(symbolicTraits, kCFNumberSInt32Type, &traitsValue); + if (traitsValue & kCTFontItalicTrait) { + slantStyle = SlantStyleRange(FontSlantStyle::ITALIC); + } + + bool fixedPitch = traitsValue & kCTFontMonoSpaceTrait; + + RefPtr<gfxCharacterMap> charmap; + if (aLoadCmaps) { + AutoCFRelease<CGFontRef> font = + CGFontCreateWithFontName(CFStringRef(psname)); + if (font) { + uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p'); + AutoCFRelease<CFDataRef> data = CGFontCopyTableForTag(font, kCMAP); + if (data) { + uint32_t offset; + charmap = new gfxCharacterMap(); + gfxFontUtils::ReadCMAP(CFDataGetBytePtr(data), CFDataGetLength(data), + *charmap, offset); + } + } + } + + // Ensure that a face named "Regular" goes to the front of the list, so it + // will take precedence over other faces with the same style attributes but + // a different name (such as "Outline"). + auto data = fontlist::Face::InitData{ + NS_ConvertUTF16toUTF8(postscriptFontName), + 0, + fixedPitch, + WeightRange(FontWeight::FromInt(cssWeight)), + stretch, + slantStyle, + charmap, + }; + if (kCFCompareEqualTo == CFStringCompare(facename, CFSTR("Regular"), 0)) { + aFaces.InsertElementAt(0, std::move(data)); + } else { + aFaces.AppendElement(std::move(data)); + } +} + +void CoreTextFontList::GetFacesInitDataForFamily( + const fontlist::Family* aFamily, nsTArray<fontlist::Face::InitData>& aFaces, + bool aLoadCmaps) const { + auto name = aFamily->Key().AsString(SharedFontList()); + CrashReporter::AutoAnnotateCrashReport autoFontName( + CrashReporter::Annotation::FontName, name); + + struct Context { + nsTArray<fontlist::Face::InitData>& mFaces; + bool mLoadCmaps; + const void* prevValue = nullptr; + }; + auto addFaceFunc = [](const void* aValue, void* aContext) -> void { + Context* context = (Context*)aContext; + if (aValue == context->prevValue) { + return; + } + context->prevValue = aValue; + CTFontDescriptorRef fontDesc = (CTFontDescriptorRef)aValue; + CoreTextFontList::AddFaceInitData(fontDesc, context->mFaces, + context->mLoadCmaps); + }; + + AutoCFRelease<CTFontDescriptorRef> descriptor = + CreateDescriptorForFamily(name, false); + AutoCFRelease<CFArrayRef> faces = + CTFontDescriptorCreateMatchingFontDescriptors(descriptor, nullptr); + + if (faces) { + Context context{aFaces, aLoadCmaps}; + CFArrayApplyFunction(faces, CFRangeMake(0, CFArrayGetCount(faces)), + addFaceFunc, &context); + } +} + +void CoreTextFontList::ReadFaceNamesForFamily( + fontlist::Family* aFamily, bool aNeedFullnamePostscriptNames) { + if (!aFamily->IsInitialized()) { + if (!InitializeFamily(aFamily)) { + return; + } + } + const uint32_t kNAME = TRUETYPE_TAG('n', 'a', 'm', 'e'); + fontlist::FontList* list = SharedFontList(); + nsAutoCString canonicalName(aFamily->DisplayName().AsString(list)); + const auto* facePtrs = aFamily->Faces(list); + for (uint32_t i = 0, n = aFamily->NumFaces(); i < n; i++) { + auto* face = facePtrs[i].ToPtr<const fontlist::Face>(list); + if (!face) { + continue; + } + nsAutoCString name(face->mDescriptor.AsString(list)); + // We create a temporary CTFontEntry 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 CTFontEntry::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<CTFontEntry>(name, WeightRange(FontWeight::NORMAL), + false, 16.0); + if (!fe) { + continue; + } + gfxFontEntry::AutoTable nameTable(fe.get(), kNAME); + if (!nameTable) { + continue; + } + uint32_t dataLength; + const char* nameData = hb_blob_get_data(nameTable, &dataLength); + AutoTArray<nsCString, 4> otherFamilyNames; + gfxFontUtils::ReadOtherFamilyNamesForFace( + canonicalName, nameData, dataLength, otherFamilyNames, false); + for (const auto& alias : otherFamilyNames) { + nsAutoCString key; + GenerateFontListKey(alias, key); + auto aliasData = mAliasTable.GetOrInsertNew(key); + aliasData->InitFromFamily(aFamily, canonicalName); + aliasData->mFaces.AppendElement(facePtrs[i]); + } + } +} + +#ifdef MOZ_BUNDLED_FONTS +void CoreTextFontList::ActivateBundledFonts() { + nsCOMPtr<nsIFile> localDir; + if (NS_FAILED(NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(localDir)))) { + return; + } + if (NS_FAILED(localDir->Append(u"fonts"_ns))) { + return; + } + nsAutoCString path; + if (NS_FAILED(localDir->GetNativePath(path))) { + return; + } + ActivateFontsFromDir(path, &mBundledFamilies); +} +#endif diff --git a/gfx/thebes/CoreTextFontList.h b/gfx/thebes/CoreTextFontList.h new file mode 100644 index 0000000000..3aa7b75247 --- /dev/null +++ b/gfx/thebes/CoreTextFontList.h @@ -0,0 +1,261 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CoreTextFontList_H +#define CoreTextFontList_H + +#include <CoreFoundation/CoreFoundation.h> + +#include "gfxPlatformFontList.h" +#include "gfxPlatformMac.h" + +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/gfx/UnscaledFontMac.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/MemoryReporting.h" + +#include "nsRefPtrHashtable.h" +#include "nsTArray.h" +#include "nsUnicharUtils.h" + +// Abstract base class for Core Text/Core Graphics-based platform font list, +// which is subclassed to create specific macOS and iOS variants. + +// A single member of a font family (i.e. a single face, such as Times Italic). +class CTFontEntry final : public gfxFontEntry { + public: + friend class CoreTextFontList; + friend class gfxMacPlatformFontList; + friend class gfxMacFont; + + CTFontEntry(const nsACString& aPostscriptName, WeightRange aWeight, + bool aIsStandardFace = false, double aSizeHint = 0.0); + + // for use with data fonts + CTFontEntry(const nsACString& aPostscriptName, CGFontRef aFontRef, + WeightRange aWeight, StretchRange aStretch, + SlantStyleRange aStyle, bool aIsDataUserFont, bool aIsLocal); + + virtual ~CTFontEntry() { ::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() MOZ_REQUIRES_SHARED(mLock); + + // 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 MOZ_GUARDED_BY(mLock); // owning reference + + const double mSizeHint; + + bool mFontRefInitialized MOZ_GUARDED_BY(mLock); + + mozilla::Atomic<bool> mRequiresAAT; + mozilla::Atomic<bool> mIsCFF; + mozilla::Atomic<bool> mIsCFFInitialized; + mozilla::Atomic<bool> mHasVariations; + mozilla::Atomic<bool> mHasVariationsInitialized; + mozilla::Atomic<bool> mHasAATSmallCaps; + mozilla::Atomic<bool> mHasAATSmallCapsInitialized; + + // To work around Core Text's mishandling of the default value for 'opsz', + // we need to record whether the font has an a optical size axis, what its + // range and default values are, and a usable close-to-default alternative. + // (See bug 1457417 for details.) + // These fields are used by gfxMacFont, but stored in the font entry so + // that only a single font instance needs to inspect the available + // variations. + gfxFontVariationAxis mOpszAxis MOZ_GUARDED_BY(mLock); + float mAdjustedDefaultOpsz MOZ_GUARDED_BY(mLock); + + nsTHashtable<nsUint32HashKey> mAvailableTables MOZ_GUARDED_BY(mLock); + + mozilla::ThreadSafeWeakPtr<mozilla::gfx::UnscaledFontMac> mUnscaledFont; +}; + +class CTFontFamily : public gfxFontFamily { + public: + CTFontFamily(const nsACString& aName, FontVisibility aVisibility) + : gfxFontFamily(aName, aVisibility) {} + + virtual ~CTFontFamily() = default; + + void LocalizedName(nsACString& aLocalizedName) override; + + void FindStyleVariationsLocked(FontInfoData* aFontInfoData = nullptr) + MOZ_REQUIRES(mLock) override; + + protected: + void AddFace(CTFontDescriptorRef aFace) MOZ_REQUIRES(mLock); +}; + +class CoreTextFontList : public gfxPlatformFontList { + using FontFamilyListEntry = mozilla::dom::SystemFontListEntry; + + public: + gfxFontFamily* CreateFontFamily(const nsACString& aName, + FontVisibility aVisibility) const override; + + static int32_t AppleWeightToCSSWeight(int32_t aAppleWeight); + + gfxFontEntry* LookupLocalFont(nsPresContext* aPresContext, + const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) override; + + gfxFontEntry* MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, + uint32_t aLength) override; + + bool FindAndAddFamiliesLocked( + nsPresContext* aPresContext, mozilla::StyleGenericFontFamily aGeneric, + const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput, + FindFamiliesFlags aFlags, gfxFontStyle* aStyle = nullptr, + nsAtom* aLanguage = nullptr, gfxFloat aDevToCssSize = 1.0) + MOZ_REQUIRES(mLock) override; + + // Values for the entryType field in FontFamilyListEntry records passed + // from chrome to content process. + enum FontFamilyEntryType { + kStandardFontFamily = 0, // a standard installed font family + kSystemFontFamily = 1, // name of 'system' font + }; + void ReadSystemFontList(mozilla::dom::SystemFontList*); + + protected: + CoreTextFontList(); + virtual ~CoreTextFontList(); + + // initialize font lists + nsresult InitFontListForPlatform() MOZ_REQUIRES(mLock) override; + void InitSharedFontListForPlatform() MOZ_REQUIRES(mLock) override; + + // handle commonly used fonts for which the name table should be loaded at + // startup + void PreloadNamesList() MOZ_REQUIRES(mLock); + + // initialize system fonts + virtual void InitSystemFontNames() = 0; + + // Hooks for the macOS-specific "single face family" hack (Osaka-mono). + virtual void InitSingleFaceList() {} + virtual void InitAliasesForSingleFaceList() {} + + virtual bool DeprecatedFamilyIsAvailable(const nsACString& aName) { + return false; + } + + virtual FontVisibility GetVisibilityForFamily(const nsACString& aName) const { + return FontVisibility::Unknown; + } + + // helper function to lookup in both hidden system fonts and normal fonts + gfxFontFamily* FindSystemFontFamily(const nsACString& aFamily) + MOZ_REQUIRES(mLock); + + static void RegisteredFontsChangedNotificationCallback( + CFNotificationCenterRef center, void* observer, CFStringRef name, + const void* object, CFDictionaryRef userInfo); + + // attempt to use platform-specific fallback for the given character + // return null if no usable result found + gfxFontEntry* PlatformGlobalFontFallback(nsPresContext* aPresContext, + const uint32_t aCh, + Script aRunScript, + const gfxFontStyle* aMatchStyle, + FontFamily& aMatchedFamily) + MOZ_REQUIRES(mLock) override; + + bool UsesSystemFallback() override { return true; } + + already_AddRefed<FontInfoData> CreateFontInfoData() override; + + // Add the specified family to mFontFamilies. + void AddFamily(CFStringRef aFamily) MOZ_REQUIRES(mLock); + + void AddFamily(const nsACString& aFamilyName, FontVisibility aVisibility) + MOZ_REQUIRES(mLock); + + static void ActivateFontsFromDir( + const nsACString& aDir, + nsTHashSet<nsCStringHashKey>* aLoadedFamilies = nullptr); + + gfxFontEntry* CreateFontEntry( + mozilla::fontlist::Face* aFace, + const mozilla::fontlist::Family* aFamily) override; + + void GetFacesInitDataForFamily( + const mozilla::fontlist::Family* aFamily, + nsTArray<mozilla::fontlist::Face::InitData>& aFaces, + bool aLoadCmaps) const override; + + static void AddFaceInitData( + CTFontDescriptorRef aFontDesc, + nsTArray<mozilla::fontlist::Face::InitData>& aFaces, bool aLoadCmaps); + + void ReadFaceNamesForFamily(mozilla::fontlist::Family* aFamily, + bool aNeedFullnamePostscriptNames) + MOZ_REQUIRES(mLock) override; + +#ifdef MOZ_BUNDLED_FONTS + void ActivateBundledFonts(); +#endif + + // default font for use with system-wide font fallback + CTFontRef mDefaultFont; + + // Font family that -apple-system maps to + nsCString mSystemFontFamilyName; + + nsTArray<nsCString> mPreloadFonts; + +#ifdef MOZ_BUNDLED_FONTS + nsTHashSet<nsCStringHashKey> mBundledFamilies; +#endif +}; + +#endif /* CoreTextFontList_H */ diff --git a/gfx/thebes/D3D11Checks.cpp b/gfx/thebes/D3D11Checks.cpp new file mode 100644 index 0000000000..72af5a3ed2 --- /dev/null +++ b/gfx/thebes/D3D11Checks.cpp @@ -0,0 +1,505 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "D3D11Checks.h" +#include "DXVA2Manager.h" +#include "gfxConfig.h" +#include "GfxDriverInfo.h" +#include "gfxWindowsPlatform.h" +#include "mozilla/Components.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/StaticPrefs_media.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/layers/TextureD3D11.h" +#include "nsIGfxInfo.h" +#include <dxgi.h> +#include <dxgi1_2.h> +#include <d3d10_1.h> +#include <d3d11.h> +#include <d3d11_1.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_NTHANDLE | + D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX; + + HRESULT hr = aDevice->CreateTexture2D(&offscreenTextureDesc, NULL, + getter_AddRefs(offscreenTexture)); + if (FAILED(hr)) { + gfxCriticalNote << "DoesRecreatingCreateTexture2DFail"; + return false; + } + + hr = offscreenTexture->QueryInterface(__uuidof(IDXGIKeyedMutex), + (void**)getter_AddRefs(keyedMutex)); + if (FAILED(hr)) { + gfxCriticalNote << "DoesRecreatingKeyedMutexFailed"; + return false; + } + D3D11_RENDER_TARGET_VIEW_DESC offscreenRTVDesc; + offscreenRTVDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + offscreenRTVDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; + offscreenRTVDesc.Texture2D.MipSlice = 0; + + RefPtr<ID3D11RenderTargetView> offscreenRTView; + hr = aDevice->CreateRenderTargetView(offscreenTexture, &offscreenRTVDesc, + getter_AddRefs(offscreenRTView)); + if (FAILED(hr)) { + gfxCriticalNote << "DoesRecreatingCreateRenderTargetViewFailed"; + return false; + } + + { + // Acquire and clear + HRESULT hr; + AutoTextureLock lock(keyedMutex, hr, INFINITE); + FLOAT color1[4] = {1, 1, 0.5, 1}; + deviceContext->ClearRenderTargetView(offscreenRTView, color1); + } + + { + HRESULT hr; + AutoTextureLock lock(keyedMutex, hr, INFINITE); + FLOAT color2[4] = {1, 1, 0, 1}; + + deviceContext->ClearRenderTargetView(offscreenRTView, color2); + D3D11_TEXTURE2D_DESC desc; + + offscreenTexture->GetDesc(&desc); + desc.Usage = D3D11_USAGE_STAGING; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + desc.MiscFlags = 0; + desc.BindFlags = 0; + RefPtr<ID3D11Texture2D> cpuTexture; + hr = aDevice->CreateTexture2D(&desc, NULL, getter_AddRefs(cpuTexture)); + if (FAILED(hr)) { + gfxCriticalNote << "DoesRecreatingCreateCPUTextureFailed"; + return false; + } + + deviceContext->CopyResource(cpuTexture, offscreenTexture); + + D3D11_MAPPED_SUBRESOURCE mapped; + hr = deviceContext->Map(cpuTexture, 0, D3D11_MAP_READ, 0, &mapped); + if (FAILED(hr)) { + gfxCriticalNote << "DoesRecreatingMapFailed " << hexa(hr); + return false; + } + uint32_t resultColor = *(uint32_t*)mapped.pData; + deviceContext->Unmap(cpuTexture, 0); + cpuTexture = nullptr; + + // XXX on some drivers resultColor will not have changed to + // match the clear + if (resultColor != 0xffffff00) { + gfxCriticalNote << "RenderTargetViewNeedsRecreating"; + result = true; + } + } + return result; +} + +/* static */ +bool D3D11Checks::DoesDeviceWork() { + static bool checked = false; + static bool result = false; + + if (checked) return result; + checked = true; + + if (StaticPrefs::gfx_direct2d_force_enabled_AtStartup() || + gfxConfig::IsForcedOnByUser(Feature::HW_COMPOSITING)) { + result = true; + return true; + } + + if (GetModuleHandleW(L"igd10umd32.dll")) { + const wchar_t* checkModules[] = {L"dlumd32.dll", L"dlumd11.dll", + L"dlumd10.dll"}; + for (size_t i = 0; i < PR_ARRAY_SIZE(checkModules); i += 1) { + if (GetModuleHandleW(checkModules[i])) { + nsString displayLinkModuleVersionString; + gfxWindowsPlatform::GetDLLVersion(checkModules[i], + displayLinkModuleVersionString); + uint64_t displayLinkModuleVersion; + if (!ParseDriverVersion(displayLinkModuleVersionString, + &displayLinkModuleVersion)) { + gfxCriticalError() + << "DisplayLink: could not parse version " << checkModules[i]; + return false; + } + if (displayLinkModuleVersion <= V(8, 6, 1, 36484)) { + NS_ConvertUTF16toUTF8 version(displayLinkModuleVersionString); + gfxCriticalError(CriticalLog::DefaultOptions(false)) + << "DisplayLink: too old version " << version.get(); + return false; + } + } + } + } + result = true; + return true; +} + +static bool TryCreateTexture2D(ID3D11Device* device, D3D11_TEXTURE2D_DESC* desc, + D3D11_SUBRESOURCE_DATA* data, + RefPtr<ID3D11Texture2D>& texture) { + // Older Intel driver version (see bug 1221348 for version #s) crash when + // creating a texture with shared keyed mutex and data. + MOZ_SEH_TRY { + return !FAILED( + device->CreateTexture2D(desc, data, getter_AddRefs(texture))); + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + // For now we want to aggregrate all the crash signature to a known crash. + gfxDevCrash(LogReason::TextureCreation) + << "Crash creating texture. See bug 1221348."; + return false; + } +} + +// See bug 1083071. On some drivers, Direct3D 11 CreateShaderResourceView fails +// with E_OUTOFMEMORY. +static bool DoesTextureSharingWorkInternal(ID3D11Device* device, + DXGI_FORMAT format, UINT bindflags) { + // CreateTexture2D is known to crash on lower feature levels, see bugs + // 1170211 and 1089413. + if (device->GetFeatureLevel() < D3D_FEATURE_LEVEL_10_0) { + return false; + } + + if (StaticPrefs::gfx_direct2d_force_enabled_AtStartup() || + gfxConfig::IsForcedOnByUser(Feature::HW_COMPOSITING)) { + return true; + } + + if (GetModuleHandleW(L"atidxx32.dll")) { + nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); + if (gfxInfo) { + nsString vendorID, vendorID2; + gfxInfo->GetAdapterVendorID(vendorID); + gfxInfo->GetAdapterVendorID2(vendorID2); + if (vendorID.EqualsLiteral("0x8086") && vendorID2.IsEmpty()) { + if (!StaticPrefs::layers_amd_switchable_gfx_enabled_AtStartup()) { + return false; + } + gfxCriticalError(CriticalLog::DefaultOptions(false)) + << "PossiblyBrokenSurfaceSharing_UnexpectedAMDGPU"; + } + } + } + + RefPtr<ID3D11Texture2D> texture; + D3D11_TEXTURE2D_DESC desc; + const int texture_size = 32; + desc.Width = texture_size; + desc.Height = texture_size; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = format; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.CPUAccessFlags = 0; + desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_NTHANDLE | + 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; + } + + RefPtr<IDXGIResource1> otherResource; + if (FAILED(texture->QueryInterface(__uuidof(IDXGIResource1), + getter_AddRefs(otherResource)))) { + gfxCriticalError() << "DoesD3D11TextureSharingWork_GetResourceFailure"; + return false; + } + + HANDLE sharedHandle; + if (FAILED(otherResource->CreateSharedHandle( + nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, + nullptr, &sharedHandle))) { + gfxCriticalError() << "DoesD3D11TextureSharingWork_GetSharedTextureFailure"; + return false; + } + + auto handle = ipc::FileDescriptor(UniqueFileHandle(sharedHandle)); + + RefPtr<ID3D11Device1> device1; + device->QueryInterface((ID3D11Device1**)getter_AddRefs(device1)); + if (!device1) { + gfxCriticalNoteOnce << "Failed to get ID3D11Device1"; + return false; + } + + RefPtr<ID3D11Resource> sharedResource; + RefPtr<ID3D11Texture2D> sharedTexture; + auto raw = handle.TakePlatformHandle(); + if (FAILED(device1->OpenSharedResource1(raw.get(), __uuidof(ID3D11Resource), + getter_AddRefs(sharedResource)))) { + gfxCriticalError(CriticalLog::DefaultOptions(false)) + << "OpenSharedResource failed for format " << format; + return false; + } + + if (FAILED(sharedResource->QueryInterface(__uuidof(ID3D11Texture2D), + getter_AddRefs(sharedTexture)))) { + gfxCriticalError() << "DoesD3D11TextureSharingWork_GetSharedTextureFailure"; + return false; + } + + // create a staging texture for readback + RefPtr<ID3D11Texture2D> cpuTexture; + desc.Usage = D3D11_USAGE_STAGING; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + desc.MiscFlags = 0; + desc.BindFlags = 0; + if (FAILED(device->CreateTexture2D(&desc, nullptr, + getter_AddRefs(cpuTexture)))) { + gfxCriticalError() << "DoesD3D11TextureSharingWork_CreateTextureFailure"; + return false; + } + + RefPtr<IDXGIKeyedMutex> sharedMutex; + sharedResource->QueryInterface(__uuidof(IDXGIKeyedMutex), + (void**)getter_AddRefs(sharedMutex)); + { + HRESULT hr; + AutoTextureLock lock(sharedMutex, hr, 30 * 1000); + if (FAILED(hr)) { + gfxCriticalError() << "DoesD3D11TextureSharingWork_AcquireSyncTimeout"; + // only wait for 30 seconds + return false; + } + + // Copy to the cpu texture so that we can readback + deviceContext->CopyResource(cpuTexture, sharedTexture); + + // We only need to hold on to the mutex during the copy. + sharedMutex->ReleaseSync(0); + } + + D3D11_MAPPED_SUBRESOURCE mapped; + uint32_t resultColor = 0; + if (SUCCEEDED( + deviceContext->Map(cpuTexture, 0, D3D11_MAP_READ, 0, &mapped))) { + // read the texture + resultColor = *(uint32_t*)mapped.pData; + deviceContext->Unmap(cpuTexture, 0); + } else { + gfxCriticalError() << "DoesD3D11TextureSharingWork_MapFailed"; + return false; + } + + // check that the color we put in is the color we get out + if (resultColor != color[0]) { + // Shared surfaces seem to be broken on dual AMD & Intel HW when using the + // AMD GPU + gfxCriticalNote << "DoesD3D11TextureSharingWork_ColorMismatch"; + return false; + } + + RefPtr<ID3D11ShaderResourceView> sharedView; + + // This if(FAILED()) is the one that actually fails on systems affected by bug + // 1083071. + if (FAILED(device->CreateShaderResourceView(sharedTexture, NULL, + getter_AddRefs(sharedView)))) { + gfxCriticalNote << "CreateShaderResourceView failed for format" << format; + return false; + } + + return true; +} + +/* static */ +bool D3D11Checks::DoesTextureSharingWork(ID3D11Device* device) { + return DoesTextureSharingWorkInternal( + device, DXGI_FORMAT_B8G8R8A8_UNORM, + D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE); +} + +/* static */ +bool D3D11Checks::DoesAlphaTextureSharingWork(ID3D11Device* device) { + return DoesTextureSharingWorkInternal(device, DXGI_FORMAT_R8_UNORM, + D3D11_BIND_SHADER_RESOURCE); +} + +/* static */ +bool D3D11Checks::GetDxgiDesc(ID3D11Device* device, DXGI_ADAPTER_DESC* out) { + RefPtr<IDXGIDevice> dxgiDevice; + HRESULT hr = + device->QueryInterface(__uuidof(IDXGIDevice), getter_AddRefs(dxgiDevice)); + if (FAILED(hr)) { + return false; + } + + RefPtr<IDXGIAdapter> dxgiAdapter; + if (FAILED(dxgiDevice->GetAdapter(getter_AddRefs(dxgiAdapter)))) { + return false; + } + + return SUCCEEDED(dxgiAdapter->GetDesc(out)); +} + +/* static */ +void D3D11Checks::WarnOnAdapterMismatch(ID3D11Device* device) { + DXGI_ADAPTER_DESC desc; + PodZero(&desc); + GetDxgiDesc(device, &desc); + + nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); + nsString vendorID; + gfxInfo->GetAdapterVendorID(vendorID); + nsresult ec; + int32_t vendor = vendorID.ToInteger(&ec, 16); + if (vendor != static_cast<int32_t>(desc.VendorId)) { + gfxCriticalNote << "VendorIDMismatch V " << hexa(vendor) << " " + << hexa(desc.VendorId); + } +} + +/* static */ +bool D3D11Checks::DoesRemotePresentWork(IDXGIAdapter* adapter) { + // Remote presentation was added in DXGI 1.2, for Windows 8 and the Platform + // Update to Windows 7. + RefPtr<IDXGIAdapter2> check; + HRESULT hr = + adapter->QueryInterface(__uuidof(IDXGIAdapter2), getter_AddRefs(check)); + return SUCCEEDED(hr) && check; +} + +/* static */ D3D11Checks::VideoFormatOptionSet D3D11Checks::FormatOptions( + ID3D11Device* device) { + auto doesNV12Work = [&]() { + if (gfxVars::DXNV12Blocked()) { + return false; + } + + DXGI_ADAPTER_DESC desc; + PodZero(&desc); + if (!GetDxgiDesc(device, &desc)) { + // Failed to retrieve device information, assume it doesn't work + return false; + } + + UINT formatSupport; + HRESULT hr = device->CheckFormatSupport(DXGI_FORMAT_NV12, &formatSupport); + if (FAILED(hr) || !(formatSupport & D3D11_FORMAT_SUPPORT_TEXTURE2D)) { + return false; + } + + nsString version; + nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); + if (gfxInfo) { + gfxInfo->GetAdapterDriverVersion(version); + } + return DXVA2Manager::IsNV12Supported(desc.VendorId, desc.DeviceId, version); + }; + + auto doesP010Work = [&]() { + if (gfxVars::DXP010Blocked() && + !StaticPrefs::media_wmf_force_allow_p010_format()) { + return false; + } + UINT formatSupport; + HRESULT hr = device->CheckFormatSupport(DXGI_FORMAT_P010, &formatSupport); + return (SUCCEEDED(hr) && (formatSupport & D3D11_FORMAT_SUPPORT_TEXTURE2D)); + }; + + auto doesP016Work = [&]() { + if (gfxVars::DXP016Blocked() && + !StaticPrefs::media_wmf_force_allow_p010_format()) { + return false; + } + UINT formatSupport; + HRESULT hr = device->CheckFormatSupport(DXGI_FORMAT_P016, &formatSupport); + return (SUCCEEDED(hr) && (formatSupport & D3D11_FORMAT_SUPPORT_TEXTURE2D)); + }; + + VideoFormatOptionSet options; + if (!doesNV12Work()) { + // If the device doesn't support NV12, there's really no point testing for + // P010 and P016. + return options; + } + options += VideoFormatOption::NV12; + if (doesP010Work()) { + options += VideoFormatOption::P010; + } + if (doesP016Work()) { + options += VideoFormatOption::P016; + } + return options; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/thebes/D3D11Checks.h b/gfx/thebes/D3D11Checks.h new file mode 100644 index 0000000000..622cf3229c --- /dev/null +++ b/gfx/thebes/D3D11Checks.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_thebes_D3D11Checks_h +#define mozilla_gfx_thebes_D3D11Checks_h + +#include "mozilla/EnumSet.h" +#include "mozilla/EnumTypeTraits.h" + +struct ID3D11Device; +struct IDXGIAdapter; +struct DXGI_ADAPTER_DESC; + +namespace mozilla { +namespace gfx { + +struct D3D11Checks { + enum class VideoFormatOption { + NV12, + P010, + P016, + }; + using VideoFormatOptionSet = EnumSet<VideoFormatOption>; + + static bool DoesRenderTargetViewNeedRecreating(ID3D11Device* aDevice); + static bool DoesDeviceWork(); + static bool DoesTextureSharingWork(ID3D11Device* device); + static bool DoesAlphaTextureSharingWork(ID3D11Device* device); + static void WarnOnAdapterMismatch(ID3D11Device* device); + static bool GetDxgiDesc(ID3D11Device* device, DXGI_ADAPTER_DESC* out); + static bool DoesRemotePresentWork(IDXGIAdapter* adapter); + static VideoFormatOptionSet FormatOptions(ID3D11Device* device); +}; + +} // namespace gfx + +// Used for IPDL serialization. +// The 'value' have to be the biggest enum from D3D11Checks::Option. +template <> +struct MaxEnumValue<::mozilla::gfx::D3D11Checks::VideoFormatOption> { + static constexpr unsigned int value = + static_cast<unsigned int>(gfx::D3D11Checks::VideoFormatOption::P016); +}; + +} // namespace mozilla + +#endif // mozilla_gfx_thebes_D3D11Checks_h diff --git a/gfx/thebes/DeviceManagerDx.cpp b/gfx/thebes/DeviceManagerDx.cpp new file mode 100644 index 0000000000..18c5cea7db --- /dev/null +++ b/gfx/thebes/DeviceManagerDx.cpp @@ -0,0 +1,1432 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/gfx/GPUParent.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/gfx/GraphicsMessages.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/layers/CompositorBridgeChild.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/DeviceAttachmentsD3D11.h" +#include "mozilla/Preferences.h" +#include "nsPrintfCString.h" +#include "nsString.h" + +// - + +#include <d3d11.h> +#include <dcomp.h> +#include <ddraw.h> +#include <dxgi.h> + +namespace mozilla { +namespace gfx { + +using namespace mozilla::widget; +using namespace mozilla::layers; + +StaticAutoPtr<DeviceManagerDx> DeviceManagerDx::sInstance; + +// We don't have access to the D3D11CreateDevice type in gfxWindowsPlatform.h, +// since it doesn't include d3d11.h, so we use a static here. It should only +// be used within InitializeD3D11. +decltype(D3D11CreateDevice)* sD3D11CreateDeviceFn = nullptr; + +// It should only be used within CreateDirectCompositionDevice. +decltype(DCompositionCreateDevice2)* sDcompCreateDevice2Fn = nullptr; +decltype(DCompositionCreateDevice3)* sDcompCreateDevice3Fn = nullptr; + +// It should only be used within CreateDCompSurfaceHandle +decltype(DCompositionCreateSurfaceHandle)* sDcompCreateSurfaceHandleFn = + nullptr; + +// We don't have access to the DirectDrawCreateEx type in gfxWindowsPlatform.h, +// since it doesn't include ddraw.h, so we use a static here. It should only +// be used within InitializeDirectDrawConfig. +decltype(DirectDrawCreateEx)* sDirectDrawCreateExFn = nullptr; + +/* static */ +void DeviceManagerDx::Init() { sInstance = new DeviceManagerDx(); } + +/* static */ +void DeviceManagerDx::Shutdown() { sInstance = nullptr; } + +DeviceManagerDx::DeviceManagerDx() + : mDeviceLock("gfxWindowsPlatform.mDeviceLock"), + mCompositorDeviceSupportsVideo(false) { + // Set up the D3D11 feature levels we can ask for. + mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_11_1); + mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_11_0); + mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_10_1); + mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_10_0); + MOZ_COUNT_CTOR(DeviceManagerDx); +} + +DeviceManagerDx::~DeviceManagerDx() { MOZ_COUNT_DTOR(DeviceManagerDx); } + +bool DeviceManagerDx::LoadD3D11() { + FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING); + MOZ_ASSERT(d3d11.IsEnabled()); + + if (sD3D11CreateDeviceFn) { + return true; + } + + nsModuleHandle module(LoadLibrarySystem32(L"d3d11.dll")); + if (!module) { + d3d11.SetFailed(FeatureStatus::Unavailable, + "Direct3D11 not available on this computer", + "FEATURE_FAILURE_D3D11_LIB"_ns); + return false; + } + + sD3D11CreateDeviceFn = + (decltype(D3D11CreateDevice)*)GetProcAddress(module, "D3D11CreateDevice"); + if (!sD3D11CreateDeviceFn) { + // We should just be on Windows Vista or XP in this case. + d3d11.SetFailed(FeatureStatus::Unavailable, + "Direct3D11 not available on this computer", + "FEATURE_FAILURE_D3D11_FUNCPTR"_ns); + return false; + } + + mD3D11Module.steal(module); + return true; +} + +bool DeviceManagerDx::LoadDcomp() { + MOZ_ASSERT(gfxConfig::GetFeature(Feature::D3D11_COMPOSITING).IsEnabled()); + MOZ_ASSERT(gfxVars::UseWebRenderANGLE()); + MOZ_ASSERT(gfxVars::UseWebRenderDCompWin()); + + if (sDcompCreateDevice2Fn) { + return true; // Already loaded. + } + + nsModuleHandle module(LoadLibrarySystem32(L"dcomp.dll")); + if (!module) { + return false; + } + + sDcompCreateDevice2Fn = (decltype(DCompositionCreateDevice2)*)GetProcAddress( + module, "DCompositionCreateDevice2"); + sDcompCreateDevice3Fn = (decltype(DCompositionCreateDevice3)*)GetProcAddress( + module, "DCompositionCreateDevice3"); + if (!sDcompCreateDevice2Fn) { + return false; + } + + // Load optional API for external compositing + sDcompCreateSurfaceHandleFn = + (decltype(DCompositionCreateSurfaceHandle)*)::GetProcAddress( + module, "DCompositionCreateSurfaceHandle"); + + mDcompModule.steal(module); + return true; +} + +void DeviceManagerDx::ReleaseD3D11() { + MOZ_ASSERT(!mCompositorDevice); + MOZ_ASSERT(!mContentDevice); + MOZ_ASSERT(!mVRDevice); + MOZ_ASSERT(!mDecoderDevice); + + mD3D11Module.reset(); + sD3D11CreateDeviceFn = nullptr; +} + +nsTArray<DXGI_OUTPUT_DESC1> DeviceManagerDx::EnumerateOutputs() { + RefPtr<IDXGIAdapter> adapter = GetDXGIAdapter(); + + if (!adapter) { + NS_WARNING("Failed to acquire a DXGI adapter for enumerating outputs."); + return nsTArray<DXGI_OUTPUT_DESC1>(); + } + + nsTArray<DXGI_OUTPUT_DESC1> outputs; + for (UINT i = 0;; ++i) { + RefPtr<IDXGIOutput> output = nullptr; + if (FAILED(adapter->EnumOutputs(i, getter_AddRefs(output)))) { + break; + } + + RefPtr<IDXGIOutput6> output6 = nullptr; + if (FAILED(output->QueryInterface(__uuidof(IDXGIOutput6), + getter_AddRefs(output6)))) { + break; + } + + DXGI_OUTPUT_DESC1 desc; + if (FAILED(output6->GetDesc1(&desc))) { + break; + } + + outputs.AppendElement(desc); + } + return outputs; +} + +bool DeviceManagerDx::GetOutputFromMonitor(HMONITOR monitor, + RefPtr<IDXGIOutput>* aOutOutput) { + RefPtr<IDXGIAdapter> adapter = GetDXGIAdapter(); + + if (!adapter) { + NS_WARNING("Failed to acquire a DXGI adapter for GetOutputFromMonitor."); + return false; + } + + for (UINT i = 0;; ++i) { + RefPtr<IDXGIOutput> output = nullptr; + if (FAILED(adapter->EnumOutputs(i, getter_AddRefs(output)))) { + break; + } + + DXGI_OUTPUT_DESC desc; + if (FAILED(output->GetDesc(&desc))) { + continue; + } + + if (desc.Monitor == monitor) { + *aOutOutput = output; + return true; + } + } + return false; +} + +void DeviceManagerDx::CheckHardwareStretchingSupport(HwStretchingSupport& aRv) { + RefPtr<IDXGIAdapter> adapter = GetDXGIAdapter(); + + if (!adapter) { + NS_WARNING( + "Failed to acquire a DXGI adapter for checking hardware stretching " + "support."); + ++aRv.mError; + return; + } + + for (UINT i = 0;; ++i) { + RefPtr<IDXGIOutput> output = nullptr; + HRESULT result = adapter->EnumOutputs(i, getter_AddRefs(output)); + if (result == DXGI_ERROR_NOT_FOUND) { + // No more outputs to check. + break; + } + + if (FAILED(result)) { + ++aRv.mError; + break; + } + + RefPtr<IDXGIOutput6> output6 = nullptr; + if (FAILED(output->QueryInterface(__uuidof(IDXGIOutput6), + getter_AddRefs(output6)))) { + ++aRv.mError; + continue; + } + + UINT flags = 0; + if (FAILED(output6->CheckHardwareCompositionSupport(&flags))) { + ++aRv.mError; + continue; + } + + bool fullScreen = flags & DXGI_HARDWARE_COMPOSITION_SUPPORT_FLAG_FULLSCREEN; + bool window = flags & DXGI_HARDWARE_COMPOSITION_SUPPORT_FLAG_WINDOWED; + if (fullScreen && window) { + ++aRv.mBoth; + } else if (fullScreen) { + ++aRv.mFullScreenOnly; + } else if (window) { + ++aRv.mWindowOnly; + } else { + ++aRv.mNone; + } + } +} + +#ifdef DEBUG +static inline bool ProcessOwnsCompositor() { + return XRE_GetProcessType() == GeckoProcessType_GPU || + XRE_GetProcessType() == GeckoProcessType_VR || + (XRE_IsParentProcess() && !gfxConfig::IsEnabled(Feature::GPU_PROCESS)); +} +#endif + +bool DeviceManagerDx::CreateCompositorDevices() { + MutexAutoLock lock(mDeviceLock); + return CreateCompositorDevicesLocked(); +} + +bool DeviceManagerDx::CreateCompositorDevicesLocked() { + MOZ_ASSERT(ProcessOwnsCompositor()); + + FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING); + MOZ_ASSERT(d3d11.IsEnabled()); + + if (int32_t sleepSec = + StaticPrefs::gfx_direct3d11_sleep_on_create_device_AtStartup()) { + printf_stderr("Attach to PID: %lu\n", GetCurrentProcessId()); + Sleep(sleepSec * 1000); + } + + if (!LoadD3D11()) { + return false; + } + + CreateCompositorDevice(d3d11); + + if (!d3d11.IsEnabled()) { + MOZ_ASSERT(!mCompositorDevice); + ReleaseD3D11(); + + return false; + } + + // We leak these everywhere and we need them our entire runtime anyway, let's + // leak it here as well. We keep the pointer to sD3D11CreateDeviceFn around + // as well for D2D1 and device resets. + mD3D11Module.disown(); + + MOZ_ASSERT(mCompositorDevice); + if (!d3d11.IsEnabled()) { + return false; + } + + // When WR is used, do not preload attachments for D3D11 Non-WR compositor. + // + // Fallback from WR to D3D11 Non-WR compositor without re-creating gpu process + // could happen when WR causes error. In this case, the attachments are loaded + // synchronously. + if (gfx::gfxVars::UseSoftwareWebRender()) { + PreloadAttachmentsOnCompositorThread(); + } + + return true; +} + +bool DeviceManagerDx::CreateVRDevice() { + MOZ_ASSERT(ProcessOwnsCompositor()); + + if (mVRDevice) { + return true; + } + + if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + NS_WARNING("Direct3D11 Compositing required for VR"); + return false; + } + + if (!LoadD3D11()) { + return false; + } + + RefPtr<IDXGIAdapter1> adapter = GetDXGIAdapterLocked(); + if (!adapter) { + NS_WARNING("Failed to acquire a DXGI adapter for VR"); + return false; + } + + UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + + HRESULT hr; + if (!CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, flags, hr, mVRDevice)) { + gfxCriticalError() << "Crash during D3D11 device creation for VR"; + return false; + } + + if (FAILED(hr) || !mVRDevice) { + NS_WARNING("Failed to acquire a D3D11 device for VR"); + return false; + } + + return true; +} + +bool DeviceManagerDx::CreateCanvasDevice() { + MutexAutoLock lock(mDeviceLock); + return CreateCanvasDeviceLocked(); +} + +bool DeviceManagerDx::CreateCanvasDeviceLocked() { + MOZ_ASSERT(ProcessOwnsCompositor()); + + if (mCanvasDevice) { + return true; + } + + if (!LoadD3D11()) { + return false; + } + + RefPtr<IDXGIAdapter1> adapter = GetDXGIAdapterLocked(); + if (!adapter) { + NS_WARNING("Failed to acquire a DXGI adapter for Canvas"); + return false; + } + + UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + + HRESULT hr; + if (!CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, flags, hr, + mCanvasDevice)) { + gfxCriticalError() << "Crash during D3D11 device creation for Canvas"; + return false; + } + + if (StaticPrefs:: + gfx_direct2d_target_independent_rasterization_disabled_AtStartup()) { + int creationFlags = 0x2; // disable target independent rasterization + const GUID D2D_INTERNAL_DEVICE_CREATION_OPTIONS = { + 0xfb3a8e1a, + 0x2e3c, + 0x4de1, + {0x84, 0x42, 0x40, 0x43, 0xe0, 0xb0, 0x94, 0x95}}; + mCanvasDevice->SetPrivateData(D2D_INTERNAL_DEVICE_CREATION_OPTIONS, + sizeof(creationFlags), &creationFlags); + } + + if (FAILED(hr) || !mCanvasDevice) { + NS_WARNING("Failed to acquire a D3D11 device for Canvas"); + return false; + } + + if (!D3D11Checks::DoesTextureSharingWork(mCanvasDevice)) { + mCanvasDevice = nullptr; + return false; + } + + if (XRE_IsGPUProcess()) { + Factory::SetDirect3D11Device(mCanvasDevice); + } + + return true; +} + +void DeviceManagerDx::CreateDirectCompositionDevice() { + MutexAutoLock lock(mDeviceLock); + CreateDirectCompositionDeviceLocked(); +} + +void DeviceManagerDx::CreateDirectCompositionDeviceLocked() { + if (!gfxVars::UseWebRenderDCompWin()) { + return; + } + + if (!mCompositorDevice) { + return; + } + + if (!LoadDcomp()) { + return; + } + + RefPtr<IDXGIDevice> dxgiDevice; + if (mCompositorDevice->QueryInterface( + IID_PPV_ARGS((IDXGIDevice**)getter_AddRefs(dxgiDevice))) != S_OK) { + return; + } + + HRESULT hr; + RefPtr<IDCompositionDesktopDevice> desktopDevice; + MOZ_SEH_TRY { + hr = sDcompCreateDevice3Fn( + dxgiDevice.get(), + IID_PPV_ARGS( + (IDCompositionDesktopDevice**)getter_AddRefs(desktopDevice))); + if (!desktopDevice) { + hr = sDcompCreateDevice2Fn( + dxgiDevice.get(), + IID_PPV_ARGS( + (IDCompositionDesktopDevice**)getter_AddRefs(desktopDevice))); + } + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { return; } + + if (!SUCCEEDED(hr)) { + return; + } + + RefPtr<IDCompositionDevice2> compositionDevice; + if (desktopDevice->QueryInterface(IID_PPV_ARGS( + (IDCompositionDevice2**)getter_AddRefs(compositionDevice))) != S_OK) { + return; + } + + mDirectCompositionDevice = compositionDevice; +} + +/* static */ +HANDLE DeviceManagerDx::CreateDCompSurfaceHandle() { + if (!sDcompCreateSurfaceHandleFn) { + return 0; + } + + HANDLE handle = 0; + HRESULT hr = sDcompCreateSurfaceHandleFn(COMPOSITIONOBJECT_ALL_ACCESS, + nullptr, &handle); + if (FAILED(hr)) { + return 0; + } + + return handle; +} + +void DeviceManagerDx::ImportDeviceInfo(const D3D11DeviceStatus& aDeviceStatus) { + MOZ_ASSERT(!ProcessOwnsCompositor()); + + MutexAutoLock lock(mDeviceLock); + mDeviceStatus = Some(aDeviceStatus); +} + +bool DeviceManagerDx::ExportDeviceInfo(D3D11DeviceStatus* aOut) { + MutexAutoLock lock(mDeviceLock); + if (mDeviceStatus) { + *aOut = mDeviceStatus.value(); + return true; + } + + return false; +} + +void DeviceManagerDx::CreateContentDevices() { + MutexAutoLock lock(mDeviceLock); + CreateContentDevicesLocked(); +} + +void DeviceManagerDx::CreateContentDevicesLocked() { + MOZ_ASSERT(gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)); + + if (!LoadD3D11()) { + return; + } + + // We should have been assigned a DeviceStatus from the parent process, + // GPU process, or the same process if using in-process compositing. + MOZ_ASSERT(mDeviceStatus); + + if (CreateContentDevice() == FeatureStatus::CrashedInHandler) { + DisableD3D11AfterCrash(); + } +} + +already_AddRefed<IDXGIAdapter1> DeviceManagerDx::GetDXGIAdapter() { + MutexAutoLock lock(mDeviceLock); + return do_AddRef(GetDXGIAdapterLocked()); +} + +IDXGIAdapter1* DeviceManagerDx::GetDXGIAdapterLocked() { + if (mAdapter) { + return mAdapter; + } + + nsModuleHandle dxgiModule(LoadLibrarySystem32(L"dxgi.dll")); + decltype(CreateDXGIFactory1)* createDXGIFactory1 = + (decltype(CreateDXGIFactory1)*)GetProcAddress(dxgiModule, + "CreateDXGIFactory1"); + if (!createDXGIFactory1) { + return nullptr; + } + static const auto fCreateDXGIFactory2 = + (decltype(CreateDXGIFactory2)*)GetProcAddress(dxgiModule, + "CreateDXGIFactory2"); + + // Try to use a DXGI 1.1 adapter in order to share resources + // across processes. + RefPtr<IDXGIFactory1> factory1; + if (StaticPrefs::gfx_direct3d11_enable_debug_layer_AtStartup()) { + RefPtr<IDXGIFactory2> factory2; + if (fCreateDXGIFactory2) { + auto hr = fCreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG, + __uuidof(IDXGIFactory2), + getter_AddRefs(factory2)); + MOZ_ALWAYS_TRUE(!FAILED(hr)); + } else { + NS_WARNING( + "fCreateDXGIFactory2 not loaded, cannot create debug IDXGIFactory2."); + } + factory1 = factory2; + } + if (!factory1) { + HRESULT hr = + createDXGIFactory1(__uuidof(IDXGIFactory1), getter_AddRefs(factory1)); + if (FAILED(hr) || !factory1) { + // This seems to happen with some people running the iZ3D driver. + // They won't get acceleration. + return nullptr; + } + } + + if (!mDeviceStatus) { + // If we haven't created a device yet, and have no existing device status, + // then this must be the compositor device. Pick the first adapter we can. + if (FAILED(factory1->EnumAdapters1(0, getter_AddRefs(mAdapter)))) { + return nullptr; + } + } else { + // In the UI and GPU process, we clear mDeviceStatus on device reset, so we + // should never reach here. Furthermore, the UI process does not create + // devices when using a GPU process. + // + // So, this should only ever get called on the content process or RDD + // process + MOZ_ASSERT(XRE_IsContentProcess() || XRE_IsRDDProcess()); + + // In the child process, we search for the adapter that matches the parent + // process. The first adapter can be mismatched on dual-GPU systems. + for (UINT index = 0;; index++) { + RefPtr<IDXGIAdapter1> adapter; + if (FAILED(factory1->EnumAdapters1(index, getter_AddRefs(adapter)))) { + break; + } + + const DxgiAdapterDesc& preferred = mDeviceStatus->adapter(); + + DXGI_ADAPTER_DESC desc; + if (SUCCEEDED(adapter->GetDesc(&desc)) && + desc.AdapterLuid.HighPart == preferred.AdapterLuid.HighPart && + desc.AdapterLuid.LowPart == preferred.AdapterLuid.LowPart && + desc.VendorId == preferred.VendorId && + desc.DeviceId == preferred.DeviceId) { + mAdapter = adapter.forget(); + break; + } + } + } + + if (!mAdapter) { + return nullptr; + } + + // We leak this module everywhere, we might as well do so here as well. + dxgiModule.disown(); + return mAdapter; +} + +bool DeviceManagerDx::CreateCompositorDeviceHelper( + FeatureState& aD3d11, IDXGIAdapter1* aAdapter, bool aAttemptVideoSupport, + RefPtr<ID3D11Device>& aOutDevice) { + // Check if a failure was injected for testing. + if (StaticPrefs::gfx_testing_device_fail()) { + aD3d11.SetFailed(FeatureStatus::Failed, + "Direct3D11 device failure simulated by preference", + "FEATURE_FAILURE_D3D11_SIM"_ns); + return false; + } + + UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + + DXGI_ADAPTER_DESC desc; + aAdapter->GetDesc(&desc); + if (desc.VendorId != 0x1414) { + // 0x1414 is Microsoft (e.g. WARP) + // When not using WARP, use + // D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS to prevent + // bug 1092260. IE 11 also uses this flag. + flags |= D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS; + } + + if (aAttemptVideoSupport) { + flags |= D3D11_CREATE_DEVICE_VIDEO_SUPPORT; + } + + HRESULT hr; + RefPtr<ID3D11Device> device; + if (!CreateDevice(aAdapter, D3D_DRIVER_TYPE_UNKNOWN, flags, hr, device)) { + if (!aAttemptVideoSupport) { + gfxCriticalError() << "Crash during D3D11 device creation"; + aD3d11.SetFailed(FeatureStatus::CrashedInHandler, + "Crashed trying to acquire a D3D11 device", + "FEATURE_FAILURE_D3D11_DEVICE1"_ns); + } + return false; + } + + if (FAILED(hr) || !device) { + if (!aAttemptVideoSupport) { + aD3d11.SetFailed(FeatureStatus::Failed, + "Failed to acquire a D3D11 device", + "FEATURE_FAILURE_D3D11_DEVICE2"_ns); + } + return false; + } + if (!D3D11Checks::DoesDeviceWork()) { + if (!aAttemptVideoSupport) { + aD3d11.SetFailed(FeatureStatus::Broken, + "Direct3D11 device was determined to be broken", + "FEATURE_FAILURE_D3D11_BROKEN"_ns); + } + return false; + } + + aOutDevice = device; + return true; +} + +// Note that it's enough for us to just use a counter for a unique ID, +// even though the counter isn't synchronized between processes. If we +// start in the GPU process and wind up in the parent process, the +// whole graphics stack is blown away anyway. But just in case, we +// make gpu process IDs negative and parent process IDs positive. +static inline int32_t GetNextDeviceCounter() { + static int32_t sDeviceCounter = 0; + return XRE_IsGPUProcess() ? --sDeviceCounter : ++sDeviceCounter; +} + +void DeviceManagerDx::CreateCompositorDevice(FeatureState& d3d11) { + if (StaticPrefs::layers_d3d11_force_warp_AtStartup()) { + CreateWARPCompositorDevice(); + return; + } + + RefPtr<IDXGIAdapter1> adapter = GetDXGIAdapterLocked(); + if (!adapter) { + d3d11.SetFailed(FeatureStatus::Unavailable, + "Failed to acquire a DXGI adapter", + "FEATURE_FAILURE_D3D11_DXGI"_ns); + return; + } + + if (XRE_IsGPUProcess() && !D3D11Checks::DoesRemotePresentWork(adapter)) { + d3d11.SetFailed(FeatureStatus::Unavailable, + "DXGI does not support out-of-process presentation", + "FEATURE_FAILURE_D3D11_REMOTE_PRESENT"_ns); + return; + } + + RefPtr<ID3D11Device> device; + if (!CreateCompositorDeviceHelper(d3d11, adapter, true, device)) { + // Try again without video support and record that it failed. + mCompositorDeviceSupportsVideo = false; + if (!CreateCompositorDeviceHelper(d3d11, adapter, false, device)) { + return; + } + } else { + mCompositorDeviceSupportsVideo = true; + } + + // Only test this when not using WARP since it can fail and cause + // GetDeviceRemovedReason to return weird values. + bool textureSharingWorks = D3D11Checks::DoesTextureSharingWork(device); + + DXGI_ADAPTER_DESC desc; + PodZero(&desc); + adapter->GetDesc(&desc); + + if (!textureSharingWorks) { + gfxConfig::SetFailed(Feature::D3D11_HW_ANGLE, FeatureStatus::Broken, + "Texture sharing doesn't work", + "FEATURE_FAILURE_HW_ANGLE_NEEDS_TEXTURE_SHARING"_ns); + } + if (D3D11Checks::DoesRenderTargetViewNeedRecreating(device)) { + gfxConfig::SetFailed(Feature::D3D11_HW_ANGLE, FeatureStatus::Broken, + "RenderTargetViews need recreating", + "FEATURE_FAILURE_HW_ANGLE_NEEDS_RTV_RECREATION"_ns); + } + if (XRE_IsParentProcess()) { + // It seems like this may only happen when we're using the NVIDIA gpu + D3D11Checks::WarnOnAdapterMismatch(device); + } + + RefPtr<ID3D10Multithread> multi; + HRESULT hr = device->QueryInterface(__uuidof(ID3D10Multithread), + getter_AddRefs(multi)); + if (SUCCEEDED(hr) && multi) { + multi->SetMultithreadProtected(TRUE); + } + + uint32_t featureLevel = device->GetFeatureLevel(); + auto formatOptions = D3D11Checks::FormatOptions(device); + mCompositorDevice = device; + + int32_t sequenceNumber = GetNextDeviceCounter(); + mDeviceStatus = Some(D3D11DeviceStatus( + false, textureSharingWorks, featureLevel, DxgiAdapterDesc::From(desc), + sequenceNumber, formatOptions)); + mCompositorDevice->SetExceptionMode(0); +} + +bool DeviceManagerDx::CreateDevice(IDXGIAdapter* aAdapter, + D3D_DRIVER_TYPE aDriverType, UINT aFlags, + HRESULT& aResOut, + RefPtr<ID3D11Device>& aOutDevice) { + if (StaticPrefs::gfx_direct3d11_enable_debug_layer_AtStartup() || + StaticPrefs::gfx_direct3d11_break_on_error_AtStartup()) { + aFlags |= D3D11_CREATE_DEVICE_DEBUG; + } + + MOZ_SEH_TRY { + aResOut = sD3D11CreateDeviceFn( + aAdapter, aDriverType, nullptr, aFlags, mFeatureLevels.Elements(), + mFeatureLevels.Length(), D3D11_SDK_VERSION, getter_AddRefs(aOutDevice), + nullptr, nullptr); + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { return false; } + + if (StaticPrefs::gfx_direct3d11_break_on_error_AtStartup()) { + do { + if (!aOutDevice) break; + + RefPtr<ID3D11Debug> debug; + if (!SUCCEEDED(aOutDevice->QueryInterface(__uuidof(ID3D11Debug), + getter_AddRefs(debug)))) + break; + + RefPtr<ID3D11InfoQueue> infoQueue; + if (!SUCCEEDED(debug->QueryInterface(__uuidof(ID3D11InfoQueue), + getter_AddRefs(infoQueue)))) + break; + + D3D11_INFO_QUEUE_FILTER filter; + PodZero(&filter); + + // Disable warnings caused by Advanced Layers that are known and not + // problematic. + D3D11_MESSAGE_ID blockIDs[] = { + D3D11_MESSAGE_ID_DEVICE_DRAW_CONSTANT_BUFFER_TOO_SMALL}; + filter.DenyList.NumIDs = MOZ_ARRAY_LENGTH(blockIDs); + filter.DenyList.pIDList = blockIDs; + infoQueue->PushStorageFilter(&filter); + + infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_CORRUPTION, true); + infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR, true); + infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_WARNING, true); + } while (false); + } + + return true; +} + +void DeviceManagerDx::CreateWARPCompositorDevice() { + ScopedGfxFeatureReporter reporterWARP( + "D3D11-WARP", StaticPrefs::layers_d3d11_force_warp_AtStartup()); + FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING); + + HRESULT hr; + RefPtr<ID3D11Device> device; + + // Use D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS + // to prevent bug 1092260. IE 11 also uses this flag. + UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + if (!CreateDevice(nullptr, D3D_DRIVER_TYPE_WARP, flags, hr, device)) { + gfxCriticalError() << "Exception occurred initializing WARP D3D11 device!"; + d3d11.SetFailed(FeatureStatus::CrashedInHandler, + "Crashed creating a D3D11 WARP device", + "FEATURE_FAILURE_D3D11_WARP_DEVICE"_ns); + } + + if (FAILED(hr) || !device) { + // This should always succeed... in theory. + gfxCriticalError() << "Failed to initialize WARP D3D11 device! " + << hexa(hr); + d3d11.SetFailed(FeatureStatus::Failed, + "Failed to create a D3D11 WARP device", + "FEATURE_FAILURE_D3D11_WARP_DEVICE2"_ns); + return; + } + + bool textureSharingWorks = D3D11Checks::DoesTextureSharingWork(device); + + RefPtr<ID3D10Multithread> multi; + hr = device->QueryInterface(__uuidof(ID3D10Multithread), + getter_AddRefs(multi)); + if (SUCCEEDED(hr) && multi) { + multi->SetMultithreadProtected(TRUE); + } + + DXGI_ADAPTER_DESC desc; + D3D11Checks::GetDxgiDesc(device, &desc); + + int featureLevel = device->GetFeatureLevel(); + + auto formatOptions = D3D11Checks::FormatOptions(device); + mCompositorDevice = device; + + int32_t sequenceNumber = GetNextDeviceCounter(); + mDeviceStatus = Some(D3D11DeviceStatus( + true, textureSharingWorks, featureLevel, DxgiAdapterDesc::From(desc), + sequenceNumber, formatOptions)); + mCompositorDevice->SetExceptionMode(0); + + reporterWARP.SetSuccessful(); +} + +FeatureStatus DeviceManagerDx::CreateContentDevice() { + RefPtr<IDXGIAdapter1> adapter; + if (!mDeviceStatus->isWARP()) { + adapter = GetDXGIAdapterLocked(); + if (!adapter) { + gfxCriticalNote << "Could not get a DXGI adapter"; + return FeatureStatus::Unavailable; + } + } + + HRESULT hr; + RefPtr<ID3D11Device> device; + + UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + D3D_DRIVER_TYPE type = + mDeviceStatus->isWARP() ? D3D_DRIVER_TYPE_WARP : D3D_DRIVER_TYPE_UNKNOWN; + if (!CreateDevice(adapter, type, flags, hr, device)) { + gfxCriticalNote + << "Recovered from crash while creating a D3D11 content device"; + gfxWindowsPlatform::RecordContentDeviceFailure( + TelemetryDeviceCode::Content); + return FeatureStatus::CrashedInHandler; + } + + if (FAILED(hr) || !device) { + gfxCriticalNote << "Failed to create a D3D11 content device: " << hexa(hr); + gfxWindowsPlatform::RecordContentDeviceFailure( + TelemetryDeviceCode::Content); + return FeatureStatus::Failed; + } + + // InitializeD2D() will abort early if the compositor device did not support + // texture sharing. If we're in the content process, we can't rely on the + // parent device alone: some systems have dual GPUs that are capable of + // binding the parent and child processes to different GPUs. As a safety net, + // we re-check texture sharing against the newly created D3D11 content device. + // If it fails, we won't use Direct2D. + if (XRE_IsContentProcess()) { + if (!D3D11Checks::DoesTextureSharingWork(device)) { + return FeatureStatus::Failed; + } + + DebugOnly<bool> ok = ContentAdapterIsParentAdapter(device); + MOZ_ASSERT(ok); + } + + mContentDevice = device; + mContentDevice->SetExceptionMode(0); + + RefPtr<ID3D10Multithread> multi; + hr = mContentDevice->QueryInterface(__uuidof(ID3D10Multithread), + getter_AddRefs(multi)); + if (SUCCEEDED(hr) && multi) { + multi->SetMultithreadProtected(TRUE); + } + return FeatureStatus::Available; +} + +RefPtr<ID3D11Device> DeviceManagerDx::CreateDecoderDevice( + bool aHardwareWebRender) { + MutexAutoLock lock(mDeviceLock); + + if (!mDeviceStatus) { + return nullptr; + } + + bool isAMD = mDeviceStatus->adapter().VendorId == 0x1002; + bool reuseDevice = false; + if (gfxVars::ReuseDecoderDevice()) { + reuseDevice = true; + } else if (isAMD) { + reuseDevice = true; + gfxCriticalNoteOnce << "Always have to reuse decoder device on AMD"; + } + + if (reuseDevice) { + // Use mCompositorDevice for decoder device only for hardware WebRender. + if (aHardwareWebRender && mCompositorDevice && + mCompositorDeviceSupportsVideo && !mDecoderDevice) { + mDecoderDevice = mCompositorDevice; + + RefPtr<ID3D10Multithread> multi; + mDecoderDevice->QueryInterface(__uuidof(ID3D10Multithread), + getter_AddRefs(multi)); + if (multi) { + MOZ_ASSERT(multi->GetMultithreadProtected()); + } + } + + if (mDecoderDevice) { + RefPtr<ID3D11Device> dev = mDecoderDevice; + return dev.forget(); + } + } + + if (!sD3D11CreateDeviceFn) { + // We should just be on Windows Vista or XP in this case. + return nullptr; + } + + RefPtr<IDXGIAdapter1> adapter = GetDXGIAdapterLocked(); + if (!adapter) { + return nullptr; + } + + HRESULT hr; + RefPtr<ID3D11Device> device; + + UINT flags = D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS | + D3D11_CREATE_DEVICE_VIDEO_SUPPORT; + if (!CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, flags, hr, device)) { + return nullptr; + } + if (FAILED(hr) || !device || !D3D11Checks::DoesDeviceWork()) { + return nullptr; + } + + RefPtr<ID3D10Multithread> multi; + device->QueryInterface(__uuidof(ID3D10Multithread), getter_AddRefs(multi)); + if (multi) { + multi->SetMultithreadProtected(TRUE); + } + if (reuseDevice) { + mDecoderDevice = device; + } + return device; +} + +// ID3D11DeviceChild, IDXGIObject and ID3D11Device implement SetPrivateData with +// the exact same parameters. +template <typename T> +static HRESULT SetDebugName(T* d3d11Object, const char* debugString) { + return d3d11Object->SetPrivateData(WKPDID_D3DDebugObjectName, + strlen(debugString), debugString); +} + +RefPtr<ID3D11Device> DeviceManagerDx::CreateMediaEngineDevice() { + MutexAutoLock lock(mDeviceLock); + if (!LoadD3D11()) { + return nullptr; + } + + HRESULT hr; + RefPtr<ID3D11Device> device; + UINT flags = D3D11_CREATE_DEVICE_VIDEO_SUPPORT | + D3D11_CREATE_DEVICE_BGRA_SUPPORT | + D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS; + if (!CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, flags, hr, device)) { + return nullptr; + } + if (FAILED(hr) || !device || !D3D11Checks::DoesDeviceWork()) { + return nullptr; + } + Unused << SetDebugName(device.get(), "MFMediaEngineDevice"); + + RefPtr<ID3D10Multithread> multi; + device->QueryInterface(__uuidof(ID3D10Multithread), getter_AddRefs(multi)); + if (multi) { + multi->SetMultithreadProtected(TRUE); + } + return device; +} + +void DeviceManagerDx::ResetDevices() { + MutexAutoLock lock(mDeviceLock); + ResetDevicesLocked(); +} + +void DeviceManagerDx::ResetDevicesLocked() { + mAdapter = nullptr; + mCompositorAttachments = nullptr; + mCompositorDevice = nullptr; + mContentDevice = nullptr; + mCanvasDevice = nullptr; + mImageDevice = nullptr; + mVRDevice = nullptr; + mDecoderDevice = nullptr; + mDirectCompositionDevice = nullptr; + mDeviceStatus = Nothing(); + mDeviceResetReason = Nothing(); + Factory::SetDirect3D11Device(nullptr); +} + +bool DeviceManagerDx::MaybeResetAndReacquireDevices() { + MutexAutoLock lock(mDeviceLock); + + DeviceResetReason resetReason; + if (!HasDeviceResetLocked(&resetReason)) { + return false; + } + + GPUProcessManager::RecordDeviceReset(resetReason); + + bool createCompositorDevice = !!mCompositorDevice; + bool createContentDevice = !!mContentDevice; + bool createCanvasDevice = !!mCanvasDevice; + bool createDirectCompositionDevice = !!mDirectCompositionDevice; + + ResetDevicesLocked(); + + if (createCompositorDevice && !CreateCompositorDevicesLocked()) { + // Just stop, don't try anything more + return true; + } + if (createContentDevice) { + CreateContentDevicesLocked(); + } + if (createCanvasDevice) { + CreateCanvasDeviceLocked(); + } + if (createDirectCompositionDevice) { + CreateDirectCompositionDeviceLocked(); + } + + return true; +} + +bool DeviceManagerDx::ContentAdapterIsParentAdapter(ID3D11Device* device) { + DXGI_ADAPTER_DESC desc; + if (!D3D11Checks::GetDxgiDesc(device, &desc)) { + gfxCriticalNote << "Could not query device DXGI adapter info"; + return false; + } + + const DxgiAdapterDesc& preferred = mDeviceStatus->adapter(); + + if (desc.VendorId != preferred.VendorId || + desc.DeviceId != preferred.DeviceId || + desc.SubSysId != preferred.SubSysId || + desc.AdapterLuid.HighPart != preferred.AdapterLuid.HighPart || + desc.AdapterLuid.LowPart != preferred.AdapterLuid.LowPart) { + gfxCriticalNote << "VendorIDMismatch P " << hexa(preferred.VendorId) << " " + << hexa(desc.VendorId); + return false; + } + + return true; +} + +static DeviceResetReason HResultToResetReason(HRESULT hr) { + switch (hr) { + case DXGI_ERROR_DEVICE_HUNG: + return DeviceResetReason::HUNG; + case DXGI_ERROR_DEVICE_REMOVED: + return DeviceResetReason::REMOVED; + case DXGI_ERROR_DEVICE_RESET: + return DeviceResetReason::RESET; + case DXGI_ERROR_DRIVER_INTERNAL_ERROR: + return DeviceResetReason::DRIVER_ERROR; + case DXGI_ERROR_INVALID_CALL: + return DeviceResetReason::INVALID_CALL; + case E_OUTOFMEMORY: + return DeviceResetReason::OUT_OF_MEMORY; + default: + MOZ_ASSERT(false); + } + return DeviceResetReason::OTHER; +} + +bool DeviceManagerDx::HasDeviceReset(DeviceResetReason* aOutReason) { + MutexAutoLock lock(mDeviceLock); + return HasDeviceResetLocked(aOutReason); +} + +bool DeviceManagerDx::HasDeviceResetLocked(DeviceResetReason* aOutReason) { + if (mDeviceResetReason) { + if (aOutReason) { + *aOutReason = mDeviceResetReason.value(); + } + return true; + } + + DeviceResetReason reason; + if (GetAnyDeviceRemovedReason(&reason)) { + mDeviceResetReason = Some(reason); + if (aOutReason) { + *aOutReason = reason; + } + return true; + } + + return false; +} + +static inline bool DidDeviceReset(const RefPtr<ID3D11Device>& aDevice, + DeviceResetReason* aOutReason) { + if (!aDevice) { + return false; + } + HRESULT hr = aDevice->GetDeviceRemovedReason(); + if (hr == S_OK) { + return false; + } + + *aOutReason = HResultToResetReason(hr); + return true; +} + +bool DeviceManagerDx::GetAnyDeviceRemovedReason(DeviceResetReason* aOutReason) { + if (DidDeviceReset(mCompositorDevice, aOutReason) || + DidDeviceReset(mContentDevice, aOutReason) || + DidDeviceReset(mCanvasDevice, aOutReason)) { + return true; + } + + if (XRE_IsParentProcess() && NS_IsMainThread() && + StaticPrefs::gfx_testing_device_reset()) { + Preferences::SetInt("gfx.testing.device-reset", 0); + *aOutReason = DeviceResetReason::FORCED_RESET; + return true; + } + + return false; +} + +void DeviceManagerDx::ForceDeviceReset(ForcedDeviceResetReason aReason) { + Telemetry::Accumulate(Telemetry::FORCED_DEVICE_RESET_REASON, + uint32_t(aReason)); + { + MutexAutoLock lock(mDeviceLock); + if (!mDeviceResetReason) { + mDeviceResetReason = Some(DeviceResetReason::FORCED_RESET); + } + } +} + +void DeviceManagerDx::DisableD3D11AfterCrash() { + gfxConfig::Disable(Feature::D3D11_COMPOSITING, + FeatureStatus::CrashedInHandler, + "Crashed while acquiring a Direct3D11 device", + "FEATURE_FAILURE_D3D11_CRASH"_ns); + ResetDevices(); +} + +RefPtr<ID3D11Device> DeviceManagerDx::GetCompositorDevice() { + /// ID3D11Device is thread-safe. We need the lock to read the + /// mDeviceLockPointer, but manipulating the pointee outside of the lock is + /// safe. See + /// https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-render-multi-thread-intro + 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; + } else { + MOZ_ASSERT(multi->GetMultithreadProtected()); + } + + mImageDevice = device; + + return mImageDevice; +} + +RefPtr<ID3D11Device> DeviceManagerDx::GetVRDevice() { + MutexAutoLock lock(mDeviceLock); + if (!mVRDevice) { + CreateVRDevice(); + } + return mVRDevice; +} + +RefPtr<ID3D11Device> DeviceManagerDx::GetCanvasDevice() { + MutexAutoLock lock(mDeviceLock); + return mCanvasDevice; +} + +RefPtr<IDCompositionDevice2> DeviceManagerDx::GetDirectCompositionDevice() { + MutexAutoLock lock(mDeviceLock); + return mDirectCompositionDevice; +} + +unsigned DeviceManagerDx::GetCompositorFeatureLevel() const { + MutexAutoLock lock(mDeviceLock); + if (!mDeviceStatus) { + return 0; + } + return mDeviceStatus->featureLevel(); +} + +bool DeviceManagerDx::TextureSharingWorks() { + MutexAutoLock lock(mDeviceLock); + if (!mDeviceStatus) { + return false; + } + return mDeviceStatus->textureSharingWorks(); +} + +bool DeviceManagerDx::CanInitializeKeyedMutexTextures() { + MutexAutoLock lock(mDeviceLock); + return mDeviceStatus && StaticPrefs::gfx_direct3d11_allow_keyed_mutex() && + gfxVars::AllowD3D11KeyedMutex(); +} + +bool DeviceManagerDx::IsWARP() { + MutexAutoLock lock(mDeviceLock); + if (!mDeviceStatus) { + return false; + } + return mDeviceStatus->isWARP(); +} + +bool DeviceManagerDx::CanUseNV12() { + MutexAutoLock lock(mDeviceLock); + if (!mDeviceStatus) { + return false; + } + return mDeviceStatus->formatOptions().contains( + D3D11Checks::VideoFormatOption::NV12); +} + +bool DeviceManagerDx::CanUseP010() { + MutexAutoLock lock(mDeviceLock); + if (!mDeviceStatus) { + return false; + } + return mDeviceStatus->formatOptions().contains( + D3D11Checks::VideoFormatOption::P010); +} + +bool DeviceManagerDx::CanUseP016() { + MutexAutoLock lock(mDeviceLock); + if (!mDeviceStatus) { + return false; + } + return mDeviceStatus->formatOptions().contains( + D3D11Checks::VideoFormatOption::P016); +} + +bool DeviceManagerDx::CanUseDComp() { + MutexAutoLock lock(mDeviceLock); + return !!mDirectCompositionDevice; +} + +void DeviceManagerDx::InitializeDirectDraw() { + MOZ_ASSERT(layers::CompositorThreadHolder::IsInCompositorThread()); + + if (mDirectDraw) { + // Already initialized. + return; + } + + FeatureState& ddraw = gfxConfig::GetFeature(Feature::DIRECT_DRAW); + if (!ddraw.IsEnabled()) { + return; + } + + // Check if DirectDraw is available on this system. + mDirectDrawDLL.own(LoadLibrarySystem32(L"ddraw.dll")); + if (!mDirectDrawDLL) { + ddraw.SetFailed(FeatureStatus::Unavailable, + "DirectDraw not available on this computer", + "FEATURE_FAILURE_DDRAW_LIB"_ns); + return; + } + + sDirectDrawCreateExFn = (decltype(DirectDrawCreateEx)*)GetProcAddress( + mDirectDrawDLL, "DirectDrawCreateEx"); + if (!sDirectDrawCreateExFn) { + ddraw.SetFailed(FeatureStatus::Unavailable, + "DirectDraw not available on this computer", + "FEATURE_FAILURE_DDRAW_LIB"_ns); + return; + } + + HRESULT hr; + MOZ_SEH_TRY { + hr = sDirectDrawCreateExFn(nullptr, getter_AddRefs(mDirectDraw), + IID_IDirectDraw7, nullptr); + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + ddraw.SetFailed(FeatureStatus::Failed, "Failed to create DirectDraw", + "FEATURE_FAILURE_DDRAW_LIB"_ns); + gfxCriticalNote << "DoesCreatingDirectDrawFailed"; + return; + } + if (FAILED(hr)) { + ddraw.SetFailed(FeatureStatus::Failed, "Failed to create DirectDraw", + "FEATURE_FAILURE_DDRAW_LIB"_ns); + gfxCriticalNote << "DoesCreatingDirectDrawFailed " << hexa(hr); + return; + } +} + +IDirectDraw7* DeviceManagerDx::GetDirectDraw() { return mDirectDraw; } + +void DeviceManagerDx::GetCompositorDevices( + RefPtr<ID3D11Device>* aOutDevice, + RefPtr<layers::DeviceAttachmentsD3D11>* aOutAttachments) { + RefPtr<ID3D11Device> device; + { + MutexAutoLock lock(mDeviceLock); + if (!mCompositorDevice) { + return; + } + if (mCompositorAttachments) { + *aOutDevice = mCompositorDevice; + *aOutAttachments = mCompositorAttachments; + return; + } + + // Otherwise, we'll try to create attachments outside the lock. + device = mCompositorDevice; + } + + // We save the attachments object even if it fails to initialize, so the + // compositor can grab the failure ID. + RefPtr<layers::DeviceAttachmentsD3D11> attachments = + layers::DeviceAttachmentsD3D11::Create(device); + { + MutexAutoLock lock(mDeviceLock); + if (device != mCompositorDevice) { + return; + } + mCompositorAttachments = attachments; + } + + *aOutDevice = device; + *aOutAttachments = attachments; +} + +/* static */ +void DeviceManagerDx::PreloadAttachmentsOnCompositorThread() { + if (!CompositorThread()) { + return; + } + + RefPtr<Runnable> task = NS_NewRunnableFunction( + "DeviceManagerDx::PreloadAttachmentsOnCompositorThread", []() -> void { + if (DeviceManagerDx* dm = DeviceManagerDx::Get()) { + RefPtr<ID3D11Device> device; + RefPtr<layers::DeviceAttachmentsD3D11> attachments; + dm->GetCompositorDevices(&device, &attachments); + } + }); + CompositorThread()->Dispatch(task.forget()); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/thebes/DeviceManagerDx.h b/gfx/thebes/DeviceManagerDx.h new file mode 100644 index 0000000000..9d127af358 --- /dev/null +++ b/gfx/thebes/DeviceManagerDx.h @@ -0,0 +1,209 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_thebes_DeviceManagerDx_h +#define mozilla_gfx_thebes_DeviceManagerDx_h + +#include "gfxPlatform.h" +#include "gfxTelemetry.h" +#include "gfxTypes.h" +#include "mozilla/Maybe.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/gfx/GraphicsMessages.h" +#include "nsTArray.h" +#include "nsWindowsHelpers.h" + +#include <windows.h> +#include <objbase.h> + +#include <d3d11.h> +#include <dxgi.h> +#include <dxgi1_6.h> + +// This header is available in the June 2010 SDK and in the Win8 SDK +#include <d3dcommon.h> +// Win 8.0 SDK types we'll need when building using older sdks. +#if !defined(D3D_FEATURE_LEVEL_11_1) // defined in the 8.0 SDK only +# define D3D_FEATURE_LEVEL_11_1 static_cast<D3D_FEATURE_LEVEL>(0xb100) +# define D3D_FL9_1_REQ_TEXTURE2D_U_OR_V_DIMENSION 2048 +# define D3D_FL9_3_REQ_TEXTURE2D_U_OR_V_DIMENSION 4096 +#endif + +struct ID3D11Device; +struct IDCompositionDevice2; +struct IDirectDraw7; + +namespace mozilla { +class ScopedGfxFeatureReporter; +namespace layers { +class DeviceAttachmentsD3D11; +} // namespace layers + +namespace gfx { +class FeatureState; + +class DeviceManagerDx final { + public: + static void Init(); + static void Shutdown(); + + DeviceManagerDx(); + ~DeviceManagerDx(); + + static DeviceManagerDx* Get() { return sInstance; } + + RefPtr<ID3D11Device> GetCompositorDevice(); + RefPtr<ID3D11Device> GetContentDevice(); + RefPtr<ID3D11Device> GetCanvasDevice(); + RefPtr<ID3D11Device> GetImageDevice(); + RefPtr<IDCompositionDevice2> GetDirectCompositionDevice(); + RefPtr<ID3D11Device> GetVRDevice(); + RefPtr<ID3D11Device> CreateDecoderDevice(bool aHardwareWebRender); + RefPtr<ID3D11Device> CreateMediaEngineDevice(); + IDirectDraw7* GetDirectDraw(); + + unsigned GetCompositorFeatureLevel() const; + bool TextureSharingWorks(); + bool IsWARP(); + bool CanUseNV12(); + bool CanUseP010(); + bool CanUseP016(); + bool CanUseDComp(); + + // Returns true if we can create a texture with + // D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX and also + // upload texture data during the CreateTexture2D + // call. This crashes on some devices, so we might + // need to avoid it. + bool CanInitializeKeyedMutexTextures(); + + // Enumerate and return all outputs on the current adapter. + nsTArray<DXGI_OUTPUT_DESC1> EnumerateOutputs(); + + // find the IDXGIOutput with a description.Monitor matching + // 'monitor'; returns false if not found or some error occurred. + bool GetOutputFromMonitor(HMONITOR monitor, RefPtr<IDXGIOutput>* aOutOutput); + + // Check if the current adapter supports hardware stretching + void CheckHardwareStretchingSupport(HwStretchingSupport& aRv); + + bool CreateCompositorDevices(); + void CreateContentDevices(); + void CreateDirectCompositionDevice(); + bool CreateCanvasDevice(); + + static HANDLE CreateDCompSurfaceHandle(); + + void GetCompositorDevices( + RefPtr<ID3D11Device>* aOutDevice, + RefPtr<layers::DeviceAttachmentsD3D11>* aOutAttachments); + + void ImportDeviceInfo(const D3D11DeviceStatus& aDeviceStatus); + + // Returns whether the device info was exported. + bool ExportDeviceInfo(D3D11DeviceStatus* aOut); + + void ResetDevices(); + void InitializeDirectDraw(); + + // Reset and reacquire the devices if a reset has happened. + // Returns whether a reset occurred not whether reacquiring + // was successful. + bool MaybeResetAndReacquireDevices(); + + // Device reset helpers. + bool HasDeviceReset(DeviceResetReason* aOutReason = nullptr); + + // Note: these set the cached device reset reason, which will be picked up + // on the next frame. + void ForceDeviceReset(ForcedDeviceResetReason aReason); + + private: + void ResetDevicesLocked() MOZ_REQUIRES(mDeviceLock); + + // Device reset helpers. + bool HasDeviceResetLocked(DeviceResetReason* aOutReason = nullptr) + MOZ_REQUIRES(mDeviceLock); + + // Pre-load any compositor resources that are expensive, and are needed when + // we attempt to create a compositor. + static void PreloadAttachmentsOnCompositorThread(); + + already_AddRefed<IDXGIAdapter1> GetDXGIAdapter(); + IDXGIAdapter1* GetDXGIAdapterLocked() MOZ_REQUIRES(mDeviceLock); + + void DisableD3D11AfterCrash(); + + bool CreateCompositorDevicesLocked() MOZ_REQUIRES(mDeviceLock); + void CreateContentDevicesLocked() MOZ_REQUIRES(mDeviceLock); + void CreateDirectCompositionDeviceLocked() MOZ_REQUIRES(mDeviceLock); + bool CreateCanvasDeviceLocked() MOZ_REQUIRES(mDeviceLock); + + void CreateCompositorDevice(mozilla::gfx::FeatureState& d3d11) + MOZ_REQUIRES(mDeviceLock); + bool CreateCompositorDeviceHelper(mozilla::gfx::FeatureState& aD3d11, + IDXGIAdapter1* aAdapter, + bool aAttemptVideoSupport, + RefPtr<ID3D11Device>& aOutDevice) + MOZ_REQUIRES(mDeviceLock); + + void CreateWARPCompositorDevice() MOZ_REQUIRES(mDeviceLock); + bool CreateVRDevice() MOZ_REQUIRES(mDeviceLock); + + mozilla::gfx::FeatureStatus CreateContentDevice() MOZ_REQUIRES(mDeviceLock); + + bool CreateDevice(IDXGIAdapter* aAdapter, D3D_DRIVER_TYPE aDriverType, + UINT aFlags, HRESULT& aResOut, + RefPtr<ID3D11Device>& aOutDevice) MOZ_REQUIRES(mDeviceLock); + + bool ContentAdapterIsParentAdapter(ID3D11Device* device) + MOZ_REQUIRES(mDeviceLock); + + bool LoadD3D11(); + bool LoadDcomp(); + void ReleaseD3D11() MOZ_REQUIRES(mDeviceLock); + + // Call GetDeviceRemovedReason on each device until one returns + // a failure. + bool GetAnyDeviceRemovedReason(DeviceResetReason* aOutReason) + MOZ_REQUIRES(mDeviceLock); + + private: + static StaticAutoPtr<DeviceManagerDx> sInstance; + + // This is assigned during device creation. Afterwards, it is released if + // devices failed, and "forgotten" if devices succeeded (meaning, we leak + // the ref and unassign the module). + nsModuleHandle mD3D11Module; + + nsModuleHandle mDcompModule; + + mutable mozilla::Mutex mDeviceLock; + nsTArray<D3D_FEATURE_LEVEL> mFeatureLevels MOZ_GUARDED_BY(mDeviceLock); + RefPtr<IDXGIAdapter1> mAdapter MOZ_GUARDED_BY(mDeviceLock); + RefPtr<ID3D11Device> mCompositorDevice MOZ_GUARDED_BY(mDeviceLock); + RefPtr<ID3D11Device> mContentDevice MOZ_GUARDED_BY(mDeviceLock); + RefPtr<ID3D11Device> mCanvasDevice MOZ_GUARDED_BY(mDeviceLock); + RefPtr<ID3D11Device> mImageDevice MOZ_GUARDED_BY(mDeviceLock); + RefPtr<ID3D11Device> mVRDevice MOZ_GUARDED_BY(mDeviceLock); + RefPtr<ID3D11Device> mDecoderDevice MOZ_GUARDED_BY(mDeviceLock); + RefPtr<IDCompositionDevice2> mDirectCompositionDevice + MOZ_GUARDED_BY(mDeviceLock); + RefPtr<layers::DeviceAttachmentsD3D11> mCompositorAttachments + MOZ_GUARDED_BY(mDeviceLock); + bool mCompositorDeviceSupportsVideo MOZ_GUARDED_BY(mDeviceLock); + Maybe<D3D11DeviceStatus> mDeviceStatus MOZ_GUARDED_BY(mDeviceLock); + Maybe<DeviceResetReason> mDeviceResetReason MOZ_GUARDED_BY(mDeviceLock); + + nsModuleHandle mDirectDrawDLL; + RefPtr<IDirectDraw7> mDirectDraw; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // mozilla_gfx_thebes_DeviceManagerDx_h diff --git a/gfx/thebes/DisplayConfigWindows.cpp b/gfx/thebes/DisplayConfigWindows.cpp new file mode 100644 index 0000000000..ba5db0aa20 --- /dev/null +++ b/gfx/thebes/DisplayConfigWindows.cpp @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <windows.h> + +#include "DisplayConfigWindows.h" + +namespace mozilla { +namespace gfx { + +using namespace std; + +optional<DisplayConfig> GetDisplayConfig() { + LONG result; + + UINT32 numPaths; + UINT32 numModes; + vector<DISPLAYCONFIG_PATH_INFO> paths; + vector<DISPLAYCONFIG_MODE_INFO> modes; + do { + result = GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &numPaths, + &numModes); + if (result != ERROR_SUCCESS) { + return {}; + } + // allocate the recommended amount of space + paths.resize(numPaths); + modes.resize(numModes); + + result = QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &numPaths, paths.data(), + &numModes, modes.data(), NULL); + // try again if there wasn't enough space + } while (result == ERROR_INSUFFICIENT_BUFFER); + + if (result != ERROR_SUCCESS) return {}; + + // shrink to fit the actual number of modes and paths returned + modes.resize(numModes); + paths.resize(numPaths); + + return DisplayConfig{paths, modes}; +} + +bool HasScaledResolution() { + auto config = GetDisplayConfig(); + if (config) { + for (auto& path : config->mPaths) { + auto& modes = config->mModes; + int targetModeIndex = path.targetInfo.modeInfoIdx; + int sourceModeIndex = path.sourceInfo.modeInfoIdx; + + // Check if the source and target resolutions are different + if ((modes[targetModeIndex] + .targetMode.targetVideoSignalInfo.activeSize.cx != + modes[sourceModeIndex].sourceMode.width) || + (modes[targetModeIndex] + .targetMode.targetVideoSignalInfo.activeSize.cy != + modes[sourceModeIndex].sourceMode.height)) { + return true; + } + } + } + return false; +} + +void GetScaledResolutions(ScaledResolutionSet& aRv) { + auto config = GetDisplayConfig(); + if (config) { + for (auto& path : config->mPaths) { + auto& modes = config->mModes; + int targetModeIndex = path.targetInfo.modeInfoIdx; + int sourceModeIndex = path.sourceInfo.modeInfoIdx; + + // Check if the source and target resolutions are different + IntSize src(modes[sourceModeIndex].sourceMode.width, + modes[sourceModeIndex].sourceMode.height); + IntSize dst( + modes[targetModeIndex].targetMode.targetVideoSignalInfo.activeSize.cx, + modes[targetModeIndex] + .targetMode.targetVideoSignalInfo.activeSize.cy); + if (src != dst) { + aRv.AppendElement(std::pair<IntSize, IntSize>{src, dst}); + } + } + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/thebes/DisplayConfigWindows.h b/gfx/thebes/DisplayConfigWindows.h new file mode 100644 index 0000000000..62bc03cf3d --- /dev/null +++ b/gfx/thebes/DisplayConfigWindows.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_thebes_DisplayConfigWindows_h +#define mozilla_gfx_thebes_DisplayConfigWindows_h + +#include <optional> // for std::optional +#include <utility> // for std::pair +#include <vector> // for std::vector +#include <wingdi.h> +#include "mozilla/gfx/Point.h" // for IntSize +#include "nsTArray.h" + +namespace mozilla { +namespace gfx { + +struct DisplayConfig { + std::vector<DISPLAYCONFIG_PATH_INFO> mPaths; + std::vector<DISPLAYCONFIG_MODE_INFO> mModes; +}; + +std::optional<DisplayConfig> GetDisplayConfig(); + +extern bool HasScaledResolution(); + +typedef nsTArray<std::pair<IntSize, IntSize>> ScaledResolutionSet; +void GetScaledResolutions(ScaledResolutionSet& aRv); + +} // namespace gfx +} // namespace mozilla + +#endif // mozilla_gfx_thebes_DisplayConfigWindows_h diff --git a/gfx/thebes/DrawMode.h b/gfx/thebes/DrawMode.h new file mode 100644 index 0000000000..f9c2ac2b64 --- /dev/null +++ b/gfx/thebes/DrawMode.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DrawMode_h +#define DrawMode_h + +#include "mozilla/TypedEnumBits.h" + +// Options for how the text should be drawn +enum class DrawMode : int { + // GLYPH_FILL and GLYPH_STROKE draw into the current context + // and may be used together with bitwise OR. + GLYPH_FILL = 1 << 0, + // Note: using GLYPH_STROKE will destroy the current path. + GLYPH_STROKE = 1 << 1, + // Appends glyphs to the current path. Can NOT be used with + // GLYPH_FILL or GLYPH_STROKE. + GLYPH_PATH = 1 << 2, + // When GLYPH_FILL and GLYPH_STROKE are both set, draws the + // stroke underneath the fill. + GLYPH_STROKE_UNDERNEATH = 1 << 3 +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(DrawMode) +#endif diff --git a/gfx/thebes/FontPaletteCache.cpp b/gfx/thebes/FontPaletteCache.cpp new file mode 100644 index 0000000000..02ca740977 --- /dev/null +++ b/gfx/thebes/FontPaletteCache.cpp @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FontPaletteCache.h" +#include "COLRFonts.h" +#include "gfxFontEntry.h" + +using namespace mozilla; + +void gfx::PaletteCache::SetPaletteValueSet( + const gfx::FontPaletteValueSet* aSet) { + mPaletteValueSet = aSet; + Clear(); +} + +already_AddRefed<gfx::FontPalette> gfx::PaletteCache::GetPaletteFor( + gfxFontEntry* aFontEntry, nsAtom* aPaletteName) { + auto entry = Lookup(std::pair(aFontEntry, aPaletteName)); + if (!entry) { + CacheData newData; + newData.mKey = std::pair(aFontEntry, aPaletteName); + + gfxFontEntry::AutoHBFace face = aFontEntry->GetHBFace(); + newData.mPalette = new FontPalette(gfx::COLRFonts::CreateColorPalette( + face, mPaletteValueSet, aPaletteName, aFontEntry->FamilyName())); + + entry.Set(std::move(newData)); + } + RefPtr result = entry.Data().mPalette; + return result.forget(); +} diff --git a/gfx/thebes/FontPaletteCache.h b/gfx/thebes/FontPaletteCache.h new file mode 100644 index 0000000000..fd2b3cc058 --- /dev/null +++ b/gfx/thebes/FontPaletteCache.h @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef FONT_PALETTE_CACHE_H +#define FONT_PALETTE_CACHE_H + +#include "mozilla/gfx/Types.h" +#include "mozilla/MruCache.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/RefPtr.h" +#include "nsAtom.h" +#include "nsTArray.h" +#include <utility> + +class gfxFontEntry; + +namespace mozilla::gfx { + +class FontPaletteValueSet; + +// A resolved font palette as an array of colors. +class FontPalette { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FontPalette); + + public: + FontPalette() = default; + explicit FontPalette(nsTArray<mozilla::gfx::sRGBColor>&& aColors) + : mColors(std::move(aColors)) {} + + const nsTArray<mozilla::gfx::sRGBColor>* Colors() const { return &mColors; } + + private: + ~FontPalette() = default; + + nsTArray<mozilla::gfx::sRGBColor> mColors; +}; + +// MRU cache used for resolved color-font palettes, to avoid reconstructing +// the palette for each glyph rendered with a given font. +using CacheKey = std::pair<RefPtr<gfxFontEntry>, RefPtr<nsAtom>>; +struct CacheData { + CacheKey mKey; + RefPtr<FontPalette> mPalette; +}; + +class PaletteCache + : public mozilla::MruCache<CacheKey, CacheData, PaletteCache> { + public: + explicit PaletteCache(const FontPaletteValueSet* aPaletteValueSet = nullptr) + : mPaletteValueSet(aPaletteValueSet) {} + + void SetPaletteValueSet(const FontPaletteValueSet* aSet); + + already_AddRefed<FontPalette> GetPaletteFor(gfxFontEntry* aFontEntry, + nsAtom* aPaletteName); + + static mozilla::HashNumber Hash(const CacheKey& aKey) { + return mozilla::HashGeneric(aKey.first.get(), aKey.second.get()); + } + static bool Match(const CacheKey& aKey, const CacheData& aVal) { + return aVal.mKey == aKey; + } + + protected: + const FontPaletteValueSet* mPaletteValueSet = nullptr; +}; + +} // namespace mozilla::gfx + +#endif diff --git a/gfx/thebes/PrintPromise.h b/gfx/thebes/PrintPromise.h new file mode 100644 index 0000000000..8213d676f3 --- /dev/null +++ b/gfx/thebes/PrintPromise.h @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PRINTPROMISE_H +#define MOZILLA_GFX_PRINTPROMISE_H + +#include "ErrorList.h" +#include "mozilla/MozPromise.h" + +namespace mozilla::gfx { + +using PrintEndDocumentPromise = MozPromise</* unused */ bool, nsresult, false>; + +} // namespace mozilla::gfx + +#endif diff --git a/gfx/thebes/PrintTarget.cpp b/gfx/thebes/PrintTarget.cpp new file mode 100644 index 0000000000..7ab6984d8d --- /dev/null +++ b/gfx/thebes/PrintTarget.cpp @@ -0,0 +1,200 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PrintTarget.h" + +#include "cairo.h" +#ifdef CAIRO_HAS_QUARTZ_SURFACE +# include "cairo-quartz.h" +#endif +#ifdef CAIRO_HAS_WIN32_SURFACE +# include "cairo-win32.h" +#endif +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/HelpersCairo.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "nsUTF8Utils.h" + +// IPP spec disallow the job-name which is over 255 characters. +// RFC: https://tools.ietf.org/html/rfc2911#section-4.1.2 +#define IPP_JOB_NAME_LIMIT_LENGTH 255 + +namespace mozilla::gfx { + +PrintTarget::PrintTarget(cairo_surface_t* aCairoSurface, const IntSize& aSize) + : mCairoSurface(aCairoSurface), + mSize(aSize), + mIsFinished(false) +#ifdef DEBUG + , + mHasActivePage(false) +#endif + +{ +#if 0 + // aCairoSurface is null when our PrintTargetThebes subclass's ctor calls us. + // Once PrintTargetThebes is removed, enable this assertion. + MOZ_ASSERT(aCairoSurface && !cairo_surface_status(aCairoSurface), + "CreateOrNull factory methods should not call us without a " + "valid cairo_surface_t*"); +#endif + + // CreateOrNull factory methods hand over ownership of aCairoSurface, + // so we don't call cairo_surface_reference(aSurface) here. + + // This code was copied from gfxASurface::Init: + if (mCairoSurface && + cairo_surface_get_content(mCairoSurface) != CAIRO_CONTENT_COLOR) { + cairo_surface_set_subpixel_antialiasing( + mCairoSurface, CAIRO_SUBPIXEL_ANTIALIASING_DISABLED); + } +} + +PrintTarget::~PrintTarget() { + // null surfaces are allowed here + cairo_surface_destroy(mCairoSurface); + mCairoSurface = nullptr; +} + +already_AddRefed<DrawTarget> PrintTarget::MakeDrawTarget( + const IntSize& aSize, DrawEventRecorder* aRecorder) { + MOZ_ASSERT(mCairoSurface, + "We shouldn't have been constructed without a cairo surface"); + + // This should not be called outside of BeginPage()/EndPage() calls since + // some backends can only provide a valid DrawTarget at that time. + MOZ_ASSERT(mHasActivePage, "We can't guarantee a valid DrawTarget"); + + if (cairo_surface_status(mCairoSurface)) { + return nullptr; + } + + // Note than aSize may not be the same as mSize (the size of mCairoSurface). + // See the comments in our header. If the sizes are different a clip will + // be applied to mCairoSurface. + RefPtr<DrawTarget> dt = + Factory::CreateDrawTargetForCairoSurface(mCairoSurface, aSize); + if (!dt || !dt->IsValid()) { + return nullptr; + } + + if (aRecorder) { + dt = CreateRecordingDrawTarget(aRecorder, dt); + if (!dt || !dt->IsValid()) { + return nullptr; + } + } + + return dt.forget(); +} + +already_AddRefed<DrawTarget> PrintTarget::GetReferenceDrawTarget() { + if (!mRefDT) { + const IntSize size(1, 1); + + cairo_surface_t* similar; + switch (cairo_surface_get_type(mCairoSurface)) { +#ifdef CAIRO_HAS_WIN32_SURFACE + case CAIRO_SURFACE_TYPE_WIN32: + similar = cairo_win32_surface_create_with_dib( + CairoContentToCairoFormat(cairo_surface_get_content(mCairoSurface)), + size.width, size.height); + break; +#endif +#ifdef CAIRO_HAS_QUARTZ_SURFACE + case CAIRO_SURFACE_TYPE_QUARTZ: + if (StaticPrefs::gfx_cairo_quartz_cg_layer_enabled()) { + similar = cairo_quartz_surface_create_cg_layer( + mCairoSurface, cairo_surface_get_content(mCairoSurface), + size.width, size.height); + break; + } + [[fallthrough]]; +#endif + default: + similar = cairo_surface_create_similar( + mCairoSurface, cairo_surface_get_content(mCairoSurface), size.width, + size.height); + break; + } + + if (cairo_surface_status(similar)) { + return nullptr; + } + + RefPtr<DrawTarget> dt = + Factory::CreateDrawTargetForCairoSurface(similar, size); + + // The DT addrefs the surface, so we need drop our own reference to it: + cairo_surface_destroy(similar); + + if (!dt || !dt->IsValid()) { + return nullptr; + } + mRefDT = std::move(dt); + } + + return do_AddRef(mRefDT); +} + +/* static */ +void PrintTarget::AdjustPrintJobNameForIPP(const nsAString& aJobName, + nsCString& aAdjustedJobName) { + CopyUTF16toUTF8(aJobName, aAdjustedJobName); + + if (aAdjustedJobName.Length() > IPP_JOB_NAME_LIMIT_LENGTH) { + uint32_t length = RewindToPriorUTF8Codepoint( + aAdjustedJobName.get(), (IPP_JOB_NAME_LIMIT_LENGTH - 3U)); + aAdjustedJobName.SetLength(length); + aAdjustedJobName.AppendLiteral("..."); + } +} + +/* static */ +void PrintTarget::AdjustPrintJobNameForIPP(const nsAString& aJobName, + nsString& aAdjustedJobName) { + nsAutoCString jobName; + AdjustPrintJobNameForIPP(aJobName, jobName); + + CopyUTF8toUTF16(jobName, aAdjustedJobName); +} + +/* static */ +already_AddRefed<DrawTarget> PrintTarget::CreateRecordingDrawTarget( + DrawEventRecorder* aRecorder, DrawTarget* aDrawTarget) { + MOZ_ASSERT(aRecorder); + MOZ_ASSERT(aDrawTarget); + + RefPtr<DrawTarget> dt; + + if (aRecorder) { + // It doesn't really matter what we pass as the DrawTarget here. + dt = gfx::Factory::CreateRecordingDrawTarget(aRecorder, aDrawTarget, + aDrawTarget->GetRect()); + } + + if (!dt || !dt->IsValid()) { + gfxCriticalNote + << "Failed to create a recording DrawTarget for PrintTarget"; + return nullptr; + } + + return dt.forget(); +} + +void PrintTarget::Finish() { + if (mIsFinished) { + return; + } + mIsFinished = true; + + // null surfaces are allowed here + cairo_surface_finish(mCairoSurface); +} + +} // namespace mozilla::gfx diff --git a/gfx/thebes/PrintTarget.h b/gfx/thebes/PrintTarget.h new file mode 100644 index 0000000000..f8da3f3c11 --- /dev/null +++ b/gfx/thebes/PrintTarget.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/. */ + +#ifndef MOZILLA_GFX_PRINTTARGET_H +#define MOZILLA_GFX_PRINTTARGET_H + +#include <functional> + +#include "mozilla/RefPtr.h" +#include "mozilla/gfx/2D.h" +#include "nsISupportsImpl.h" +#include "nsStringFwd.h" + +namespace mozilla { +namespace gfx { + +class DrawEventRecorder; + +/** + * A class that is used to draw output that is to be sent to a printer or print + * preview. + * + * This class wraps a cairo_surface_t* and provides access to it via a + * DrawTarget. The various checkpointing methods manage the state of the + * platform specific cairo_surface_t*. + */ +class PrintTarget { + public: + NS_INLINE_DECL_REFCOUNTING(PrintTarget); + + /// Must be matched 1:1 by an EndPrinting/AbortPrinting call. + virtual nsresult BeginPrinting(const nsAString& aTitle, + const nsAString& aPrintToFileName, + int32_t aStartPage, int32_t aEndPage) { + return NS_OK; + } + virtual nsresult EndPrinting() { return NS_OK; } + virtual nsresult AbortPrinting() { +#ifdef DEBUG + mHasActivePage = false; +#endif + return NS_OK; + } + /** + * Note: not all print devices implement mixed page sizing. Most PrintTarget + * subclasses will ignore `aSizeInPoints`. + */ + virtual nsresult BeginPage(const IntSize& aSizeInPoints) { +#ifdef DEBUG + MOZ_ASSERT(!mHasActivePage, "Missing EndPage() call"); + mHasActivePage = true; +#endif + return NS_OK; + } + virtual nsresult EndPage() { +#ifdef DEBUG + mHasActivePage = false; +#endif + return NS_OK; + } + + /** + * Releases the resources used by this PrintTarget. Typically this should be + * called after calling EndPrinting(). Calling this more than once is + * allowed, but subsequent calls are a no-op. + * + * Note that any DrawTarget obtained from this PrintTarget will no longer be + * useful after this method has been called. + */ + virtual void Finish(); + + const IntSize& GetSize() const { return mSize; } + + /** + * Makes a DrawTarget to draw the printer output to, or returns null on + * failure. + * + * If aRecorder is passed a recording DrawTarget will be created instead of + * the type of DrawTarget that would normally be returned for a particular + * subclass of this class. This argument is only intended to be used in + * the e10s content process if printing output can't otherwise be transfered + * over to the parent process using the normal DrawTarget type. + * + * NOTE: this should only be called between BeginPage()/EndPage() calls, and + * the returned DrawTarget should not be drawn to after EndPage() has been + * called. + * + * XXX For consistency with the old code this takes a size parameter even + * though we already have the size passed to our subclass's CreateOrNull + * factory methods. The size passed to the factory method comes from + * nsIDeviceContextSpec::MakePrintTarget overrides, whereas the size + * passed to us comes from nsDeviceContext::CreateRenderingContext. In at + * least one case (nsDeviceContextSpecAndroid::MakePrintTarget) these are + * different. At some point we should align the two sources and get rid of + * this method's size parameter. + * + * XXX For consistency with the old code this returns a new DrawTarget for + * each call. Perhaps we can create and cache a DrawTarget in our subclass's + * CreateOrNull factory methods and return that on each call? Currently that + * seems to cause Mochitest failures on Windows though, which coincidentally + * is the only platform where we get passed an aRecorder. Probably the + * issue is that we get called more than once with a different aRecorder, so + * storing one recording DrawTarget for our lifetime doesn't currently work. + * + * XXX Could we pass aRecorder to our subclass's CreateOrNull factory methods? + * We'd need to check that our consumers always pass the same aRecorder for + * our entire lifetime. + * + * XXX Once PrintTargetThebes is removed this can become non-virtual. + * + * XXX In the long run, this class and its sub-classes should be converted to + * use STL classes and mozilla::RefCounted<> so the can be moved to Moz2D. + * + * TODO: Consider adding a SetDPI method that calls + * cairo_surface_set_fallback_resolution. + */ + virtual already_AddRefed<DrawTarget> MakeDrawTarget( + const IntSize& aSize, DrawEventRecorder* aRecorder = nullptr); + + /** + * Returns a reference DrawTarget. Unlike MakeDrawTarget, this method is not + * restricted to being called between BeginPage()/EndPage() calls, and the + * returned DrawTarget is still valid to use after EndPage() has been called. + */ + virtual already_AddRefed<DrawTarget> GetReferenceDrawTarget(); + + static void AdjustPrintJobNameForIPP(const nsAString& aJobName, + nsCString& aAdjustedJobName); + static void AdjustPrintJobNameForIPP(const nsAString& aJobName, + nsString& aAdjustedJobName); + + protected: + // Only created via subclass's constructors + explicit PrintTarget(cairo_surface_t* aCairoSurface, const IntSize& aSize); + + // Protected because we're refcounted + virtual ~PrintTarget(); + + static already_AddRefed<DrawTarget> CreateRecordingDrawTarget( + DrawEventRecorder* aRecorder, DrawTarget* aDrawTarget); + + cairo_surface_t* mCairoSurface; + RefPtr<DrawTarget> mRefDT; // reference DT + + IntSize mSize; + bool mIsFinished; +#ifdef DEBUG + bool mHasActivePage; +#endif +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_PRINTTARGET_H */ diff --git a/gfx/thebes/PrintTargetCG.h b/gfx/thebes/PrintTargetCG.h new file mode 100644 index 0000000000..ee3c1ab3ac --- /dev/null +++ b/gfx/thebes/PrintTargetCG.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PRINTTARGETCG_H +#define MOZILLA_GFX_PRINTTARGETCG_H + +#include <Carbon/Carbon.h> +#include "PrintTarget.h" + +class nsIOutputStream; + +namespace mozilla::gfx { + +/** + * CoreGraphics printing target. + */ +class PrintTargetCG final : public PrintTarget { + public: + static already_AddRefed<PrintTargetCG> CreateOrNull( + nsIOutputStream* aOutputStream, PMPrintSession aPrintSession, + PMPageFormat aPageFormat, PMPrintSettings aPrintSettings, + const IntSize& aSize); + + nsresult BeginPrinting(const nsAString& aTitle, + const nsAString& aPrintToFileName, int32_t aStartPage, + int32_t aEndPage) final; + nsresult EndPrinting() final; + nsresult AbortPrinting() final; + nsresult BeginPage(const IntSize& aSizeInPoints) final; + nsresult EndPage() final; + + already_AddRefed<DrawTarget> GetReferenceDrawTarget() final; + + private: + PrintTargetCG(CGContextRef aPrintToStreamContext, + PMPrintSession aPrintSession, PMPageFormat aPageFormat, + PMPrintSettings aPrintSettings, const IntSize& aSize); + ~PrintTargetCG(); + + CGContextRef mPrintToStreamContext = nullptr; + PMPrintSession mPrintSession; + PMPageFormat mPageFormat; + PMPrintSettings mPrintSettings; +}; + +} // namespace mozilla::gfx + +#endif /* MOZILLA_GFX_PRINTTARGETCG_H */ diff --git a/gfx/thebes/PrintTargetCG.mm b/gfx/thebes/PrintTargetCG.mm new file mode 100644 index 0000000000..a88be6eafa --- /dev/null +++ b/gfx/thebes/PrintTargetCG.mm @@ -0,0 +1,325 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "mozilla/StaticPrefs_print.h" +#include "nsObjCExceptions.h" +#include "nsString.h" +#include "nsIOutputStream.h" + +namespace mozilla::gfx { + +static size_t PutBytesNull(void* info, const void* buffer, size_t count) { + return count; +} + +PrintTargetCG::PrintTargetCG(CGContextRef aPrintToStreamContext, + PMPrintSession aPrintSession, + PMPageFormat aPageFormat, + PMPrintSettings aPrintSettings, + const IntSize& aSize) + : PrintTarget(/* aCairoSurface */ nullptr, aSize), + mPrintToStreamContext(aPrintToStreamContext), + mPrintSession(aPrintSession), + mPageFormat(aPageFormat), + mPrintSettings(aPrintSettings) { + NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; + + MOZ_ASSERT(mPrintSession && mPageFormat && mPrintSettings); + + ::PMRetain(mPrintSession); + ::PMRetain(mPageFormat); + ::PMRetain(mPrintSettings); + + // TODO: Add memory reporting like gfxQuartzSurface. + // RecordMemoryUsed(mSize.height * 4 + sizeof(gfxQuartzSurface)); + + NS_OBJC_END_TRY_IGNORE_BLOCK; +} + +PrintTargetCG::~PrintTargetCG() { + NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; + + ::PMRelease(mPrintSession); + ::PMRelease(mPageFormat); + ::PMRelease(mPrintSettings); + + if (mPrintToStreamContext) { + CGContextRelease(mPrintToStreamContext); + } + + NS_OBJC_END_TRY_IGNORE_BLOCK; +} + +static size_t WriteStreamBytes(void* aInfo, const void* aBuffer, + size_t aCount) { + auto* stream = static_cast<nsIOutputStream*>(aInfo); + auto* data = static_cast<const char*>(aBuffer); + size_t remaining = aCount; + do { + uint32_t wrote = 0; + // Handle potential narrowing from size_t to uint32_t. + uint32_t toWrite = uint32_t( + std::min(remaining, size_t(std::numeric_limits<uint32_t>::max()))); + if (NS_WARN_IF(NS_FAILED(stream->Write(data, toWrite, &wrote)))) { + break; + } + data += wrote; + remaining -= size_t(wrote); + } while (remaining); + return aCount; +} + +static void ReleaseStream(void* aInfo) { + auto* stream = static_cast<nsIOutputStream*>(aInfo); + stream->Close(); + NS_RELEASE(stream); +} + +static CGContextRef CreatePrintToStreamContext(nsIOutputStream* aOutputStream, + const IntSize& aSize) { + MOZ_ASSERT(aOutputStream); + + NS_ADDREF(aOutputStream); // Matched by the NS_RELEASE in ReleaseStream. + + CGRect pageBox{{0.0, 0.0}, {CGFloat(aSize.width), CGFloat(aSize.height)}}; + CGDataConsumerCallbacks callbacks = {WriteStreamBytes, ReleaseStream}; + CGDataConsumerRef consumer = CGDataConsumerCreate(aOutputStream, &callbacks); + + // This metadata is added by the CorePrinting APIs in the non-stream case. + NSString* bundleName = [NSBundle.mainBundle.localizedInfoDictionary + objectForKey:(NSString*)kCFBundleNameKey]; + CFMutableDictionaryRef auxiliaryInfo = CFDictionaryCreateMutable( + kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CFDictionaryAddValue(auxiliaryInfo, kCGPDFContextCreator, + (__bridge CFStringRef)bundleName); + + CGContextRef pdfContext = + CGPDFContextCreate(consumer, &pageBox, auxiliaryInfo); + CGDataConsumerRelease(consumer); + CFRelease(auxiliaryInfo); + return pdfContext; +} + +/* static */ already_AddRefed<PrintTargetCG> PrintTargetCG::CreateOrNull( + nsIOutputStream* aOutputStream, PMPrintSession aPrintSession, + PMPageFormat aPageFormat, PMPrintSettings aPrintSettings, + const IntSize& aSize) { + if (!Factory::CheckSurfaceSize(aSize)) { + return nullptr; + } + + CGContextRef printToStreamContext = nullptr; + if (aOutputStream) { + printToStreamContext = CreatePrintToStreamContext(aOutputStream, aSize); + if (!printToStreamContext) { + return nullptr; + } + } + + RefPtr<PrintTargetCG> target = new PrintTargetCG( + printToStreamContext, aPrintSession, aPageFormat, aPrintSettings, aSize); + + return target.forget(); +} + +already_AddRefed<DrawTarget> PrintTargetCG::GetReferenceDrawTarget() { + if (!mRefDT) { + const IntSize size(1, 1); + + CGDataConsumerCallbacks callbacks = {PutBytesNull, nullptr}; + CGDataConsumerRef consumer = CGDataConsumerCreate(nullptr, &callbacks); + CGContextRef pdfContext = CGPDFContextCreate(consumer, nullptr, nullptr); + CGDataConsumerRelease(consumer); + + cairo_surface_t* similar = cairo_quartz_surface_create_for_cg_context( + pdfContext, size.width, size.height); + + CGContextRelease(pdfContext); + + if (cairo_surface_status(similar)) { + return nullptr; + } + + RefPtr<DrawTarget> dt = + Factory::CreateDrawTargetForCairoSurface(similar, size); + + // The DT addrefs the surface, so we need drop our own reference to it: + cairo_surface_destroy(similar); + + if (!dt || !dt->IsValid()) { + return nullptr; + } + mRefDT = dt.forget(); + } + + return do_AddRef(mRefDT); +} + +nsresult PrintTargetCG::BeginPrinting(const nsAString& aTitle, + const nsAString& aPrintToFileName, + int32_t aStartPage, int32_t aEndPage) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + if (mPrintToStreamContext) { + return NS_OK; + } + + // Print Core of Application Service sent print job with names exceeding + // 255 bytes. This is a workaround until fix it. + // (https://openradar.appspot.com/34428043) + nsAutoString adjustedTitle; + PrintTarget::AdjustPrintJobNameForIPP(aTitle, adjustedTitle); + + if (!adjustedTitle.IsEmpty()) { + CFStringRef cfString = ::CFStringCreateWithCharacters( + NULL, reinterpret_cast<const UniChar*>(adjustedTitle.BeginReading()), + adjustedTitle.Length()); + if (cfString) { + ::PMPrintSettingsSetJobName(mPrintSettings, cfString); + ::CFRelease(cfString); + } + } + + OSStatus status; + status = ::PMSetFirstPage(mPrintSettings, aStartPage, false); + NS_ASSERTION(status == noErr, "PMSetFirstPage failed"); + status = ::PMSetLastPage(mPrintSettings, aEndPage, false); + NS_ASSERTION(status == noErr, "PMSetLastPage failed"); + + status = ::PMSessionBeginCGDocumentNoDialog(mPrintSession, mPrintSettings, + mPageFormat); + + return status == noErr ? NS_OK : NS_ERROR_ABORT; + + NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +nsresult PrintTargetCG::EndPrinting() { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + if (mPrintToStreamContext) { + CGContextFlush(mPrintToStreamContext); + CGPDFContextClose(mPrintToStreamContext); + return NS_OK; + } + + ::PMSessionEndDocumentNoDialog(mPrintSession); + return NS_OK; + + NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +nsresult PrintTargetCG::AbortPrinting() { +#ifdef DEBUG + mHasActivePage = false; +#endif + return EndPrinting(); +} + +nsresult PrintTargetCG::BeginPage(const IntSize& aSizeInPoints) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + unsigned int width; + unsigned int height; + if (StaticPrefs:: + print_save_as_pdf_use_page_rule_size_as_paper_size_enabled()) { + width = static_cast<unsigned int>(aSizeInPoints.width); + height = static_cast<unsigned int>(aSizeInPoints.height); + } else { + width = static_cast<unsigned int>(mSize.width); + height = static_cast<unsigned int>(mSize.height); + } + + CGContextRef context; + if (mPrintToStreamContext) { + CGRect bounds = CGRectMake(0, 0, width, height); + CGContextBeginPage(mPrintToStreamContext, &bounds); + context = mPrintToStreamContext; + } else { + // XXX Why are we calling this if we don't check the return value? + PMSessionError(mPrintSession); + + // XXX For mixed sheet sizes that aren't simply an orientation switch, we + // will want to be able to pass a sheet size here, using something like: + // PMRect bounds = { 0, 0, double(height), double(width) }; + // But the docs for PMSessionBeginPageNoDialog's `pageFrame` parameter say: + // "You should pass NULL, as this parameter is currentlyunsupported." + // https://developer.apple.com/documentation/applicationservices/1463416-pmsessionbeginpagenodialog?language=objc + // And indeed, it doesn't appear to do anything. + // (It seems weird that CGContextBeginPage (above) supports passing a rect, + // and that that works for setting sheet sizes in PDF output, but the Core + // Printing API does not.) + // We can always switch to PrintTargetPDF - we use that for Windows/Linux + // anyway. But Core Graphics output is better than Cairo's in some cases. + // + // For now, we support switching sheet orientation only: + if (StaticPrefs:: + print_save_as_pdf_use_page_rule_size_as_paper_size_enabled()) { + ::PMOrientation pageOrientation = + width < height ? kPMPortrait : kPMLandscape; + ::PMSetOrientation(mPageFormat, pageOrientation, kPMUnlocked); + // We don't need to reset the orientation, since we set it for every page. + } + OSStatus status = + ::PMSessionBeginPageNoDialog(mPrintSession, mPageFormat, nullptr); + if (status != noErr) { + return NS_ERROR_ABORT; + } + + // This call will fail if it wasn't called between the PMSessionBeginPage/ + // PMSessionEndPage calls: + ::PMSessionGetCGGraphicsContext(mPrintSession, &context); + + if (!context) { + return NS_ERROR_FAILURE; + } + } + + // 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(aSizeInPoints); + + NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +nsresult PrintTargetCG::EndPage() { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + cairo_surface_finish(mCairoSurface); + mCairoSurface = nullptr; + + if (mPrintToStreamContext) { + CGContextEndPage(mPrintToStreamContext); + } else { + OSStatus status = ::PMSessionEndPageNoDialog(mPrintSession); + if (status != noErr) { + return NS_ERROR_ABORT; + } + } + + return PrintTarget::EndPage(); + + NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +} // namespace mozilla::gfx diff --git a/gfx/thebes/PrintTargetPDF.cpp b/gfx/thebes/PrintTargetPDF.cpp new file mode 100644 index 0000000000..3b61696f4c --- /dev/null +++ b/gfx/thebes/PrintTargetPDF.cpp @@ -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/. */ + +#include "PrintTargetPDF.h" + +#include "cairo.h" +#include "cairo-pdf.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/StaticPrefs_print.h" +#include "nsContentUtils.h" +#include "nsString.h" + +namespace mozilla::gfx { + +static cairo_status_t write_func(void* closure, const unsigned char* data, + unsigned int length) { + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + return CAIRO_STATUS_SUCCESS; + } + nsCOMPtr<nsIOutputStream> out = reinterpret_cast<nsIOutputStream*>(closure); + do { + uint32_t wrote = 0; + if (NS_FAILED(out->Write((const char*)data, length, &wrote))) { + break; + } + data += wrote; + length -= wrote; + } while (length > 0); + NS_ASSERTION(length == 0, "not everything was written to the file"); + return CAIRO_STATUS_SUCCESS; +} + +PrintTargetPDF::PrintTargetPDF(cairo_surface_t* aCairoSurface, + const IntSize& aSize, nsIOutputStream* aStream) + : PrintTarget(aCairoSurface, aSize), mStream(aStream) {} + +PrintTargetPDF::~PrintTargetPDF() { + // We get called first, then PrintTarget's dtor. That means that mStream + // is destroyed before PrintTarget's dtor calls cairo_surface_destroy. This + // can be a problem if Finish() hasn't been called on us, since + // cairo_surface_destroy will then call cairo_surface_finish and that will + // end up invoking write_func above with the by now dangling pointer mStream + // that mCairoSurface stored. To prevent that from happening we must call + // Flush here before mStream is deleted. + Finish(); +} + +/* static */ +already_AddRefed<PrintTargetPDF> PrintTargetPDF::CreateOrNull( + nsIOutputStream* aStream, const IntSize& aSizeInPoints) { + if (NS_WARN_IF(!aStream)) { + return nullptr; + } + + cairo_surface_t* surface = cairo_pdf_surface_create_for_stream( + write_func, (void*)aStream, aSizeInPoints.width, aSizeInPoints.height); + if (cairo_surface_status(surface)) { + return nullptr; + } + + nsAutoString creatorName; + if (NS_SUCCEEDED(nsContentUtils::GetLocalizedString( + nsContentUtils::eBRAND_PROPERTIES, "brandFullName", creatorName)) && + !creatorName.IsEmpty()) { + creatorName.Append(u" " MOZILLA_VERSION); + cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_CREATOR, + NS_ConvertUTF16toUTF8(creatorName).get()); + } + + // The new object takes ownership of our surface reference. + RefPtr<PrintTargetPDF> target = + new PrintTargetPDF(surface, aSizeInPoints, aStream); + return target.forget(); +} + +nsresult PrintTargetPDF::BeginPage(const IntSize& aSizeInPoints) { + if (StaticPrefs:: + print_save_as_pdf_use_page_rule_size_as_paper_size_enabled()) { + cairo_pdf_surface_set_size(mCairoSurface, aSizeInPoints.width, + aSizeInPoints.height); + if (cairo_surface_status(mCairoSurface)) { + return NS_ERROR_FAILURE; + } + } + return PrintTarget::BeginPage(aSizeInPoints); +} + +nsresult PrintTargetPDF::EndPage() { + cairo_surface_show_page(mCairoSurface); + return PrintTarget::EndPage(); +} + +void PrintTargetPDF::Finish() { + if (mIsFinished || + AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + // We don't want to call Close() on mStream more than once, and we don't + // want to block shutdown if for some reason the user shuts down the + // browser mid print. + return; + } + PrintTarget::Finish(); + mStream->Close(); +} + +} // namespace mozilla::gfx diff --git a/gfx/thebes/PrintTargetPDF.h b/gfx/thebes/PrintTargetPDF.h new file mode 100644 index 0000000000..cecc4de6ec --- /dev/null +++ b/gfx/thebes/PrintTargetPDF.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_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 BeginPage(const IntSize& aSizeInPoints) override; + nsresult EndPage() override; + void Finish() override; + + private: + PrintTargetPDF(cairo_surface_t* aCairoSurface, const IntSize& aSize, + nsIOutputStream* aStream); + virtual ~PrintTargetPDF(); + + nsCOMPtr<nsIOutputStream> mStream; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_PRINTTARGETPDF_H */ diff --git a/gfx/thebes/PrintTargetRecording.cpp b/gfx/thebes/PrintTargetRecording.cpp new file mode 100644 index 0000000000..4d73995184 --- /dev/null +++ b/gfx/thebes/PrintTargetRecording.cpp @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PrintTargetRecording.h" + +#include "cairo.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Logging.h" + +namespace mozilla { +namespace gfx { + +PrintTargetRecording::PrintTargetRecording(cairo_surface_t* aCairoSurface, + const IntSize& aSize) + : PrintTarget(aCairoSurface, aSize) {} + +/* static */ +already_AddRefed<PrintTargetRecording> PrintTargetRecording::CreateOrNull( + const IntSize& aSize) { + if (!Factory::CheckSurfaceSize(aSize)) { + return nullptr; + } + + // Perhaps surprisingly, this surface is never actually drawn to. This class + // creates a DrawTargetRecording using CreateRecordingDrawTarget, and + // that needs another DrawTarget to be passed to it. You might expect the + // type of the DrawTarget that is passed to matter because it would seem + // logical to encoded its type in the recording, and on replaying the + // recording a DrawTarget of the same type would be created. However, the + // passed DrawTarget's type doesn't seem to be encoded any more accurately + // than just "BackendType::CAIRO". Even if it were, the code that replays the + // recording is PrintTranslator::TranslateRecording which (indirectly) calls + // MakePrintTarget on the type of nsIDeviceContextSpecProxy that is created + // for the platform that we're running on, and the type of DrawTarget that + // that returns is hardcoded. + // + // The only reason that we use cairo_recording_surface_create here is: + // + // * It's pretty much the only cairo_*_surface_create methods that's both + // available on all platforms and doesn't require allocating a + // potentially large surface. + // + // * Since we need a DrawTarget to pass to CreateRecordingDrawTarget we + // might as well leverage our base class's machinery to create a + // DrawTarget (it's as good a way as any other that will work), and to do + // that we need a cairo_surface_t. + // + // So the fact that this is a "recording" PrintTarget and the function that + // we call here is cairo_recording_surface_create is simply a coincidence. We + // could use any cairo_*_surface_create method and this class would still + // work. + // + cairo_surface_t* surface = + cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA, nullptr); + + if (cairo_surface_status(surface)) { + return nullptr; + } + + // The new object takes ownership of our surface reference. + RefPtr<PrintTargetRecording> target = + new PrintTargetRecording(surface, aSize); + + return target.forget(); +} + +already_AddRefed<DrawTarget> PrintTargetRecording::MakeDrawTarget( + const IntSize& aSize, DrawEventRecorder* aRecorder) { + MOZ_ASSERT(aRecorder, "A DrawEventRecorder is required"); + + if (!aRecorder) { + return nullptr; + } + + RefPtr<DrawTarget> dt = PrintTarget::MakeDrawTarget(aSize, nullptr); + if (dt) { + dt = CreateRecordingDrawTarget(aRecorder, dt); + if (!dt || !dt->IsValid()) { + return nullptr; + } + } + + return dt.forget(); +} + +already_AddRefed<DrawTarget> PrintTargetRecording::CreateRecordingDrawTarget( + DrawEventRecorder* aRecorder, DrawTarget* aDrawTarget) { + MOZ_ASSERT(aRecorder); + MOZ_ASSERT(aDrawTarget); + + RefPtr<DrawTarget> dt; + + if (aRecorder) { + // It doesn't really matter what we pass as the DrawTarget here. + dt = gfx::Factory::CreateRecordingDrawTarget(aRecorder, aDrawTarget, + aDrawTarget->GetRect()); + } + + if (!dt || !dt->IsValid()) { + gfxCriticalNote + << "Failed to create a recording DrawTarget for PrintTarget"; + return nullptr; + } + + return dt.forget(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/thebes/PrintTargetRecording.h b/gfx/thebes/PrintTargetRecording.h new file mode 100644 index 0000000000..dccf35e615 --- /dev/null +++ b/gfx/thebes/PrintTargetRecording.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PRINTTARGETRECORDING_H +#define MOZILLA_GFX_PRINTTARGETRECORDING_H + +#include "PrintTarget.h" + +namespace mozilla { +namespace gfx { + +/** + * Recording printing target. + * + * This exists for use on e10s's content process in order to record print + * output, send it over to the parent process, and replay it on a DrawTarget + * there for printing. + */ +class PrintTargetRecording final : public PrintTarget { + public: + static already_AddRefed<PrintTargetRecording> CreateOrNull( + const IntSize& aSize); + + already_AddRefed<DrawTarget> MakeDrawTarget( + const IntSize& aSize, DrawEventRecorder* aRecorder = nullptr) override; + + private: + PrintTargetRecording(cairo_surface_t* aCairoSurface, const IntSize& aSize); + + already_AddRefed<DrawTarget> CreateWrapAndRecordDrawTarget( + DrawEventRecorder* aRecorder, DrawTarget* aDrawTarget); +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_PRINTTARGETRECORDING_H */ diff --git a/gfx/thebes/PrintTargetSkPDF.cpp b/gfx/thebes/PrintTargetSkPDF.cpp new file mode 100644 index 0000000000..2834877407 --- /dev/null +++ b/gfx/thebes/PrintTargetSkPDF.cpp @@ -0,0 +1,127 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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(const IntSize& aSizeInPoints) { + mPageCanvas = mPDFDoc->beginPage(mSize.width, mSize.height); + + return !mPageCanvas ? NS_ERROR_FAILURE + : PrintTarget::BeginPage(aSizeInPoints); +} + +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..af1db0cce7 --- /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(const IntSize& aSizeInPoints) 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..466ad7b119 --- /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(const IntSize& aSizeInPoints) { +#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..65dfcbf7ed --- /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(const IntSize& aSizeInPoints) 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..498309d3ae --- /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(const IntSize& aSizeInPoints) { + PrintTarget::BeginPage(aSizeInPoints); + 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..f305fb9bf5 --- /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(const IntSize& aSizeInPoints) 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..928e9eb2d6 --- /dev/null +++ b/gfx/thebes/SharedFontList-impl.h @@ -0,0 +1,380 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SharedFontList_impl_h +#define SharedFontList_impl_h + +#include "SharedFontList.h" + +#include "base/shared_memory.h" + +#include "gfxFontUtils.h" +#include "nsClassHashtable.h" +#include "nsTHashMap.h" +#include "nsXULAppAPI.h" +#include "mozilla/UniquePtr.h" + +// This is split out from SharedFontList.h because that header is included +// quite widely (via gfxPlatformFontList.h, gfxTextRun.h, etc), and other code +// such as the generated DOM bindings code gets upset at (indirect) inclusion +// of <windows.h> via SharedMemoryBasic.h. So this header, which defines the +// actual shared-memory FontList class, is included only by the .cpp files that +// implement or directly interface with the font list, to avoid polluting other +// headers. + +namespace mozilla { +namespace fontlist { + +/** + * Data used to initialize a font family alias (a "virtual" family that refers + * to some or all of the faces of another family, used when alternate family + * names are found in the font resource for localization or for styled + * subfamilies). AliasData records are collected incrementally while scanning + * the fonts, and then used to set up the Aliases list in the shared font list. + */ +struct AliasData { + nsTArray<Pointer> mFaces; + nsCString mBaseFamily; + uint32_t mIndex = 0; + FontVisibility mVisibility = FontVisibility::Unknown; + bool mBundled = false; + bool mBadUnderline = false; + bool mForceClassic = false; + + void InitFromFamily(const Family* aFamily, const nsCString& aBaseFamily) { + mBaseFamily = aBaseFamily; + mIndex = aFamily->Index(); + mVisibility = aFamily->Visibility(); + mBundled = aFamily->IsBundled(); + mBadUnderline = aFamily->IsBadUnderlineFamily(); + mForceClassic = aFamily->IsForceClassic(); + } +}; + +/** + * The Shared Font List is a collection of data that lives in shared memory + * so that all processes can use it, rather than maintaining their own copies, + * and provides the metadata needed for CSS font-matching (a list of all the + * available font families and their faces, style properties, etc, as well as + * character coverage). + * + * An important assumption is that all processes see the same collection of + * installed fonts; therefore it is valid for them all to share the same set + * of font metadata. The data is updated only by the parent process; content + * processes have read-only access to it. + * + * The total size of this data varies greatly depending on the user's installed + * fonts; and it is not known at startup because we load a lot of the font data + * on first use rather than preloading during initialization (because that's + * too expensive/slow). + * + * Therefore, the shared memory area needs to be able to grow during the + * session; we can't predict how much space will be needed, and we can't afford + * to pre-allocate such a huge block that it could never overflow. To handle + * this, we maintain a (generally short) array of blocks of shared memory, + * and then allocate our Family, Face, etc. objects within these. Because we + * only ever add data (never delete what we've already stored), we can use a + * simplified allocator that doesn't ever need to free blocks; the only time + * the memory is released during a session is the (rare) case where a font is + * installed or deleted while the browser is running, and in this case we just + * delete the entire shared font list and start afresh. + */ +class FontList { + public: + friend struct Pointer; + + explicit FontList(uint32_t aGeneration); + ~FontList(); + + /** + * Initialize the master list of installed font families. This must be + * set during font-list creation, before the list is shared with any + * content processes. All installed font families known to the browser + * appear in this list, although some may be marked as "hidden" so that + * they are not exposed to the font-family property. + * + * The passed-in array may be modified (to eliminate duplicates of bundled + * fonts, or restrict the available list to a specified subset), so if the + * caller intends to make further use of it this should be kept in mind. + * + * Once initialized, the master family list is immutable; in the (rare) + * event that the system's collection of installed fonts changes, we discard + * the FontList and create a new one. + * + * In some cases, a font family may be known by multiple names (e.g. + * localizations in multiple languages, or there may be legacy family names + * that correspond to specific styled faces like "Arial Black"). Such names + * do not appear in this master list, but are referred to as aliases (see + * SetAliases below); the alias list need not be populated before the font + * list is shared to content processes and used. + * + * Only used in the parent process. + */ + void SetFamilyNames(nsTArray<Family::InitData>& aFamilies); + + /** + * Aliases are Family records whose Face entries are already part of another + * family (either because the family has multiple localized names, or because + * the alias family is a legacy name like "Arial Narrow" that is a subset of + * the faces in the main "Arial" family). The table of aliases is initialized + * from a hash of alias family name -> array of Face records. + * + * Like the master family list, the list of family aliases is immutable once + * initialized. + * + * Only used in the parent process. + */ + void SetAliases(nsClassHashtable<nsCStringHashKey, AliasData>& aAliasTable); + + /** + * Local names are PostScript or Full font names of individual faces, used + * to look up faces for @font-face { src: local(...) } rules. Some platforms + * (e.g. macOS) can look up local names directly using platform font APIs, + * in which case the local names table here is unused. + * + * The list of local names is immutable once initialized. Local font name + * lookups may occur before this list has been set up, in which case they + * will use the SearchForLocalFace method. + * + * Only used in the parent process. + */ + void SetLocalNames( + nsTHashMap<nsCStringHashKey, LocalFaceRec::InitData>& aLocalNameTable); + + /** + * Look up a Family record by name, typically to satisfy the font-family + * property or a font family listed in preferences. + */ + Family* FindFamily(const nsCString& aName, bool aPrimaryNameOnly = false); + + /** + * Look up an individual Face by PostScript or Full name, for @font-face + * rules using src:local(...). This requires the local names list to have + * been initialized. + */ + LocalFaceRec* FindLocalFace(const nsCString& aName); + + /** + * Search families for a face with local name aName; should only be used if + * the mLocalFaces array has not yet been set up, as this will be a more + * expensive search than FindLocalFace. + */ + void SearchForLocalFace(const nsACString& aName, Family** aFamily, + Face** aFace); + + /** + * Return the localized name for the given family in the current system + * locale (if multiple localizations are available). + */ + nsCString LocalizedFamilyName(const Family* aFamily); + + bool Initialized() { return mBlocks.Length() > 0 && NumFamilies() > 0; } + + uint32_t NumFamilies() { return GetHeader().mFamilyCount; } + Family* Families() { + return GetHeader().mFamilies.ToArray<Family>(this, NumFamilies()); + } + + uint32_t NumAliases() { return GetHeader().mAliasCount; } + Family* AliasFamilies() { + return GetHeader().mAliases.ToArray<Family>(this, NumAliases()); + } + + uint32_t NumLocalFaces() { return GetHeader().mLocalFaceCount; } + LocalFaceRec* LocalFaces() { + return GetHeader().mLocalFaces.ToArray<LocalFaceRec>(this, NumLocalFaces()); + } + + /** + * Ask the font list to initialize the character map for a given face. + */ + void LoadCharMapFor(Face& aFace, const Family* aFamily); + + /** + * Allocate shared-memory space for a record of aSize bytes. The returned + * pointer will be 32-bit aligned. (This method may trigger the allocation of + * a new shared memory block, if required.) + * + * Only used in the parent process. + */ + Pointer Alloc(uint32_t aSize); + + uint32_t GetGeneration() { return GetHeader().mGeneration; } + + /** + * Header fields present in every shared-memory block. The mBlockSize field + * is not modified after initial block creation (before the block has been + * shared to any other process), so does not need to be std::atomic<>. + * The mAllocated field is checked during Pointer::ToPtr(), so we make that + * atomic to avoid data races. + */ + struct BlockHeader { + std::atomic<uint32_t> mAllocated; // Space allocated from this block. + uint32_t mBlockSize; // Total size of this block. + }; + + /** + * Header info that is stored at the beginning of the first shared-memory + * block for the font list. + * (Subsequent blocks have only the mBlockHeader.) + * The mGeneration and mFamilyCount fields are set by the parent process + * during font-list construction, before the list has been shared with any + * other process, and subsequently never change; therefore, we don't need + * to use std::atomic<> for these. + */ + struct Header { + BlockHeader mBlockHeader; + uint32_t mGeneration; // Font-list generation ID + uint32_t mFamilyCount; // Number of font families in the list + std::atomic<uint32_t> mBlockCount; // Total number of blocks that exist + std::atomic<uint32_t> mAliasCount; // Number of family aliases + std::atomic<uint32_t> mLocalFaceCount; // Number of local face names + Pointer mFamilies; // Pointer to array of |mFamilyCount| families + Pointer mAliases; // Pointer to array of |mAliasCount| aliases + Pointer mLocalFaces; // Pointer to array of |mLocalFaceCount| face records + }; + + /** + * Used by the parent process to pass a handle to a shared block to a + * specific child process. This is used when a child process requests + * an additional block that was not already passed to it (because the + * list has changed/grown since the child was first initialized). + */ + void ShareShmBlockToProcess(uint32_t aIndex, base::ProcessId aPid, + base::SharedMemoryHandle* aOut) { + MOZ_RELEASE_ASSERT(mReadOnlyShmems.Length() == mBlocks.Length()); + if (aIndex >= mReadOnlyShmems.Length()) { + // Block index out of range + *aOut = base::SharedMemory::NULLHandle(); + return; + } + *aOut = mReadOnlyShmems[aIndex]->CloneHandle(); + if (!*aOut) { + MOZ_CRASH("failed to share block"); + } + } + + /** + * Collect an array of handles to all the shmem blocks, ready to be + * shared to the given process. This is used at child process startup + * to pass the complete list at once. + */ + void ShareBlocksToProcess(nsTArray<base::SharedMemoryHandle>* aBlocks, + base::ProcessId aPid); + + base::SharedMemoryHandle ShareBlockToProcess(uint32_t aIndex, + base::ProcessId aPid); + + void ShmBlockAdded(uint32_t aGeneration, uint32_t aIndex, + base::SharedMemoryHandle aHandle); + /** + * Support for memory reporter. + */ + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + size_t AllocatedShmemSize() const; + + /** + * Using a larger block size will speed up allocation, at the cost of more + * wasted space in the shared memory (on average). + */ +#if ANDROID + // Android devices usually have a much smaller number of fonts than desktop + // systems, and memory is more constrained, so use a smaller default block + // size. + static constexpr uint32_t SHM_BLOCK_SIZE = 64 * 1024; +#elif XP_LINUX + // On Linux, font face descriptors are rather large (serialized FcPatterns), + // so use a larger block size for efficiency. + static constexpr uint32_t SHM_BLOCK_SIZE = 1024 * 1024; +#else + // Default block size for Windows and macOS. + static constexpr uint32_t SHM_BLOCK_SIZE = 256 * 1024; +#endif + static_assert(SHM_BLOCK_SIZE <= (1 << Pointer::kBlockShift), + "SHM_BLOCK_SIZE too large"); + + private: + struct ShmBlock { + // Takes ownership of aShmem. Note that in a child process, aShmem will be + // mapped as read-only. + explicit ShmBlock(mozilla::UniquePtr<base::SharedMemory>&& aShmem) + : mShmem(std::move(aShmem)) {} + + // Get pointer to the mapped memory. + void* Memory() const { return mShmem->memory(); } + + // Only the parent process does allocation, so only it will update this + // field. Content processes read the value when checking Pointer validity. + uint32_t Allocated() const { + return static_cast<BlockHeader*>(Memory())->mAllocated; + } + + void StoreAllocated(uint32_t aSize) { + MOZ_ASSERT(XRE_IsParentProcess()); + static_cast<BlockHeader*>(Memory())->mAllocated.store(aSize); + } + + // This is stored by the parent process during block creation and never + // changes, so does not need to be atomic. + // Note that some blocks may be larger than SHM_BLOCK_SIZE, if needed for + // individual large allocations. + uint32_t& BlockSize() const { + MOZ_ASSERT(XRE_IsParentProcess()); + return static_cast<BlockHeader*>(Memory())->mBlockSize; + } + + mozilla::UniquePtr<base::SharedMemory> mShmem; + }; + + Header& GetHeader() const; + + /** + * Create a new shared memory block and append to the FontList's list + * of blocks. + * + * Only used in the parent process. + */ + bool AppendShmBlock(uint32_t aSizeNeeded); + + /** + * Used by child processes to ensure all the blocks are registered. + * Returns false on failure. + * Pass aMustLock=true to take the gfxPlatformFontList lock during the + * update (not required when calling from the constructor). + */ + [[nodiscard]] bool UpdateShmBlocks(bool aMustLock); + + /** + * This makes a *sync* IPC call to get a shared block from the parent. + * As such, it may block for a while if the parent is busy; fortunately, + * we'll generally only call this a handful of times in the course of an + * entire session. If the requested block does not yet exist (because the + * child is wanting to allocate an object, and there wasn't room in any + * existing block), the parent will create a new shared block and return it. + * This may (in rare cases) return null, if the parent has recreated the + * font list and we actually need to reinitialize. + */ + ShmBlock* GetBlockFromParent(uint32_t aIndex); + + void DetachShmBlocks(); + + /** + * Array of pointers to the shared-memory block records. + * NOTE: if mBlocks.Length() < GetHeader().mBlockCount, then the parent has + * added a block (or blocks) to the list, and we need to update! + */ + nsTArray<mozilla::UniquePtr<ShmBlock>> mBlocks; + + /** + * Auxiliary array, used only in the parent process; holds read-only copies + * of the shmem blocks; these are what will be shared to child processes. + */ + nsTArray<mozilla::UniquePtr<base::SharedMemory>> mReadOnlyShmems; +}; + +} // namespace fontlist +} // namespace mozilla + +#endif /* SharedFontList_impl_h */ diff --git a/gfx/thebes/SharedFontList.cpp b/gfx/thebes/SharedFontList.cpp new file mode 100644 index 0000000000..6d87898887 --- /dev/null +++ b/gfx/thebes/SharedFontList.cpp @@ -0,0 +1,1412 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SharedFontList-impl.h" +#include "gfxPlatformFontList.h" +#include "gfxFontUtils.h" +#include "gfxFont.h" +#include "nsReadableUtils.h" +#include "prerror.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/Logging.h" +#include "mozilla/Unused.h" + +#define LOG_FONTLIST(args) \ + MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug, args) +#define LOG_FONTLIST_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug) + +namespace mozilla { +namespace fontlist { + +static double WSSDistance(const Face* aFace, const gfxFontStyle& aStyle) { + double stretchDist = StretchDistance(aFace->mStretch, aStyle.stretch); + double styleDist = StyleDistance(aFace->mStyle, aStyle.style); + double weightDist = WeightDistance(aFace->mWeight, aStyle.weight); + + // Sanity-check that the distances are within the expected range + // (update if implementation of the distance functions is changed). + MOZ_ASSERT(stretchDist >= 0.0 && stretchDist <= 2000.0); + MOZ_ASSERT(styleDist >= 0.0 && styleDist <= 500.0); + MOZ_ASSERT(weightDist >= 0.0 && weightDist <= 1600.0); + + // weight/style/stretch priority: stretch >> style >> weight + // so we multiply the stretch and style values to make them dominate + // the result + return stretchDist * kStretchFactor + styleDist * kStyleFactor + + weightDist * kWeightFactor; +} + +void* Pointer::ToPtr(FontList* aFontList, + size_t aSize) const MOZ_NO_THREAD_SAFETY_ANALYSIS { + // On failure, we'll return null; callers need to handle this appropriately + // (e.g. via fallback). + void* result = nullptr; + + if (IsNull()) { + return result; + } + + // Ensure the list doesn't get replaced out from under us. Font-list rebuild + // happens on the main thread, so only non-main-thread callers need to lock + // it here. + bool isMainThread = NS_IsMainThread(); + if (!isMainThread) { + gfxPlatformFontList::PlatformFontList()->Lock(); + } + + uint32_t blockIndex = Block(); + + // If the Pointer refers to a block we have not yet mapped in this process, + // we first need to retrieve new block handle(s) from the parent and update + // our mBlocks list. + auto& blocks = aFontList->mBlocks; + if (blockIndex >= blocks.Length()) { + if (MOZ_UNLIKELY(XRE_IsParentProcess())) { + // Shouldn't happen! A content process tried to pass a bad Pointer? + goto cleanup; + } + // If we're not on the main thread, we can't do the IPC involved in + // UpdateShmBlocks; just let the lookup fail for now. + if (!isMainThread) { + goto cleanup; + } + // UpdateShmBlocks can fail, if the parent has replaced the font list with + // a new generation. In that case we just return null, and whatever font + // the content process was trying to use will appear unusable for now. It's + // about to receive a notification of the new font list anyhow, at which + // point it will flush its caches and reflow everything, so the temporary + // failure of this font will be forgotten. + // UpdateShmBlocks will take the platform font-list lock during the update. + if (MOZ_UNLIKELY(!aFontList->UpdateShmBlocks(true))) { + goto cleanup; + } + MOZ_ASSERT(blockIndex < blocks.Length(), "failure in UpdateShmBlocks?"); + // This is wallpapering bug 1667977; it's unclear if we will always survive + // this, as the content process may be unable to shape/render text if all + // font lookups are failing. + // In at least some cases, however, this can occur transiently while the + // font list is being rebuilt by the parent; content will then be notified + // that the list has changed, and should refresh everything successfully. + if (MOZ_UNLIKELY(blockIndex >= blocks.Length())) { + goto cleanup; + } + } + + { + // Don't create a pointer that's outside what the block has allocated! + const auto& block = blocks[blockIndex]; + if (MOZ_LIKELY(Offset() + aSize <= block->Allocated())) { + result = static_cast<char*>(block->Memory()) + Offset(); + } + } + +cleanup: + if (!isMainThread) { + gfxPlatformFontList::PlatformFontList()->Unlock(); + } + + return result; +} + +void String::Assign(const nsACString& aString, FontList* aList) { + // We only assign to previously-empty strings; they are never modified + // after initial assignment. + MOZ_ASSERT(mPointer.IsNull()); + mLength = aString.Length(); + mPointer = aList->Alloc(mLength + 1); + auto* p = mPointer.ToArray<char>(aList, mLength); + std::memcpy(p, aString.BeginReading(), mLength); + p[mLength] = '\0'; +} + +Family::Family(FontList* aList, const InitData& aData) + : mFaceCount(0), + mKey(aList, aData.mKey), + mName(aList, aData.mName), + mCharacterMap(Pointer::Null()), + mFaces(Pointer::Null()), + mIndex(aData.mIndex), + mVisibility(aData.mVisibility), + mIsSimple(false), + mIsBundled(aData.mBundled), + mIsBadUnderlineFamily(aData.mBadUnderline), + mIsForceClassic(aData.mForceClassic), + mIsAltLocale(aData.mAltLocale) {} + +class SetCharMapRunnable : public mozilla::Runnable { + public: + SetCharMapRunnable(uint32_t aListGeneration, + std::pair<uint32_t, bool> aFamilyIndex, + uint32_t aFaceIndex, gfxCharacterMap* aCharMap) + : Runnable("SetCharMapRunnable"), + mListGeneration(aListGeneration), + mFamilyIndex(aFamilyIndex), + mFaceIndex(aFaceIndex), + mCharMap(aCharMap) {} + + NS_IMETHOD Run() override { + auto* list = gfxPlatformFontList::PlatformFontList()->SharedFontList(); + if (!list || list->GetGeneration() != mListGeneration) { + return NS_OK; + } + dom::ContentChild::GetSingleton()->SendSetCharacterMap( + mListGeneration, mFamilyIndex.first, mFamilyIndex.second, mFaceIndex, + *mCharMap); + return NS_OK; + } + + private: + uint32_t mListGeneration; + std::pair<uint32_t, bool> mFamilyIndex; + uint32_t mFaceIndex; + RefPtr<gfxCharacterMap> mCharMap; +}; + +void Face::SetCharacterMap(FontList* aList, gfxCharacterMap* aCharMap, + const Family* aFamily) { + if (!XRE_IsParentProcess()) { + Maybe<std::pair<uint32_t, bool>> familyIndex = aFamily->FindIndex(aList); + if (!familyIndex) { + NS_WARNING("Family index not found! Ignoring SetCharacterMap"); + return; + } + const auto* faces = aFamily->Faces(aList); + uint32_t faceIndex = 0; + while (faceIndex < aFamily->NumFaces()) { + if (faces[faceIndex].ToPtr<Face>(aList) == this) { + break; + } + ++faceIndex; + } + if (faceIndex >= aFamily->NumFaces()) { + NS_WARNING("Face not found in family! Ignoring SetCharacterMap"); + return; + } + if (NS_IsMainThread()) { + dom::ContentChild::GetSingleton()->SendSetCharacterMap( + aList->GetGeneration(), familyIndex->first, familyIndex->second, + faceIndex, *aCharMap); + } else { + NS_DispatchToMainThread(new SetCharMapRunnable( + aList->GetGeneration(), familyIndex.value(), faceIndex, aCharMap)); + } + return; + } + auto pfl = gfxPlatformFontList::PlatformFontList(); + mCharacterMap = pfl->GetShmemCharMap(aCharMap); +} + +void Family::AddFaces(FontList* aList, const nsTArray<Face::InitData>& aFaces) { + MOZ_ASSERT(XRE_IsParentProcess()); + if (mFaceCount > 0) { + // Already initialized! + return; + } + + uint32_t count = aFaces.Length(); + bool isSimple = false; + // A family is "simple" (i.e. simplified style selection may be used instead + // of the full CSS font-matching algorithm) if there is at maximum one normal, + // bold, italic, and bold-italic face; in this case, they are stored at known + // positions in the mFaces array. + const Face::InitData* slots[4] = {nullptr, nullptr, nullptr, nullptr}; + if (count >= 2 && count <= 4) { + // Check if this can be treated as a "simple" family + isSimple = true; + for (const auto& f : aFaces) { + if (!f.mWeight.IsSingle() || !f.mStretch.IsSingle() || + !f.mStyle.IsSingle()) { + isSimple = false; + break; + } + if (!f.mStretch.Min().IsNormal()) { + isSimple = false; + break; + } + // Figure out which slot (0-3) this face belongs in + size_t slot = 0; + static_assert((kBoldMask | kItalicMask) == 0b11, "bad bold/italic bits"); + if (f.mWeight.Min().IsBold()) { + slot |= kBoldMask; + } + if (f.mStyle.Min().IsItalic() || f.mStyle.Min().IsOblique()) { + slot |= kItalicMask; + } + if (slots[slot]) { + // More than one face mapped to the same slot - not a simple family! + isSimple = false; + break; + } + slots[slot] = &f; + } + if (isSimple) { + // Ensure all 4 slots will exist, even if some are empty. + count = 4; + } + } + + // Allocate space for the face records, and initialize them. + // coverity[suspicious_sizeof] + Pointer p = aList->Alloc(count * sizeof(Pointer)); + auto* facePtrs = p.ToArray<Pointer>(aList, count); + for (size_t i = 0; i < count; i++) { + if (isSimple && !slots[i]) { + facePtrs[i] = Pointer::Null(); + } else { + const auto* initData = isSimple ? slots[i] : &aFaces[i]; + Pointer fp = aList->Alloc(sizeof(Face)); + auto* face = fp.ToPtr<Face>(aList); + (void)new (face) Face(aList, *initData); + facePtrs[i] = fp; + if (initData->mCharMap) { + face->SetCharacterMap(aList, initData->mCharMap, this); + } + } + } + + mIsSimple = isSimple; + mFaces = p; + mFaceCount.store(count); + + if (LOG_FONTLIST_ENABLED()) { + const nsCString& fam = DisplayName().AsString(aList); + for (unsigned j = 0; j < aFaces.Length(); j++) { + nsAutoCString weight, style, stretch; + aFaces[j].mWeight.ToString(weight); + aFaces[j].mStyle.ToString(style); + aFaces[j].mStretch.ToString(stretch); + LOG_FONTLIST( + ("(shared-fontlist) family (%s) added face (%s) index %u, weight " + "%s, style %s, stretch %s", + fam.get(), aFaces[j].mDescriptor.get(), aFaces[j].mIndex, + weight.get(), style.get(), stretch.get())); + } + } +} + +bool Family::FindAllFacesForStyleInternal(FontList* aList, + const gfxFontStyle& aStyle, + nsTArray<Face*>& aFaceList) const { + MOZ_ASSERT(aFaceList.IsEmpty()); + if (!IsInitialized()) { + return false; + } + + Pointer* facePtrs = Faces(aList); + if (!facePtrs) { + return false; + } + + // Depending on the kind of family, we have to do varying amounts of work + // to figure out what face(s) to use for the requested style properties. + + // If the family has only one face, we simply use it; no further style + // checking needed. (However, for bitmap fonts we may still need to check + // whether the size is acceptable.) + if (NumFaces() == 1) { + MOZ_ASSERT(!facePtrs[0].IsNull()); + auto* face = facePtrs[0].ToPtr<Face>(aList); + if (face && face->HasValidDescriptor()) { + aFaceList.AppendElement(face); +#ifdef MOZ_WIDGET_GTK + if (face->mSize) { + return true; + } +#endif + } + return false; + } + + // Most families are "simple", having just Regular/Bold/Italic/BoldItalic, + // or some subset of these. In this case, we have exactly 4 entries in + // mAvailableFonts, stored in the above order; note that some of the entries + // may be nullptr. We can then pick the required entry based on whether the + // request is for bold or non-bold, italic or non-italic, without running + // the more complex matching algorithm used for larger families with many + // weights and/or widths. + + if (mIsSimple) { + // Family has no more than the "standard" 4 faces, at fixed indexes; + // calculate which one we want. + // Note that we cannot simply return it as not all 4 faces are necessarily + // present. + bool wantBold = aStyle.weight.IsBold(); + bool wantItalic = !aStyle.style.IsNormal(); + uint8_t faceIndex = + (wantItalic ? kItalicMask : 0) | (wantBold ? kBoldMask : 0); + + // If the desired style is available, use it directly. + auto* face = facePtrs[faceIndex].ToPtr<Face>(aList); + if (face && face->HasValidDescriptor()) { + aFaceList.AppendElement(face); +#ifdef MOZ_WIDGET_GTK + if (face->mSize) { + return true; + } +#endif + return false; + } + + // Order to check fallback faces in a simple family, depending on the + // requested style. + static const uint8_t simpleFallbacks[4][3] = { + {kBoldFaceIndex, kItalicFaceIndex, + kBoldItalicFaceIndex}, // fallback sequence for Regular + {kRegularFaceIndex, kBoldItalicFaceIndex, kItalicFaceIndex}, // Bold + {kBoldItalicFaceIndex, kRegularFaceIndex, kBoldFaceIndex}, // Italic + {kItalicFaceIndex, kBoldFaceIndex, kRegularFaceIndex} // BoldItalic + }; + const uint8_t* order = simpleFallbacks[faceIndex]; + + for (uint8_t trial = 0; trial < 3; ++trial) { + // check remaining faces in order of preference to find the first that + // actually exists + face = facePtrs[order[trial]].ToPtr<Face>(aList); + if (face && face->HasValidDescriptor()) { + aFaceList.AppendElement(face); +#ifdef MOZ_WIDGET_GTK + if (face->mSize) { + return true; + } +#endif + return false; + } + } + + // We can only reach here if we failed to resolve the face pointer, which + // can happen if we're on a stylo thread and caught the font list being + // updated; in that case we just fail quietly and let font fallback do + // something for the time being. + return false; + } + + // Pick the font(s) that are closest to the desired weight, style, and + // stretch. Iterate over all fonts, measuring the weight/style distance. + // Because of unicode-range values, there may be more than one font for a + // given but the 99% use case is only a single font entry per + // weight/style/stretch distance value. To optimize this, only add entries + // to the matched font array when another entry already has the same + // weight/style/stretch distance and add the last matched font entry. For + // normal platform fonts with a single font entry for each + // weight/style/stretch combination, only the last matched font entry will + // be added. + double minDistance = INFINITY; + Face* matched = nullptr; + // Keep track of whether we've included any non-scalable font resources in + // the selected set. + bool anyNonScalable = false; + for (uint32_t i = 0; i < NumFaces(); i++) { + auto* face = facePtrs[i].ToPtr<Face>(aList); + if (face) { + // weight/style/stretch priority: stretch >> style >> weight + double distance = WSSDistance(face, aStyle); + if (distance < minDistance) { + matched = face; + if (!aFaceList.IsEmpty()) { + aFaceList.Clear(); + } + minDistance = distance; + } else if (distance == minDistance) { + if (matched) { + aFaceList.AppendElement(matched); +#ifdef MOZ_WIDGET_GTK + if (matched->mSize) { + anyNonScalable = true; + } +#endif + } + matched = face; + } + } + } + + MOZ_ASSERT(matched, "didn't match a font within a family"); + if (matched) { + aFaceList.AppendElement(matched); +#ifdef MOZ_WIDGET_GTK + if (matched->mSize) { + anyNonScalable = true; + } +#endif + } + + return anyNonScalable; +} + +void Family::FindAllFacesForStyle(FontList* aList, const gfxFontStyle& aStyle, + nsTArray<Face*>& aFaceList, + bool aIgnoreSizeTolerance) const { +#ifdef MOZ_WIDGET_GTK + bool anyNonScalable = +#else + Unused << +#endif + FindAllFacesForStyleInternal(aList, aStyle, aFaceList); + +#ifdef MOZ_WIDGET_GTK + // aFaceList now contains whatever faces are the best style match for + // the requested style. If specifically-sized bitmap faces are supported, + // we need to additionally filter the list to choose the appropriate size. + // + // It would be slightly more efficient to integrate this directly into the + // face-selection algorithm above, but it's a rare case that doesn't apply + // at all to most font families. + // + // Currently we only support pixel-sized bitmap font faces on Linux/Gtk (i.e. + // when using the gfxFcPlatformFontList implementation), so this filtering is + // not needed on other platforms. + // + // (Note that color-bitmap emoji fonts like Apple Color Emoji or Noto Color + // Emoji don't count here; they package multiple bitmap sizes into a single + // OpenType wrapper, so they appear as a single "scalable" face in our list.) + if (anyNonScalable) { + uint16_t best = 0; + gfxFloat dist = 0.0; + for (const auto& f : aFaceList) { + if (f->mSize == 0) { + // Scalable face; no size distance to compute. + continue; + } + gfxFloat d = fabs(gfxFloat(f->mSize) - aStyle.size); + if (!aIgnoreSizeTolerance && (d * 5.0 > f->mSize)) { + continue; // Too far from the requested size, ignore. + } + // If we haven't found a "best" bitmap size yet, or if this is a better + // match, remember it. + if (!best || d < dist) { + best = f->mSize; + dist = d; + } + } + // Discard all faces except the chosen "best" size; or if no pixel size was + // chosen, all except scalable faces. + // This may eliminate *all* faces in the family, if all were bitmaps and + // none was a good enough size match, in which case we'll fall back to the + // next font-family name. + aFaceList.RemoveElementsBy([=](const auto& e) { return e->mSize != best; }); + } +#endif +} + +Face* Family::FindFaceForStyle(FontList* aList, const gfxFontStyle& aStyle, + bool aIgnoreSizeTolerance) const { + AutoTArray<Face*, 4> faces; + FindAllFacesForStyle(aList, aStyle, faces, aIgnoreSizeTolerance); + return faces.IsEmpty() ? nullptr : faces[0]; +} + +void Family::SearchAllFontsForChar(FontList* aList, + GlobalFontMatch* aMatchData) { + auto* charmap = mCharacterMap.ToPtr<const SharedBitSet>(aList); + if (!charmap) { + // If the face list is not yet initialized, or if character maps have + // not been loaded, go ahead and do this now (by sending a message to the + // parent process, if we're running in a child). + // After this, all faces should have their mCharacterMap set up, and the + // family's mCharacterMap should also be set; but in the code below we + // don't assume this all succeeded, so it still checks. + if (!gfxPlatformFontList::PlatformFontList()->InitializeFamily(this, + true)) { + return; + } + charmap = mCharacterMap.ToPtr<const SharedBitSet>(aList); + } + if (charmap && !charmap->test(aMatchData->mCh)) { + return; + } + + uint32_t numFaces = NumFaces(); + uint32_t charMapsLoaded = 0; // number of faces whose charmap is loaded + Pointer* facePtrs = Faces(aList); + if (!facePtrs) { + return; + } + for (uint32_t i = 0; i < numFaces; i++) { + auto* face = facePtrs[i].ToPtr<Face>(aList); + if (!face) { + continue; + } + MOZ_ASSERT(face->HasValidDescriptor()); + // Get the face's character map, if available (may be null!) + charmap = face->mCharacterMap.ToPtr<const SharedBitSet>(aList); + if (charmap) { + ++charMapsLoaded; + } + // Check style distance if the char is supported, or if charmap not known + // (so that we don't trigger cmap-loading for faces that would be a worse + // match than what we've already found). + if (!charmap || charmap->test(aMatchData->mCh)) { + double distance = WSSDistance(face, aMatchData->mStyle); + if (distance < aMatchData->mMatchDistance) { + // It's a better style match: get a fontEntry, and if we haven't + // already checked character coverage, do it now (note that + // HasCharacter() will trigger loading the fontEntry's cmap, if + // needed). + RefPtr<gfxFontEntry> fe = + gfxPlatformFontList::PlatformFontList()->GetOrCreateFontEntry(face, + this); + if (!fe) { + continue; + } + if (!charmap && !fe->HasCharacter(aMatchData->mCh)) { + continue; + } + if (aMatchData->mPresentation != eFontPresentation::Any) { + RefPtr<gfxFont> font = fe->FindOrMakeFont(&aMatchData->mStyle); + if (!font) { + continue; + } + bool hasColorGlyph = + font->HasColorGlyphFor(aMatchData->mCh, aMatchData->mNextCh); + if (hasColorGlyph != PrefersColor(aMatchData->mPresentation)) { + distance += kPresentationMismatch; + if (distance >= aMatchData->mMatchDistance) { + continue; + } + } + } + aMatchData->mBestMatch = fe; + aMatchData->mMatchDistance = distance; + aMatchData->mMatchedSharedFamily = this; + } + } + } + if (mCharacterMap.IsNull() && charMapsLoaded == numFaces) { + SetupFamilyCharMap(aList); + } +} + +void Family::SetFacePtrs(FontList* aList, nsTArray<Pointer>& aFaces) { + if (aFaces.Length() >= 2 && aFaces.Length() <= 4) { + // Check whether the faces meet the criteria for a "simple" family: no more + // than one each of Regular, Bold, Italic, BoldItalic styles. If so, store + // them at the appropriate slots in mFaces and set the mIsSimple flag to + // accelerate font-matching. + bool isSimple = true; + Pointer slots[4] = {Pointer::Null(), Pointer::Null(), Pointer::Null(), + Pointer::Null()}; + for (const Pointer& fp : aFaces) { + auto* f = fp.ToPtr<const Face>(aList); + if (!f->mWeight.IsSingle() || !f->mStyle.IsSingle() || + !f->mStretch.IsSingle()) { + isSimple = false; + break; + } + if (!f->mStretch.Min().IsNormal()) { + isSimple = false; + break; + } + size_t slot = 0; + if (f->mWeight.Min().IsBold()) { + slot |= kBoldMask; + } + if (f->mStyle.Min().IsItalic() || f->mStyle.Min().IsOblique()) { + slot |= kItalicMask; + } + if (!slots[slot].IsNull()) { + isSimple = false; + break; + } + slots[slot] = fp; + } + if (isSimple) { + size_t size = 4 * sizeof(Pointer); + mFaces = aList->Alloc(size); + memcpy(mFaces.ToPtr(aList, size), slots, size); + mFaceCount.store(4); + mIsSimple = true; + return; + } + } + size_t size = aFaces.Length() * sizeof(Pointer); + mFaces = aList->Alloc(size); + memcpy(mFaces.ToPtr(aList, size), aFaces.Elements(), size); + mFaceCount.store(aFaces.Length()); +} + +void Family::SetupFamilyCharMap(FontList* aList) { + // Set the character map of the family to the union of all the face cmaps, + // to allow font fallback searches to more rapidly reject the family. + if (!mCharacterMap.IsNull()) { + return; + } + if (!XRE_IsParentProcess()) { + // |this| could be a Family record in either the Families() or Aliases() + // arrays; FindIndex will map it back to its index and which array. + Maybe<std::pair<uint32_t, bool>> index = FindIndex(aList); + if (!index) { + NS_WARNING("Family index not found! Ignoring SetupFamilyCharMap"); + return; + } + if (NS_IsMainThread()) { + dom::ContentChild::GetSingleton()->SendSetupFamilyCharMap( + aList->GetGeneration(), index->first, index->second); + return; + } + NS_DispatchToMainThread(NS_NewRunnableFunction( + "SetupFamilyCharMap callback", + [gen = aList->GetGeneration(), idx = index->first, + alias = index->second] { + dom::ContentChild::GetSingleton()->SendSetupFamilyCharMap(gen, idx, + alias); + })); + return; + } + gfxSparseBitSet familyMap; + Pointer firstMapShmPointer; + const SharedBitSet* firstMap = nullptr; + bool merged = false; + Pointer* faces = Faces(aList); + if (!faces) { + return; + } + for (size_t i = 0; i < NumFaces(); i++) { + auto* f = faces[i].ToPtr<const Face>(aList); + if (!f) { + continue; // Skip missing face (in an incomplete "simple" family) + } + auto* faceMap = f->mCharacterMap.ToPtr<const SharedBitSet>(aList); + if (!faceMap) { + continue; // If there's a face where setting up the cmap failed, we skip + // it as unusable. + } + if (!firstMap) { + firstMap = faceMap; + firstMapShmPointer = f->mCharacterMap; + } else if (faceMap != firstMap) { + if (!merged) { + familyMap.Union(*firstMap); + merged = true; + } + familyMap.Union(*faceMap); + } + } + // If we created a merged cmap, we need to save that on the family; or if we + // found no usable cmaps at all, we need to store the empty familyMap so that + // we won't repeatedly attempt this for an unusable family. + if (merged || firstMapShmPointer.IsNull()) { + mCharacterMap = + gfxPlatformFontList::PlatformFontList()->GetShmemCharMap(&familyMap); + } else { + // If all [usable] faces had the same cmap, we can just share it. + mCharacterMap = firstMapShmPointer; + } +} + +Maybe<std::pair<uint32_t, bool>> Family::FindIndex(FontList* aList) const { + const auto* start = aList->Families(); + const auto* end = start + aList->NumFamilies(); + if (this >= start && this < end) { + uint32_t index = this - start; + MOZ_RELEASE_ASSERT(start + index == this, "misaligned Family ptr!"); + return Some(std::pair(index, false)); + } + + start = aList->AliasFamilies(); + end = start + aList->NumAliases(); + if (this >= start && this < end) { + uint32_t index = this - start; + MOZ_RELEASE_ASSERT(start + index == this, "misaligned AliasFamily ptr!"); + return Some(std::pair(index, true)); + } + + return Nothing(); +} + +FontList::FontList(uint32_t aGeneration) { + if (XRE_IsParentProcess()) { + // Create the initial shared block, and initialize Header + if (AppendShmBlock(SHM_BLOCK_SIZE)) { + Header& header = GetHeader(); + header.mBlockHeader.mAllocated.store(sizeof(Header)); + header.mGeneration = aGeneration; + header.mFamilyCount = 0; + header.mBlockCount.store(1); + header.mAliasCount.store(0); + header.mLocalFaceCount.store(0); + header.mFamilies = Pointer::Null(); + header.mAliases = Pointer::Null(); + header.mLocalFaces = Pointer::Null(); + } else { + MOZ_CRASH("parent: failed to initialize FontList"); + } + } else { + // Initialize using the list of shmem blocks passed by the parent via + // SetXPCOMProcessAttributes. + auto& blocks = dom::ContentChild::GetSingleton()->SharedFontListBlocks(); + for (auto& handle : blocks) { + auto newShm = MakeUnique<base::SharedMemory>(); + if (!newShm->IsHandleValid(handle)) { + // Bail out and let UpdateShmBlocks try to do its thing below. + break; + } + if (!newShm->SetHandle(std::move(handle), true)) { + MOZ_CRASH("failed to set shm handle"); + } + if (!newShm->Map(SHM_BLOCK_SIZE) || !newShm->memory()) { + MOZ_CRASH("failed to map shared memory"); + } + uint32_t size = static_cast<BlockHeader*>(newShm->memory())->mBlockSize; + MOZ_ASSERT(size >= SHM_BLOCK_SIZE); + if (size != SHM_BLOCK_SIZE) { + newShm->Unmap(); + if (!newShm->Map(size) || !newShm->memory()) { + MOZ_CRASH("failed to map shared memory"); + } + } + mBlocks.AppendElement(new ShmBlock(std::move(newShm))); + } + blocks.Clear(); + // Update in case of any changes since the initial message was sent. + for (unsigned retryCount = 0; retryCount < 3; ++retryCount) { + if (UpdateShmBlocks(false)) { + return; + } + // The only reason for UpdateShmBlocks to fail is if the parent recreated + // the list after we read its first block, but before we finished getting + // them all, and so the generation check failed on a subsequent request. + // Discarding whatever we've got and retrying should get us a new, + // consistent set of memory blocks in this case. If this doesn't work + // after a couple of retries, bail out. + DetachShmBlocks(); + } + NS_WARNING("child: failed to initialize shared FontList"); + } +} + +FontList::~FontList() { DetachShmBlocks(); } + +FontList::Header& FontList::GetHeader() const MOZ_NO_THREAD_SAFETY_ANALYSIS { + // We only need to lock if we're not on the main thread. + bool isMainThread = NS_IsMainThread(); + if (!isMainThread) { + gfxPlatformFontList::PlatformFontList()->Lock(); + } + + // It's invalid to try and access this before the first block exists. + MOZ_ASSERT(mBlocks.Length() > 0); + auto& result = *static_cast<Header*>(mBlocks[0]->Memory()); + + if (!isMainThread) { + gfxPlatformFontList::PlatformFontList()->Unlock(); + } + + return result; +} + +bool FontList::AppendShmBlock(uint32_t aSizeNeeded) { + MOZ_ASSERT(XRE_IsParentProcess()); + uint32_t size = std::max(aSizeNeeded, SHM_BLOCK_SIZE); + auto newShm = MakeUnique<base::SharedMemory>(); + if (!newShm->CreateFreezeable(size)) { + MOZ_CRASH("failed to create shared memory"); + return false; + } + if (!newShm->Map(size) || !newShm->memory()) { + MOZ_CRASH("failed to map shared memory"); + return false; + } + auto readOnly = MakeUnique<base::SharedMemory>(); + if (!newShm->ReadOnlyCopy(readOnly.get())) { + MOZ_CRASH("failed to create read-only copy"); + return false; + } + + ShmBlock* block = new ShmBlock(std::move(newShm)); + block->StoreAllocated(sizeof(BlockHeader)); + block->BlockSize() = size; + + mBlocks.AppendElement(block); + GetHeader().mBlockCount.store(mBlocks.Length()); + + mReadOnlyShmems.AppendElement(std::move(readOnly)); + + // We don't need to broadcast the addition of the initial block, + // because child processes can't have initialized their list at all + // prior to the first block being set up. + if (mBlocks.Length() > 1) { + if (NS_IsMainThread()) { + dom::ContentParent::BroadcastShmBlockAdded(GetGeneration(), + mBlocks.Length() - 1); + } else { + NS_DispatchToMainThread(NS_NewRunnableFunction( + "ShmBlockAdded callback", + [generation = GetGeneration(), index = mBlocks.Length() - 1] { + dom::ContentParent::BroadcastShmBlockAdded(generation, index); + })); + } + } + + return true; +} + +void FontList::ShmBlockAdded(uint32_t aGeneration, uint32_t aIndex, + base::SharedMemoryHandle aHandle) { + MOZ_ASSERT(!XRE_IsParentProcess()); + MOZ_ASSERT(mBlocks.Length() > 0); + + auto newShm = MakeUnique<base::SharedMemory>(); + if (!newShm->IsHandleValid(aHandle)) { + return; + } + if (!newShm->SetHandle(std::move(aHandle), true)) { + MOZ_CRASH("failed to set shm handle"); + } + + if (aIndex != mBlocks.Length()) { + return; + } + if (aGeneration != GetGeneration()) { + return; + } + + if (!newShm->Map(SHM_BLOCK_SIZE) || !newShm->memory()) { + MOZ_CRASH("failed to map shared memory"); + } + + uint32_t size = static_cast<BlockHeader*>(newShm->memory())->mBlockSize; + MOZ_ASSERT(size >= SHM_BLOCK_SIZE); + if (size != SHM_BLOCK_SIZE) { + newShm->Unmap(); + if (!newShm->Map(size) || !newShm->memory()) { + MOZ_CRASH("failed to map shared memory"); + } + } + + mBlocks.AppendElement(new ShmBlock(std::move(newShm))); +} + +void FontList::DetachShmBlocks() { + for (auto& i : mBlocks) { + i->mShmem = nullptr; + } + mBlocks.Clear(); + mReadOnlyShmems.Clear(); +} + +FontList::ShmBlock* FontList::GetBlockFromParent(uint32_t aIndex) { + MOZ_ASSERT(!XRE_IsParentProcess()); + // If we have no existing blocks, we don't want a generation check yet; + // the header in the first block will define the generation of this list + uint32_t generation = aIndex == 0 ? 0 : GetGeneration(); + base::SharedMemoryHandle handle = base::SharedMemory::NULLHandle(); + if (!dom::ContentChild::GetSingleton()->SendGetFontListShmBlock( + generation, aIndex, &handle)) { + return nullptr; + } + auto newShm = MakeUnique<base::SharedMemory>(); + if (!newShm->IsHandleValid(handle)) { + return nullptr; + } + if (!newShm->SetHandle(std::move(handle), true)) { + MOZ_CRASH("failed to set shm handle"); + } + if (!newShm->Map(SHM_BLOCK_SIZE) || !newShm->memory()) { + MOZ_CRASH("failed to map shared memory"); + } + uint32_t size = static_cast<BlockHeader*>(newShm->memory())->mBlockSize; + MOZ_ASSERT(size >= SHM_BLOCK_SIZE); + if (size != SHM_BLOCK_SIZE) { + newShm->Unmap(); + if (!newShm->Map(size) || !newShm->memory()) { + MOZ_CRASH("failed to map shared memory"); + } + } + return new ShmBlock(std::move(newShm)); +} + +// We don't take the lock when called from the constructor, so disable thread- +// safety analysis here. +bool FontList::UpdateShmBlocks(bool aMustLock) MOZ_NO_THREAD_SAFETY_ANALYSIS { + MOZ_ASSERT(!XRE_IsParentProcess()); + if (aMustLock) { + gfxPlatformFontList::PlatformFontList()->Lock(); + } + bool result = true; + while (!mBlocks.Length() || mBlocks.Length() < GetHeader().mBlockCount) { + ShmBlock* newBlock = GetBlockFromParent(mBlocks.Length()); + if (!newBlock) { + result = false; + break; + } + mBlocks.AppendElement(newBlock); + } + if (aMustLock) { + gfxPlatformFontList::PlatformFontList()->Unlock(); + } + return result; +} + +void FontList::ShareBlocksToProcess(nsTArray<base::SharedMemoryHandle>* aBlocks, + base::ProcessId aPid) { + MOZ_RELEASE_ASSERT(mReadOnlyShmems.Length() == mBlocks.Length()); + for (auto& shmem : mReadOnlyShmems) { + auto handle = shmem->CloneHandle(); + if (!handle) { + // If something went wrong here, we just bail out; the child will need to + // request the blocks as needed, at some performance cost. (Although in + // practice this may mean resources are so constrained the child process + // isn't really going to work at all. But that's not our problem here.) + aBlocks->Clear(); + return; + } + aBlocks->AppendElement(std::move(handle)); + } +} + +base::SharedMemoryHandle FontList::ShareBlockToProcess(uint32_t aIndex, + base::ProcessId aPid) { + MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); + MOZ_RELEASE_ASSERT(mReadOnlyShmems.Length() == mBlocks.Length()); + MOZ_RELEASE_ASSERT(aIndex < mReadOnlyShmems.Length()); + + return mReadOnlyShmems[aIndex]->CloneHandle(); +} + +Pointer FontList::Alloc(uint32_t aSize) { + // Only the parent process does allocation. + MOZ_ASSERT(XRE_IsParentProcess()); + + // 4-byte alignment is good enough for anything we allocate in the font list, + // as our "Pointer" (block index/offset) is a 32-bit value even on x64. + auto align = [](uint32_t aSize) -> size_t { return (aSize + 3u) & ~3u; }; + + aSize = align(aSize); + + int32_t blockIndex = -1; + uint32_t curAlloc, size; + + if (aSize < SHM_BLOCK_SIZE - sizeof(BlockHeader)) { + // Try to allocate in the most recently added block first, as this is + // highly likely to succeed; if not, try earlier blocks (to fill gaps). + const int32_t blockCount = mBlocks.Length(); + for (blockIndex = blockCount - 1; blockIndex >= 0; --blockIndex) { + size = mBlocks[blockIndex]->BlockSize(); + curAlloc = mBlocks[blockIndex]->Allocated(); + if (size - curAlloc >= aSize) { + break; + } + } + } + + if (blockIndex < 0) { + // Couldn't find enough space (or the requested size is too large to use + // a part of a block): create a new block. + if (!AppendShmBlock(aSize + sizeof(BlockHeader))) { + return Pointer::Null(); + } + blockIndex = mBlocks.Length() - 1; + curAlloc = mBlocks[blockIndex]->Allocated(); + } + + // We've found a block; allocate space from it, and return + mBlocks[blockIndex]->StoreAllocated(curAlloc + aSize); + + return Pointer(blockIndex, curAlloc); +} + +void FontList::SetFamilyNames(nsTArray<Family::InitData>& aFamilies) { + // Only the parent process should ever assign the list of families. + MOZ_ASSERT(XRE_IsParentProcess()); + + Header& header = GetHeader(); + MOZ_ASSERT(!header.mFamilyCount); + + gfxPlatformFontList::PlatformFontList()->ApplyWhitelist(aFamilies); + aFamilies.Sort(); + + size_t count = aFamilies.Length(); + + // Any font resources with an empty family-name will have been collected in + // a family with empty name, and sorted to the start of the list. Such fonts + // are not generally usable, or recognized as "installed", so we drop them. + if (count > 1 && aFamilies[0].mKey.IsEmpty()) { + aFamilies.RemoveElementAt(0); + --count; + } + + // Check for duplicate family entries (can occur if there is a bundled font + // that has the same name as a system-installed one); in this case we keep + // the bundled one as it will always be exposed. + if (count > 1) { + for (size_t i = 1; i < count; ++i) { + if (aFamilies[i].mKey.Equals(aFamilies[i - 1].mKey)) { + // Decide whether to discard the current entry or the preceding one + size_t discard = + aFamilies[i].mBundled && !aFamilies[i - 1].mBundled ? i - 1 : i; + aFamilies.RemoveElementAt(discard); + --count; + --i; + } + } + } + + header.mFamilies = Alloc(count * sizeof(Family)); + if (header.mFamilies.IsNull()) { + return; + } + + // We can't call Families() here because the mFamilyCount field has not yet + // been set! + auto* families = header.mFamilies.ToArray<Family>(this, count); + for (size_t i = 0; i < count; i++) { + (void)new (&families[i]) Family(this, aFamilies[i]); + LOG_FONTLIST(("(shared-fontlist) family %u (%s)", (unsigned)i, + aFamilies[i].mName.get())); + } + + header.mFamilyCount = count; +} + +void FontList::SetAliases( + nsClassHashtable<nsCStringHashKey, AliasData>& aAliasTable) { + MOZ_ASSERT(XRE_IsParentProcess()); + + Header& header = GetHeader(); + + // Build an array of Family::InitData records based on the entries in + // aAliasTable, then sort them and store into the fontlist. + nsTArray<Family::InitData> aliasArray; + aliasArray.SetCapacity(aAliasTable.Count()); + for (const auto& entry : aAliasTable) { + aliasArray.AppendElement(Family::InitData( + entry.GetKey(), entry.GetData()->mBaseFamily, entry.GetData()->mIndex, + entry.GetData()->mVisibility, entry.GetData()->mBundled, + entry.GetData()->mBadUnderline, entry.GetData()->mForceClassic, true)); + } + aliasArray.Sort(); + + size_t count = aliasArray.Length(); + + // Drop any entry with empty family-name as being unusable. + if (count && aliasArray[0].mKey.IsEmpty()) { + aliasArray.RemoveElementAt(0); + --count; + } + + if (count < header.mAliasCount) { + // This shouldn't happen, but handle it safely by just bailing out. + NS_WARNING("cannot reduce number of aliases"); + return; + } + fontlist::Pointer ptr = Alloc(count * sizeof(Family)); + auto* aliases = ptr.ToArray<Family>(this, count); + for (size_t i = 0; i < count; i++) { + (void)new (&aliases[i]) Family(this, aliasArray[i]); + LOG_FONTLIST(("(shared-fontlist) alias family %u (%s: %s)", (unsigned)i, + aliasArray[i].mKey.get(), aliasArray[i].mName.get())); + aliases[i].SetFacePtrs(this, aAliasTable.Get(aliasArray[i].mKey)->mFaces); + if (LOG_FONTLIST_ENABLED()) { + const auto& faces = aAliasTable.Get(aliasArray[i].mKey)->mFaces; + for (unsigned j = 0; j < faces.Length(); j++) { + auto* face = faces[j].ToPtr<const Face>(this); + const nsCString& desc = face->mDescriptor.AsString(this); + nsAutoCString weight, style, stretch; + face->mWeight.ToString(weight); + face->mStyle.ToString(style); + face->mStretch.ToString(stretch); + LOG_FONTLIST( + ("(shared-fontlist) face (%s) index %u, weight %s, style %s, " + "stretch %s", + desc.get(), face->mIndex, weight.get(), style.get(), + stretch.get())); + } + } + } + + // Set the pointer before the count, so that any other process trying to read + // will not risk out-of-bounds access to the array. + header.mAliases = ptr; + header.mAliasCount.store(count); +} + +void FontList::SetLocalNames( + nsTHashMap<nsCStringHashKey, LocalFaceRec::InitData>& aLocalNameTable) { + MOZ_ASSERT(XRE_IsParentProcess()); + Header& header = GetHeader(); + if (header.mLocalFaceCount > 0) { + return; // already been done! + } + auto faceArray = ToTArray<nsTArray<nsCString>>(aLocalNameTable.Keys()); + faceArray.Sort(); + size_t count = faceArray.Length(); + Family* families = Families(); + fontlist::Pointer ptr = Alloc(count * sizeof(LocalFaceRec)); + auto* faces = ptr.ToArray<LocalFaceRec>(this, count); + for (size_t i = 0; i < count; i++) { + (void)new (&faces[i]) LocalFaceRec(); + const auto& rec = aLocalNameTable.Get(faceArray[i]); + faces[i].mKey.Assign(faceArray[i], this); + // Local face name records will refer to the canonical family name; we don't + // need to search aliases here. + const auto* family = FindFamily(rec.mFamilyName, /*aPrimaryNameOnly*/ true); + if (!family) { + // Skip this record if the family was excluded by the font whitelist pref. + continue; + } + faces[i].mFamilyIndex = family - families; + if (rec.mFaceIndex == uint32_t(-1)) { + // The InitData record contains an mFaceDescriptor rather than an index, + // so now we need to look for the appropriate index in the family. + faces[i].mFaceIndex = 0; + const Pointer* faceList = + static_cast<const Pointer*>(family->Faces(this)); + for (uint32_t j = 0; j < family->NumFaces(); j++) { + if (!faceList[j].IsNull()) { + auto* f = faceList[j].ToPtr<const Face>(this); + if (f && rec.mFaceDescriptor == f->mDescriptor.AsString(this)) { + faces[i].mFaceIndex = j; + break; + } + } + } + } else { + faces[i].mFaceIndex = rec.mFaceIndex; + } + } + header.mLocalFaces = ptr; + header.mLocalFaceCount.store(count); +} + +nsCString FontList::LocalizedFamilyName(const Family* aFamily) { + // If the given family was created for an alternate locale or legacy name, + // search for a standard family that corresponds to it. This is a linear + // search of the font list, but (a) this is only used to show names in + // Preferences, so is not performance-critical for layout etc.; and (b) few + // such family names are normally present anyway, the vast majority of fonts + // just have a single family name and we return it directly. + if (aFamily->IsAltLocaleFamily()) { + // Currently only the Windows backend actually does this; on other systems, + // the family index is unused and will be kNoIndex for all fonts. + if (aFamily->Index() != Family::kNoIndex) { + const Family* families = Families(); + for (uint32_t i = 0; i < NumFamilies(); ++i) { + if (families[i].Index() == aFamily->Index() && + families[i].IsBundled() == aFamily->IsBundled() && + !families[i].IsAltLocaleFamily()) { + return families[i].DisplayName().AsString(this); + } + } + } + } + + // For standard families (or if we failed to find the expected standard + // family for some reason), just return the DisplayName. + return aFamily->DisplayName().AsString(this); +} + +Family* FontList::FindFamily(const nsCString& aName, bool aPrimaryNameOnly) { + struct FamilyNameComparator { + FamilyNameComparator(FontList* aList, const nsCString& aTarget) + : mList(aList), mTarget(aTarget) {} + + int operator()(const Family& aVal) const { + return Compare(mTarget, + nsDependentCString(aVal.Key().BeginReading(mList))); + } + + private: + FontList* mList; + const nsCString& mTarget; + }; + + const Header& header = GetHeader(); + + Family* families = Families(); + if (!families) { + return nullptr; + } + + size_t match; + if (BinarySearchIf(families, 0, header.mFamilyCount, + FamilyNameComparator(this, aName), &match)) { + return &families[match]; + } + + if (aPrimaryNameOnly) { + return nullptr; + } + + if (header.mAliasCount) { + Family* aliases = AliasFamilies(); + size_t match; + if (aliases && BinarySearchIf(aliases, 0, header.mAliasCount, + FamilyNameComparator(this, aName), &match)) { + return &aliases[match]; + } + } + +#ifdef XP_WIN + // For Windows only, because of how DWrite munges font family names in some + // cases (see + // https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Components.PostAttachments/00/02/24/90/36/WPF%20Font%20Selection%20Model.pdf + // and discussion on the OpenType list), try stripping a possible style-name + // suffix from the end of the requested family name. + // After the deferred font loader has finished, this is no longer needed as + // the "real" family names will have been found in AliasFamilies() above. + if (aName.Contains(' ')) { + auto pfl = gfxPlatformFontList::PlatformFontList(); + pfl->mLock.AssertCurrentThreadIn(); + if (header.mAliasCount) { + // Aliases have been fully loaded by the parent process, so just discard + // any stray mAliasTable and mLocalNameTable entries from earlier calls + // to this code, and return. + pfl->mAliasTable.Clear(); + pfl->mLocalNameTable.Clear(); + return nullptr; + } + + // Do we already have an aliasData record for this name? If so, we just + // return its base family. + if (auto lookup = pfl->mAliasTable.Lookup(aName)) { + return FindFamily(lookup.Data()->mBaseFamily, true); + } + + // Strip the style suffix (after last space in the name) to get a "base" + // family name. + const char* data = aName.BeginReading(); + int32_t index = aName.Length(); + while (--index > 0) { + if (data[index] == ' ') { + break; + } + } + if (index <= 0) { + return nullptr; + } + nsAutoCString base(Substring(aName, 0, index)); + if (BinarySearchIf(families, 0, header.mFamilyCount, + FamilyNameComparator(this, base), &match)) { + // This may be a possible base family to satisfy the search; call + // ReadFaceNamesForFamily and see if the desired name ends up in + // mAliasTable. + // Note that ReadFaceNamesForFamily may store entries in mAliasTable + // (and mLocalNameTable), but if this is happening in a content + // process (which is the common case) those entries will not be saved + // into the shared font list; they're just used here until the "real" + // alias list is ready, then discarded. + Family* baseFamily = &families[match]; + pfl->ReadFaceNamesForFamily(baseFamily, false); + if (auto lookup = pfl->mAliasTable.Lookup(aName)) { + if (lookup.Data()->mFaces.Length() != baseFamily->NumFaces()) { + // If the alias family doesn't have all the faces of the base family, + // then style matching may end up resolving to a face that isn't + // supposed to be available in the legacy styled family. To ensure + // such mis-styling will get fixed, we start the async font info + // loader (if it hasn't yet been triggered), which will pull in the + // full metadata we need and then force a reflow. + pfl->InitOtherFamilyNames(/* aDeferOtherFamilyNamesLoading */ true); + } + return baseFamily; + } + } + } +#endif + + return nullptr; +} + +LocalFaceRec* FontList::FindLocalFace(const nsCString& aName) { + struct FaceNameComparator { + FaceNameComparator(FontList* aList, const nsCString& aTarget) + : mList(aList), mTarget(aTarget) {} + + int operator()(const LocalFaceRec& aVal) const { + return Compare(mTarget, + nsDependentCString(aVal.mKey.BeginReading(mList))); + } + + private: + FontList* mList; + const nsCString& mTarget; + }; + + Header& header = GetHeader(); + + LocalFaceRec* faces = LocalFaces(); + size_t match; + if (faces && BinarySearchIf(faces, 0, header.mLocalFaceCount, + FaceNameComparator(this, aName), &match)) { + return &faces[match]; + } + + return nullptr; +} + +void FontList::SearchForLocalFace(const nsACString& aName, Family** aFamily, + Face** aFace) { + Header& header = GetHeader(); + MOZ_ASSERT(header.mLocalFaceCount == 0, + "do not use when local face names are already set up!"); + LOG_FONTLIST( + ("(shared-fontlist) local face search for (%s)", aName.BeginReading())); + char initial = aName[0]; + Family* families = Families(); + if (!families) { + return; + } + for (uint32_t i = 0; i < header.mFamilyCount; i++) { + Family* family = &families[i]; + if (family->Key().BeginReading(this)[0] != initial) { + continue; + } + LOG_FONTLIST(("(shared-fontlist) checking family (%s)", + family->Key().AsString(this).BeginReading())); + if (!family->IsInitialized()) { + if (!gfxPlatformFontList::PlatformFontList()->InitializeFamily(family)) { + continue; + } + } + Pointer* faces = family->Faces(this); + if (!faces) { + continue; + } + for (uint32_t j = 0; j < family->NumFaces(); j++) { + auto* face = faces[j].ToPtr<Face>(this); + if (!face) { + continue; + } + nsAutoCString psname, fullname; + if (gfxPlatformFontList::PlatformFontList()->ReadFaceNames( + family, face, psname, fullname)) { + LOG_FONTLIST(("(shared-fontlist) read psname (%s) fullname (%s)", + psname.get(), fullname.get())); + ToLowerCase(psname); + ToLowerCase(fullname); + if (aName == psname || aName == fullname) { + *aFamily = family; + *aFace = face; + return; + } + } + } + } +} + +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..59ac223efe --- /dev/null +++ b/gfx/thebes/SharedFontList.h @@ -0,0 +1,402 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SharedFontList_h +#define SharedFontList_h + +#include "gfxFontEntry.h" +#include <atomic> + +class gfxCharacterMap; +struct gfxFontStyle; +struct GlobalFontMatch; + +namespace mozilla { +namespace fontlist { + +class FontList; // See the separate SharedFontList-impl.h header + +/** + * A Pointer in the shared font list contains a packed index/offset pair, + * with a 12-bit index into the array of shared-memory blocks, and a 20-bit + * offset into the block. + * The maximum size of each block is therefore 2^20 bytes (1 MB) if sub-parts + * of the block are to be allocated; however, a larger block (up to 2^32 bytes) + * can be created and used as a single allocation if necessary. + */ +struct Pointer { + private: + friend class FontList; + static const uint32_t kIndexBits = 12u; + static const uint32_t kBlockShift = 20u; + static_assert(kIndexBits + kBlockShift == 32u, "bad Pointer bit count"); + + static const uint32_t kNullValue = 0xffffffffu; + static const uint32_t kOffsetMask = (1u << kBlockShift) - 1; + + public: + static Pointer Null() { return Pointer(); } + + Pointer() : mBlockAndOffset(kNullValue) {} + + Pointer(uint32_t aBlock, uint32_t aOffset) + : mBlockAndOffset((aBlock << kBlockShift) | aOffset) { + MOZ_ASSERT(aBlock < (1u << kIndexBits) && aOffset < (1u << kBlockShift)); + } + + Pointer(const Pointer& aOther) { + mBlockAndOffset.store(aOther.mBlockAndOffset); + } + + Pointer(Pointer&& aOther) { mBlockAndOffset.store(aOther.mBlockAndOffset); } + + /** + * Check if a Pointer has the null value. + * + * NOTE! + * In a child process, it is possible that conversion to a "real" pointer + * using ToPtr() will fail even when IsNull() is false, so calling code + * that may run in child processes must be prepared to handle this. + */ + bool IsNull() const { return mBlockAndOffset == kNullValue; } + + uint32_t Block() const { return mBlockAndOffset >> kBlockShift; } + + uint32_t Offset() const { return mBlockAndOffset & kOffsetMask; } + + /** + * Convert a fontlist::Pointer to a standard C++ pointer. This requires the + * FontList, which will know where the shared memory block is mapped in + * the current process's address space. + * + * aSize is the expected size of the pointed-to object, for bounds checking. + * + * NOTE! + * In child processes this may fail and return nullptr, even if IsNull() is + * false, in cases where the font list is in the process of being rebuilt. + */ + void* ToPtr(FontList* aFontList, size_t aSize) const; + + template <typename T> + T* ToPtr(FontList* aFontList) const { + return static_cast<T*>(ToPtr(aFontList, sizeof(T))); + } + + template <typename T> + T* ToArray(FontList* aFontList, size_t aCount) const { + return static_cast<T*>(ToPtr(aFontList, sizeof(T) * aCount)); + } + + Pointer& operator=(const Pointer& aOther) { + mBlockAndOffset.store(aOther.mBlockAndOffset); + return *this; + } + + Pointer& operator=(Pointer&& aOther) { + mBlockAndOffset.store(aOther.mBlockAndOffset); + return *this; + } + + // We store the block index and the offset within the block as a single + // atomic 32-bit value so we can safely modify a Pointer without other + // processes seeing a broken (partially-updated) value. + std::atomic<uint32_t> mBlockAndOffset; +}; + +/** + * Family and face names are stored as String records, which hold a length + * (in utf-8 code units) and a Pointer to the string's UTF-8 characters. + */ +struct String { + String() : mPointer(Pointer::Null()), mLength(0) {} + + String(FontList* aList, const nsACString& aString) + : mPointer(Pointer::Null()) { + Assign(aString, aList); + } + + const nsCString AsString(FontList* aList) const { + MOZ_ASSERT(!mPointer.IsNull()); + // It's tempting to use AssignLiteral here so that we get an nsCString that + // simply wraps the character data in the shmem block without needing to + // allocate or copy. But that's unsafe because in the event of font-list + // reinitalization, that shared memory will be unmapped; then any copy of + // the nsCString that may still be around will crash if accessed. + return nsCString(mPointer.ToArray<const char>(aList, mLength), mLength); + } + + void Assign(const nsACString& aString, FontList* aList); + + const char* BeginReading(FontList* aList) const { + MOZ_ASSERT(!mPointer.IsNull()); + auto* str = mPointer.ToArray<const char>(aList, mLength); + return str ? str : ""; + } + + uint32_t Length() const { return mLength; } + + /** + * Return whether the String has been set to a value. + * + * NOTE! + * In a child process, accessing the value could fail even if IsNull() + * returned false. In this case, the nsCString constructor used by AsString() + * will be passed a null pointer, and return an empty string despite the + * non-zero Length() recorded here. + */ + bool IsNull() const { return mPointer.IsNull(); } + + private: + Pointer mPointer; + uint32_t mLength; +}; + +/** + * A Face record represents an individual font resource; it has the style + * properties needed for font matching, as well as a pointer to a character + * map that records the supported character set. This may be Null if we have + * not yet loaded the data. + * The mDescriptor and mIndex fields provide the information needed to + * instantiate a (platform-specific) font reference that can be used with + * platform font APIs; their content depends on the requirements of the + * platform APIs (e.g. font PostScript name, file pathname, serialized + * fontconfig pattern, etc). + */ +struct Face { + // Data required to initialize a Face + struct InitData { + nsCString mDescriptor; // descriptor that can be used to instantiate a + // platform font reference + uint16_t mIndex; // an index used with descriptor (on some platforms) +#ifdef MOZ_WIDGET_GTK + uint16_t mSize; // pixel size if bitmap; zero indicates scalable +#endif + bool mFixedPitch; // is the face fixed-pitch (monospaced)? + mozilla::WeightRange mWeight; // CSS font-weight value + mozilla::StretchRange mStretch; // CSS font-stretch value + mozilla::SlantStyleRange mStyle; // CSS font-style value + RefPtr<gfxCharacterMap> mCharMap; // character map, or null if not loaded + }; + + // Note that mCharacterMap is not set from the InitData by this constructor; + // the caller must use SetCharacterMap to handle that separately if required. + Face(FontList* aList, const InitData& aData) + : mDescriptor(aList, aData.mDescriptor), + mIndex(aData.mIndex), +#ifdef MOZ_WIDGET_GTK + mSize(aData.mSize), +#endif + mFixedPitch(aData.mFixedPitch), + mWeight(aData.mWeight), + mStretch(aData.mStretch), + mStyle(aData.mStyle), + mCharacterMap(Pointer::Null()) { + } + + bool HasValidDescriptor() const { + return !mDescriptor.IsNull() && mIndex != uint16_t(-1); + } + + void SetCharacterMap(FontList* aList, gfxCharacterMap* aCharMap, + const Family* aFamily); + + String mDescriptor; + uint16_t mIndex; +#ifdef MOZ_WIDGET_GTK + uint16_t mSize; +#endif + bool mFixedPitch; + mozilla::WeightRange mWeight; + mozilla::StretchRange mStretch; + mozilla::SlantStyleRange mStyle; + Pointer mCharacterMap; +}; + +/** + * A Family record represents an available (installed) font family; it has + * a name (for display purposes) and a key (lowercased, for case-insensitive + * lookups), as well as a pointer to an array of Faces. Depending on the + * platform, the array of faces may be lazily initialized the first time we + * want to use the family. + */ +struct Family { + static constexpr uint32_t kNoIndex = uint32_t(-1); + + // Data required to initialize a Family + struct InitData { + InitData(const nsACString& aKey, // lookup key (lowercased) + const nsACString& aName, // display name + uint32_t aIndex = kNoIndex, // [win] system collection index + FontVisibility aVisibility = FontVisibility::Unknown, + bool aBundled = false, // [win] font was bundled with the app + // rather than system-installed + bool aBadUnderline = false, // underline-position in font is bad + bool aForceClassic = false, // [win] use "GDI classic" rendering + bool aAltLocale = false // font is alternate localized family + ) + : mKey(aKey), + mName(aName), + mIndex(aIndex), + mVisibility(aVisibility), + mBundled(aBundled), + mBadUnderline(aBadUnderline), + mForceClassic(aForceClassic), + mAltLocale(aAltLocale) {} + bool operator<(const InitData& aRHS) const { return mKey < aRHS.mKey; } + bool operator==(const InitData& aRHS) const { + return mKey == aRHS.mKey && mName == aRHS.mName && + mVisibility == aRHS.mVisibility && mBundled == aRHS.mBundled && + mBadUnderline == aRHS.mBadUnderline; + } + nsCString mKey; + nsCString mName; + uint32_t mIndex; + FontVisibility mVisibility; + bool mBundled; + bool mBadUnderline; + bool mForceClassic; + bool mAltLocale; + }; + + /** + * Font families are considered "simple" if they contain only 4 faces with + * style attributes corresponding to Regular, Bold, Italic, and BoldItalic + * respectively, or a subset of these (e.g. only Regular and Bold). In this + * case, the faces are stored at predefined positions in the mFaces array, + * and a simplified (faster) style-matching algorithm can be used. + */ + enum { + // Indexes into mFaces for families where mIsSimple is true + kRegularFaceIndex = 0, + kBoldFaceIndex = 1, + kItalicFaceIndex = 2, + kBoldItalicFaceIndex = 3, + // mask values for selecting face with bold and/or italic attributes + kBoldMask = 0x01, + kItalicMask = 0x02 + }; + + Family(FontList* aList, const InitData& aData); + + void AddFaces(FontList* aList, const nsTArray<Face::InitData>& aFaces); + + void SetFacePtrs(FontList* aList, nsTArray<Pointer>& aFaces); + + const String& Key() const { return mKey; } + + const String& DisplayName() const { return mName; } + + uint32_t Index() const { return mIndex; } + bool IsBundled() const { return mIsBundled; } + + uint32_t NumFaces() const { + MOZ_ASSERT(IsInitialized()); + return mFaceCount; + } + + Pointer* Faces(FontList* aList) const { + MOZ_ASSERT(IsInitialized()); + return mFaces.ToArray<Pointer>(aList, mFaceCount); + } + + FontVisibility Visibility() const { return mVisibility; } + bool IsHidden() const { return Visibility() == FontVisibility::Hidden; } + + bool IsBadUnderlineFamily() const { return mIsBadUnderlineFamily; } + bool IsForceClassic() const { return mIsForceClassic; } + bool IsSimple() const { return mIsSimple; } + bool IsAltLocaleFamily() const { return mIsAltLocale; } + + // IsInitialized indicates whether the family has been populated with faces, + // and is therefore ready to use. + // It is possible that character maps have not yet been loaded. + bool IsInitialized() const { return !mFaces.IsNull(); } + + // IsFullyInitialized indicates that not only faces but also character maps + // have been set up, so the family can be searched without the possibility + // that IPC messaging will be triggered. + bool IsFullyInitialized() const { + return IsInitialized() && !mCharacterMap.IsNull(); + } + + void FindAllFacesForStyle(FontList* aList, const gfxFontStyle& aStyle, + nsTArray<Face*>& aFaceList, + bool aIgnoreSizeTolerance = false) const; + + Face* FindFaceForStyle(FontList* aList, const gfxFontStyle& aStyle, + bool aIgnoreSizeTolerance = false) const; + + void SearchAllFontsForChar(FontList* aList, GlobalFontMatch* aMatchData); + + void SetupFamilyCharMap(FontList* aList); + + // Return the index of this family in the font-list's Families() or + // AliasFamilies() list, and which of those it belongs to. + // Returns Nothing if the family cannot be found. + mozilla::Maybe<std::pair<uint32_t, bool>> FindIndex(FontList* aList) const; + + private: + // Returns true if there are specifically-sized bitmap faces in the list, + // so size selection still needs to be done. (Currently only on Linux.) + bool FindAllFacesForStyleInternal(FontList* aList, const gfxFontStyle& aStyle, + nsTArray<Face*>& aFaceList) const; + + std::atomic<uint32_t> mFaceCount; + String mKey; + String mName; + Pointer mCharacterMap; // If non-null, union of character coverage of all + // faces in the family + Pointer mFaces; // Pointer to array of |mFaceCount| face pointers + uint32_t mIndex; // [win] Top bit set indicates app-bundled font family + FontVisibility mVisibility; + bool mIsSimple; // family allows simplified style matching: mFaces contains + // exactly 4 entries [Regular, Bold, Italic, BoldItalic]. + bool mIsBundled : 1; + bool mIsBadUnderlineFamily : 1; + bool mIsForceClassic : 1; + bool mIsAltLocale : 1; +}; + +/** + * For platforms where we build an index of local font face names (PS-name + * and fullname of the font) to support @font-face{src:local(...)}, we map + * each face name to an index into the family list, and an index into the + * family's list of faces. + */ +struct LocalFaceRec { + /** + * The InitData struct needs to record the family name rather than index, + * as we may be collecting these records at the same time as building the + * family list, so we don't yet know the final family index. + * Likewise, in some cases we don't know the final face index because the + * faces may be re-sorted to fit into predefined positions in a "simple" + * family (if we're reading names before the family has been fully set up). + * In that case, we'll store uint32_t(-1) as mFaceIndex, and record the + * string descriptor instead. + * When actually recorded in the FontList's mLocalFaces array, the family + * will be stored as a simple index into the mFamilies array, and the face + * as an index into the family's mFaces. + */ + struct InitData { + nsCString mFamilyName; + nsCString mFaceDescriptor; + uint32_t mFaceIndex = uint32_t(-1); + InitData(const nsACString& aFamily, const nsACString& aFace) + : mFamilyName(aFamily), mFaceDescriptor(aFace) {} + InitData(const nsACString& aFamily, uint32_t aFaceIndex) + : mFamilyName(aFamily), mFaceIndex(aFaceIndex) {} + InitData() = default; + }; + String mKey; + uint32_t mFamilyIndex; // Index into the font list's Families array + uint32_t mFaceIndex; // Index into the family's Faces array +}; + +} // namespace fontlist +} // namespace mozilla + +#undef ERROR // This is defined via Windows.h, but conflicts with some bindings + // code when this gets included in the same compilation unit. + +#endif /* SharedFontList_h */ diff --git a/gfx/thebes/SkMemoryReporter.cpp b/gfx/thebes/SkMemoryReporter.cpp new file mode 100644 index 0000000000..d8448908f8 --- /dev/null +++ b/gfx/thebes/SkMemoryReporter.cpp @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SkMemoryReporter.h" + +#include "skia/include/core/SkGraphics.h" + +namespace mozilla { +namespace gfx { + +NS_IMETHODIMP +SkMemoryReporter::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + MOZ_COLLECT_REPORT("explicit/skia-font-cache", KIND_HEAP, UNITS_BYTES, + SkGraphics::GetFontCacheUsed(), + "Memory used in the skia font cache."); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(SkMemoryReporter, nsIMemoryReporter); + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/thebes/SkMemoryReporter.h b/gfx/thebes/SkMemoryReporter.h new file mode 100644 index 0000000000..e092b48541 --- /dev/null +++ b/gfx/thebes/SkMemoryReporter.h @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_SKMEMORYREPORTER_H +#define GFX_SKMEMORYREPORTER_H + +#include "nsIMemoryReporter.h" + +namespace mozilla { +namespace gfx { + +class SkMemoryReporter final : public nsIMemoryReporter { + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override; + + private: + ~SkMemoryReporter() = default; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* GFX_SKMEMORYREPORTER_H */ diff --git a/gfx/thebes/SoftwareVsyncSource.cpp b/gfx/thebes/SoftwareVsyncSource.cpp new file mode 100644 index 0000000000..b8e12390e7 --- /dev/null +++ b/gfx/thebes/SoftwareVsyncSource.cpp @@ -0,0 +1,143 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "SoftwareVsyncSource.h" +#include "base/task.h" +#include "gfxPlatform.h" +#include "nsThreadUtils.h" + +namespace mozilla::gfx { + +SoftwareVsyncSource::SoftwareVsyncSource(const TimeDuration& aInitialVsyncRate) + : mVsyncEnabled(false), + mVsyncRate(TimeDuration{aInitialVsyncRate}, + "SoftwareVsyncSource::mVsyncRate") { + MOZ_ASSERT(NS_IsMainThread()); + mVsyncThread = new base::Thread("SoftwareVsyncThread"); + MOZ_RELEASE_ASSERT(mVsyncThread->Start(), + "GFX: Could not start software vsync thread"); +} + +SoftwareVsyncSource::~SoftwareVsyncSource() { + MOZ_ASSERT(NS_IsMainThread()); + if (mVsyncThread) { + mVsyncThread->Stop(); + delete mVsyncThread; + } +}; + +void SoftwareVsyncSource::EnableVsync() { + MOZ_ASSERT(mVsyncThread->IsRunning()); + if (NS_IsMainThread()) { + if (mVsyncEnabled) { + return; + } + mVsyncEnabled = true; + + mVsyncThread->message_loop()->PostTask( + NewRunnableMethod("SoftwareVsyncSource::EnableVsync", this, + &SoftwareVsyncSource::EnableVsync)); + return; + } + + MOZ_ASSERT(IsInSoftwareVsyncThread()); + TimeStamp vsyncTime = TimeStamp::Now(); + TimeStamp outputTime = vsyncTime + GetVsyncRate(); + NotifyVsync(vsyncTime, outputTime); +} + +void SoftwareVsyncSource::DisableVsync() { + MOZ_ASSERT(mVsyncThread->IsRunning()); + if (NS_IsMainThread()) { + if (!mVsyncEnabled) { + return; + } + mVsyncEnabled = false; + + mVsyncThread->message_loop()->PostTask( + NewRunnableMethod("SoftwareVsyncSource::DisableVsync", this, + &SoftwareVsyncSource::DisableVsync)); + return; + } + + MOZ_ASSERT(IsInSoftwareVsyncThread()); + if (mCurrentVsyncTask) { + mCurrentVsyncTask->Cancel(); + mCurrentVsyncTask = nullptr; + } +} + +bool SoftwareVsyncSource::IsVsyncEnabled() { + MOZ_ASSERT(NS_IsMainThread()); + return mVsyncEnabled; +} + +bool SoftwareVsyncSource::IsInSoftwareVsyncThread() { + return mVsyncThread->thread_id() == PlatformThread::CurrentId(); +} + +void SoftwareVsyncSource::NotifyVsync(const TimeStamp& aVsyncTimestamp, + const TimeStamp& aOutputTimestamp) { + MOZ_ASSERT(IsInSoftwareVsyncThread()); + + TimeStamp displayVsyncTime = aVsyncTimestamp; + TimeStamp now = TimeStamp::Now(); + // Posted tasks can only have integer millisecond delays + // whereas TimeDurations can have floating point delays. + // Thus the vsync timestamp can be in the future, which large parts + // of the system can't handle, including animations. Force the timestamp to be + // now. + if (aVsyncTimestamp > now) { + displayVsyncTime = now; + } + + VsyncSource::NotifyVsync(displayVsyncTime, aOutputTimestamp); + + // Prevent skew by still scheduling based on the original + // vsync timestamp + ScheduleNextVsync(aVsyncTimestamp); +} + +TimeDuration SoftwareVsyncSource::GetVsyncRate() { + auto rate = mVsyncRate.Lock(); + return *rate; +} + +void SoftwareVsyncSource::SetVsyncRate(const TimeDuration& aNewRate) { + auto rate = mVsyncRate.Lock(); + *rate = aNewRate; +} + +void SoftwareVsyncSource::ScheduleNextVsync(TimeStamp aVsyncTimestamp) { + MOZ_ASSERT(IsInSoftwareVsyncThread()); + TimeDuration vsyncRate = GetVsyncRate(); + TimeStamp nextVsync = aVsyncTimestamp + vsyncRate; + TimeDuration delay = nextVsync - TimeStamp::Now(); + if (delay.ToMilliseconds() < 0) { + delay = TimeDuration::FromMilliseconds(0); + nextVsync = TimeStamp::Now(); + } + + TimeStamp outputTime = nextVsync + vsyncRate; + + mCurrentVsyncTask = NewCancelableRunnableMethod<TimeStamp, TimeStamp>( + "SoftwareVsyncSource::NotifyVsync", this, + &SoftwareVsyncSource::NotifyVsync, nextVsync, outputTime); + + RefPtr<Runnable> addrefedTask = mCurrentVsyncTask; + mVsyncThread->message_loop()->PostDelayedTask(addrefedTask.forget(), + delay.ToMilliseconds()); +} + +void SoftwareVsyncSource::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + DisableVsync(); + mVsyncThread->Stop(); + delete mVsyncThread; + mVsyncThread = nullptr; +} + +} // namespace mozilla::gfx diff --git a/gfx/thebes/SoftwareVsyncSource.h b/gfx/thebes/SoftwareVsyncSource.h new file mode 100644 index 0000000000..0a86432db3 --- /dev/null +++ b/gfx/thebes/SoftwareVsyncSource.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef GFX_SOFTWARE_VSYNC_SOURCE_H +#define GFX_SOFTWARE_VSYNC_SOURCE_H + +#include "mozilla/DataMutex.h" +#include "mozilla/Monitor.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TimeStamp.h" +#include "base/thread.h" +#include "nsISupportsImpl.h" +#include "VsyncSource.h" + +namespace mozilla::gfx { + +// Fallback option to use a software timer to mimic vsync. Useful for gtests +// To mimic a hardware vsync thread, we create a dedicated software timer +// vsync thread. +class SoftwareVsyncSource : public VsyncSource { + public: + explicit SoftwareVsyncSource(const TimeDuration& aInitialVsyncRate); + virtual ~SoftwareVsyncSource(); + + void EnableVsync() override; + void DisableVsync() override; + bool IsVsyncEnabled() override; + bool IsInSoftwareVsyncThread(); + void NotifyVsync(const TimeStamp& aVsyncTimestamp, + const TimeStamp& aOutputTimestamp) override; + TimeDuration GetVsyncRate() override; + void ScheduleNextVsync(TimeStamp aVsyncTimestamp); + void Shutdown() override; + + // Can be called on any thread + void SetVsyncRate(const TimeDuration& aNewRate); + + protected: + // Use a chromium thread because nsITimers* fire on the main thread + base::Thread* mVsyncThread; + RefPtr<CancelableRunnable> mCurrentVsyncTask; // only access on vsync thread + bool mVsyncEnabled; // Only access on main thread + + private: + DataMutex<TimeDuration> mVsyncRate; // can be accessed on any thread +}; + +} // namespace mozilla::gfx + +#endif /* GFX_SOFTWARE_VSYNC_SOURCE_H */ diff --git a/gfx/thebes/StandardFonts-android.inc b/gfx/thebes/StandardFonts-android.inc new file mode 100644 index 0000000000..f23960cf6b --- /dev/null +++ b/gfx/thebes/StandardFonts-android.inc @@ -0,0 +1,196 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 on ~all Android devices +static const char* kBaseFonts_Android[] = { + "AndroidClock", + "Carrois Gothic SC", + "Coming Soon", + "Cutive Mono", + "Dancing Script", + "Droid Sans Mono", + "Noto Color Emoji", + "Noto Naskh Arabic", + "Noto Naskh Arabic UI", + "Noto Sans Armenian", + "Noto Sans Bengali", + "Noto Sans Bengali UI", + "Noto Sans Devanagari", + "Noto Sans Devanagari UI", + "Noto Sans Ethiopic", + "Noto Sans Georgian", + "Noto Sans Hebrew", + "Noto Sans Kannada", + "Noto Sans Khmer", + "Noto Sans Khmer UI", + "Noto Sans Lao", + "Noto Sans Lao UI", + "Noto Sans Malayalam", + "Noto Sans Malayalam UI", + "Noto Sans Myanmar", + "Noto Sans Myanmar UI", + "Noto Sans Sinhala", + "Noto Sans Symbols", + "Noto Sans Tamil", + "Noto Sans Tamil UI", + "Noto Sans Telugu", + "Noto Sans Telugu UI", + "Noto Sans Thai", + "Noto Sans Thai UI", + "Noto Serif", + "Roboto" +}; + +// Additional font families that were present on Android versions 5-8 +static const char* kBaseFonts_Android5_8[] = { + "Noto Sans Tibetan" +}; + +// Additional font families that were present on Android versions 9 and higher +static const char* kBaseFonts_Android9_Higher[] = { + "Noto Sans Adlam", + "Noto Sans Ahom", + "Noto Sans Anatolian Hieroglyphs", + "Noto Sans Avestan", + "Noto Sans Balinese", + "Noto Sans Bamum", + "Noto Sans Bassa Vah", + "Noto Sans Batak", + "Noto Sans Bhaiksuki", + "Noto Sans Brahmi", + "Noto Sans Buginese", + "Noto Sans Buhid", + "Noto Sans Canadian Aboriginal", + "Noto Sans Carian", + "Noto Sans Chakma", + "Noto Sans Cham", + "Noto Sans Cherokee", + "Noto Sans CJK JP", + "Noto Sans Coptic", + "Noto Sans Cuneiform", + "Noto Sans Cypriot", + "Noto Sans Deseret", + "Noto Sans Egyptian Hieroglyphs", + "Noto Sans Elbasan", + "Noto Sans Glagolitic", + "Noto Sans Gothic", + "Noto Sans Gujarati", + "Noto Sans Gujarati UI", + "Noto Sans Gurmukhi", + "Noto Sans Gurmukhi UI", + "Noto Sans Hanunoo", + "Noto Sans Hatran", + "Noto Sans Imperial Aramaic", + "Noto Sans Inscriptional Pahlavi", + "Noto Sans Inscriptional Parthian", + "Noto Sans Javanese", + "Noto Sans Kaithi", + "Noto Sans Kayah Li", + "Noto Sans Kharoshthi", + "Noto Sans Lepcha", + "Noto Sans Limbu", + "Noto Sans Linear A", + "Noto Sans Linear B", + "Noto Sans Lisu", + "Noto Sans Lycian", + "Noto Sans Lydian", + "Noto Sans Mandaic", + "Noto Sans Manichaean", + "Noto Sans Marchen", + "Noto Sans Meetei Mayek", + "Noto Sans Meroitic", + "Noto Sans Miao", + "Noto Sans Mongolian", + "Noto Sans Mro", + "Noto Sans Multani", + "Noto Sans Nabataean", + "Noto Sans New Tai Lue", + "Noto Sans Newa", + "Noto Sans NKo", + "Noto Sans Ogham", + "Noto Sans Ol Chiki", + "Noto Sans Old Italic", + "Noto Sans Old North Arabian", + "Noto Sans Old Permic", + "Noto Sans Old Persian", + "Noto Sans Old South Arabian", + "Noto Sans Old Turkic", + "Noto Sans Oriya", + "Noto Sans Oriya UI", + "Noto Sans Osage", + "Noto Sans Osmanya", + "Noto Sans Pahawh Hmong", + "Noto Sans Palmyrene", + "Noto Sans Pau Cin Hau", + "Noto Sans Phags Pa", + "Noto Sans Phoenician", + "Noto Sans Rejang", + "Noto Sans Runic", + "Noto Sans Samaritan", + "Noto Sans Saurashtra", + "Noto Sans Sharada", + "Noto Sans Shavian", + "Noto Sans Sinhala UI", + "Noto Sans Sora Sompeng", + "Noto Sans Sundanese", + "Noto Sans Syloti Nagri", + "Noto Sans Syriac Eastern", + "Noto Sans Syriac Estrangela", + "Noto Sans Syriac Western", + "Noto Sans Tagalog", + "Noto Sans Tagbanwa", + "Noto Sans Tai Le", + "Noto Sans Tai Tham", + "Noto Sans Tai Viet", + "Noto Sans Thaana", + "Noto Sans Tifinagh", + "Noto Sans Ugaritic", + "Noto Sans Vai", + "Noto Sans Yi", + "Noto Serif Armenian", + "Noto Serif Bengali", + "Noto Serif CJK JP", + "Noto Serif Devanagari", + "Noto Serif Ethiopic", + "Noto Serif Georgian", + "Noto Serif Gujarati", + "Noto Serif Gurmukhi", + "Noto Serif Hebrew", + "Noto Serif Kannada", + "Noto Serif Khmer", + "Noto Serif Lao", + "Noto Serif Malayalam", + "Noto Serif Myanmar", + "Noto Serif Sinhala", + "Noto Serif Tamil", + "Noto Serif Telugu", + "Noto Serif Thai" +}; + +// Additional font families that were present on Android versions 9-11 +static const char* kBaseFonts_Android9_11[] = { + "Noto Sans Tibetan" +}; + +// Additional font families that are present on Android versions 12+ +static const char* kBaseFonts_Android12_Higher[] = { + "Noto Color Emoji Flags", + "Noto Sans Grantha", + "Noto Sans Gunjala Gondi", + "Noto Sans Hanifi Rohingya", + "Noto Sans Khojki", + "Noto Sans Masaram Gondi", + "Noto Sans Medefaidrin", + "Noto Sans Modi", + "Noto Sans Soyombo", + "Noto Sans Takri", + "Noto Sans Wancho", + "Noto Sans Warang Citi", + "Noto Serif Dogra", + "Noto Serif Nyiakeng Puachue Hmong", + "Noto Serif Tibetan", + "Noto Serif Yezidi", + "Source Sans Pro", + "Source Sans Pro SemiBold" +};
\ No newline at end of file diff --git a/gfx/thebes/StandardFonts-linux.inc b/gfx/thebes/StandardFonts-linux.inc new file mode 100644 index 0000000000..bf5c285b0d --- /dev/null +++ b/gfx/thebes/StandardFonts-linux.inc @@ -0,0 +1,1065 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 22.04: +static const char* kBaseFonts_Ubuntu_22_04[] = { + "aakar", + "Abyssinica SIL", + "Ani", + "AnjaliOldLipi", + "Bitstream Charter", + "C059", + "Chandas", + "Chilanka", + "Courier 10 Pitch", + "D050000L", + "DejaVu Sans", + "DejaVu Sans Mono", + "DejaVu Serif", + "Dhurjati", + "Droid Sans Fallback", + "Dyuthi", + "FreeMono", + "FreeSans", + "FreeSerif", + "Gargi", + "Garuda", + "Gayathri", + "Gayathri Thin", + "Gidugu", + "Gubbi", + "Gurajada", + "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", + "LakkiReddy", + "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", + "Mallanna", + "Mandali", + "Manjari", + "Manjari Thin", + "Meera", + "Mitra", + "mry_KacstQurn", + "Mukti", + "Nakula", + "NATS", + "Navilu", + "Nimbus Mono PS", + "Nimbus Roman", + "Nimbus Sans", + "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", + "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 HK", + "Noto Serif CJK JP", + "Noto Serif CJK KR", + "Noto Serif CJK SC", + "Noto Serif CJK TC", + "NTR", + "OpenSymbol", + "ori1Uni", + "P052", + "Padauk", + "Padauk Book", + "padmaa", + "padmmaa", + "Pagul", + "Peddana", + "Phetsarath OT", + "Ponnala", + "Pothana2000", + "Potti Sreeramulu", + "Purisa", + "Rachana", + "RaghuMalayalamSans", + "Ramabhadra", + "Ramaraja", + "Rasa", + "Rasa Light", + "Rasa Medium", + "Rasa SemiBold", + "RaviPrakash", + "Rekha", + "Saab", + "Sahadeva", + "Samanata", + "Samyak Devanagari", + "Samyak Gujarati", + "Samyak Malayalam", + "Samyak Tamil", + "Sarai", + "Sawasdee", + "Sree Krushnadevaraya", + "Standard Symbols PS", + "Suranna", + "Suravaram", + "Suruma", + "Syamala Ramana", + "TenaliRamakrishna", + "Tibetan Machine Uni", + "Timmana", + "Tlwg Mono", + "Tlwg Typewriter", + "Tlwg Typist", + "Tlwg Typo", + "Ubuntu", + "Ubuntu Condensed", + "Ubuntu Light", + "Ubuntu Mono", + "Ubuntu Thin", + "Umpush", + "Uroob", + "URW Bookman", + "URW Gothic", + "utkal", + "Vemana2000", + "Waree", + "Yrsa", + "Yrsa Light", + "Yrsa Medium", + "Yrsa SemiBold", + "Z003", + "गार्गी", + "नालिमाटी", + "অনি", + "মুক্তি", +}; + +// Additional font families installed when all languages are enabled via the +// Language Support utility on Ubuntu 22.04: +static const char* kLangFonts_Ubuntu_22_04[] = { + "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", + "Ezra SIL", + "Ezra SIL SR", + "Homa", + "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", + "Nazli", + "Noto Kufi Arabic", + "Noto Looped Lao", + "Noto Looped Lao Bold", + "Noto Looped Lao Regular", + "Noto Looped Lao UI", + "Noto Looped Lao UI Bold", + "Noto Looped Lao UI Regular", + "Noto Looped Thai", + "Noto Looped Thai Bold", + "Noto Looped Thai Regular", + "Noto Looped Thai UI", + "Noto Looped Thai UI Bold", + "Noto Looped Thai UI Regular", + "Noto Music", + "Noto Naskh Arabic", + "Noto Naskh Arabic UI", + "Noto Nastaliq Urdu", + "Noto Rashi Hebrew", + "Noto Sans", + "Noto Sans Adlam", + "Noto Sans Adlam Unjoined", + "Noto Sans AnatoHiero", + "Noto Sans Anatolian Hieroglyphs", + "Noto Sans Arabic", + "Noto Sans Arabic UI", + "Noto Sans Armenian", + "Noto Sans Avestan", + "Noto Sans Balinese", + "Noto Sans Bamum", + "Noto Sans Bassa Vah", + "Noto Sans Batak", + "Noto Sans Bengali", + "Noto Sans Bengali UI", + "Noto Sans Bhaiksuki", + "Noto Sans Brahmi", + "Noto Sans Buginese", + "Noto Sans Buhid", + "Noto Sans CanAborig", + "Noto Sans Canadian Aboriginal", + "Noto Sans Carian", + "Noto Sans CaucAlban", + "Noto Sans Caucasian Albanian", + "Noto Sans Chakma", + "Noto Sans Cham", + "Noto Sans Cherokee", + "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 Sans Coptic", + "Noto Sans Cuneiform", + "Noto Sans Cypriot", + "Noto Sans Deseret", + "Noto Sans Devanagari", + "Noto Sans Devanagari UI", + "Noto Sans Display", + "Noto Sans Duployan", + "Noto Sans EgyptHiero", + "Noto Sans Egyptian Hieroglyphs", + "Noto Sans Elbasan", + "Noto Sans Elymaic", + "Noto Sans Ethiopic", + "Noto Sans Georgian", + "Noto Sans Glagolitic", + "Noto Sans Gothic", + "Noto Sans Grantha", + "Noto Sans Gujarati", + "Noto Sans Gujarati UI", + "Noto Sans Gunjala Gondi", + "Noto Sans Gurmukhi", + "Noto Sans Gurmukhi UI", + "Noto Sans Hanifi Rohingya", + "Noto Sans Hanunoo", + "Noto Sans Hatran", + "Noto Sans Hebrew", + "Noto Sans ImpAramaic", + "Noto Sans Imperial Aramaic", + "Noto Sans Indic Siyaq Numbers", + "Noto Sans Inscriptional Pahlavi", + "Noto Sans Inscriptional Parthian", + "Noto Sans InsPahlavi", + "Noto Sans InsParthi", + "Noto Sans Javanese", + "Noto Sans Kaithi", + "Noto Sans Kannada", + "Noto Sans Kannada UI", + "Noto Sans Kayah Li", + "Noto Sans Kharoshthi", + "Noto Sans Khmer", + "Noto Sans Khmer UI", + "Noto Sans Khojki", + "Noto Sans Khudawadi", + "Noto Sans Lao", + "Noto Sans Lao UI", + "Noto Sans Lepcha", + "Noto Sans Limbu", + "Noto Sans Linear A", + "Noto Sans Linear B", + "Noto Sans Lisu", + "Noto Sans Lycian", + "Noto Sans Lydian", + "Noto Sans Mahajani", + "Noto Sans Malayalam", + "Noto Sans Malayalam UI", + "Noto Sans Mandaic", + "Noto Sans Manichaean", + "Noto Sans Marchen", + "Noto Sans Masaram Gondi", + "Noto Sans Math", + "Noto Sans Mayan Numerals", + "Noto Sans Medefaidrin", + "Noto Sans Meetei Mayek", + "Noto Sans Mende Kikakui", + "Noto Sans Meroitic", + "Noto Sans Miao", + "Noto Sans Modi", + "Noto Sans Mongolian", + "Noto Sans Mro", + "Noto Sans Multani", + "Noto Sans Myanmar", + "Noto Sans Myanmar UI", + "Noto Sans Nabataean", + "Noto Sans New Tai Lue", + "Noto Sans Newa", + "Noto Sans NKo", + "Noto Sans Nushu", + "Noto Sans Ogham", + "Noto Sans Ol Chiki", + "Noto Sans Old Hungarian", + "Noto Sans Old Italic", + "Noto Sans Old North Arabian", + "Noto Sans Old Permic", + "Noto Sans Old Persian", + "Noto Sans Old Sogdian", + "Noto Sans Old South Arabian", + "Noto Sans Old Turkic", + "Noto Sans OldHung", + "Noto Sans OldNorArab", + "Noto Sans OldSouArab", + "Noto Sans Oriya", + "Noto Sans Oriya UI", + "Noto Sans Osage", + "Noto Sans Osmanya", + "Noto Sans Pahawh Hmong", + "Noto Sans Palmyrene", + "Noto Sans Pau Cin Hau", + "Noto Sans PhagsPa", + "Noto Sans Phoenician", + "Noto Sans Psalter Pahlavi", + "Noto Sans PsaPahlavi", + "Noto Sans Rejang", + "Noto Sans Runic", + "Noto Sans Samaritan", + "Noto Sans Saurashtra", + "Noto Sans Sharada", + "Noto Sans Shavian", + "Noto Sans Siddham", + "Noto Sans SignWrit", + "Noto Sans SignWriting", + "Noto Sans Sinhala", + "Noto Sans Sinhala UI", + "Noto Sans Sogdian", + "Noto Sans Sora Sompeng", + "Noto Sans Soyombo", + "Noto Sans Sundanese", + "Noto Sans Syloti Nagri", + "Noto Sans Symbols", + "Noto Sans Symbols2", + "Noto Sans Syriac", + "Noto Sans Tagalog", + "Noto Sans Tagbanwa", + "Noto Sans Tai Le", + "Noto Sans Tai Tham", + "Noto Sans Tai Viet", + "Noto Sans Takri", + "Noto Sans Tamil", + "Noto Sans Tamil Supplement", + "Noto Sans Tamil UI", + "Noto Sans Telugu", + "Noto Sans Telugu UI", + "Noto Sans Thaana", + "Noto Sans Thai", + "Noto Sans Thai UI", + "Noto Sans Tifinagh", + "Noto Sans Tifinagh Adrar", + "Noto Sans Tifinagh Agraw Imazighen", + "Noto Sans Tifinagh Ahaggar", + "Noto Sans Tifinagh Air", + "Noto Sans Tifinagh APT", + "Noto Sans Tifinagh Azawagh", + "Noto Sans Tifinagh Ghat", + "Noto Sans Tifinagh Hawad", + "Noto Sans Tifinagh Rhissa Ixa", + "Noto Sans Tifinagh SIL", + "Noto Sans Tifinagh Tawellemmet", + "Noto Sans Tirhuta", + "Noto Sans Ugaritic", + "Noto Sans Vai", + "Noto Sans Wancho", + "Noto Sans Warang Citi", + "Noto Sans Yi", + "Noto Sans Zanabazar", + "Noto Sans Zanabazar Square", + "Noto Serif", + "Noto Serif Ahom", + "Noto Serif Armenian", + "Noto Serif Balinese", + "Noto Serif Bengali", + "Noto Serif CJK HK Black", + "Noto Serif CJK HK ExtraLight", + "Noto Serif CJK HK Light", + "Noto Serif CJK HK Medium", + "Noto Serif CJK HK SemiBold", + "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", + "Noto Serif Devanagari", + "Noto Serif Display", + "Noto Serif Dogra", + "Noto Serif Ethiopic", + "Noto Serif Georgian", + "Noto Serif Grantha", + "Noto Serif Gujarati", + "Noto Serif Gurmukhi", + "Noto Serif Hebrew", + "Noto Serif Hmong Nyiakeng", + "Noto Serif Kannada", + "Noto Serif Khmer", + "Noto Serif Khojki", + "Noto Serif Lao", + "Noto Serif Malayalam", + "Noto Serif Myanmar", + "Noto Serif Sinhala", + "Noto Serif Tamil", + "Noto Serif Tamil Slanted", + "Noto Serif Tangut", + "Noto Serif Telugu", + "Noto Serif Thai", + "Noto Serif Tibetan", + "Noto Serif Yezidi", + "Noto Traditional Nushu", + "Scheherazade", + "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", +}; + +// Standard fonts from previous LTS version 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", + "מרים", +}; + +// The following Fedora font lists were obtained by installing @fonts and +// fontconfig into a container, then running this command: +// fc-list : family | tr ',' '\n' | sort -fu + +// List of standard font families installed on Fedora 39 Workstation. +static const char* kBaseFonts_Fedora_39[] = { + "Cantarell", + "Jomolhari", + "Madan", + "Noto Color Emoji", + "Noto Naskh Arabic", + "Noto Sans", + "Noto Sans Arabic", + "Noto Sans Armenian", + "Noto Sans Bengali", + "Noto Sans Canadian Aboriginal", + "Noto Sans Canadian Aboriginal Light", + "Noto Sans Cherokee", + "Noto Sans CJK HK", + "Noto Sans CJK JP", + "Noto Sans CJK KR", + "Noto Sans CJK SC", + "Noto Sans CJK TC", + "Noto Sans Devanagari", + "Noto Sans Ethiopic", + "Noto Sans Georgian", + "Noto Sans Gujarati", + "Noto Sans Gurmukhi", + "Noto Sans Hebrew", + "Noto Sans Kannada", + "Noto Sans Khmer", + "Noto Sans Lao", + "Noto Sans Math", + "Noto Sans Mono", + "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 Oriya", + "Noto Sans Sinhala", + "Noto Sans Symbols", + "Noto Sans Symbols 2", + "Noto Sans Tamil", + "Noto Sans Telugu", + "Noto Sans Thaana", + "Noto Sans Thai", + "Noto Serif", + "Noto Serif Armenian", + "Noto Serif Bengali", + "Noto Serif CJK HK", + "Noto Serif CJK JP", + "Noto Serif CJK KR", + "Noto Serif CJK SC", + "Noto Serif CJK TC", + "Noto Serif Devanagari", + "Noto Serif Georgian", + "Noto Serif Gujarati", + "Noto Serif Gurmukhi", + "Noto Serif Hebrew", + "Noto Serif Kannada", + "Noto Serif Khmer", + "Noto Serif Lao", + "Noto Serif Oriya", + "Noto Serif Sinhala", + "Noto Serif Tamil", + "Noto Serif Telugu", + "Noto Serif Thai", + "Nuosu SIL", + "Padauk", + "PakType Naskh Basic", + "RIT Meera New", + "RIT Rachana", + "STIX", + "STIX Two Math", + "STIX Two Text", + "STIX Two Text Medium", + "STIX Two Text SemiBold", + "Vazirmatn", + "ആർഐടി രചന", + "രചന", +}; + +// List of standard font families installed on Fedora 38 Workstation. +static const char* kBaseFonts_Fedora_38[] = { + "Cantarell", + "Jomolhari", + "Liberation Mono", + "Liberation Sans", + "Liberation Serif", + "Lohit Assamese", + "Lohit Bengali", + "Lohit Devanagari", + "Lohit Gujarati", + "Lohit Kannada", + "Lohit Marathi", + "Lohit Odia", + "Lohit Tamil", + "Lohit Telugu", + "Mingzat", + "Noto Color Emoji", + "Noto Naskh Arabic", + "Noto Sans", + "Noto Sans Arabic", + "Noto Sans Armenian", + "Noto Sans Canadian Aboriginal", + "Noto Sans Cherokee", + "Noto Sans CJK HK", + "Noto Sans CJK JP", + "Noto Sans CJK KR", + "Noto Sans CJK SC", + "Noto Sans CJK TC", + "Noto Sans Ethiopic", + "Noto Sans Georgian", + "Noto Sans Gurmukhi", + "Noto Sans Hebrew", + "Noto Sans Khmer", + "Noto Sans Lao", + "Noto Sans Math", + "Noto Sans Mono", + "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", + "Noto Sans Thaana", + "Noto Sans Thai", + "Noto Serif", + "Nuosu SIL", + "Padauk", + "PakType Naskh Basic", + "RIT Meera New", + "STIX", + "STIX Two Math", + "STIX Two Text", + "STIX Two Text Medium", + "STIX Two Text SemiBold", + "Vazirmatn", +}; diff --git a/gfx/thebes/StandardFonts-macos.inc b/gfx/thebes/StandardFonts-macos.inc new file mode 100644 index 0000000000..46342eb3c4 --- /dev/null +++ b/gfx/thebes/StandardFonts-macos.inc @@ -0,0 +1,295 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// List of standard font families installed as part of macOS 10.15 "Catalina" +// from https://developer.apple.com/fonts/system-fonts/ +static const char* kBaseFonts[] = { + "Al Bayan", + "Al Nile", + "Al Tarikh", + "American Typewriter", + "Andale Mono", + "Apple Braille", + "Apple Chancery", + "Apple Color Emoji", + "Apple SD Gothic Neo", + "Apple Symbols", + "AppleGothic", + "AppleMyungjo", + "Arial", + "Arial Black", + "Arial Hebrew", + "Arial Hebrew Scholar", + "Arial Narrow", + "Arial Rounded MT Bold", + "Arial Unicode MS", + "Avenir", + "Avenir Next", + "Ayuthaya", + "Baghdad", + "Bangla MN", + "Bangla Sangam MN", + "Baskerville", + "Beirut", + "Big Caslon", + "Bodoni 72", + "Bodoni 72 Oldstyle", + "Bodoni 72 Smallcaps", + "Bodoni Ornaments", + "Bradley Hand", + "Brush Script MT", + "Chalkboard", + "Chalkboard SE", + "Chalkduster", + "Charter", + "Cochin", + "Comic Sans MS", + "Copperplate", + "Corsiva Hebrew", + "Courier", + "Courier New", + "Damascus", + "DecoType Naskh", + "Devanagari MT", + "Devanagari Sangam MN", + "Didot", + "DIN Alternate", + "DIN Condensed", + "Diwan Kufi", + "Diwan Thuluth", + "Euphemia UCAS", + "Farah", + "Farisi", + "Futura", + "Galvji", + "GB18030 Bitmap", + "Geeza Pro", + "Geneva", + "Georgia", + "Gill Sans", + "Gujarati MT", + "Gujarati Sangam MN", + "Gurmukhi MN", + "Gurmukhi MT", + "Gurmukhi Sangam MN", + "Heiti SC", + "Heiti TC", + "Helvetica", + "Helvetica Neue", + "Hiragino Maru Gothic ProN", + "Hiragino Mincho ProN", + "Hiragino Sans", + "Hiragino Sans GB", + "Hoefler Text", + "Impact", + "InaiMathi", + "ITF Devanagari", + "ITF Devanagari Marathi", + "Kailasa", + "Kannada MN", + "Kannada Sangam MN", + "Kefa", + "Khmer MN", + "Khmer Sangam MN", + "Kohinoor Bangla", + "Kohinoor Devanagari", + "Kohinoor Gujarati", + "Kohinoor Telugu", + "Kokonor", + "Krungthep", + "KufiStandardGK", + "Lao MN", + "Lao Sangam MN", + "Lucida Grande", + "Luminari", + "Malayalam MN", + "Malayalam Sangam MN", + "Marker Felt", + "Menlo", + "Microsoft Sans Serif", + "Mishafi", + "Mishafi Gold", + "Monaco", + "Mshtakan", + "Mukta Mahee", + "Muna", + "Myanmar MN", + "Myanmar Sangam MN", + "Nadeem", + "New Peninim MT", + "Noteworthy", + "Noto Nastaliq Urdu", + "Noto Sans Adlam", + "Noto Sans Armenian", + "Noto Sans Avestan", + "Noto Sans Bamum", + "Noto Sans Bassa Vah", + "Noto Sans Batak", + "Noto Sans Bhaiksuki", + "Noto Sans Brahmi", + "Noto Sans Buginese", + "Noto Sans Buhid", + "Noto Sans Canadian Aboriginal", + "Noto Sans Carian", + "Noto Sans Caucasian Albanian", + "Noto Sans Chakma", + "Noto Sans Cham", + "Noto Sans Coptic", + "Noto Sans Cuneiform", + "Noto Sans Cypriot", + "Noto Sans Duployan", + "Noto Sans Egyptian Hieroglyphs", + "Noto Sans Elbasan", + "Noto Sans Glagolitic", + "Noto Sans Gothic", + "Noto Sans Gunjala Gondi", + "Noto Sans Hanifi Rohingya", + "Noto Sans Hanunoo", + "Noto Sans Hatran", + "Noto Sans Imperial Aramaic", + "Noto Sans Inscriptional Pahlavi", + "Noto Sans Inscriptional Parthian", + "Noto Sans Javanese", + "Noto Sans Kaithi", + "Noto Sans Kannada", + "Noto Sans Kayah Li", + "Noto Sans Kharoshthi", + "Noto Sans Khojki", + "Noto Sans Khudawadi", + "Noto Sans Lepcha", + "Noto Sans Limbu", + "Noto Sans Linear A", + "Noto Sans Linear B", + "Noto Sans Lisu", + "Noto Sans Lycian", + "Noto Sans Lydian", + "Noto Sans Mahajani", + "Noto Sans Mandaic", + "Noto Sans Manichaean", + "Noto Sans Marchen", + "Noto Sans Masaram Gondi", + "Noto Sans Meetei Mayek", + "Noto Sans Mende Kikakui", + "Noto Sans Meroitic", + "Noto Sans Miao", + "Noto Sans Modi", + "Noto Sans Mongolian", + "Noto Sans Mro", + "Noto Sans Multani", + "Noto Sans Myanmar", + "Noto Sans Nabataean", + "Noto Sans New Tai Lue", + "Noto Sans Newa", + "Noto Sans NKo", + "Noto Sans Ol Chiki", + "Noto Sans Old Hungarian", + "Noto Sans Old Italic", + "Noto Sans Old North Arabian", + "Noto Sans Old Permic", + "Noto Sans Old Persian", + "Noto Sans Old South Arabian", + "Noto Sans Old Turkic", + "Noto Sans Oriya", + "Noto Sans Osage", + "Noto Sans Osmanya", + "Noto Sans Pahawh Hmong", + "Noto Sans Palmyrene", + "Noto Sans Pau Cin Hau", + "Noto Sans PhagsPa", + "Noto Sans Phoenician", + "Noto Sans Psalter Pahlavi", + "Noto Sans Rejang", + "Noto Sans Samaritan", + "Noto Sans Saurashtra", + "Noto Sans Sharada", + "Noto Sans Siddham", + "Noto Sans Sora Sompeng", + "Noto Sans Sundanese", + "Noto Sans Syloti Nagri", + "Noto Sans Syriac", + "Noto Sans Tagalog", + "Noto Sans Tagbanwa", + "Noto Sans Tai Le", + "Noto Sans Tai Tham", + "Noto Sans Tai Viet", + "Noto Sans Takri", + "Noto Sans Thaana", + "Noto Sans Tifinagh", + "Noto Sans Tirhuta", + "Noto Sans Ugaritic", + "Noto Sans Vai", + "Noto Sans Wancho", + "Noto Sans Warang Citi", + "Noto Sans Yi", + "Noto Sans Zawgyi", + "Noto Serif Ahom", + "Noto Serif Balinese", + "Noto Serif Hmong Nyiakeng", + "Noto Serif Myanmar", + "Noto Serif Yezidi", + "Optima", + "Oriya MN", + "Oriya Sangam MN", + "Palatino", + "Papyrus", + "Phosphate", + "PingFang HK", + "PingFang SC", + "PingFang TC", + "Plantagenet Cherokee", + "PT Mono", + "PT Sans", + "PT Sans Caption", + "PT Sans Narrow", + "PT Serif", + "PT Serif Caption", + "Raanana", + "Rockwell", + "Sana", + "Sathu", + "Savoye LET", + "Shree Devanagari 714", + "SignPainter", + "Silom", + "Sinhala MN", + "Sinhala Sangam MN", + "Skia", + "Snell Roundhand", + "Songti SC", + "Songti TC", + "STIXGeneral", + "STIXIntegralsD", + "STIXIntegralsSm", + "STIXIntegralsUp", + "STIXIntegralsUpD", + "STIXIntegralsUpSm", + "STIXNonUnicode", + "STIXSizeFiveSym", + "STIXSizeFourSym", + "STIXSizeOneSym", + "STIXSizeThreeSym", + "STIXSizeTwoSym", + "STIXVariants", + "STSong", + "Sukhumvit Set", + "Symbol", + "Tahoma", + "Tamil MN", + "Tamil Sangam MN", + "Telugu MN", + "Telugu Sangam MN", + "Thonburi", + "Times", + "Times New Roman", + "Trattatello", + "Trebuchet MS", + "Verdana", + "Waseem", + "Webdings", + "Wingdings", + "Wingdings 2", + "Wingdings 3", + "Zapf Dingbats", + "Zapfino", +}; diff --git a/gfx/thebes/StandardFonts-win10.inc b/gfx/thebes/StandardFonts-win10.inc new file mode 100644 index 0000000000..c929992ed8 --- /dev/null +++ b/gfx/thebes/StandardFonts-win10.inc @@ -0,0 +1,202 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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", // 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 + "Miriam Fixed", // 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 N-B", // Japanese Supplemental Fonts + "UD Digi Kyokasho N-R", // Japanese Supplemental Fonts + "UD Digi Kyokasho NK-B", // Japanese Supplemental Fonts + "UD Digi Kyokasho NK-R", // Japanese Supplemental Fonts + "UD Digi Kyokasho NP-B", // Japanese Supplemental Fonts + "UD Digi Kyokasho NP-R", // Japanese Supplemental Fonts + "Urdu Typesetting", // Arabic Script Supplemental Fonts + "Utsaah", // Devanagari Supplemental Fonts + "Vani", // Telugu Supplemental Fonts + "Vijaya", // Tamil Supplemental Fonts + "Vrinda", // Bangla Script Supplemental Fonts + "Yu Mincho", // Japanese Supplemental Fonts +// Latin/Greek/Cyrillic scripts are already well-supported by the base fonts, +// so we do not include these even when the LangPack collection is enabled. +// "Arial Nova", // Pan-European Supplemental Fonts - EXCLUDED +// "Georgia Pro", // Pan-European Supplemental Fonts - EXCLUDED +// "Gill Sans Nova", // Pan-European Supplemental Fonts - EXCLUDED +// "Neue Haas Grotesk Text Pro", // Pan-European Supplemental Fonts - EXCLUDED +// "Rockwell Nova", // Pan-European Supplemental Fonts - EXCLUDED +// "Verdana Pro", // Pan-European Supplemental Fonts - EXCLUDED +}; diff --git a/gfx/thebes/ThebesRLBox.h b/gfx/thebes/ThebesRLBox.h new file mode 100644 index 0000000000..2f4188a962 --- /dev/null +++ b/gfx/thebes/ThebesRLBox.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef THEBES_RLBOX +#define THEBES_RLBOX + +#include "ThebesRLBoxTypes.h" + +// Load general firefox configuration of RLBox +#include "mozilla/rlbox/rlbox_config.h" + +#ifdef MOZ_WASM_SANDBOXING_GRAPHITE +// Include the generated header file so that we are able to resolve the symbols +// in the wasm binary +# include "rlbox.wasm.h" +# define RLBOX_USE_STATIC_CALLS() rlbox_wasm2c_sandbox_lookup_symbol +# include "mozilla/rlbox/rlbox_wasm2c_sandbox.hpp" +#else +# define RLBOX_USE_STATIC_CALLS() rlbox_noop_sandbox_lookup_symbol +# include "mozilla/rlbox/rlbox_noop_sandbox.hpp" +#endif + +#include "mozilla/rlbox/rlbox.hpp" + +// Struct info needed for rlbox_load_structs_from_library +#include "graphite2/Font.h" +#include "graphite2/GraphiteExtra.h" +#include "graphite2/Segment.h" + +#include "graphite2/GraphiteStructsForRLBox.h" +rlbox_load_structs_from_library(graphite); + +#endif diff --git a/gfx/thebes/ThebesRLBoxTypes.h b/gfx/thebes/ThebesRLBoxTypes.h new file mode 100644 index 0000000000..83a2f62df4 --- /dev/null +++ b/gfx/thebes/ThebesRLBoxTypes.h @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef THEBES_RLBOX_TYPES +#define THEBES_RLBOX_TYPES + +#include "mozilla/rlbox/rlbox_types.hpp" + +#ifdef MOZ_WASM_SANDBOXING_GRAPHITE +RLBOX_DEFINE_BASE_TYPES_FOR(gr, wasm2c) +#else +RLBOX_DEFINE_BASE_TYPES_FOR(gr, noop) +#endif + +#endif diff --git a/gfx/thebes/VsyncSource.cpp b/gfx/thebes/VsyncSource.cpp new file mode 100644 index 0000000000..d7cfb339aa --- /dev/null +++ b/gfx/thebes/VsyncSource.cpp @@ -0,0 +1,160 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "VsyncSource.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "mozilla/VsyncDispatcher.h" +#include "MainThreadUtils.h" +#include "gfxPlatform.h" + +#ifdef MOZ_WAYLAND +# include "WaylandVsyncSource.h" +#endif + +namespace mozilla { +namespace gfx { + +VsyncSource::VsyncSource() : mState("VsyncSource::State") { + MOZ_ASSERT(NS_IsMainThread()); +} + +VsyncSource::~VsyncSource() { MOZ_ASSERT(NS_IsMainThread()); } + +// Called on the vsync thread +void VsyncSource::NotifyVsync(const TimeStamp& aVsyncTimestamp, + const TimeStamp& aOutputTimestamp) { + VsyncId vsyncId; + nsTArray<DispatcherRefWithCount> dispatchers; + + { + auto state = mState.Lock(); + vsyncId = state->mVsyncId.Next(); + dispatchers = state->mDispatchers.Clone(); + state->mVsyncId = vsyncId; + } + + // Notify our listeners, outside of the lock. + const VsyncEvent event(vsyncId, aVsyncTimestamp, aOutputTimestamp); + for (const auto& dispatcher : dispatchers) { + dispatcher.mDispatcher->NotifyVsync(event); + } +} + +void VsyncSource::AddVsyncDispatcher(VsyncDispatcher* aVsyncDispatcher) { + MOZ_ASSERT(aVsyncDispatcher); + { + auto state = mState.Lock(); + + // Find the dispatcher in mDispatchers. If it is already present, increment + // the count. If not, add it with a count of 1. + bool found = false; + for (auto& dispatcherRefWithCount : state->mDispatchers) { + if (dispatcherRefWithCount.mDispatcher == aVsyncDispatcher) { + dispatcherRefWithCount.mCount++; + found = true; + break; + } + } + if (!found) { + state->mDispatchers.AppendElement( + DispatcherRefWithCount{aVsyncDispatcher, 1}); + } + } + + UpdateVsyncStatus(); +} + +void VsyncSource::RemoveVsyncDispatcher(VsyncDispatcher* aVsyncDispatcher) { + MOZ_ASSERT(aVsyncDispatcher); + { + auto state = mState.Lock(); + + // Find the dispatcher in mDispatchers. If found, decrement the count. + // If the count becomes zero, remove it from mDispatchers. + for (auto it = state->mDispatchers.begin(); it != state->mDispatchers.end(); + ++it) { + if (it->mDispatcher == aVsyncDispatcher) { + it->mCount--; + if (it->mCount == 0) { + state->mDispatchers.RemoveElementAt(it); + } + break; + } + } + + // In the future we should probably MOZ_RELEASE_ASSERT here that we don't + // try to remove a dispatcher which isn't in mDispatchers. + } + + UpdateVsyncStatus(); +} + +// This is the base class implementation. Subclasses override this method. +TimeDuration VsyncSource::GetVsyncRate() { + // If hardware queries fail / are unsupported, we have to just guess. + return TimeDuration::FromMilliseconds(1000.0 / 60.0); +} + +void VsyncSource::UpdateVsyncStatus() { + if (!NS_IsMainThread()) { + NS_DispatchToMainThread(NS_NewRunnableFunction( + "VsyncSource::UpdateVsyncStatus", + [self = RefPtr{this}] { self->UpdateVsyncStatus(); })); + return; + } + + MOZ_ASSERT(NS_IsMainThread()); + // WARNING: This function SHOULD NOT BE CALLED WHILE HOLDING LOCKS + // NotifyVsync grabs a lock to dispatch vsync events + // When disabling vsync, we wait for the underlying thread to stop on some + // platforms We can deadlock if we wait for the underlying vsync thread to + // stop while the vsync thread is in NotifyVsync. + bool enableVsync = false; + { // scope lock + auto state = mState.Lock(); + enableVsync = !state->mDispatchers.IsEmpty(); + } + + if (enableVsync) { + EnableVsync(); + } else { + DisableVsync(); + } + + if (IsVsyncEnabled() != enableVsync) { + NS_WARNING("Vsync status did not change."); + } +} + +// static +Maybe<TimeDuration> VsyncSource::GetFastestVsyncRate() { + Maybe<TimeDuration> retVal; + if (!gfxPlatform::Initialized()) { + return retVal; + } + + RefPtr<VsyncDispatcher> vsyncDispatcher = + gfxPlatform::GetPlatform()->GetGlobalVsyncDispatcher(); + RefPtr<VsyncSource> vsyncSource = vsyncDispatcher->GetCurrentVsyncSource(); + if (vsyncSource->IsVsyncEnabled()) { + retVal.emplace(vsyncSource->GetVsyncRate()); +#ifdef MOZ_WAYLAND + Maybe<TimeDuration> waylandRate = WaylandVsyncSource::GetFastestVsyncRate(); + if (waylandRate) { + if (!retVal) { + retVal.emplace(*waylandRate); + } else if (*waylandRate < *retVal) { + retVal = waylandRate; + } + } +#endif + } + + return retVal; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/thebes/VsyncSource.h b/gfx/thebes/VsyncSource.h new file mode 100644 index 0000000000..948b6ada9c --- /dev/null +++ b/gfx/thebes/VsyncSource.h @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_VSYNCSOURCE_H +#define GFX_VSYNCSOURCE_H + +#include "nsTArray.h" +#include "mozilla/DataMutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Maybe.h" +#include "mozilla/Mutex.h" +#include "mozilla/TimeStamp.h" +#include "nsISupportsImpl.h" +#include "mozilla/layers/LayersTypes.h" + +namespace mozilla { +class VsyncDispatcher; +class VsyncObserver; +struct VsyncEvent; + +class VsyncIdType {}; +typedef layers::BaseTransactionId<VsyncIdType> VsyncId; + +namespace gfx { + +// Controls how and when to enable/disable vsync. Lives as long as the +// gfxPlatform does on the parent process +class VsyncSource { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VsyncSource) + + typedef mozilla::VsyncDispatcher VsyncDispatcher; + + public: + VsyncSource(); + + // Notified when this display's vsync callback occurs, on the vsync thread + // Different platforms give different aVsyncTimestamp values. + // macOS: TimeStamp::Now() or the output time of the previous vsync + // callback, whichever is older. + // Windows: It's messy, see gfxWindowsPlatform. + // Android: TODO + // + // @param aVsyncTimestamp The time of the Vsync that just occured. Needs to + // be at or before the time of the NotifyVsync call. + // @param aOutputTimestamp The estimated timestamp at which drawing will + // appear on the screen, if the drawing happens within a certain + // (unknown) budget. Useful for Audio/Video sync. On platforms where + // this timestamp is provided by the system (macOS), it is a much more + // stable and consistent timing source than the time at which the vsync + // callback is called. + virtual void NotifyVsync(const TimeStamp& aVsyncTimestamp, + const TimeStamp& aOutputTimestamp); + + // Can be called on any thread. + // Adding the same dispatcher multiple times will increment a count. + // This means that the sequence "Add, Add, Remove" has the same behavior as + // "Add, Remove, Add". + void AddVsyncDispatcher(VsyncDispatcher* aDispatcher); + void RemoveVsyncDispatcher(VsyncDispatcher* aDispatcher); + + virtual TimeDuration GetVsyncRate(); + + // These should all only be called on the main thread + virtual void EnableVsync() = 0; + virtual void DisableVsync() = 0; + virtual bool IsVsyncEnabled() = 0; + virtual void Shutdown() = 0; + + // Returns the rate of the fastest enabled VsyncSource or Nothing(). + static Maybe<TimeDuration> GetFastestVsyncRate(); + + protected: + virtual ~VsyncSource(); + + private: + // Can be called on any thread + void UpdateVsyncStatus(); + + struct DispatcherRefWithCount { + // The dispatcher. + RefPtr<VsyncDispatcher> mDispatcher; + // The number of add calls minus the number of remove calls for this + // dispatcher. Should always be > 0 as long as this dispatcher is in + // mDispatchers. + size_t mCount = 0; + }; + + struct State { + // The set of VsyncDispatchers which are registered with this source. + // At the moment, the length of this array is always zero or one. + // The ability to register multiple dispatchers is not used yet; it is + // intended for when we have one dispatcher per widget. + nsTArray<DispatcherRefWithCount> mDispatchers; + + // The vsync ID which we used for the last vsync event. + VsyncId mVsyncId; + }; + + DataMutex<State> mState; +}; + +} // namespace gfx + +struct VsyncEvent { + VsyncId mId; + TimeStamp mTime; + TimeStamp mOutputTime; // estimate + + VsyncEvent(const VsyncId& aId, const TimeStamp& aVsyncTime, + const TimeStamp& aOutputTime) + : mId(aId), mTime(aVsyncTime), mOutputTime(aOutputTime) {} + VsyncEvent() = default; +}; + +} // namespace mozilla + +#endif /* GFX_VSYNCSOURCE_H */ diff --git a/gfx/thebes/XlibDisplay.cpp b/gfx/thebes/XlibDisplay.cpp new file mode 100644 index 0000000000..3bae50194e --- /dev/null +++ b/gfx/thebes/XlibDisplay.cpp @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "XlibDisplay.h" + +#include "mozilla/Assertions.h" + +namespace mozilla::gfx { + +XlibDisplay::XlibDisplay(Display* aDisplay, bool aOwned) + : mDisplay(aDisplay), mOwned(aOwned) { + MOZ_ASSERT(mDisplay); +} + +XlibDisplay::~XlibDisplay() { + if (mOwned) { + XCloseDisplay(mDisplay); + } +} + +/* static */ +std::shared_ptr<XlibDisplay> XlibDisplay::Borrow(Display* aDisplay) { + if (!aDisplay) { + return nullptr; + } + return std::shared_ptr<XlibDisplay>(new XlibDisplay(aDisplay, false)); +} + +/* static */ +std::shared_ptr<XlibDisplay> XlibDisplay::Open(const char* aDisplayName) { + Display* disp = XOpenDisplay(aDisplayName); + if (!disp) { + return nullptr; + } + return std::shared_ptr<XlibDisplay>(new XlibDisplay(disp, true)); +} + +} // namespace mozilla::gfx diff --git a/gfx/thebes/XlibDisplay.h b/gfx/thebes/XlibDisplay.h new file mode 100644 index 0000000000..0c0f8413a4 --- /dev/null +++ b/gfx/thebes/XlibDisplay.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_XLIBDISPLAY_H +#define GFX_XLIBDISPLAY_H + +#include <X11/Xlib.h> +#include "X11UndefineNone.h" + +#include <memory> + +namespace mozilla::gfx { + +// Represents an X11 display connection which may be either borrowed +// (e.g., from GTK) or owned; in the latter case it will be closed +// with this object becomes unreferenced. See also the `EglDisplay` +// class. +class XlibDisplay final { + public: + ~XlibDisplay(); + + // Explicit `->get()` may be needed with some `Xlib.h` macros that + // expand to C-style pointer casts. + Display* get() const { return mDisplay; } + operator Display*() const { return mDisplay; } + + static std::shared_ptr<XlibDisplay> Borrow(Display* aDisplay); + static std::shared_ptr<XlibDisplay> Open(const char* aDisplayName); + + private: + Display* const mDisplay; + bool const mOwned; + + XlibDisplay(Display*, bool); +}; + +} // namespace mozilla::gfx + +#endif // GFX_XLIBDISPLAY_H diff --git a/gfx/thebes/cairo-xlib-utils.h b/gfx/thebes/cairo-xlib-utils.h new file mode 100644 index 0000000000..0125ff12cb --- /dev/null +++ b/gfx/thebes/cairo-xlib-utils.h @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CAIROXLIBUTILS_H_ +#define CAIROXLIBUTILS_H_ + +#include "cairo.h" +#include <X11/Xlib.h> + +CAIRO_BEGIN_DECLS + +/** + * This callback encapsulates Xlib-based rendering. We assume that the + * execution of the callback is equivalent to compositing some RGBA image of + * size (bounds_width, bounds_height) onto the drawable at offset (offset_x, + * offset_y), clipped to the union of the clip_rects if num_rects is greater + * than zero. This includes the assumption that the same RGBA image + * is composited if you call the callback multiple times with the same closure, + * display and visual during a single cairo_draw_with_xlib call. + * + * @return True on success, False on non-recoverable error + */ +typedef cairo_bool_t (*cairo_xlib_drawing_callback)( + void* closure, Screen* screen, Drawable drawable, Visual* visual, + short offset_x, short offset_y, XRectangle* clip_rects, + unsigned int num_rects); + +/** + * This structure captures the result of the native drawing, in case the + * caller may wish to reapply the drawing efficiently later. + */ +typedef struct { + cairo_surface_t* surface; + cairo_bool_t uniform_alpha; + cairo_bool_t uniform_color; + double alpha; /* valid only if uniform_alpha is TRUE */ + double r, g, b; /* valid only if uniform_color is TRUE */ +} cairo_xlib_drawing_result_t; + +/** + * This type specifies whether the native drawing callback draws all pixels + * in its bounds opaquely, independent of the contents of the target drawable, + * or whether it leaves pixels transparent/translucent or depends on the + * existing contents of the target drawable in some way. + */ +typedef enum _cairo_xlib_drawing_opacity { + CAIRO_XLIB_DRAWING_OPAQUE, + CAIRO_XLIB_DRAWING_TRANSPARENT +} cairo_xlib_drawing_opacity_t; + +/** + * This type encodes the capabilities of the native drawing callback. + * + * If CAIRO_XLIB_DRAWING_SUPPORTS_OFFSET is set, 'offset_x' and 'offset_y' + * can be nonzero in the call to the callback; otherwise they will be zero. + * + * If CAIRO_XLIB_DRAWING_SUPPORTS_CLIP_RECT is set, then 'num_rects' can be + * zero or one in the call to the callback. If + * CAIRO_XLIB_DRAWING_SUPPORTS_CLIP_LIST is set, then 'num_rects' can be + * anything in the call to the callback. Otherwise 'num_rects' will be zero. + * Do not set both of these values. + * + * If CAIRO_XLIB_DRAWING_SUPPORTS_ALTERNATE_SCREEN is set, then 'screen' can + * be any screen on any display, otherwise it will be the default screen of + * the display passed into cairo_draw_with_xlib. + * + * If CAIRO_XLIB_DRAWING_SUPPORTS_NONDEFAULT_VISUAL is set, then 'visual' can be + * any visual, otherwise it will be equal to + * DefaultVisualOfScreen (screen). + */ +typedef enum { + CAIRO_XLIB_DRAWING_SUPPORTS_OFFSET = 0x01, + CAIRO_XLIB_DRAWING_SUPPORTS_CLIP_RECT = 0x02, + CAIRO_XLIB_DRAWING_SUPPORTS_CLIP_LIST = 0x04, + CAIRO_XLIB_DRAWING_SUPPORTS_ALTERNATE_SCREEN = 0x08, + CAIRO_XLIB_DRAWING_SUPPORTS_NONDEFAULT_VISUAL = 0x10 +} cairo_xlib_drawing_support_t; + +/** + * Draw Xlib output into any cairo context. All cairo transforms and effects + * are honored, including the current operator. This is equivalent to a + * cairo_set_source_surface and then cairo_paint. + * @param cr the context to draw into + * @param callback the code to perform Xlib rendering + * @param closure associated data + * @param dpy an X display to use in case the cairo context has no associated X + * display + * @param width the width of the putative image drawn by the callback + * @param height the height of the putative image drawn by the callback + * @param is_opaque set to CAIRO_XLIB_DRAWING_IS_OPAQUE to indicate + * that all alpha values of the putative image will be 1.0; the pixels drawn + * into the Drawable must not depend on the prior contents of the Drawable in + * any way + * @param capabilities the capabilities of the callback as described above. + * @param result if non-NULL, we *may* fill in the struct with information about + * the rendered image. 'surface' may be filled in with a surface representing + * the image, similar to the target of 'cr'. If 'uniform_alpha' is True then + * every pixel of the image has the same alpha value 'alpha'. If + * 'uniform_color' is True then every pixel of the image has the same RGB + * color (r, g, b). If the image has uniform color and alpha (or alpha is zero, + * in which case the color is always uniform) then we won't bother returning + * a surface for it. + */ +void cairo_draw_with_xlib(cairo_t* cr, cairo_xlib_drawing_callback callback, + void* closure, Display* dpy, unsigned int width, + unsigned int height, + cairo_xlib_drawing_opacity_t is_opaque, + cairo_xlib_drawing_support_t capabilities, + cairo_xlib_drawing_result_t* result); + +CAIRO_END_DECLS + +#endif /*CAIROXLIBUTILS_H_*/ diff --git a/gfx/thebes/d3dkmtQueryStatistics.h b/gfx/thebes/d3dkmtQueryStatistics.h new file mode 100644 index 0000000000..58ce2cd155 --- /dev/null +++ b/gfx/thebes/d3dkmtQueryStatistics.h @@ -0,0 +1,150 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +/* This file is based on a header file that was briefly seen in the + * Windows 8 RC SDK. The work for this file itself was based on the one in + * ProcessHacker at + * http://processhacker.svn.sourceforge.net/viewvc/processhacker/2.x/trunk/plugins/ExtendedTools/d3dkmt.h?revision=4758&view=markup + * For more details see Mozilla Bug 689870. + * [Bug 917496 indicates that some of these structs may not match reality, and + * therefore should not be trusted. See the reference to bug 917496 in + * gfxWindowsPlatform.cpp.] + */ + +typedef struct _D3DKMTQS_COUNTER { + ULONG Count; + ULONGLONG Bytes; +} D3DKMTQS_COUNTER; + +typedef struct _D3DKMTQS_ADAPTER_INFO { + ULONG NbSegments; + ULONG NodeCount; + + ULONG Filler[3]; + ULONGLONG Filler2[2]; // Assumed sizeof(LONGLONG) = sizeof(ULONGLONG) + struct { + ULONG Filler[14]; + } Filler_RDMAB; + struct { + ULONG Filler[9]; + } Filler_R; + struct { + ULONG Filler[4]; + D3DKMTQS_COUNTER Filler2; + } Filler_P; + struct { + D3DKMTQS_COUNTER Filler[16]; + ULONG Filler2[2]; + } Filler_PF; + struct { + ULONGLONG Filler[8]; + } Filler_PT; + struct { + ULONG Filler[2]; + } Filler_SR; + struct { + ULONG Filler[7]; + } Filler_L; + struct { + D3DKMTQS_COUNTER Filler[7]; + } Filler_A; + struct { + D3DKMTQS_COUNTER Filler[4]; + } Filler_T; + ULONG64 Reserved[8]; +} D3DKMTQS_ADAPTER_INFO; + +typedef struct _D3DKMTQS_SEGMENT_INFO { + ULONGLONG Filler[3]; + struct { + ULONGLONG Filler; + ULONG Filler2[2]; + } Filler_M; + + ULONG Aperture; + + ULONGLONG Filler3[5]; + ULONG64 Filler4[8]; +} D3DKMTQS_SEGMENT_INFO; + +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 { + ULONGLONG BytesCommitted; + ULONGLONG Filler[2]; + ULONG Filler2; + struct { + ULONG Filler; + D3DKMTQS_COUNTER Filler2[6]; + ULONGLONG Filler3; + } Filler3; + struct { + ULONGLONG Filler; + } Filler4; + ULONG64 Reserved[8]; +} D3DKMTQS_PROCESS_SEGMENT_INFO; + +typedef struct _D3DKMTQS_PROCESS_NODE_INFO { + LARGE_INTEGER RunningTime; // 100ns + ULONG ContextSwitch; + ULONG PreemptionStatistics[16]; + ULONG PacketStatistics[32]; + ULONG64 Reserved[8]; +} D3DKMTQS_PROCESS_NODE_INFO; + +typedef enum _D3DKMTQS_TYPE { + D3DKMTQS_ADAPTER = 0, + D3DKMTQS_PROCESS = 1, + D3DKMTQS_SEGMENT = 3, + D3DKMTQS_PROCESS_SEGMENT = 4, + D3DKMTQS_PROCESS_NODE = 6, +} D3DKMTQS_TYPE; + +typedef union _D3DKMTQS_RESULT { + D3DKMTQS_ADAPTER_INFO AdapterInfo; + D3DKMTQS_SEGMENT_INFO SegmentInfo; + D3DKMTQS_PROCESS_INFO ProcessInfo; + D3DKMTQS_PROCESS_SEGMENT_INFO ProcessSegmentInfo; + D3DKMTQS_PROCESS_NODE_INFO ProcessNodeInformation; +} D3DKMTQS_RESULT; + +typedef struct _D3DKMTQS_QUERY_SEGMENT { + ULONG SegmentId; +} D3DKMTQS_QUERY_SEGMENT; + +typedef struct _D3DKMTQS_QUERY_NODE { + ULONG NodeId; +} D3DKMTQS_QUERY_NODE; + +typedef struct _D3DKMTQS { + D3DKMTQS_TYPE Type; + LUID AdapterLuid; + HANDLE hProcess; + D3DKMTQS_RESULT QueryResult; + + union { + D3DKMTQS_QUERY_SEGMENT QuerySegment; + D3DKMTQS_QUERY_SEGMENT QueryProcessSegment; + D3DKMTQS_QUERY_NODE QueryProcessNode; + }; +} D3DKMTQS; + +extern "C" { +typedef __checkReturn NTSTATUS(APIENTRY* PFND3DKMTQS)(const D3DKMTQS*); +} diff --git a/gfx/thebes/genLanguageTagList.pl b/gfx/thebes/genLanguageTagList.pl new file mode 100644 index 0000000000..0fa6c65f82 --- /dev/null +++ b/gfx/thebes/genLanguageTagList.pl @@ -0,0 +1,86 @@ +#!/usr/bin/env perl + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# This tool is used to prepare a list of valid language subtags from the +# IANA registry (http://www.iana.org/assignments/language-subtag-registry). + +# Run as +# +# perl genLanguageTagList.pl language-subtag-registry > gfxLanguageTagList.cpp +# +# where language-subtag-registry is a copy of the IANA registry file. + +use strict; + +my $timestamp = gmtime(); +print <<__END; +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Derived from the IANA language subtag registry by genLanguageTagList.pl. + * + * Created on $timestamp. + * + * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * * + */ + +__END + +my $isLanguage = 0; + +while (<>) { + # strip leading/trailing whitespace, if any + chomp; + s/^\s+//; + s/\s+$//; + + # assume File-Date precedes the actual list; + # record the date, and begin assignment to an array of valid tags + if (m/^File-Date:\s*(.+)$/) { + print "// Based on IANA registry dated $1\n\n"; + print "static const uint32_t sLanguageTagList[] = {"; + next; + } + + if (m/^%%/) { + $isLanguage = 0; + next; + } + + # we only care about records of type 'language' + if (m/^Type:\s*(.+)$/) { + $isLanguage = ($1 eq 'language'); + next; + } + + # append the tag to our string, with ";" as a delimiter + if ($isLanguage && m/^Subtag:\s*([a-z]{2,3})\s*$/) { + my $tagstr = $1; + print "\n TRUETYPE_TAG(", + join(",", map { $_ eq " " ? " 0 " : "'" . $_ . "'" } split(//, substr($tagstr . " ", 0, 4))), + "), // ", $tagstr; + next; + } + + if ($isLanguage && m/^Description:\s*(.+)$/) { + print " = $1"; + $isLanguage = 0; # only print first Description field + next; + } +} + +# at end of file, terminate our assignment to the array of tags +print <<__END; + + 0x0 // end of language code list +}; + +/* + * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * * + */ +__END diff --git a/gfx/thebes/gencjkcisvs.py b/gfx/thebes/gencjkcisvs.py new file mode 100644 index 0000000000..6a28e7d492 --- /dev/null +++ b/gfx/thebes/gencjkcisvs.py @@ -0,0 +1,88 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +import os.path +import re +import sys + +f = open(sys.argv[1] if len(sys.argv) > 1 else "StandardizedVariants.txt") + +line = f.readline() +m = re.compile("^# (StandardizedVariants(-\d+(\.\d+)*)?\.txt)").search(line) +fileversion = m.group(1) +vsdict = {} +r = re.compile( + "^([0-9A-F]{4,6}) (FE0[0-9A-F]); CJK COMPATIBILITY IDEOGRAPH-([0-9A-F]{4,6});" +) +while True: + line = f.readline() + if not line: + break + if "CJK COMPATIBILITY IDEOGRAPH-" not in line: + continue + + m = r.search(line) + unified = int(m.group(1), 16) + vs = int(m.group(2), 16) + compat = int(m.group(3), 16) + + if vs not in vsdict: + vsdict[vs] = {} + vsdict[vs][unified] = compat + +f.close + +offsets = [] +length = 10 + 11 * len(vsdict) +for k, mappings in sorted(vsdict.items()): + offsets.append(length) + length += 4 + 5 * len(mappings) + +f = open(sys.argv[2] if len(sys.argv) > 2 else "CJKCompatSVS.cpp", "wb") +f.write( + """// Generated by %s. Do not edit. + +#include <stdint.h> + +#define U16(v) (((v) >> 8) & 0xFF), ((v) & 0xFF) +#define U24(v) (((v) >> 16) & 0xFF), (((v) >> 8) & 0xFF), ((v) & 0xFF) +#define U32(v) (((v) >> 24) & 0xFF), (((v) >> 16) & 0xFF), (((v) >> 8) & 0xFF), ((v) & 0xFF) +#define GLYPH(v) U16(v >= 0x2F800 ? (v) - (0x2F800 - 0xFB00) : (v)) + +// Fallback mappings for CJK Compatibility Ideographs Standardized Variants +// taken from %s. +// Using OpenType format 14 cmap subtable structure to reuse the lookup code +// for fonts. The glyphID field is used to store the corresponding codepoints +// CJK Compatibility Ideographs. To fit codepoints into the 16-bit glyphID +// field, CJK Compatibility Ideographs Supplement (U+2F800..U+2FA1F) will be +// mapped to 0xFB00..0xFD1F. +extern const uint8_t sCJKCompatSVSTable[] = { +""" + % (os.path.basename(sys.argv[0]), fileversion) +) +f.write(" U16(14), // format\n") +f.write(" U32(%d), // length\n" % length) +f.write(" U32(%d), // numVarSelectorRecords\n" % len(vsdict)) +for i, k in enumerate(sorted(vsdict.keys())): + f.write( + " U24(0x%04X), U32(0), U32(%d), // varSelectorRecord[%d]\n" + % (k, offsets[i], i) + ) +for k, mappings in sorted(vsdict.items()): + f.write(" // 0x%04X\n" % k) + f.write(" U32(%d), // numUVSMappings\n" % len(mappings)) + for unified, compat in sorted(mappings.items()): + f.write(" U24(0x%04X), GLYPH(0x%04X),\n" % (unified, compat)) +f.write( + """}; + +#undef U16 +#undef U24 +#undef U32 +#undef GLYPH + +static_assert(sizeof sCJKCompatSVSTable == %d, "Table generator has a bug."); +""" + % length +) diff --git a/gfx/thebes/gfx2DGlue.h b/gfx/thebes/gfx2DGlue.h new file mode 100644 index 0000000000..578d742502 --- /dev/null +++ b/gfx/thebes/gfx2DGlue.h @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_2D_GLUE_H +#define GFX_2D_GLUE_H + +#include "gfxMatrix.h" +#include "gfxPoint.h" +#include "gfxRect.h" +#include "gfxTypes.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/gfx/Rect.h" +#include "mozilla/gfx/Types.h" + +namespace mozilla { +namespace gfx { + +inline Rect ToRect(const gfxRect& aRect) { + return Rect(Float(aRect.X()), Float(aRect.Y()), Float(aRect.Width()), + Float(aRect.Height())); +} + +inline RectDouble ToRectDouble(const gfxRect& aRect) { + return RectDouble(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); +} + +inline Matrix ToMatrix(const gfxMatrix& aMatrix) { + return Matrix(Float(aMatrix._11), Float(aMatrix._12), Float(aMatrix._21), + Float(aMatrix._22), Float(aMatrix._31), Float(aMatrix._32)); +} + +inline gfxMatrix ThebesMatrix(const Matrix& aMatrix) { + return gfxMatrix(aMatrix._11, aMatrix._12, aMatrix._21, aMatrix._22, + aMatrix._31, aMatrix._32); +} + +inline Point ToPoint(const gfxPoint& aPoint) { + return Point(Float(aPoint.x), Float(aPoint.y)); +} + +inline Size ToSize(const gfxSize& aSize) { + return Size(Float(aSize.width), Float(aSize.height)); +} + +inline gfxPoint ThebesPoint(const Point& aPoint) { + return gfxPoint(aPoint.x, aPoint.y); +} + +inline gfxSize ThebesSize(const Size& aSize) { + return gfxSize(aSize.width, aSize.height); +} + +inline gfxRect ThebesRect(const Rect& aRect) { + return gfxRect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); +} + +inline gfxRect ThebesRect(const IntRect& aRect) { + return gfxRect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); +} + +inline gfxRect ThebesRect(const RectDouble& aRect) { + return gfxRect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); +} + +inline gfxImageFormat SurfaceFormatToImageFormat(SurfaceFormat aFormat) { + switch (aFormat) { + case SurfaceFormat::B8G8R8A8: + return SurfaceFormat::A8R8G8B8_UINT32; + case SurfaceFormat::B8G8R8X8: + return SurfaceFormat::X8R8G8B8_UINT32; + case SurfaceFormat::R5G6B5_UINT16: + return SurfaceFormat::R5G6B5_UINT16; + case SurfaceFormat::A8: + return SurfaceFormat::A8; + default: + return SurfaceFormat::UNKNOWN; + } +} + +inline SurfaceFormat ImageFormatToSurfaceFormat(gfxImageFormat aFormat) { + switch (aFormat) { + case SurfaceFormat::A8R8G8B8_UINT32: + return SurfaceFormat::B8G8R8A8; + case SurfaceFormat::X8R8G8B8_UINT32: + return SurfaceFormat::B8G8R8X8; + case SurfaceFormat::R5G6B5_UINT16: + return SurfaceFormat::R5G6B5_UINT16; + case SurfaceFormat::A8: + return SurfaceFormat::A8; + default: + case SurfaceFormat::UNKNOWN: + return SurfaceFormat::B8G8R8A8; + } +} + +inline gfxContentType ContentForFormat(const SurfaceFormat& aFormat) { + switch (aFormat) { + case SurfaceFormat::R5G6B5_UINT16: + case SurfaceFormat::B8G8R8X8: + case SurfaceFormat::R8G8B8X8: + return gfxContentType::COLOR; + case SurfaceFormat::A8: + return gfxContentType::ALPHA; + case SurfaceFormat::B8G8R8A8: + case SurfaceFormat::R8G8B8A8: + default: + return gfxContentType::COLOR_ALPHA; + } +} + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/thebes/gfxASurface.cpp b/gfx/thebes/gfxASurface.cpp new file mode 100644 index 0000000000..f77c836fb9 --- /dev/null +++ b/gfx/thebes/gfxASurface.cpp @@ -0,0 +1,503 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIMemoryReporter.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Base64.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/Attributes.h" +#include "mozilla/MemoryReporting.h" +#include "nsISupportsImpl.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/gfx/HelpersCairo.h" +#include "gfx2DGlue.h" + +#include "gfxASurface.h" +#include "gfxContext.h" +#include "gfxImageSurface.h" +#include "gfxPlatform.h" +#include "gfxRect.h" + +#include "cairo.h" +#include <algorithm> + +#ifdef CAIRO_HAS_WIN32_SURFACE +# include "gfxWindowsSurface.h" +#endif + +#ifdef MOZ_X11 +# include "gfxXlibSurface.h" +#endif + +#ifdef CAIRO_HAS_QUARTZ_SURFACE +# include "gfxQuartzSurface.h" +#endif + +#include <stdio.h> +#include <limits.h> + +#include "nsComponentManagerUtils.h" +#include "nsISupportsUtils.h" +#include "nsCOMPtr.h" +#include "nsServiceManagerUtils.h" +#include "nsString.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +static cairo_user_data_key_t gfxasurface_pointer_key; + +gfxASurface::gfxASurface() + : mSurface(nullptr), + mFloatingRefs(0), + mBytesRecorded(0), + mSurfaceValid(false) { + MOZ_COUNT_CTOR(gfxASurface); +} + +gfxASurface::~gfxASurface() { + RecordMemoryFreed(); + + MOZ_COUNT_DTOR(gfxASurface); +} + +// Surfaces use refcounting that's tied to the cairo surface refcnt, to avoid +// refcount mismatch issues. +nsrefcnt gfxASurface::AddRef(void) { + if (mSurfaceValid) { + if (mFloatingRefs) { + // eat a floating ref + mFloatingRefs--; + } else { + cairo_surface_reference(mSurface); + } + + return (nsrefcnt)cairo_surface_get_reference_count(mSurface); + } + // the surface isn't valid, but we still need to refcount + // the gfxASurface + return ++mFloatingRefs; +} + +nsrefcnt gfxASurface::Release(void) { + if (mSurfaceValid) { + NS_ASSERTION( + mFloatingRefs == 0, + "gfxASurface::Release with floating refs still hanging around!"); + + // Note that there is a destructor set on user data for mSurface, + // which will delete this gfxASurface wrapper when the surface's refcount + // goes out of scope. + nsrefcnt refcnt = (nsrefcnt)cairo_surface_get_reference_count(mSurface); + cairo_surface_destroy(mSurface); + + // |this| may not be valid any more, don't use it! + + return --refcnt; + } + if (--mFloatingRefs == 0) { + delete this; + return 0; + } + return mFloatingRefs; +} + +void gfxASurface::SurfaceDestroyFunc(void* data) { + gfxASurface* surf = (gfxASurface*)data; + // fprintf (stderr, "Deleting wrapper for %p (wrapper: %p)\n", surf->mSurface, + // data); + delete surf; +} + +gfxASurface* gfxASurface::GetSurfaceWrapper(cairo_surface_t* csurf) { + if (!csurf) return nullptr; + return (gfxASurface*)cairo_surface_get_user_data(csurf, + &gfxasurface_pointer_key); +} + +void gfxASurface::SetSurfaceWrapper(cairo_surface_t* csurf, + gfxASurface* asurf) { + if (!csurf) return; + cairo_surface_set_user_data(csurf, &gfxasurface_pointer_key, asurf, + SurfaceDestroyFunc); +} + +already_AddRefed<gfxASurface> gfxASurface::Wrap(cairo_surface_t* csurf, + const IntSize& aSize) { + RefPtr<gfxASurface> result; + + /* Do we already have a wrapper for this surface? */ + result = GetSurfaceWrapper(csurf); + if (result) { + // fprintf(stderr, "Existing wrapper for %p -> %p\n", csurf, result); + return result.forget(); + } + + /* No wrapper; figure out the surface type and create it */ + cairo_surface_type_t stype = cairo_surface_get_type(csurf); + + if (stype == CAIRO_SURFACE_TYPE_IMAGE) { + result = new gfxImageSurface(csurf); + } +#ifdef CAIRO_HAS_WIN32_SURFACE + else if (stype == CAIRO_SURFACE_TYPE_WIN32 || + stype == CAIRO_SURFACE_TYPE_WIN32_PRINTING) { + result = new gfxWindowsSurface(csurf); + } +#endif +#ifdef MOZ_X11 + else if (stype == CAIRO_SURFACE_TYPE_XLIB) { + result = new gfxXlibSurface(csurf); + } +#endif +#ifdef CAIRO_HAS_QUARTZ_SURFACE + else if (stype == CAIRO_SURFACE_TYPE_QUARTZ) { + result = new gfxQuartzSurface(csurf, aSize); + } +#endif + else { + result = new gfxUnknownSurface(csurf, aSize); + } + + // fprintf(stderr, "New wrapper for %p -> %p\n", csurf, result); + + return result.forget(); +} + +void gfxASurface::Init(cairo_surface_t* surface, bool existingSurface) { + SetSurfaceWrapper(surface, this); + MOZ_ASSERT(surface, "surface should be a valid pointer"); + + mSurface = surface; + mSurfaceValid = !cairo_surface_status(surface); + if (!mSurfaceValid) { + gfxWarning() << "ASurface Init failed with Cairo status " + << cairo_surface_status(surface) << " on " << hexa(surface); + } + + if (existingSurface || !mSurfaceValid) { + mFloatingRefs = 0; + } else { + mFloatingRefs = 1; + if (cairo_surface_get_content(surface) != CAIRO_CONTENT_COLOR) { + cairo_surface_set_subpixel_antialiasing( + surface, CAIRO_SUBPIXEL_ANTIALIASING_DISABLED); + } + } +} + +gfxSurfaceType gfxASurface::GetType() const { + if (!mSurfaceValid) return (gfxSurfaceType)-1; + + return (gfxSurfaceType)cairo_surface_get_type(mSurface); +} + +gfxContentType gfxASurface::GetContentType() const { + if (!mSurfaceValid) return (gfxContentType)-1; + + return (gfxContentType)cairo_surface_get_content(mSurface); +} + +void gfxASurface::SetDeviceOffset(const gfxPoint& offset) { + if (!mSurfaceValid) return; + cairo_surface_set_device_offset(mSurface, offset.x, offset.y); +} + +gfxPoint gfxASurface::GetDeviceOffset() const { + if (!mSurfaceValid) return gfxPoint(0.0, 0.0); + gfxPoint pt; + cairo_surface_get_device_offset(mSurface, &pt.x.value, &pt.y.value); + return pt; +} + +void gfxASurface::Flush() const { + if (!mSurfaceValid) return; + cairo_surface_flush(mSurface); + gfxPlatform::ClearSourceSurfaceForSurface(const_cast<gfxASurface*>(this)); +} + +void gfxASurface::MarkDirty() { + if (!mSurfaceValid) return; + cairo_surface_mark_dirty(mSurface); + gfxPlatform::ClearSourceSurfaceForSurface(this); +} + +void gfxASurface::MarkDirty(const gfxRect& r) { + if (!mSurfaceValid) return; + cairo_surface_mark_dirty_rectangle(mSurface, (int)r.X(), (int)r.Y(), + (int)r.Width(), (int)r.Height()); + gfxPlatform::ClearSourceSurfaceForSurface(this); +} + +void gfxASurface::SetData(const cairo_user_data_key_t* key, void* user_data, + thebes_destroy_func_t destroy) { + if (!mSurfaceValid) return; + cairo_surface_set_user_data(mSurface, key, user_data, destroy); +} + +void* gfxASurface::GetData(const cairo_user_data_key_t* key) { + if (!mSurfaceValid) return nullptr; + return cairo_surface_get_user_data(mSurface, key); +} + +void gfxASurface::Finish() { + // null surfaces are allowed here + cairo_surface_finish(mSurface); +} + +already_AddRefed<gfxImageSurface> gfxASurface::CopyToARGB32ImageSurface() { + if (!mSurface || !mSurfaceValid) { + return nullptr; + } + + const IntSize size = GetSize(); + RefPtr<gfxImageSurface> imgSurface = + new gfxImageSurface(size, SurfaceFormat::A8R8G8B8_UINT32); + + RefPtr<DrawTarget> dt = gfxPlatform::CreateDrawTargetForSurface( + imgSurface, IntSize(size.width, size.height)); + RefPtr<SourceSurface> source = + gfxPlatform::GetSourceSurfaceForSurface(dt, this); + + dt->CopySurface(source, IntRect(0, 0, size.width, size.height), IntPoint()); + + return imgSurface.forget(); +} + +int gfxASurface::CairoStatus() { + if (!mSurfaceValid) return -1; + + return cairo_surface_status(mSurface); +} + +nsresult gfxASurface::BeginPrinting(const nsAString& aTitle, + const nsAString& aPrintToFileName) { + return NS_OK; +} + +nsresult gfxASurface::EndPrinting() { return NS_OK; } + +nsresult gfxASurface::AbortPrinting() { return NS_OK; } + +nsresult gfxASurface::BeginPage() { return NS_OK; } + +nsresult gfxASurface::EndPage() { return NS_OK; } + +gfxContentType gfxASurface::ContentFromFormat(gfxImageFormat format) { + switch (format) { + case SurfaceFormat::A8R8G8B8_UINT32: + return gfxContentType::COLOR_ALPHA; + case SurfaceFormat::X8R8G8B8_UINT32: + case SurfaceFormat::R5G6B5_UINT16: + return gfxContentType::COLOR; + case SurfaceFormat::A8: + return gfxContentType::ALPHA; + + case SurfaceFormat::UNKNOWN: + default: + return gfxContentType::COLOR; + } +} + +int32_t gfxASurface::BytePerPixelFromFormat(gfxImageFormat format) { + switch (format) { + case SurfaceFormat::A8R8G8B8_UINT32: + case SurfaceFormat::X8R8G8B8_UINT32: + return 4; + case SurfaceFormat::R5G6B5_UINT16: + return 2; + case SurfaceFormat::A8: + return 1; + default: + NS_WARNING("Unknown byte per pixel value for Image format"); + } + return 0; +} + +/** Memory reporting **/ + +static const char* sDefaultSurfaceDescription = + "Memory used by gfx surface of the given type."; + +struct SurfaceMemoryReporterAttrs { + const char* path; + const char* description; +}; + +static const SurfaceMemoryReporterAttrs sSurfaceMemoryReporterAttrs[] = { + {"gfx-surface-image", nullptr}, + {"gfx-surface-pdf", nullptr}, + {"gfx-surface-ps", nullptr}, + {"gfx-surface-xlib", + "Memory used by xlib surfaces to store pixmaps. This memory lives in " + "the X server's process rather than in this application, so the bytes " + "accounted for here aren't counted in vsize, resident, explicit, or any " + "of " + "the other measurements on this page."}, + {"gfx-surface-xcb", nullptr}, + {"gfx-surface-glitz???", nullptr}, // should never be used + {"gfx-surface-quartz", nullptr}, + {"gfx-surface-win32", nullptr}, + {"gfx-surface-beos", nullptr}, + {"gfx-surface-directfb???", nullptr}, // should never be used + {"gfx-surface-svg", nullptr}, + {"gfx-surface-os2", nullptr}, + {"gfx-surface-win32printing", nullptr}, + {"gfx-surface-quartzimage", nullptr}, + {"gfx-surface-script", nullptr}, + {"gfx-surface-qpainter", nullptr}, + {"gfx-surface-recording", nullptr}, + {"gfx-surface-vg", nullptr}, + {"gfx-surface-gl", nullptr}, + {"gfx-surface-drm", nullptr}, + {"gfx-surface-tee", nullptr}, + {"gfx-surface-xml", nullptr}, + {"gfx-surface-skia", nullptr}, + {"gfx-surface-subsurface", nullptr}, +}; + +static_assert(MOZ_ARRAY_LENGTH(sSurfaceMemoryReporterAttrs) == + size_t(gfxSurfaceType::Max), + "sSurfaceMemoryReporterAttrs exceeds max capacity"); +static_assert(uint32_t(CAIRO_SURFACE_TYPE_SKIA) == + uint32_t(gfxSurfaceType::Skia), + "CAIRO_SURFACE_TYPE_SKIA not equal to gfxSurfaceType::Skia"); + +/* Surface size memory reporting */ + +class SurfaceMemoryReporter final : public nsIMemoryReporter { + ~SurfaceMemoryReporter() = default; + + // We can touch this array on several different threads, and we don't + // want to introduce memory barriers when recording the memory used. To + // assure dynamic race checkers like TSan that this is OK, we use + // relaxed memory ordering here. + static Atomic<size_t, Relaxed> + sSurfaceMemoryUsed[size_t(gfxSurfaceType::Max)]; + + public: + static void AdjustUsedMemory(gfxSurfaceType aType, int32_t aBytes) { + // A read-modify-write operation like += would require a memory barrier + // here, which would defeat the purpose of using relaxed memory + // ordering. So separate out the read and write operations. + sSurfaceMemoryUsed[size_t(aType)] = + sSurfaceMemoryUsed[size_t(aType)] + aBytes; + }; + + // This memory reporter is sometimes allocated on the compositor thread, + // but always released on the main thread, so its refcounting needs to be + // threadsafe. + NS_DECL_THREADSAFE_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + const size_t len = ArrayLength(sSurfaceMemoryReporterAttrs); + for (size_t i = 0; i < len; i++) { + int64_t amount = sSurfaceMemoryUsed[i]; + + if (amount != 0) { + const char* path = sSurfaceMemoryReporterAttrs[i].path; + const char* desc = sSurfaceMemoryReporterAttrs[i].description; + if (!desc) { + desc = sDefaultSurfaceDescription; + } + + aHandleReport->Callback(""_ns, nsCString(path), KIND_OTHER, UNITS_BYTES, + amount, nsCString(desc), aData); + } + } + + return NS_OK; + } +}; + +Atomic<size_t, Relaxed> + SurfaceMemoryReporter::sSurfaceMemoryUsed[size_t(gfxSurfaceType::Max)]; + +NS_IMPL_ISUPPORTS(SurfaceMemoryReporter, nsIMemoryReporter) + +void gfxASurface::RecordMemoryUsedForSurfaceType(gfxSurfaceType aType, + int32_t aBytes) { + if (int(aType) < 0 || aType >= gfxSurfaceType::Max) { + NS_WARNING("Invalid type to RecordMemoryUsedForSurfaceType!"); + return; + } + + static bool registered = false; + if (!registered) { + RegisterStrongMemoryReporter(new SurfaceMemoryReporter()); + registered = true; + } + + SurfaceMemoryReporter::AdjustUsedMemory(aType, aBytes); +} + +void gfxASurface::RecordMemoryUsed(int32_t aBytes) { + RecordMemoryUsedForSurfaceType(GetType(), aBytes); + mBytesRecorded += aBytes; +} + +void gfxASurface::RecordMemoryFreed() { + if (mBytesRecorded) { + RecordMemoryUsedForSurfaceType(GetType(), -mBytesRecorded); + mBytesRecorded = 0; + } +} + +size_t gfxASurface::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + // We don't measure mSurface because cairo doesn't allow it. + return 0; +} + +size_t gfxASurface::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +/* static */ +uint8_t gfxASurface::BytesPerPixel(gfxImageFormat aImageFormat) { + switch (aImageFormat) { + case SurfaceFormat::A8R8G8B8_UINT32: + return 4; + case SurfaceFormat::X8R8G8B8_UINT32: + return 4; + case SurfaceFormat::R5G6B5_UINT16: + return 2; + case SurfaceFormat::A8: + return 1; + case SurfaceFormat::UNKNOWN: + default: + MOZ_ASSERT_UNREACHABLE("Not really sure what you want me to say here"); + return 0; + } +} + +void gfxASurface::SetOpaqueRect(const gfxRect& aRect) { + if (aRect.IsEmpty()) { + mOpaqueRect = nullptr; + } else if (!!mOpaqueRect) { + *mOpaqueRect = aRect; + } else { + mOpaqueRect = MakeUnique<gfxRect>(aRect); + } +} + +/* static */ const gfxRect& gfxASurface::GetEmptyOpaqueRect() { + static const gfxRect empty(0, 0, 0, 0); + return empty; +} + +const IntSize gfxASurface::GetSize() const { return IntSize(-1, -1); } + +SurfaceFormat gfxASurface::GetSurfaceFormat() const { + if (!mSurfaceValid) { + return SurfaceFormat::UNKNOWN; + } + return GfxFormatForCairoSurface(mSurface); +} + +already_AddRefed<gfxImageSurface> gfxASurface::GetAsImageSurface() { + return nullptr; +} diff --git a/gfx/thebes/gfxASurface.h b/gfx/thebes/gfxASurface.h new file mode 100644 index 0000000000..d2e6daf771 --- /dev/null +++ b/gfx/thebes/gfxASurface.h @@ -0,0 +1,184 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_ASURFACE_H +#define GFX_ASURFACE_H + +#include "mozilla/MemoryReporting.h" +#include "mozilla/UniquePtr.h" + +#include "gfxPoint.h" +#include "gfxRect.h" +#include "gfxTypes.h" +#include "nscore.h" +#include "nsSize.h" +#include "mozilla/gfx/Rect.h" + +#include "nsStringFwd.h" + +class gfxImageSurface; + +template <typename T> +struct already_AddRefed; + +/** + * A surface is something you can draw on. Instantiate a subclass of this + * abstract class, and use gfxContext to draw on this surface. + */ +class gfxASurface { + public: +#ifdef MOZILLA_INTERNAL_API + nsrefcnt AddRef(void); + nsrefcnt Release(void); +#else + virtual nsrefcnt AddRef(void); + virtual nsrefcnt Release(void); +#endif + + public: + /** Wrap the given cairo surface and return a gfxASurface for it. + * This adds a reference to csurf (owned by the returned gfxASurface). + */ + static already_AddRefed<gfxASurface> Wrap( + cairo_surface_t* csurf, + const mozilla::gfx::IntSize& aSize = mozilla::gfx::IntSize(-1, -1)); + + /*** this DOES NOT addref the surface */ + cairo_surface_t* CairoSurface() { return mSurface; } + + gfxSurfaceType GetType() const; + + gfxContentType GetContentType() const; + + void SetDeviceOffset(const gfxPoint& offset); + gfxPoint GetDeviceOffset() const; + + void Flush() const; + void MarkDirty(); + void MarkDirty(const gfxRect& r); + + /* Printing backend functions */ + virtual nsresult BeginPrinting(const nsAString& aTitle, + const nsAString& aPrintToFileName); + virtual nsresult EndPrinting(); + virtual nsresult AbortPrinting(); + virtual nsresult BeginPage(); + virtual nsresult EndPage(); + + void SetData(const cairo_user_data_key_t* key, void* user_data, + thebes_destroy_func_t destroy); + void* GetData(const cairo_user_data_key_t* key); + + virtual void Finish(); + + /** + * Returns an image surface for this surface, or nullptr if not supported. + * This will not copy image data, just wraps an image surface around + * pixel data already available in memory. + */ + virtual already_AddRefed<gfxImageSurface> GetAsImageSurface(); + + /** + * Creates a new ARGB32 image surface with the same contents as this surface. + * Returns null on error. + */ + already_AddRefed<gfxImageSurface> CopyToARGB32ImageSurface(); + + int CairoStatus(); + + static gfxContentType ContentFromFormat(gfxImageFormat format); + + /** + * Record number of bytes for given surface type. Use positive bytes + * for allocations and negative bytes for deallocations. + */ + static void RecordMemoryUsedForSurfaceType(gfxSurfaceType aType, + int32_t aBytes); + + /** + * Same as above, but use current surface type as returned by GetType(). + * The bytes will be accumulated until RecordMemoryFreed is called, + * in which case the value that was recorded for this surface will + * be freed. + */ + void RecordMemoryUsed(int32_t aBytes); + void RecordMemoryFreed(); + + virtual int32_t KnownMemoryUsed() { return mBytesRecorded; } + + virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + // gfxASurface has many sub-classes. This method indicates if a sub-class + // is capable of measuring its own size accurately. If not, the caller + // must fall back to a computed size. (Note that gfxASurface can actually + // measure itself, but we must |return false| here because it serves as the + // (conservative) default for all the sub-classes. Therefore, this + // function should only be called on a |gfxASurface*| that actually points + // to a sub-class of gfxASurface.) + virtual bool SizeOfIsMeasured() const { return false; } + + static int32_t BytePerPixelFromFormat(gfxImageFormat format); + + virtual const mozilla::gfx::IntSize GetSize() const; + + virtual mozilla::gfx::SurfaceFormat GetSurfaceFormat() const; + + void SetOpaqueRect(const gfxRect& aRect); + + const gfxRect& GetOpaqueRect() { + if (!!mOpaqueRect) return *mOpaqueRect; + return GetEmptyOpaqueRect(); + } + + static uint8_t BytesPerPixel(gfxImageFormat aImageFormat); + + protected: + gfxASurface(); + + static gfxASurface* GetSurfaceWrapper(cairo_surface_t* csurf); + static void SetSurfaceWrapper(cairo_surface_t* csurf, gfxASurface* asurf); + + // NB: Init() *must* be called from within subclass's + // constructors. It's unsafe to call it after the ctor finishes; + // leaks and use-after-frees are possible. + void Init(cairo_surface_t* surface, bool existingSurface = false); + + // out-of-line helper to allow GetOpaqueRect() to be inlined + // without including gfxRect.h here + static const gfxRect& GetEmptyOpaqueRect(); + + virtual ~gfxASurface(); + + cairo_surface_t* mSurface; + mozilla::UniquePtr<gfxRect> mOpaqueRect; + + private: + static void SurfaceDestroyFunc(void* data); + + int32_t mFloatingRefs; + int32_t mBytesRecorded; + + protected: + bool mSurfaceValid; +}; + +/** + * An Unknown surface; used to wrap unknown cairo_surface_t returns from cairo + */ +class gfxUnknownSurface : public gfxASurface { + public: + gfxUnknownSurface(cairo_surface_t* surf, const mozilla::gfx::IntSize& aSize) + : mSize(aSize) { + Init(surf, true); + } + + virtual ~gfxUnknownSurface() = default; + const mozilla::gfx::IntSize GetSize() const override { return mSize; } + + private: + mozilla::gfx::IntSize mSize; +}; + +#endif /* GFX_ASURFACE_H */ diff --git a/gfx/thebes/gfxAlphaRecovery.cpp b/gfx/thebes/gfxAlphaRecovery.cpp new file mode 100644 index 0000000000..de36b84b22 --- /dev/null +++ b/gfx/thebes/gfxAlphaRecovery.cpp @@ -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/. */ + +#include "gfxAlphaRecovery.h" + +#include "gfxImageSurface.h" + +#define MOZILLA_SSE_INCLUDE_HEADER_FOR_SSE2 +#include "mozilla/SSE.h" + +#include <xsimd/xsimd.hpp> + +/* 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() && + RecoverAlphaGeneric<xsimd::sse2>(blackSurf, whiteSurf)) { + return true; + } +#endif +#ifdef MOZILLA_MAY_SUPPORT_NEON + if (mozilla::supports_neon() && + RecoverAlphaGeneric<xsimd::neon>(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..ce57a20293 --- /dev/null +++ b/gfx/thebes/gfxAlphaRecovery.h @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "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); + + /* This does the same as the previous function, but uses SIMD + * optimizations. Usually this should not be called directly. + */ + template <class Arch> + static bool RecoverAlphaGeneric(gfxImageSurface* blackSurface, + const gfxImageSurface* whiteSurface); + + /** 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 gfxRecoverAlphaGeneric.hpp. + */ + + 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/gfxAlphaRecoveryGeneric.h b/gfx/thebes/gfxAlphaRecoveryGeneric.h new file mode 100644 index 0000000000..84db0fea0e --- /dev/null +++ b/gfx/thebes/gfxAlphaRecoveryGeneric.h @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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_GENERIC_H_ +#define _GFXALPHARECOVERY_GENERIC_H_ + +#include "gfxAlphaRecovery.h" +#include "gfxImageSurface.h" +#include "nsDebug.h" +#include <xsimd/xsimd.hpp> + +template <typename Arch> +bool gfxAlphaRecovery::RecoverAlphaGeneric(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; + } + + alignas(Arch::alignment()) static const uint8_t greenMaski[] = { + 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, + 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, + }; + alignas(Arch::alignment()) static const uint8_t alphaMaski[] = { + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + }; + + using batch_type = xsimd::batch<uint8_t, Arch>; + constexpr size_t batch_size = batch_type::size; + static_assert(batch_size == 16); + + batch_type greenMask = batch_type::load_aligned(greenMaski); + batch_type alphaMask = batch_type::load_aligned(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) { + auto black1 = batch_type::load_aligned(blackData); + auto white1 = batch_type::load_aligned(whiteData); + auto black2 = batch_type::load_aligned(blackData + batch_size); + auto white2 = batch_type::load_aligned(whiteData + batch_size); + + // Execute the same instructions as described in RecoverPixel, only + // using an SSE2 packed saturated subtract. + white1 = xsimd::ssub(white1, black1); + white2 = xsimd::ssub(white2, black2); + white1 = xsimd::ssub(greenMask, white1); + white2 = xsimd::ssub(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 = xsimd::bitwise_andnot(black1, alphaMask); + black2 = xsimd::bitwise_andnot(black2, alphaMask); + white1 = xsimd::slide_left<2>(white1); + white2 = xsimd::slide_left<2>(white2); + white1 &= alphaMask; + white2 &= alphaMask; + black1 |= white1; + black2 |= white2; + + black1.store_aligned(blackData); + black2.store_aligned(blackData + batch_size); + blackData += 2 * batch_size; + whiteData += 2 * batch_size; + } + for (; j < size.width - 4; j += 4) { + auto black = batch_type::load_aligned(blackData); + auto white = batch_type::load_aligned(whiteData); + + white = xsimd::ssub(white, black); + white = xsimd::ssub(greenMask, white); + black = xsimd::bitwise_andnot(black, alphaMask); + white = xsimd::slide_left<2>(white); + white &= alphaMask; + black |= white; + black.store_aligned(blackData); + blackData += batch_size; + whiteData += batch_size; + } + // 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; +} +#endif diff --git a/gfx/thebes/gfxAlphaRecoveryNeon.cpp b/gfx/thebes/gfxAlphaRecoveryNeon.cpp new file mode 100644 index 0000000000..21c2dcf834 --- /dev/null +++ b/gfx/thebes/gfxAlphaRecoveryNeon.cpp @@ -0,0 +1,9 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxAlphaRecoveryGeneric.h" + +template bool gfxAlphaRecoveryGeneric::RecoverAlpha<xsimd::neon>( + gfxImageSurface* blackSurf, const gfxImageSurface* whiteSurf); diff --git a/gfx/thebes/gfxAlphaRecoverySSE2.cpp b/gfx/thebes/gfxAlphaRecoverySSE2.cpp new file mode 100644 index 0000000000..22c33bb1cf --- /dev/null +++ b/gfx/thebes/gfxAlphaRecoverySSE2.cpp @@ -0,0 +1,9 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxAlphaRecoveryGeneric.h" + +template bool gfxAlphaRecovery::RecoverAlphaGeneric<xsimd::sse2>( + gfxImageSurface* blackSurf, const gfxImageSurface* whiteSurf); diff --git a/gfx/thebes/gfxAndroidPlatform.cpp b/gfx/thebes/gfxAndroidPlatform.cpp new file mode 100644 index 0000000000..a121d5550a --- /dev/null +++ b/gfx/thebes/gfxAndroidPlatform.cpp @@ -0,0 +1,369 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "base/basictypes.h" + +#include "gfxAndroidPlatform.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/CountingAllocatorBase.h" +#include "mozilla/intl/LocaleService.h" +#include "mozilla/intl/OSPreferences.h" +#include "mozilla/java/GeckoAppShellWrappers.h" +#include "mozilla/jni/Utils.h" +#include "mozilla/layers/AndroidHardwareBuffer.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_webgl.h" +#include "mozilla/widget/AndroidVsync.h" + +#include "gfx2DGlue.h" +#include "gfxFT2FontList.h" +#include "gfxImageSurface.h" +#include "gfxTextRun.h" +#include "nsXULAppAPI.h" +#include "nsIScreen.h" +#include "nsServiceManagerUtils.h" +#include "nsUnicodeProperties.h" +#include "cairo.h" +#include "VsyncSource.h" + +#include "ft2build.h" +#include FT_FREETYPE_H +#include FT_MODULE_H + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::gfx; +using namespace mozilla::unicode; +using mozilla::intl::LocaleService; +using mozilla::intl::OSPreferences; + +static FT_Library gPlatformFTLibrary = nullptr; + +class FreetypeReporter final : public nsIMemoryReporter, + public CountingAllocatorBase<FreetypeReporter> { + private: + ~FreetypeReporter() {} + + public: + NS_DECL_ISUPPORTS + + static void* Malloc(FT_Memory, long size) { return CountingMalloc(size); } + + static void Free(FT_Memory, void* p) { return CountingFree(p); } + + static void* Realloc(FT_Memory, long cur_size, long new_size, void* p) { + return CountingRealloc(p, new_size); + } + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + MOZ_COLLECT_REPORT("explicit/freetype", KIND_HEAP, UNITS_BYTES, + MemoryAllocated(), "Memory used by Freetype."); + + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(FreetypeReporter, nsIMemoryReporter) + +static FT_MemoryRec_ sFreetypeMemoryRecord; + +gfxAndroidPlatform::gfxAndroidPlatform() { + // A custom allocator. It counts allocations, enabling memory reporting. + sFreetypeMemoryRecord.user = nullptr; + sFreetypeMemoryRecord.alloc = FreetypeReporter::Malloc; + sFreetypeMemoryRecord.free = FreetypeReporter::Free; + sFreetypeMemoryRecord.realloc = FreetypeReporter::Realloc; + + // These two calls are equivalent to FT_Init_FreeType(), but allow us to + // provide a custom memory allocator. + FT_New_Library(&sFreetypeMemoryRecord, &gPlatformFTLibrary); + FT_Add_Default_Modules(gPlatformFTLibrary); + + Factory::SetFTLibrary(gPlatformFTLibrary); + + RegisterStrongMemoryReporter(new FreetypeReporter()); + + mOffscreenFormat = GetScreenDepth() == 16 ? SurfaceFormat::R5G6B5_UINT16 + : SurfaceFormat::X8R8G8B8_UINT32; + + if (StaticPrefs::gfx_android_rgb16_force_AtStartup()) { + mOffscreenFormat = SurfaceFormat::R5G6B5_UINT16; + } +} + +gfxAndroidPlatform::~gfxAndroidPlatform() { + FT_Done_Library(gPlatformFTLibrary); + gPlatformFTLibrary = nullptr; + layers::AndroidHardwareBufferManager::Shutdown(); + layers::AndroidHardwareBufferApi::Shutdown(); +} + +void gfxAndroidPlatform::InitAcceleration() { gfxPlatform::InitAcceleration(); } + +already_AddRefed<gfxASurface> gfxAndroidPlatform::CreateOffscreenSurface( + const IntSize& aSize, gfxImageFormat aFormat) { + if (!Factory::AllowedSurfaceSize(aSize)) { + return nullptr; + } + + RefPtr<gfxASurface> newSurface; + newSurface = new gfxImageSurface(aSize, aFormat); + + return newSurface.forget(); +} + +static bool IsJapaneseLocale() { + static bool sInitialized = false; + static bool sIsJapanese = false; + + if (!sInitialized) { + sInitialized = true; + + nsAutoCString appLocale; + LocaleService::GetInstance()->GetAppLocaleAsBCP47(appLocale); + + const nsDependentCSubstring lang(appLocale, 0, 2); + if (lang.EqualsLiteral("ja")) { + sIsJapanese = true; + } else { + OSPreferences::GetInstance()->GetSystemLocale(appLocale); + + const nsDependentCSubstring lang(appLocale, 0, 2); + if (lang.EqualsLiteral("ja")) { + sIsJapanese = true; + } + } + } + + return sIsJapanese; +} + +void gfxAndroidPlatform::GetCommonFallbackFonts( + uint32_t aCh, Script aRunScript, eFontPresentation aPresentation, + nsTArray<const char*>& aFontList) { + static const char kDroidSansJapanese[] = "Droid Sans Japanese"; + static const char kMotoyaLMaru[] = "MotoyaLMaru"; + static const char kNotoSansCJKJP[] = "Noto Sans CJK JP"; + static const char kNotoColorEmoji[] = "Noto Color Emoji"; + + if (PrefersColor(aPresentation)) { + aFontList.AppendElement(kNotoColorEmoji); + } + + if (IS_IN_BMP(aCh)) { + // try language-specific "Droid Sans *" and "Noto Sans *" fonts for + // certain blocks, as most devices probably have these + uint8_t block = (aCh >> 8) & 0xff; + switch (block) { + case 0x05: + aFontList.AppendElement("Noto Sans Hebrew"); + aFontList.AppendElement("Droid Sans Hebrew"); + aFontList.AppendElement("Noto Sans Armenian"); + aFontList.AppendElement("Droid Sans Armenian"); + break; + case 0x06: + aFontList.AppendElement("Noto Sans Arabic"); + aFontList.AppendElement("Droid Sans Arabic"); + break; + case 0x09: + aFontList.AppendElement("Noto Sans Devanagari"); + aFontList.AppendElement("Noto Sans Bengali"); + aFontList.AppendElement("Droid Sans Devanagari"); + break; + case 0x0a: + aFontList.AppendElement("Noto Sans Gurmukhi"); + aFontList.AppendElement("Noto Sans Gujarati"); + break; + case 0x0b: + aFontList.AppendElement("Noto Sans Tamil"); + aFontList.AppendElement("Noto Sans Oriya"); + aFontList.AppendElement("Droid Sans Tamil"); + break; + case 0x0c: + aFontList.AppendElement("Noto Sans Telugu"); + aFontList.AppendElement("Noto Sans Kannada"); + break; + case 0x0d: + aFontList.AppendElement("Noto Sans Malayalam"); + aFontList.AppendElement("Noto Sans Sinhala"); + break; + case 0x0e: + aFontList.AppendElement("Noto Sans Thai"); + aFontList.AppendElement("Noto Sans Lao"); + aFontList.AppendElement("Droid Sans Thai"); + break; + case 0x0f: + aFontList.AppendElement("Noto Sans Tibetan"); + break; + case 0x10: + case 0x2d: + aFontList.AppendElement("Noto Sans Georgian"); + aFontList.AppendElement("Droid Sans Georgian"); + break; + case 0x12: + case 0x13: + aFontList.AppendElement("Noto Sans Ethiopic"); + aFontList.AppendElement("Droid Sans Ethiopic"); + break; + case 0x21: + case 0x23: + case 0x24: + case 0x26: + case 0x27: + case 0x29: + aFontList.AppendElement("Noto Sans Symbols"); + break; + case 0xf9: + case 0xfa: + if (IsJapaneseLocale()) { + aFontList.AppendElement(kMotoyaLMaru); + aFontList.AppendElement(kNotoSansCJKJP); + aFontList.AppendElement(kDroidSansJapanese); + } + break; + default: + if (block >= 0x2e && block <= 0x9f && IsJapaneseLocale()) { + aFontList.AppendElement(kMotoyaLMaru); + aFontList.AppendElement(kNotoSansCJKJP); + aFontList.AppendElement(kDroidSansJapanese); + } + break; + } + } + // and try Droid Sans Fallback as a last resort + aFontList.AppendElement("Droid Sans Fallback"); +} + +bool gfxAndroidPlatform::CreatePlatformFontList() { + return gfxPlatformFontList::Initialize(new gfxFT2FontList); +} + +void gfxAndroidPlatform::ReadSystemFontList( + mozilla::dom::SystemFontList* aFontList) { + gfxFT2FontList::PlatformFontList()->ReadSystemFontList(aFontList); +} + +bool gfxAndroidPlatform::FontHintingEnabled() { + // In "mobile" builds, we sometimes use non-reflow-zoom, so we + // might not want hinting. Let's see. + +#ifdef MOZ_WIDGET_ANDROID + // On Android, we currently only use gecko to render web + // content that can always be be non-reflow-zoomed. So turn off + // hinting. + // + // XXX when gecko-android-java is used as an "app runtime", we may + // want to re-enable hinting for non-browser processes there. + // + // If this value is changed, we must ensure that the argument passed + // to SkInitCairoFT() in GPUParent::RecvInit() is also updated. + return false; +#endif // MOZ_WIDGET_ANDROID + + // Currently, we don't have any other targets, but if/when we do, + // decide how to handle them here. + + MOZ_ASSERT_UNREACHABLE("oops, what platform is this?"); + return gfxPlatform::FontHintingEnabled(); +} + +bool gfxAndroidPlatform::RequiresLinearZoom() { +#ifdef MOZ_WIDGET_ANDROID + // On Android, we currently only use gecko to render web + // content that can always be be non-reflow-zoomed. + // + // XXX when gecko-android-java is used as an "app runtime", we may + // want to use linear zoom only for the web browser process, not other apps. + return true; +#endif + + MOZ_ASSERT_UNREACHABLE("oops, what platform is this?"); + return gfxPlatform::RequiresLinearZoom(); +} + +bool gfxAndroidPlatform::CheckVariationFontSupport() { + // Don't attempt to use variations on Android API versions up to Marshmallow, + // because the system freetype version is too old and the parent process may + // access it during printing (bug 1845174). + return jni::GetAPIVersion() > 23; +} + +class AndroidVsyncSource final : public VsyncSource, + public widget::AndroidVsync::Observer { + public: + AndroidVsyncSource() : mAndroidVsync(widget::AndroidVsync::GetInstance()) {} + + bool IsVsyncEnabled() override { + MOZ_ASSERT(NS_IsMainThread()); + return mObservingVsync; + } + + void EnableVsync() override { + MOZ_ASSERT(NS_IsMainThread()); + + if (mObservingVsync) { + return; + } + + float fps = java::GeckoAppShell::GetScreenRefreshRate(); + MOZ_ASSERT(fps > 0.0f); + mVsyncRate = TimeDuration::FromMilliseconds(1000.0 / fps); + mAndroidVsync->RegisterObserver(this, widget::AndroidVsync::RENDER); + mObservingVsync = true; + } + + void DisableVsync() override { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mObservingVsync) { + return; + } + mAndroidVsync->UnregisterObserver(this, widget::AndroidVsync::RENDER); + mObservingVsync = false; + } + + TimeDuration GetVsyncRate() override { return mVsyncRate; } + + void Shutdown() override { DisableVsync(); } + + // Override for the widget::AndroidVsync::Observer method + void OnVsync(const TimeStamp& aTimeStamp) override { + // Use the timebase from the frame callback as the vsync time, unless it + // is in the future. + TimeStamp now = TimeStamp::Now(); + TimeStamp vsyncTime = aTimeStamp < now ? aTimeStamp : now; + TimeStamp outputTime = vsyncTime + GetVsyncRate(); + + NotifyVsync(vsyncTime, outputTime); + } + + void OnMaybeUpdateRefreshRate() override { + NS_DispatchToMainThread( + NS_NewRunnableFunction(__func__, [self = RefPtr{this}]() { + if (!self->mObservingVsync) { + return; + } + self->DisableVsync(); + self->EnableVsync(); + })); + } + + private: + virtual ~AndroidVsyncSource() { DisableVsync(); } + + RefPtr<widget::AndroidVsync> mAndroidVsync; + TimeDuration mVsyncRate; + bool mObservingVsync = false; +}; + +already_AddRefed<mozilla::gfx::VsyncSource> +gfxAndroidPlatform::CreateGlobalHardwareVsyncSource() { + RefPtr<AndroidVsyncSource> vsyncSource = new AndroidVsyncSource(); + return vsyncSource.forget(); +} diff --git a/gfx/thebes/gfxAndroidPlatform.h b/gfx/thebes/gfxAndroidPlatform.h new file mode 100644 index 0000000000..598acdb571 --- /dev/null +++ b/gfx/thebes/gfxAndroidPlatform.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_PLATFORM_ANDROID_H +#define GFX_PLATFORM_ANDROID_H + +#include "gfxPlatform.h" +#include "gfxUserFontSet.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" + +class gfxAndroidPlatform final : public gfxPlatform { + public: + gfxAndroidPlatform(); + virtual ~gfxAndroidPlatform(); + + static gfxAndroidPlatform* GetPlatform() { + return (gfxAndroidPlatform*)gfxPlatform::GetPlatform(); + } + + already_AddRefed<gfxASurface> CreateOffscreenSurface( + const IntSize& aSize, gfxImageFormat aFormat) override; + + gfxImageFormat GetOffscreenFormat() override { return mOffscreenFormat; } + + // platform implementations of font functions + bool CreatePlatformFontList() override; + + void ReadSystemFontList(mozilla::dom::SystemFontList*) override; + + void GetCommonFallbackFonts(uint32_t aCh, Script aRunScript, + eFontPresentation aPresentation, + nsTArray<const char*>& aFontList) override; + + bool FontHintingEnabled() override; + bool RequiresLinearZoom() override; + + already_AddRefed<mozilla::gfx::VsyncSource> CreateGlobalHardwareVsyncSource() + override; + + static bool CheckVariationFontSupport(); + + protected: + void InitAcceleration() override; + + bool AccelerateLayersByDefault() override { return true; } + + private: + gfxImageFormat mOffscreenFormat; +}; + +#endif /* GFX_PLATFORM_ANDROID_H */ diff --git a/gfx/thebes/gfxBaseSharedMemorySurface.cpp b/gfx/thebes/gfxBaseSharedMemorySurface.cpp new file mode 100644 index 0000000000..0ef7bd33d0 --- /dev/null +++ b/gfx/thebes/gfxBaseSharedMemorySurface.cpp @@ -0,0 +1,10 @@ +// vim:set ts=4 sts=2 sw=2 et cin: +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxBaseSharedMemorySurface.h" +#include "cairo.h" + +const cairo_user_data_key_t SHM_KEY = {0}; diff --git a/gfx/thebes/gfxBaseSharedMemorySurface.h b/gfx/thebes/gfxBaseSharedMemorySurface.h new file mode 100644 index 0000000000..bf211edab5 --- /dev/null +++ b/gfx/thebes/gfxBaseSharedMemorySurface.h @@ -0,0 +1,165 @@ +// vim:set ts=4 sts=2 sw=2 et cin: +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_SHARED_MEMORYSURFACE_H +#define GFX_SHARED_MEMORYSURFACE_H + +#include "mozilla/gfx/2D.h" +#include "mozilla/ipc/Shmem.h" +#include "mozilla/ipc/SharedMemory.h" + +#include "gfxASurface.h" +#include "gfxImageSurface.h" +#include "pratom.h" + +typedef struct _cairo_user_data_key cairo_user_data_key_t; + +struct SharedImageInfo { + int32_t width; + int32_t height; + gfxImageFormat format; + int32_t readCount; +}; + +inline SharedImageInfo* GetShmInfoPtr(const mozilla::ipc::Shmem& aShmem) { + return reinterpret_cast<SharedImageInfo*>( + aShmem.get<char>() + aShmem.Size<char>() - sizeof(SharedImageInfo)); +} + +extern const cairo_user_data_key_t SHM_KEY; + +template <typename Base, typename Sub> +class gfxBaseSharedMemorySurface : public Base { + typedef mozilla::ipc::SharedMemory SharedMemory; + typedef mozilla::ipc::Shmem Shmem; + + protected: + virtual ~gfxBaseSharedMemorySurface() { + MOZ_COUNT_DTOR(gfxBaseSharedMemorySurface); + } + + public: + /** + * Return a new gfxSharedImageSurface around a shmem segment newly + * allocated by this function. |aAllocator| is the object used to + * allocate the new shmem segment. Null is returned if creating + * the surface failed. + * + * NB: the *caller* is responsible for freeing the Shmem allocated + * by this function. + */ + template <class ShmemAllocator> + static already_AddRefed<Sub> Create(ShmemAllocator* aAllocator, + const mozilla::gfx::IntSize& aSize, + gfxImageFormat aFormat) { + return Create<ShmemAllocator, false>(aAllocator, aSize, aFormat); + } + + /** + * Return a new gfxSharedImageSurface that wraps a shmem segment + * already created by the Create() above. Bad things will happen + * if an attempt is made to wrap any other shmem segment. Null is + * returned if creating the surface failed. + */ + static already_AddRefed<Sub> Open(const Shmem& aShmem) { + SharedImageInfo* shmInfo = GetShmInfoPtr(aShmem); + mozilla::gfx::IntSize size(shmInfo->width, shmInfo->height); + if (!mozilla::gfx::Factory::CheckSurfaceSize(size)) return nullptr; + + gfxImageFormat format = shmInfo->format; + long stride = gfxImageSurface::ComputeStride(size, format); + + RefPtr<Sub> s = new Sub(size, stride, format, aShmem); + // We didn't create this Shmem and so don't free it on errors + return (s->CairoStatus() != 0) ? nullptr : s.forget(); + } + + template <class ShmemAllocator> + static already_AddRefed<Sub> CreateUnsafe(ShmemAllocator* aAllocator, + const mozilla::gfx::IntSize& aSize, + gfxImageFormat aFormat) { + return Create<ShmemAllocator, true>(aAllocator, aSize, aFormat); + } + + Shmem& GetShmem() { return mShmem; } + + static bool IsSharedImage(gfxASurface* aSurface) { + return (aSurface && aSurface->GetType() == gfxSurfaceType::Image && + aSurface->GetData(&SHM_KEY)); + } + + protected: + gfxBaseSharedMemorySurface(const mozilla::gfx::IntSize& aSize, long aStride, + gfxImageFormat aFormat, const Shmem& aShmem) + : Base(aShmem.get<unsigned char>(), aSize, aStride, aFormat) { + MOZ_COUNT_CTOR(gfxBaseSharedMemorySurface); + + mShmem = aShmem; + this->SetData(&SHM_KEY, this, nullptr); + } + + private: + void WriteShmemInfo() { + SharedImageInfo* shmInfo = GetShmInfoPtr(mShmem); + shmInfo->width = this->mSize.width; + shmInfo->height = this->mSize.height; + shmInfo->format = this->mFormat; + shmInfo->readCount = 0; + } + + int32_t ReadLock() { + SharedImageInfo* shmInfo = GetShmInfoPtr(mShmem); + return PR_ATOMIC_INCREMENT(&shmInfo->readCount); + } + + int32_t ReadUnlock() { + SharedImageInfo* shmInfo = GetShmInfoPtr(mShmem); + return PR_ATOMIC_DECREMENT(&shmInfo->readCount); + } + + int32_t GetReadCount() { + SharedImageInfo* shmInfo = GetShmInfoPtr(mShmem); + return shmInfo->readCount; + } + + static size_t GetAlignedSize(const mozilla::gfx::IntSize& aSize, + long aStride) { +#define MOZ_ALIGN_WORD(x) (((x) + 3) & ~3) + return MOZ_ALIGN_WORD(sizeof(SharedImageInfo) + aSize.height * aStride); + } + + template <class ShmemAllocator, bool Unsafe> + static already_AddRefed<Sub> Create(ShmemAllocator* aAllocator, + const mozilla::gfx::IntSize& aSize, + gfxImageFormat aFormat) { + if (!mozilla::gfx::Factory::CheckSurfaceSize(aSize)) return nullptr; + + Shmem shmem; + long stride = gfxImageSurface::ComputeStride(aSize, aFormat); + size_t size = GetAlignedSize(aSize, stride); + if (!Unsafe) { + if (!aAllocator->AllocShmem(size, &shmem)) return nullptr; + } else { + if (!aAllocator->AllocUnsafeShmem(size, &shmem)) return nullptr; + } + + RefPtr<Sub> s = new Sub(aSize, stride, aFormat, shmem); + if (s->CairoStatus() != 0) { + aAllocator->DeallocShmem(shmem); + return nullptr; + } + s->WriteShmemInfo(); + return s.forget(); + } + + Shmem mShmem; + + // Calling these is very bad, disallow it + gfxBaseSharedMemorySurface(const gfxBaseSharedMemorySurface&); + gfxBaseSharedMemorySurface& operator=(const gfxBaseSharedMemorySurface&); +}; + +#endif /* GFX_SHARED_MEMORYSURFACE_H */ diff --git a/gfx/thebes/gfxBlur.cpp b/gfx/thebes/gfxBlur.cpp new file mode 100644 index 0000000000..605bd5567c --- /dev/null +++ b/gfx/thebes/gfxBlur.cpp @@ -0,0 +1,1224 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxBlur.h" + +#include "gfx2DGlue.h" +#include "gfxContext.h" +#include "gfxPlatform.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Blur.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/Maybe.h" +#include "nsExpirationTracker.h" +#include "nsClassHashtable.h" +#include "gfxUtils.h" +#include <limits> +#include <cmath> + +using namespace mozilla; +using namespace mozilla::gfx; + +gfxAlphaBoxBlur::~gfxAlphaBoxBlur() = default; + +UniquePtr<gfxContext> gfxAlphaBoxBlur::Init(gfxContext* aDestinationCtx, + const gfxRect& aRect, + const IntSize& aSpreadRadius, + const IntSize& aBlurRadius, + const gfxRect* aDirtyRect, + const gfxRect* aSkipRect, + bool aUseHardwareAccel) { + DrawTarget* refDT = aDestinationCtx->GetDrawTarget(); + Maybe<Rect> dirtyRect = aDirtyRect ? Some(ToRect(*aDirtyRect)) : Nothing(); + Maybe<Rect> skipRect = aSkipRect ? Some(ToRect(*aSkipRect)) : Nothing(); + RefPtr<DrawTarget> dt = InitDrawTarget( + refDT, ToRect(aRect), aSpreadRadius, aBlurRadius, + dirtyRect.ptrOr(nullptr), skipRect.ptrOr(nullptr), aUseHardwareAccel); + if (!dt || !dt->IsValid()) { + return nullptr; + } + + auto context = MakeUnique<gfxContext>(dt); + context->SetMatrix(Matrix::Translation(-mBlur.GetRect().TopLeft())); + return context; +} + +already_AddRefed<DrawTarget> gfxAlphaBoxBlur::InitDrawTarget( + const DrawTarget* aReferenceDT, const Rect& aRect, + const IntSize& aSpreadRadius, const IntSize& aBlurRadius, + const Rect* aDirtyRect, const Rect* aSkipRect, bool aUseHardwareAccel) { + mBlur.Init(aRect, aSpreadRadius, aBlurRadius, aDirtyRect, aSkipRect); + size_t blurDataSize = mBlur.GetSurfaceAllocationSize(); + if (blurDataSize == 0) { + return nullptr; + } + + BackendType backend = aReferenceDT->GetBackendType(); + + // Check if the backend has an accelerated DrawSurfaceWithShadow. + // Currently, only D2D1.1 supports this. + // Otherwise, DrawSurfaceWithShadow only supports square blurs without spread. + // When blurring small draw targets such as short spans text, the cost of + // creating and flushing an accelerated draw target may exceed the speedup + // gained from the faster blur. It's up to the users of this blur + // to determine whether they want to use hardware acceleration. + if (aBlurRadius.IsSquare() && aSpreadRadius.IsEmpty() && aUseHardwareAccel && + backend == BackendType::DIRECT2D1_1) { + mAccelerated = true; + } + + if (mAccelerated) { + // Note: CreateShadowDrawTarget is only implemented for Cairo. + mDrawTarget = aReferenceDT->CreateShadowDrawTarget( + mBlur.GetSize(), SurfaceFormat::A8, + AlphaBoxBlur::CalculateBlurSigma(aBlurRadius.width)); + if (mDrawTarget) { + // See Bug 1526045 - this is to force DT initialization. + mDrawTarget->ClearRect(gfx::Rect()); + } + } else { + // Make an alpha-only surface to draw on. We will play with the data after + // everything is drawn to create a blur effect. + // This will be freed when the DrawTarget dies + mData = static_cast<uint8_t*>(calloc(1, blurDataSize)); + if (!mData) { + return nullptr; + } + mDrawTarget = + Factory::DoesBackendSupportDataDrawtarget(backend) + ? Factory::CreateDrawTargetForData(backend, mData, mBlur.GetSize(), + mBlur.GetStride(), + SurfaceFormat::A8) + : gfxPlatform::CreateDrawTargetForData( + mData, mBlur.GetSize(), mBlur.GetStride(), SurfaceFormat::A8); + } + + if (!mDrawTarget || !mDrawTarget->IsValid()) { + if (mData) { + free(mData); + } + + return nullptr; + } + + if (mData) { + mDrawTarget->AddUserData(reinterpret_cast<UserDataKey*>(mDrawTarget.get()), + mData, free); + } + + mDrawTarget->SetTransform(Matrix::Translation(-mBlur.GetRect().TopLeft())); + return do_AddRef(mDrawTarget); +} + +already_AddRefed<SourceSurface> gfxAlphaBoxBlur::DoBlur( + const sRGBColor* aShadowColor, IntPoint* aOutTopLeft) { + if (aOutTopLeft) { + *aOutTopLeft = mBlur.GetRect().TopLeft(); + } + + RefPtr<SourceSurface> blurMask; + if (mData) { + mBlur.Blur(mData); + blurMask = mDrawTarget->Snapshot(); + } else if (mAccelerated) { + blurMask = mDrawTarget->Snapshot(); + RefPtr<DrawTarget> blurDT = mDrawTarget->CreateSimilarDrawTarget( + blurMask->GetSize(), SurfaceFormat::A8); + if (!blurDT) { + return nullptr; + } + blurDT->DrawSurfaceWithShadow( + blurMask, Point(0, 0), + ShadowOptions( + DeviceColor::MaskOpaqueWhite(), Point(0, 0), + AlphaBoxBlur::CalculateBlurSigma(mBlur.GetBlurRadius().width)), + CompositionOp::OP_OVER); + blurMask = blurDT->Snapshot(); + } + + if (!aShadowColor) { + return blurMask.forget(); + } + + RefPtr<DrawTarget> shadowDT = mDrawTarget->CreateSimilarDrawTarget( + blurMask->GetSize(), SurfaceFormat::B8G8R8A8); + if (!shadowDT) { + return nullptr; + } + ColorPattern shadowColor(ToDeviceColor(*aShadowColor)); + shadowDT->MaskSurface(shadowColor, blurMask, Point(0, 0)); + + return shadowDT->Snapshot(); +} + +void gfxAlphaBoxBlur::Paint(gfxContext* aDestinationCtx) { + if (mDrawTarget && !mAccelerated && !mData) { + return; + } + + DrawTarget* dest = aDestinationCtx->GetDrawTarget(); + if (!dest) { + NS_WARNING("Blurring not supported for Thebes contexts!"); + return; + } + + RefPtr<gfxPattern> thebesPat = aDestinationCtx->GetPattern(); + Pattern* pat = thebesPat->GetPattern(dest, nullptr); + if (!pat) { + NS_WARNING("Failed to get pattern for blur!"); + return; + } + + IntPoint topLeft; + RefPtr<SourceSurface> mask = DoBlur(nullptr, &topLeft); + if (!mask) { + NS_ERROR("Failed to create mask!"); + return; + } + + // Avoid a semi-expensive clip operation if we can, otherwise + // clip to the dirty rect + Rect* dirtyRect = mBlur.GetDirtyRect(); + if (dirtyRect) { + dest->PushClipRect(*dirtyRect); + } + + Matrix oldTransform = dest->GetTransform(); + Matrix newTransform = oldTransform; + newTransform.PreTranslate(topLeft); + dest->SetTransform(newTransform); + + dest->MaskSurface(*pat, mask, Point(0, 0)); + + dest->SetTransform(oldTransform); + + if (dirtyRect) { + dest->PopClip(); + } +} + +IntSize gfxAlphaBoxBlur::CalculateBlurRadius(const gfxPoint& aStd) { + mozilla::gfx::Point std(Float(aStd.x), Float(aStd.y)); + IntSize size = AlphaBoxBlur::CalculateBlurRadius(std); + return IntSize(size.width, size.height); +} + +struct BlurCacheKey : public PLDHashEntryHdr { + typedef const BlurCacheKey& KeyType; + typedef const BlurCacheKey* KeyTypePointer; + enum { ALLOW_MEMMOVE = true }; + + IntSize mMinSize; + IntSize mBlurRadius; + sRGBColor mShadowColor; + BackendType mBackend; + RectCornerRadii mCornerRadii; + bool mIsInset; + + // Only used for inset blurs + IntSize mInnerMinSize; + + BlurCacheKey(const IntSize& aMinSize, const IntSize& aBlurRadius, + const RectCornerRadii* aCornerRadii, + const sRGBColor& aShadowColor, BackendType aBackendType) + : BlurCacheKey(aMinSize, IntSize(0, 0), aBlurRadius, aCornerRadii, + aShadowColor, false, aBackendType) {} + + explicit BlurCacheKey(const BlurCacheKey* aOther) + : mMinSize(aOther->mMinSize), + mBlurRadius(aOther->mBlurRadius), + mShadowColor(aOther->mShadowColor), + mBackend(aOther->mBackend), + mCornerRadii(aOther->mCornerRadii), + mIsInset(aOther->mIsInset), + mInnerMinSize(aOther->mInnerMinSize) {} + + explicit BlurCacheKey(const IntSize& aOuterMinSize, + const IntSize& aInnerMinSize, + const IntSize& aBlurRadius, + const RectCornerRadii* aCornerRadii, + const sRGBColor& aShadowColor, bool aIsInset, + BackendType aBackendType) + : mMinSize(aOuterMinSize), + mBlurRadius(aBlurRadius), + mShadowColor(aShadowColor), + mBackend(aBackendType), + mCornerRadii(aCornerRadii ? *aCornerRadii : RectCornerRadii()), + mIsInset(aIsInset), + mInnerMinSize(aInnerMinSize) {} + + BlurCacheKey(BlurCacheKey&&) = default; + + static PLDHashNumber HashKey(const KeyTypePointer aKey) { + PLDHashNumber hash = 0; + hash = AddToHash(hash, aKey->mMinSize.width, aKey->mMinSize.height); + hash = AddToHash(hash, aKey->mBlurRadius.width, aKey->mBlurRadius.height); + + hash = AddToHash( + hash, HashBytes(&aKey->mShadowColor.r, sizeof(aKey->mShadowColor.r))); + hash = AddToHash( + hash, HashBytes(&aKey->mShadowColor.g, sizeof(aKey->mShadowColor.g))); + hash = AddToHash( + hash, HashBytes(&aKey->mShadowColor.b, sizeof(aKey->mShadowColor.b))); + hash = AddToHash( + hash, HashBytes(&aKey->mShadowColor.a, sizeof(aKey->mShadowColor.a))); + + for (int i = 0; i < 4; i++) { + hash = AddToHash(hash, aKey->mCornerRadii[i].width, + aKey->mCornerRadii[i].height); + } + + hash = AddToHash(hash, (uint32_t)aKey->mBackend); + + if (aKey->mIsInset) { + hash = AddToHash(hash, aKey->mInnerMinSize.width, + aKey->mInnerMinSize.height); + } + return hash; + } + + bool KeyEquals(KeyTypePointer aKey) const { + if (aKey->mMinSize == mMinSize && aKey->mBlurRadius == mBlurRadius && + aKey->mCornerRadii == mCornerRadii && + aKey->mShadowColor == mShadowColor && aKey->mBackend == mBackend) { + if (mIsInset) { + return (mInnerMinSize == aKey->mInnerMinSize); + } + + return true; + } + + return false; + } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } +}; + +/** + * This class is what is cached. It need to be allocated in an object separated + * to the cache entry to be able to be tracked by the nsExpirationTracker. + * */ +struct BlurCacheData { + BlurCacheData(SourceSurface* aBlur, const IntMargin& aBlurMargin, + BlurCacheKey&& aKey) + : mBlur(aBlur), mBlurMargin(aBlurMargin), mKey(std::move(aKey)) {} + + BlurCacheData(BlurCacheData&& aOther) = default; + + nsExpirationState* GetExpirationState() { return &mExpirationState; } + + nsExpirationState mExpirationState; + RefPtr<SourceSurface> mBlur; + IntMargin mBlurMargin; + BlurCacheKey mKey; +}; + +/** + * This class implements a cache with no maximum size, that retains the + * SourceSurfaces used to draw the blurs. + * + * An entry stays in the cache as long as it is used often. + */ +class BlurCache final : public nsExpirationTracker<BlurCacheData, 4> { + public: + BlurCache() + : nsExpirationTracker<BlurCacheData, 4>(GENERATION_MS, "BlurCache") {} + + virtual void NotifyExpired(BlurCacheData* aObject) override { + RemoveObject(aObject); + mHashEntries.Remove(aObject->mKey); + } + + BlurCacheData* Lookup(const IntSize& aMinSize, const IntSize& aBlurRadius, + const RectCornerRadii* aCornerRadii, + const sRGBColor& aShadowColor, + BackendType aBackendType) { + BlurCacheData* blur = mHashEntries.Get(BlurCacheKey( + aMinSize, aBlurRadius, aCornerRadii, aShadowColor, aBackendType)); + if (blur) { + MarkUsed(blur); + } + + return blur; + } + + BlurCacheData* LookupInsetBoxShadow(const IntSize& aOuterMinSize, + const IntSize& aInnerMinSize, + const IntSize& aBlurRadius, + const RectCornerRadii* aCornerRadii, + const sRGBColor& aShadowColor, + BackendType aBackendType) { + bool insetBoxShadow = true; + BlurCacheKey key(aOuterMinSize, aInnerMinSize, aBlurRadius, aCornerRadii, + aShadowColor, insetBoxShadow, aBackendType); + BlurCacheData* blur = mHashEntries.Get(key); + if (blur) { + MarkUsed(blur); + } + + return blur; + } + + void RegisterEntry(UniquePtr<BlurCacheData> aValue) { + nsresult rv = AddObject(aValue.get()); + if (NS_FAILED(rv)) { + // We are OOM, and we cannot track this object. We don't want stall + // entries in the hash table (since the expiration tracker is responsible + // for removing the cache entries), so we avoid putting that entry in the + // table, which is a good thing considering we are short on memory + // anyway, we probably don't want to retain things. + return; + } + mHashEntries.InsertOrUpdate(aValue->mKey, std::move(aValue)); + } + + protected: + static const uint32_t GENERATION_MS = 1000; + /** + * FIXME use nsTHashtable to avoid duplicating the BlurCacheKey. + * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47 + */ + nsClassHashtable<BlurCacheKey, BlurCacheData> mHashEntries; +}; + +static BlurCache* gBlurCache = nullptr; + +static IntSize ComputeMinSizeForShadowShape(const RectCornerRadii* aCornerRadii, + const IntSize& aBlurRadius, + IntMargin& aOutSlice, + const IntSize& aRectSize) { + Size cornerSize(0, 0); + if (aCornerRadii) { + const RectCornerRadii& corners = *aCornerRadii; + for (const auto i : mozilla::AllPhysicalCorners()) { + cornerSize.width = std::max(cornerSize.width, corners[i].width); + cornerSize.height = std::max(cornerSize.height, corners[i].height); + } + } + + IntSize margin = IntSize::Ceil(cornerSize) + aBlurRadius; + aOutSlice = + IntMargin(margin.height, margin.width, margin.height, margin.width); + + IntSize minSize(aOutSlice.LeftRight() + 1, aOutSlice.TopBottom() + 1); + + // If aRectSize is smaller than minSize, the border-image approach won't + // work; there's no way to squeeze parts of the min box-shadow source + // image such that the result looks correct. So we need to adjust minSize + // in such a way that we can later draw it without stretching in the affected + // dimension. We also need to adjust "slice" to ensure that we're not trying + // to slice away more than we have. + if (aRectSize.width < minSize.width) { + minSize.width = aRectSize.width; + aOutSlice.left = 0; + aOutSlice.right = 0; + } + if (aRectSize.height < minSize.height) { + minSize.height = aRectSize.height; + aOutSlice.top = 0; + aOutSlice.bottom = 0; + } + + MOZ_ASSERT(aOutSlice.LeftRight() <= minSize.width); + MOZ_ASSERT(aOutSlice.TopBottom() <= minSize.height); + return minSize; +} + +static void CacheBlur(DrawTarget* aDT, const IntSize& aMinSize, + const IntSize& aBlurRadius, + const RectCornerRadii* aCornerRadii, + const sRGBColor& aShadowColor, + const IntMargin& aBlurMargin, SourceSurface* aBoxShadow) { + gBlurCache->RegisterEntry(MakeUnique<BlurCacheData>( + aBoxShadow, aBlurMargin, + BlurCacheKey(aMinSize, aBlurRadius, aCornerRadii, aShadowColor, + aDT->GetBackendType()))); +} + +// Blurs a small surface and creates the colored box shadow. +static already_AddRefed<SourceSurface> CreateBoxShadow( + DrawTarget* aDestDrawTarget, const IntSize& aMinSize, + const RectCornerRadii* aCornerRadii, const IntSize& aBlurRadius, + const sRGBColor& aShadowColor, bool aMirrorCorners, + IntMargin& aOutBlurMargin) { + gfxAlphaBoxBlur blur; + Rect minRect(Point(0, 0), Size(aMinSize)); + Rect blurRect(minRect); + // If mirroring corners, we only need to draw the top-left quadrant. + // Use ceil to preserve the remaining 1x1 middle area for minimized box + // shadows. + if (aMirrorCorners) { + blurRect.SizeTo(ceil(blurRect.Width() * 0.5f), + ceil(blurRect.Height() * 0.5f)); + } + IntSize zeroSpread(0, 0); + RefPtr<DrawTarget> blurDT = + blur.InitDrawTarget(aDestDrawTarget, blurRect, zeroSpread, aBlurRadius); + if (!blurDT) { + return nullptr; + } + + ColorPattern black(DeviceColor::MaskOpaqueBlack()); + + if (aCornerRadii) { + RefPtr<Path> roundedRect = + MakePathForRoundedRect(*blurDT, minRect, *aCornerRadii); + blurDT->Fill(roundedRect, black); + } else { + blurDT->FillRect(minRect, black); + } + + IntPoint topLeft; + RefPtr<SourceSurface> result = blur.DoBlur(&aShadowColor, &topLeft); + if (!result) { + return nullptr; + } + + // Since blurRect is at (0, 0), we can find the inflated margin by + // negating the new rect origin, which would have been negative if + // the rect was inflated. + aOutBlurMargin = IntMargin(-topLeft.y, -topLeft.x, -topLeft.y, -topLeft.x); + + return result.forget(); +} + +static already_AddRefed<SourceSurface> GetBlur( + gfxContext* aDestinationCtx, const IntSize& aRectSize, + const IntSize& aBlurRadius, const RectCornerRadii* aCornerRadii, + const sRGBColor& aShadowColor, bool aMirrorCorners, + IntMargin& aOutBlurMargin, IntMargin& aOutSlice, IntSize& aOutMinSize) { + if (!gBlurCache) { + gBlurCache = new BlurCache(); + } + + IntSize minSize = ComputeMinSizeForShadowShape(aCornerRadii, aBlurRadius, + aOutSlice, aRectSize); + + // We can get seams using the min size rect when drawing to the destination + // rect if we have a non-pixel aligned destination transformation. In those + // cases, fallback to just rendering the destination rect. During printing, we + // record all the Moz 2d commands and replay them on the parent side with + // Cairo. Cairo printing uses StretchDIBits to stretch the surface. However, + // since our source image is only 1px for some parts, we make thousands of + // calls. Instead just render the blur ourself here as one image and send it + // over for printing. + // TODO: May need to change this with the blob renderer in WR since it also + // records. + Matrix destMatrix = aDestinationCtx->CurrentMatrix(); + bool useDestRect = !destMatrix.IsRectilinear() || + destMatrix.HasNonIntegerTranslation() || + aDestinationCtx->GetDrawTarget()->IsRecording(); + if (useDestRect) { + minSize = aRectSize; + } + + int32_t maxTextureSize = gfxPlatform::MaxTextureSize(); + if (minSize.width > maxTextureSize || minSize.height > maxTextureSize) { + return nullptr; + } + + aOutMinSize = minSize; + + DrawTarget* destDT = aDestinationCtx->GetDrawTarget(); + + if (!useDestRect) { + BlurCacheData* cached = + gBlurCache->Lookup(minSize, aBlurRadius, aCornerRadii, aShadowColor, + destDT->GetBackendType()); + if (cached) { + // See CreateBoxShadow() for these values + aOutBlurMargin = cached->mBlurMargin; + RefPtr<SourceSurface> blur = cached->mBlur; + return blur.forget(); + } + } + + RefPtr<SourceSurface> boxShadow = + CreateBoxShadow(destDT, minSize, aCornerRadii, aBlurRadius, aShadowColor, + aMirrorCorners, aOutBlurMargin); + if (!boxShadow) { + return nullptr; + } + + if (RefPtr<SourceSurface> opt = destDT->OptimizeSourceSurface(boxShadow)) { + boxShadow = opt; + } + + if (!useDestRect) { + CacheBlur(destDT, minSize, aBlurRadius, aCornerRadii, aShadowColor, + aOutBlurMargin, boxShadow); + } + return boxShadow.forget(); +} + +void gfxAlphaBoxBlur::ShutdownBlurCache() { + delete gBlurCache; + gBlurCache = nullptr; +} + +static Rect RectWithEdgesTRBL(Float aTop, Float aRight, Float aBottom, + Float aLeft) { + return Rect(aLeft, aTop, aRight - aLeft, aBottom - aTop); +} + +static bool ShouldStretchSurface(DrawTarget* aDT, SourceSurface* aSurface) { + // Use stretching if possible, since it leads to less seams when the + // destination is transformed. However, don't do this if we're using cairo, + // because if cairo is using pixman it won't render anything for large + // stretch factors because pixman's internal fixed point precision is not + // high enough to handle those scale factors. + return aDT->GetBackendType() != BackendType::CAIRO; +} + +static void RepeatOrStretchSurface(DrawTarget* aDT, SourceSurface* aSurface, + const Rect& aDest, const Rect& aSrc, + const Rect& aSkipRect) { + if (aSkipRect.Contains(aDest)) { + return; + } + + if (ShouldStretchSurface(aDT, aSurface)) { + aDT->DrawSurface(aSurface, aDest, aSrc); + return; + } + + SurfacePattern pattern(aSurface, ExtendMode::REPEAT, + Matrix::Translation(aDest.TopLeft() - aSrc.TopLeft()), + SamplingFilter::GOOD, RoundedToInt(aSrc)); + aDT->FillRect(aDest, pattern); +} + +static void DrawCorner(DrawTarget* aDT, SourceSurface* aSurface, + const Rect& aDest, const Rect& aSrc, + const Rect& aSkipRect) { + if (aSkipRect.Contains(aDest)) { + return; + } + + aDT->DrawSurface(aSurface, aDest, aSrc); +} + +static void DrawMinBoxShadow(DrawTarget* aDestDrawTarget, + SourceSurface* aSourceBlur, const Rect& aDstOuter, + const Rect& aDstInner, const Rect& aSrcOuter, + const Rect& aSrcInner, const Rect& aSkipRect, + bool aMiddle = false) { + // Corners: top left, top right, bottom left, bottom right + DrawCorner(aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.X(), aDstInner.Y(), + aDstOuter.X()), + RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.X(), aSrcInner.Y(), + aSrcOuter.X()), + aSkipRect); + + DrawCorner(aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstOuter.Y(), aDstOuter.XMost(), aDstInner.Y(), + aDstInner.XMost()), + RectWithEdgesTRBL(aSrcOuter.Y(), aSrcOuter.XMost(), aSrcInner.Y(), + aSrcInner.XMost()), + aSkipRect); + + DrawCorner(aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.X(), + aDstOuter.YMost(), aDstOuter.X()), + RectWithEdgesTRBL(aSrcInner.YMost(), aSrcInner.X(), + aSrcOuter.YMost(), aSrcOuter.X()), + aSkipRect); + + DrawCorner(aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstInner.YMost(), aDstOuter.XMost(), + aDstOuter.YMost(), aDstInner.XMost()), + RectWithEdgesTRBL(aSrcInner.YMost(), aSrcOuter.XMost(), + aSrcOuter.YMost(), aSrcInner.XMost()), + aSkipRect); + + // Edges: top, left, right, bottom + RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.XMost(), + aDstInner.Y(), aDstInner.X()), + RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(), + aSrcInner.Y(), aSrcInner.X()), + aSkipRect); + RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstInner.Y(), aDstInner.X(), + aDstInner.YMost(), aDstOuter.X()), + RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(), + aSrcInner.YMost(), aSrcOuter.X()), + aSkipRect); + + RepeatOrStretchSurface( + aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstInner.Y(), aDstOuter.XMost(), aDstInner.YMost(), + aDstInner.XMost()), + RectWithEdgesTRBL(aSrcInner.Y(), aSrcOuter.XMost(), aSrcInner.YMost(), + aSrcInner.XMost()), + aSkipRect); + RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.XMost(), + aDstOuter.YMost(), aDstInner.X()), + RectWithEdgesTRBL(aSrcInner.YMost(), aSrcInner.XMost(), + aSrcOuter.YMost(), aSrcInner.X()), + aSkipRect); + + // Middle part + if (aMiddle) { + RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstInner.Y(), aDstInner.XMost(), + aDstInner.YMost(), aDstInner.X()), + RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.XMost(), + aSrcInner.YMost(), aSrcInner.X()), + aSkipRect); + } +} + +static void DrawMirroredRect(DrawTarget* aDT, SourceSurface* aSurface, + const Rect& aDest, const Point& aSrc, + Float aScaleX, Float aScaleY) { + SurfacePattern pattern( + aSurface, ExtendMode::CLAMP, + Matrix::Scaling(aScaleX, aScaleY) + .PreTranslate(-aSrc) + .PostTranslate(aScaleX < 0 ? aDest.XMost() : aDest.X(), + aScaleY < 0 ? aDest.YMost() : aDest.Y())); + aDT->FillRect(aDest, pattern); +} + +static void DrawMirroredBoxShadow(DrawTarget* aDT, SourceSurface* aSurface, + const Rect& aDestRect) { + Point center(ceil(aDestRect.X() + aDestRect.Width() / 2), + ceil(aDestRect.Y() + aDestRect.Height() / 2)); + Rect topLeft(aDestRect.X(), aDestRect.Y(), center.x - aDestRect.X(), + center.y - aDestRect.Y()); + Rect bottomRight(topLeft.BottomRight(), aDestRect.Size() - topLeft.Size()); + Rect topRight(bottomRight.X(), topLeft.Y(), bottomRight.Width(), + topLeft.Height()); + Rect bottomLeft(topLeft.X(), bottomRight.Y(), topLeft.Width(), + bottomRight.Height()); + DrawMirroredRect(aDT, aSurface, topLeft, Point(), 1, 1); + DrawMirroredRect(aDT, aSurface, topRight, Point(), -1, 1); + DrawMirroredRect(aDT, aSurface, bottomLeft, Point(), 1, -1); + DrawMirroredRect(aDT, aSurface, bottomRight, Point(), -1, -1); +} + +static void DrawMirroredCorner(DrawTarget* aDT, SourceSurface* aSurface, + const Rect& aDest, const Point& aSrc, + const Rect& aSkipRect, Float aScaleX, + Float aScaleY) { + if (aSkipRect.Contains(aDest)) { + return; + } + + DrawMirroredRect(aDT, aSurface, aDest, aSrc, aScaleX, aScaleY); +} + +static void RepeatOrStretchMirroredSurface(DrawTarget* aDT, + SourceSurface* aSurface, + const Rect& aDest, const Rect& aSrc, + const Rect& aSkipRect, Float aScaleX, + Float aScaleY) { + if (aSkipRect.Contains(aDest)) { + return; + } + + if (ShouldStretchSurface(aDT, aSurface)) { + aScaleX *= aDest.Width() / aSrc.Width(); + aScaleY *= aDest.Height() / aSrc.Height(); + DrawMirroredRect(aDT, aSurface, aDest, aSrc.TopLeft(), aScaleX, aScaleY); + return; + } + + SurfacePattern pattern( + aSurface, ExtendMode::REPEAT, + Matrix::Scaling(aScaleX, aScaleY) + .PreTranslate(-aSrc.TopLeft()) + .PostTranslate(aScaleX < 0 ? aDest.XMost() : aDest.X(), + aScaleY < 0 ? aDest.YMost() : aDest.Y()), + SamplingFilter::GOOD, RoundedToInt(aSrc)); + aDT->FillRect(aDest, pattern); +} + +static void DrawMirroredMinBoxShadow( + DrawTarget* aDestDrawTarget, SourceSurface* aSourceBlur, + const Rect& aDstOuter, const Rect& aDstInner, const Rect& aSrcOuter, + const Rect& aSrcInner, const Rect& aSkipRect, bool aMiddle = false) { + // Corners: top left, top right, bottom left, bottom right + // Compute quadrant bounds and then clip them to corners along + // dimensions where we need to stretch from min size. + Point center(ceil(aDstOuter.X() + aDstOuter.Width() / 2), + ceil(aDstOuter.Y() + aDstOuter.Height() / 2)); + Rect topLeft(aDstOuter.X(), aDstOuter.Y(), center.x - aDstOuter.X(), + center.y - aDstOuter.Y()); + Rect bottomRight(topLeft.BottomRight(), aDstOuter.Size() - topLeft.Size()); + Rect topRight(bottomRight.X(), topLeft.Y(), bottomRight.Width(), + topLeft.Height()); + Rect bottomLeft(topLeft.X(), bottomRight.Y(), topLeft.Width(), + bottomRight.Height()); + + // Check if the middle part has been minimized along each dimension. + // If so, those will be strecthed/drawn separately and need to be clipped out. + if (aSrcInner.Width() == 1) { + topLeft.SetRightEdge(aDstInner.X()); + topRight.SetLeftEdge(aDstInner.XMost()); + bottomLeft.SetRightEdge(aDstInner.X()); + bottomRight.SetLeftEdge(aDstInner.XMost()); + } + if (aSrcInner.Height() == 1) { + topLeft.SetBottomEdge(aDstInner.Y()); + topRight.SetBottomEdge(aDstInner.Y()); + bottomLeft.SetTopEdge(aDstInner.YMost()); + bottomRight.SetTopEdge(aDstInner.YMost()); + } + + DrawMirroredCorner(aDestDrawTarget, aSourceBlur, topLeft, aSrcOuter.TopLeft(), + aSkipRect, 1, 1); + DrawMirroredCorner(aDestDrawTarget, aSourceBlur, topRight, + aSrcOuter.TopLeft(), aSkipRect, -1, 1); + DrawMirroredCorner(aDestDrawTarget, aSourceBlur, bottomLeft, + aSrcOuter.TopLeft(), aSkipRect, 1, -1); + DrawMirroredCorner(aDestDrawTarget, aSourceBlur, bottomRight, + aSrcOuter.TopLeft(), aSkipRect, -1, -1); + + // Edges: top, bottom, left, right + // Draw middle edges where they need to be stretched. The top and left + // sections that are part of the top-left quadrant will be mirrored to + // the bottom and right sections, respectively. + if (aSrcInner.Width() == 1) { + Rect dstTop = RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.XMost(), + aDstInner.Y(), aDstInner.X()); + Rect srcTop = RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(), + aSrcInner.Y(), aSrcInner.X()); + Rect dstBottom = RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.XMost(), + aDstOuter.YMost(), aDstInner.X()); + Rect srcBottom = RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(), + aSrcInner.Y(), aSrcInner.X()); + // If we only need to stretch along the X axis and we're drawing + // the middle section, just sample all the way to the center of the + // source on the Y axis to avoid extra draw calls. + if (aMiddle && aSrcInner.Height() != 1) { + dstTop.SetBottomEdge(center.y); + srcTop.SetHeight(dstTop.Height()); + dstBottom.SetTopEdge(dstTop.YMost()); + srcBottom.SetHeight(dstBottom.Height()); + } + RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstTop, srcTop, + aSkipRect, 1, 1); + RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstBottom, + srcBottom, aSkipRect, 1, -1); + } + + if (aSrcInner.Height() == 1) { + Rect dstLeft = RectWithEdgesTRBL(aDstInner.Y(), aDstInner.X(), + aDstInner.YMost(), aDstOuter.X()); + Rect srcLeft = RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(), + aSrcInner.YMost(), aSrcOuter.X()); + Rect dstRight = RectWithEdgesTRBL(aDstInner.Y(), aDstOuter.XMost(), + aDstInner.YMost(), aDstInner.XMost()); + Rect srcRight = RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(), + aSrcInner.YMost(), aSrcOuter.X()); + // Only stretching on Y axis, so sample source to the center of the X axis. + if (aMiddle && aSrcInner.Width() != 1) { + dstLeft.SetRightEdge(center.x); + srcLeft.SetWidth(dstLeft.Width()); + dstRight.SetLeftEdge(dstLeft.XMost()); + srcRight.SetWidth(dstRight.Width()); + } + RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstLeft, + srcLeft, aSkipRect, 1, 1); + RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstRight, + srcRight, aSkipRect, -1, 1); + } + + // If we need to stretch along both dimensions, then the middle part + // must be drawn separately. + if (aMiddle && aSrcInner.Width() == 1 && aSrcInner.Height() == 1) { + RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstInner.Y(), aDstInner.XMost(), + aDstInner.YMost(), aDstInner.X()), + RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.XMost(), + aSrcInner.YMost(), aSrcInner.X()), + aSkipRect); + } +} + +/*** + * We draw a blurred a rectangle by only blurring a smaller rectangle and + * splitting the rectangle into 9 parts. + * First, a small minimum source rect is calculated and used to create a blur + * mask since the actual blurring itself is expensive. Next, we use the mask + * with the given shadow color to create a minimally-sized box shadow of the + * right color. Finally, we cut out the 9 parts from the box-shadow source and + * paint each part in the right place, stretching the non-corner parts to fill + * the space between the corners. + */ + +/* static */ +void gfxAlphaBoxBlur::BlurRectangle(gfxContext* aDestinationCtx, + const gfxRect& aRect, + const RectCornerRadii* aCornerRadii, + const gfxPoint& aBlurStdDev, + const sRGBColor& aShadowColor, + const gfxRect& aDirtyRect, + const gfxRect& aSkipRect) { + if (!RectIsInt32Safe(ToRect(aRect))) { + return; + } + + IntSize blurRadius = CalculateBlurRadius(aBlurStdDev); + bool mirrorCorners = !aCornerRadii || aCornerRadii->AreRadiiSame(); + + IntRect rect = RoundedToInt(ToRect(aRect)); + IntMargin blurMargin; + IntMargin slice; + IntSize minSize; + RefPtr<SourceSurface> boxShadow = + GetBlur(aDestinationCtx, rect.Size(), blurRadius, aCornerRadii, + aShadowColor, mirrorCorners, blurMargin, slice, minSize); + if (!boxShadow) { + return; + } + + DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget(); + destDrawTarget->PushClipRect(ToRect(aDirtyRect)); + + // Copy the right parts from boxShadow into destDrawTarget. The middle parts + // will be stretched, border-image style. + + Rect srcOuter(Point(blurMargin.left, blurMargin.top), Size(minSize)); + Rect srcInner(srcOuter); + srcOuter.Inflate(Margin(blurMargin)); + srcInner.Deflate(Margin(slice)); + + Rect dstOuter(rect); + Rect dstInner(rect); + dstOuter.Inflate(Margin(blurMargin)); + dstInner.Deflate(Margin(slice)); + + Rect skipRect = ToRect(aSkipRect); + + if (minSize == rect.Size()) { + // The target rect is smaller than the minimal size so just draw the surface + if (mirrorCorners) { + DrawMirroredBoxShadow(destDrawTarget, boxShadow, dstOuter); + } else { + destDrawTarget->DrawSurface(boxShadow, dstOuter, srcOuter); + } + } else { + if (mirrorCorners) { + DrawMirroredMinBoxShadow(destDrawTarget, boxShadow, dstOuter, dstInner, + srcOuter, srcInner, skipRect, true); + } else { + DrawMinBoxShadow(destDrawTarget, boxShadow, dstOuter, dstInner, srcOuter, + srcInner, skipRect, true); + } + } + + // A note about anti-aliasing and seems between adjacent parts: + // We don't explicitly disable anti-aliasing in the DrawSurface calls above, + // so if there's a transform on destDrawTarget that is not pixel-aligned, + // there will be seams between adjacent parts of the box-shadow. It's hard to + // avoid those without the use of an intermediate surface. + // You might think that we could avoid those by just turning off AA, but there + // is a problem with that: Box-shadow rendering needs to clip out the + // element's border box, and we'd like that clip to have anti-aliasing - + // especially if the element has rounded corners! So we can't do that unless + // we have a way to say "Please anti-alias the clip, but don't antialias the + // destination rect of the DrawSurface call". + + destDrawTarget->PopClip(); +} + +static already_AddRefed<Path> GetBoxShadowInsetPath( + DrawTarget* aDrawTarget, const Rect aOuterRect, const Rect aInnerRect, + const RectCornerRadii* aInnerClipRadii) { + /*** + * We create an inset path by having two rects. + * + * ----------------------- + * | ________________ | + * | | | | + * | | | | + * | ------------------ | + * |_____________________| + * + * The outer rect and the inside rect. The path + * creates a frame around the content where we draw the inset shadow. + */ + RefPtr<PathBuilder> builder = + aDrawTarget->CreatePathBuilder(FillRule::FILL_EVEN_ODD); + AppendRectToPath(builder, aOuterRect, true); + + if (aInnerClipRadii) { + AppendRoundedRectToPath(builder, aInnerRect, *aInnerClipRadii, false); + } else { + AppendRectToPath(builder, aInnerRect, false); + } + return builder->Finish(); +} + +static void FillDestinationPath( + gfxContext* aDestinationCtx, const Rect& aDestinationRect, + const Rect& aShadowClipRect, const sRGBColor& aShadowColor, + const RectCornerRadii* aInnerClipRadii = nullptr) { + // When there is no blur radius, fill the path onto the destination + // surface. + aDestinationCtx->SetColor(aShadowColor); + DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget(); + RefPtr<Path> shadowPath = GetBoxShadowInsetPath( + destDrawTarget, aDestinationRect, aShadowClipRect, aInnerClipRadii); + + aDestinationCtx->SetPath(shadowPath); + aDestinationCtx->Fill(); +} + +static void CacheInsetBlur(const IntSize& aMinOuterSize, + const IntSize& aMinInnerSize, + const IntSize& aBlurRadius, + const RectCornerRadii* aCornerRadii, + const sRGBColor& aShadowColor, + BackendType aBackendType, + SourceSurface* aBoxShadow) { + bool isInsetBlur = true; + BlurCacheKey key(aMinOuterSize, aMinInnerSize, aBlurRadius, aCornerRadii, + aShadowColor, isInsetBlur, aBackendType); + IntMargin blurMargin(0, 0, 0, 0); + + gBlurCache->RegisterEntry( + MakeUnique<BlurCacheData>(aBoxShadow, blurMargin, std::move(key))); +} + +already_AddRefed<SourceSurface> gfxAlphaBoxBlur::GetInsetBlur( + const Rect& aOuterRect, const Rect& aWhitespaceRect, bool aIsDestRect, + const sRGBColor& aShadowColor, const IntSize& aBlurRadius, + const RectCornerRadii* aInnerClipRadii, DrawTarget* aDestDrawTarget, + bool aMirrorCorners) { + if (!gBlurCache) { + gBlurCache = new BlurCache(); + } + + IntSize outerSize = IntSize::Truncate(aOuterRect.Size()); + IntSize whitespaceSize = IntSize::Truncate(aWhitespaceRect.Size()); + if (!aIsDestRect) { + BlurCacheData* cached = gBlurCache->LookupInsetBoxShadow( + outerSize, whitespaceSize, aBlurRadius, aInnerClipRadii, aShadowColor, + aDestDrawTarget->GetBackendType()); + if (cached) { + // So we don't forget the actual cached blur + RefPtr<SourceSurface> cachedBlur = cached->mBlur; + return cachedBlur.forget(); + } + } + + // If we can do a min rect, the whitespace rect will be expanded in Init to + // aOuterRect. + Rect blurRect = aIsDestRect ? aOuterRect : aWhitespaceRect; + // If mirroring corners, we only need to draw the top-left quadrant. + // Use ceil to preserve the remaining 1x1 middle area for minimized box + // shadows. + if (aMirrorCorners) { + blurRect.SizeTo(ceil(blurRect.Width() * 0.5f), + ceil(blurRect.Height() * 0.5f)); + } + IntSize zeroSpread(0, 0); + RefPtr<DrawTarget> minDrawTarget = + InitDrawTarget(aDestDrawTarget, blurRect, zeroSpread, aBlurRadius); + if (!minDrawTarget) { + return nullptr; + } + + // This is really annoying. When we create the AlphaBoxBlur, the DrawTarget + // has a translation applied to it that is the topLeft point. This is actually + // the rect we gave it plus the blur radius. The rects we give this for the + // outer and whitespace rects are based at (0, 0). We could either translate + // those rects when we don't have a destination rect or ignore the translation + // when using the dest rect. The dest rects layout gives us expect this + // translation. + if (!aIsDestRect) { + minDrawTarget->SetTransform(Matrix()); + } + + // Fill in the path between the inside white space / outer rects + // NOT the inner frame + RefPtr<Path> maskPath = GetBoxShadowInsetPath( + minDrawTarget, aOuterRect, aWhitespaceRect, aInnerClipRadii); + + ColorPattern black(DeviceColor::MaskOpaqueBlack()); + minDrawTarget->Fill(maskPath, black); + + // Blur and fill in with the color we actually wanted + RefPtr<SourceSurface> minInsetBlur = DoBlur(&aShadowColor); + if (!minInsetBlur) { + return nullptr; + } + + if (RefPtr<SourceSurface> opt = + aDestDrawTarget->OptimizeSourceSurface(minInsetBlur)) { + minInsetBlur = opt; + } + + if (!aIsDestRect) { + CacheInsetBlur(outerSize, whitespaceSize, aBlurRadius, aInnerClipRadii, + aShadowColor, aDestDrawTarget->GetBackendType(), + minInsetBlur); + } + + return minInsetBlur.forget(); +} + +/*** + * We create our minimal rect with 2 rects. + * The first is the inside whitespace rect, that is "cut out" + * from the box. This is (1). This must be the size + * of the blur radius + corner radius so we can have a big enough + * inside cut. + * + * The second (2) is one blur radius surrounding the inner + * frame of (1). This is the amount of blur space required + * to get a proper blend. + * + * B = one blur size + * W = one blur + corner radii - known as inner margin + * ___________________________________ + * | | + * | | | | + * | (2) | (1) | (2) | + * | B | W | B | + * | | | | + * | | | | + * | | | + * |________________________________| + */ +static void GetBlurMargins(const RectCornerRadii* aInnerClipRadii, + const IntSize& aBlurRadius, Margin& aOutBlurMargin, + Margin& aOutInnerMargin) { + Size cornerSize(0, 0); + if (aInnerClipRadii) { + const RectCornerRadii& corners = *aInnerClipRadii; + for (const auto i : mozilla::AllPhysicalCorners()) { + cornerSize.width = std::max(cornerSize.width, corners[i].width); + cornerSize.height = std::max(cornerSize.height, corners[i].height); + } + } + + // Only the inside whitespace size cares about the border radius size. + // Outer sizes only care about blur. + IntSize margin = IntSize::Ceil(cornerSize) + aBlurRadius; + + aOutInnerMargin.SizeTo(margin.height, margin.width, margin.height, + margin.width); + aOutBlurMargin.SizeTo(aBlurRadius.height, aBlurRadius.width, + aBlurRadius.height, aBlurRadius.width); +} + +static bool GetInsetBoxShadowRects(const Margin& aBlurMargin, + const Margin& aInnerMargin, + const Rect& aShadowClipRect, + const Rect& aDestinationRect, + Rect& aOutWhitespaceRect, + Rect& aOutOuterRect) { + // We always copy (2 * blur radius) + corner radius worth of data to the + // destination rect This covers the blend of the path + the actual blur Need + // +1 so that we copy the edges correctly as we'll copy over the min box + // shadow corners then the +1 for the edges between Note, the (x,y) + // coordinates are from the blur margin since the frame outside the whitespace + // rect is 1 blur radius extra space. + Rect insideWhiteSpace(aBlurMargin.left, aBlurMargin.top, + aInnerMargin.LeftRight() + 1, + aInnerMargin.TopBottom() + 1); + + // If the inner white space rect is larger than the shadow clip rect + // our approach does not work as we'll just copy one corner + // and cover the destination. In those cases, fallback to the destination rect + bool useDestRect = (aShadowClipRect.Width() <= aInnerMargin.LeftRight()) || + (aShadowClipRect.Height() <= aInnerMargin.TopBottom()); + + if (useDestRect) { + aOutWhitespaceRect = aShadowClipRect; + aOutOuterRect = aDestinationRect; + } else { + aOutWhitespaceRect = insideWhiteSpace; + aOutOuterRect = aOutWhitespaceRect; + aOutOuterRect.Inflate(aBlurMargin); + } + + return useDestRect; +} + +void gfxAlphaBoxBlur::BlurInsetBox( + gfxContext* aDestinationCtx, const Rect& aDestinationRect, + const Rect& aShadowClipRect, const IntSize& aBlurRadius, + const sRGBColor& aShadowColor, const RectCornerRadii* aInnerClipRadii, + const Rect& aSkipRect, const Point& aShadowOffset) { + if ((aBlurRadius.width == 0 && aBlurRadius.height == 0) || + aShadowClipRect.IsEmpty()) { + FillDestinationPath(aDestinationCtx, aDestinationRect, aShadowClipRect, + aShadowColor, aInnerClipRadii); + return; + } + + DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget(); + + Margin innerMargin; + Margin blurMargin; + GetBlurMargins(aInnerClipRadii, aBlurRadius, blurMargin, innerMargin); + + Rect whitespaceRect; + Rect outerRect; + bool useDestRect = + GetInsetBoxShadowRects(blurMargin, innerMargin, aShadowClipRect, + aDestinationRect, whitespaceRect, outerRect); + + // Check that the inset margin between the outer and whitespace rects is + // symmetric, and that all corner radii are the same, in which case the blur + // can be mirrored. + Margin checkMargin = outerRect - whitespaceRect; + bool mirrorCorners = checkMargin.left == checkMargin.right && + checkMargin.top == checkMargin.bottom && + (!aInnerClipRadii || aInnerClipRadii->AreRadiiSame()); + RefPtr<SourceSurface> minBlur = + GetInsetBlur(outerRect, whitespaceRect, useDestRect, aShadowColor, + aBlurRadius, aInnerClipRadii, destDrawTarget, mirrorCorners); + if (!minBlur) { + return; + } + + if (useDestRect) { + Rect destBlur = aDestinationRect; + destBlur.Inflate(blurMargin); + if (mirrorCorners) { + DrawMirroredBoxShadow(destDrawTarget, minBlur.get(), destBlur); + } else { + Rect srcBlur(Point(0, 0), Size(minBlur->GetSize())); + MOZ_ASSERT(RoundedOut(srcBlur).Size() == RoundedOut(destBlur).Size()); + destDrawTarget->DrawSurface(minBlur, destBlur, srcBlur); + } + } else { + Rect srcOuter(outerRect); + Rect srcInner(srcOuter); + srcInner.Deflate(blurMargin); // The outer color fill + srcInner.Deflate(innerMargin); // The inner whitespace + + // The shadow clip rect already takes into account the spread radius + Rect outerFillRect(aShadowClipRect); + outerFillRect.Inflate(blurMargin); + FillDestinationPath(aDestinationCtx, aDestinationRect, outerFillRect, + aShadowColor); + + // Inflate once for the frame around the whitespace + Rect destRect(aShadowClipRect); + destRect.Inflate(blurMargin); + + // Deflate for the blurred in white space + Rect destInnerRect(aShadowClipRect); + destInnerRect.Deflate(innerMargin); + + if (mirrorCorners) { + DrawMirroredMinBoxShadow(destDrawTarget, minBlur, destRect, destInnerRect, + srcOuter, srcInner, aSkipRect); + } else { + DrawMinBoxShadow(destDrawTarget, minBlur, destRect, destInnerRect, + srcOuter, srcInner, aSkipRect); + } + } +} diff --git a/gfx/thebes/gfxBlur.h b/gfx/thebes/gfxBlur.h new file mode 100644 index 0000000000..da6ccae9a7 --- /dev/null +++ b/gfx/thebes/gfxBlur.h @@ -0,0 +1,203 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_BLUR_H +#define GFX_BLUR_H + +#include "gfxTypes.h" +#include "gfxRect.h" +#include "nsSize.h" +#include "gfxPoint.h" +#include "mozilla/RefPtr.h" +#include "mozilla/gfx/Blur.h" + +class gfxContext; + +namespace mozilla { +namespace gfx { +struct sRGBColor; +struct RectCornerRadii; +class SourceSurface; +class DrawTarget; +} // namespace gfx +} // namespace mozilla + +/** + * Implementation of a triple box blur approximation of a Gaussian blur. + * + * A Gaussian blur is good for blurring because, when done independently + * in the horizontal and vertical directions, it matches the result that + * would be obtained using a different (rotated) set of axes. A triple + * box blur is a very close approximation of a Gaussian. + * + * Creates an 8-bit alpha channel context for callers to draw in, + * spreads the contents of that context, blurs the contents, and applies + * it as an alpha mask on a different existing context. + * + * A spread N makes each output pixel the maximum value of all source + * pixels within a square of side length 2N+1 centered on the output pixel. + * + * A temporary surface is created in the Init function. The caller then draws + * any desired content onto the context acquired through GetContext, and lastly + * calls Paint to apply the blurred content as an alpha mask. + */ +class gfxAlphaBoxBlur final { + typedef mozilla::gfx::sRGBColor sRGBColor; + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::RectCornerRadii RectCornerRadii; + + public: + gfxAlphaBoxBlur() = default; + + ~gfxAlphaBoxBlur(); + + /** + * Constructs a box blur and initializes the temporary surface. + * + * @param aDestinationCtx The destination to blur to. + * + * @param aRect The coordinates of the surface to create in device units. + * + * @param aBlurRadius The blur radius in pixels. This is the radius of + * the entire (triple) kernel function. Each individual box blur has + * radius approximately 1/3 this value, or diameter approximately 2/3 + * this value. This parameter should nearly always be computed using + * CalculateBlurRadius, below. + * + * @param aDirtyRect A pointer to a dirty rect, measured in device units, + * if available. This will be used for optimizing the blur operation. It + * is safe to pass nullptr here. + * + * @param aSkipRect A pointer to a rect, measured in device units, that + * represents an area where blurring is unnecessary and shouldn't be done + * for speed reasons. It is safe to pass nullptr here. + * + * @param aUseHardwareAccel Flag to state whether or not we can use hardware + * acceleration to speed up this blur. + */ + mozilla::UniquePtr<gfxContext> Init( + gfxContext* aDestinationCtx, const gfxRect& aRect, + const mozilla::gfx::IntSize& aSpreadRadius, + const mozilla::gfx::IntSize& aBlurRadius, const gfxRect* aDirtyRect, + const gfxRect* aSkipRect, bool aUseHardwareAccel = true); + + already_AddRefed<DrawTarget> InitDrawTarget( + const mozilla::gfx::DrawTarget* aReferenceDT, + const mozilla::gfx::Rect& aRect, + const mozilla::gfx::IntSize& aSpreadRadius, + const mozilla::gfx::IntSize& aBlurRadius, + const mozilla::gfx::Rect* aDirtyRect = nullptr, + const mozilla::gfx::Rect* aSkipRect = nullptr, + bool aUseHardwareAccel = true); + + /** + * Performs the blur and optionally colors the result if aShadowColor is not + * null. + */ + already_AddRefed<mozilla::gfx::SourceSurface> DoBlur( + const mozilla::gfx::sRGBColor* aShadowColor = nullptr, + mozilla::gfx::IntPoint* aOutTopLeft = nullptr); + + /** + * Does the actual blurring/spreading and mask applying. Users of this + * object must have drawn whatever they want to be blurred onto the internal + * gfxContext returned by GetContext before calling this. + * + * @param aDestinationCtx The graphics context on which to apply the + * blurred mask. + */ + void Paint(gfxContext* aDestinationCtx); + + /** + * Calculates a blur radius that, when used with box blur, approximates + * a Gaussian blur with the given standard deviation. The result of + * this function should be used as the aBlurRadius parameter to Init, + * above. + */ + static mozilla::gfx::IntSize CalculateBlurRadius( + const gfxPoint& aStandardDeviation); + + /** + * Blurs a coloured rectangle onto aDestinationCtx. This is equivalent + * to calling Init(), drawing a rectangle onto the returned surface + * and then calling Paint, but may let us optimize better in the + * backend. + * + * @param aDestinationCtx The destination to blur to. + * @param aRect The rectangle to blur in device pixels. + * @param aCornerRadii Corner radii for aRect, if it is a rounded + * rectangle. + * @param aBlurRadius The standard deviation of the blur. + * @param aShadowColor The color to draw the blurred shadow. + * @param aDirtyRect An area in device pixels that is dirty and + * needs to be redrawn. + * @param aSkipRect An area in device pixels to avoid blurring + * over, to prevent unnecessary work. + */ + static void BlurRectangle(gfxContext* aDestinationCtx, const gfxRect& aRect, + const RectCornerRadii* aCornerRadii, + const gfxPoint& aBlurStdDev, + const sRGBColor& aShadowColor, + const gfxRect& aDirtyRect, + const gfxRect& aSkipRect); + + static void ShutdownBlurCache(); + + /*** + * Blurs an inset box shadow according to a given path. + * This is equivalent to calling Init(), drawing the inset path, + * and calling paint. Do not call Init() if using this method. + * + * @param aDestinationCtx The destination to blur to. + * @param aDestinationRect The destination rect in device pixels + * @param aShadowClipRect The destiniation inner rect of the + * inset path in device pixels. + * @param aBlurRadius The standard deviation of the blur. + * @param aShadowColor The color of the blur. + * @param aInnerClipRadii Corner radii for the inside rect if it is a + * rounded rect. + * @param aSkipRect An area in device pixels we don't have to + * paint in. + */ + void BlurInsetBox(gfxContext* aDestinationCtx, + const mozilla::gfx::Rect& aDestinationRect, + const mozilla::gfx::Rect& aShadowClipRect, + const mozilla::gfx::IntSize& aBlurRadius, + const mozilla::gfx::sRGBColor& aShadowColor, + const RectCornerRadii* aInnerClipRadii, + const mozilla::gfx::Rect& aSkipRect, + const mozilla::gfx::Point& aShadowOffset); + + protected: + already_AddRefed<mozilla::gfx::SourceSurface> GetInsetBlur( + const mozilla::gfx::Rect& aOuterRect, + const mozilla::gfx::Rect& aWhitespaceRect, bool aIsDestRect, + const mozilla::gfx::sRGBColor& aShadowColor, + const mozilla::gfx::IntSize& aBlurRadius, + const RectCornerRadii* aInnerClipRadii, DrawTarget* aDestDrawTarget, + bool aMirrorCorners); + + /** + * The DrawTarget of the temporary alpha surface. + */ + RefPtr<DrawTarget> mDrawTarget; + + /** + * The temporary alpha surface. + */ + uint8_t* mData = nullptr; + + /** + * The object that actually does the blurring for us. + */ + mozilla::gfx::AlphaBoxBlur mBlur; + + /** + * Indicates using DrawTarget-accelerated blurs. + */ + bool mAccelerated = false; +}; + +#endif /* GFX_BLUR_H */ diff --git a/gfx/thebes/gfxColor.h b/gfx/thebes/gfxColor.h new file mode 100644 index 0000000000..eec2b0b70e --- /dev/null +++ b/gfx/thebes/gfxColor.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_COLOR_H +#define GFX_COLOR_H + +#include "mozilla/Attributes.h" // for MOZ_ALWAYS_INLINE +#include "mozilla/gfx/Types.h" // for mozilla::gfx::SurfaceFormatBit + +/** + * Fast approximate division by 255. It has the property that + * for all 0 <= n <= 255*255, GFX_DIVIDE_BY_255(n) == n/255. + * But it only uses two adds and two shifts instead of an + * integer division (which is expensive on many processors). + * + * equivalent to ((v)/255) + */ +#define GFX_DIVIDE_BY_255(v) \ + (((((unsigned)(v)) << 8) + ((unsigned)(v)) + 255) >> 16) + +/** + * Fast premultiply + * + * equivalent to (((c)*(a))/255) + */ +uint8_t MOZ_ALWAYS_INLINE gfxPreMultiply(uint8_t c, uint8_t a) { + return GFX_DIVIDE_BY_255((c) * (a)); +} + +/** + * Pack the 4 8-bit channels (A,R,G,B) + * into a 32-bit packed NON-premultiplied pixel. + */ +uint32_t MOZ_ALWAYS_INLINE gfxPackedPixelNoPreMultiply(uint8_t a, uint8_t r, + uint8_t g, uint8_t b) { + return (((a) << mozilla::gfx::SurfaceFormatBit::OS_A) | + ((r) << mozilla::gfx::SurfaceFormatBit::OS_R) | + ((g) << mozilla::gfx::SurfaceFormatBit::OS_G) | + ((b) << mozilla::gfx::SurfaceFormatBit::OS_B)); +} + +/** + * Pack the 4 8-bit channels (A,R,G,B) + * into a 32-bit packed premultiplied pixel. + */ +uint32_t MOZ_ALWAYS_INLINE gfxPackedPixel(uint8_t a, uint8_t r, uint8_t g, + uint8_t b) { + if (a == 0x00) + return 0x00000000; + else if (a == 0xFF) { + return gfxPackedPixelNoPreMultiply(a, r, g, b); + } else { + return ((a) << mozilla::gfx::SurfaceFormatBit::OS_A) | + (gfxPreMultiply(r, a) << mozilla::gfx::SurfaceFormatBit::OS_R) | + (gfxPreMultiply(g, a) << mozilla::gfx::SurfaceFormatBit::OS_G) | + (gfxPreMultiply(b, a) << mozilla::gfx::SurfaceFormatBit::OS_B); + } +} + +#endif /* _GFX_COLOR_H */ diff --git a/gfx/thebes/gfxContext.cpp b/gfx/thebes/gfxContext.cpp new file mode 100644 index 0000000000..6f097ce49f --- /dev/null +++ b/gfx/thebes/gfxContext.cpp @@ -0,0 +1,602 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <math.h> + +#include "mozilla/Alignment.h" + +#include "cairo.h" + +#include "gfxContext.h" + +#include "gfxMatrix.h" +#include "gfxUtils.h" +#include "gfxPattern.h" +#include "gfxPlatform.h" + +#include "gfx2DGlue.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/ProfilerLabels.h" +#include <algorithm> +#include "TextDrawTarget.h" + +#if XP_WIN +# include "gfxWindowsPlatform.h" +# include "mozilla/gfx/DeviceManagerDx.h" +#endif + +using namespace mozilla; +using namespace mozilla::gfx; + +#ifdef DEBUG +# define CURRENTSTATE_CHANGED() mAzureState.mContentChanged = true; +#else +# define CURRENTSTATE_CHANGED() +#endif + +PatternFromState::operator Pattern&() { + const gfxContext::AzureState& state = mContext->mAzureState; + + if (state.pattern) { + return *state.pattern->GetPattern( + mContext->mDT, + state.patternTransformChanged ? &state.patternTransform : nullptr); + } + + mPattern = new (mColorPattern.addr()) ColorPattern(state.color); + return *mPattern; +} + +/* static */ +UniquePtr<gfxContext> gfxContext::CreateOrNull(DrawTarget* aTarget) { + if (!aTarget || !aTarget->IsValid()) { + gfxCriticalNote << "Invalid target in gfxContext::CreateOrNull " + << hexa(aTarget); + return nullptr; + } + + return MakeUnique<gfxContext>(aTarget); +} + +gfxContext::~gfxContext() { + while (!mSavedStates.IsEmpty()) { + Restore(); + } + for (unsigned int c = 0; c < mAzureState.pushedClips.Length(); c++) { + mDT->PopClip(); + } +} + +mozilla::layout::TextDrawTarget* gfxContext::GetTextDrawer() const { + if (mDT->GetBackendType() == BackendType::WEBRENDER_TEXT) { + return static_cast<mozilla::layout::TextDrawTarget*>(&*mDT); + } + return nullptr; +} + +void gfxContext::Save() { + mSavedStates.AppendElement(mAzureState); + mAzureState.pushedClips.Clear(); +#ifdef DEBUG + mAzureState.mContentChanged = false; +#endif +} + +void gfxContext::Restore() { +#ifdef DEBUG + // gfxContext::Restore is used to restore AzureState. We need to restore it + // only if it was altered. The following APIs do change the content of + // AzureState, a user should save the state before using them and restore it + // after finishing painting: + // 1. APIs to setup how to paint, such as SetColor()/SetAntialiasMode(). All + // gfxContext SetXXXX public functions belong to this category, except + // gfxContext::SetPath & gfxContext::SetMatrix. + // 2. Clip functions, such as Clip() or PopClip(). You may call PopClip() + // directly instead of using gfxContext::Save if the clip region is the + // only thing that you altered in the target context. + // 3. Function of setup transform matrix, such as Multiply() and + // SetMatrix(). Using gfxContextMatrixAutoSaveRestore is more recommended + // if transform data is the only thing that you are going to alter. + // + // You will hit the assertion message below if there is no above functions + // been used between a pair of gfxContext::Save and gfxContext::Restore. + // Considerate to remove that pair of Save/Restore if hitting that assertion. + // + // In the other hand, the following APIs do not alter the content of the + // current AzureState, therefore, there is no need to save & restore + // AzureState: + // 1. constant member functions of gfxContext. + // 2. Paint calls, such as Line()/Rectangle()/Fill(). Those APIs change the + // content of drawing buffer, which is not part of AzureState. + // 3. Path building APIs, such as SetPath()/MoveTo()/LineTo()/NewPath(). + // Surprisingly, path information is not stored in AzureState either. + // Save current AzureState before using these type of APIs does nothing but + // make performance worse. + NS_ASSERTION( + mAzureState.mContentChanged || mAzureState.pushedClips.Length() > 0, + "The context of the current AzureState is not altered after " + "Save() been called. you may consider to remove this pair of " + "gfxContext::Save/Restore."); +#endif + + for (unsigned int c = 0; c < mAzureState.pushedClips.Length(); c++) { + mDT->PopClip(); + } + + mAzureState = mSavedStates.PopLastElement(); + + ChangeTransform(mAzureState.transform, false); +} + +// drawing + +void gfxContext::Fill(const Pattern& aPattern) { + AUTO_PROFILER_LABEL("gfxContext::Fill", GRAPHICS); + + CompositionOp op = GetOp(); + + if (mPathIsRect) { + MOZ_ASSERT(!mTransformChanged); + + if (op == CompositionOp::OP_SOURCE) { + // Emulate cairo operator source which is bound by mask! + mDT->ClearRect(mRect); + mDT->FillRect(mRect, aPattern, DrawOptions(1.0f)); + } else { + mDT->FillRect(mRect, aPattern, DrawOptions(1.0f, op, mAzureState.aaMode)); + } + } else { + EnsurePath(); + mDT->Fill(mPath, aPattern, DrawOptions(1.0f, op, mAzureState.aaMode)); + } +} + +// XXX snapToPixels is only valid when snapping for filled +// rectangles and for even-width stroked rectangles. +// For odd-width stroked rectangles, we need to offset x/y by +// 0.5... +void gfxContext::Rectangle(const gfxRect& rect, bool snapToPixels) { + Rect rec = ToRect(rect); + + if (snapToPixels) { + gfxRect newRect(rect); + if (UserToDevicePixelSnapped(newRect, SnapOption::IgnoreScale)) { + gfxMatrix mat = CurrentMatrixDouble(); + if (mat.Invert()) { + // We need the user space rect. + rec = ToRect(mat.TransformBounds(newRect)); + } else { + rec = Rect(); + } + } + } + + if (!mPathBuilder && !mPathIsRect) { + mPathIsRect = true; + mRect = rec; + return; + } + + EnsurePathBuilder(); + + mPathBuilder->MoveTo(rec.TopLeft()); + mPathBuilder->LineTo(rec.TopRight()); + mPathBuilder->LineTo(rec.BottomRight()); + mPathBuilder->LineTo(rec.BottomLeft()); + mPathBuilder->Close(); +} + +void gfxContext::SnappedClip(const gfxRect& rect) { + Rect rec = ToRect(rect); + + gfxRect newRect(rect); + if (UserToDevicePixelSnapped(newRect, SnapOption::IgnoreScale)) { + gfxMatrix mat = CurrentMatrixDouble(); + if (mat.Invert()) { + // We need the user space rect. + rec = ToRect(mat.TransformBounds(newRect)); + } else { + rec = Rect(); + } + } + + Clip(rec); +} + +bool gfxContext::UserToDevicePixelSnapped(gfxRect& rect, + SnapOptions aOptions) const { + if (mDT->GetUserData(&sDisablePixelSnapping)) { + return false; + } + + // if we're not at 1.0 scale, don't snap, unless we're + // ignoring the scale. If we're not -just- a scale, + // never snap. + const gfxFloat epsilon = 0.0000001; +#define WITHIN_E(a, b) (fabs((a) - (b)) < epsilon) + Matrix mat = mAzureState.transform; + if (!aOptions.contains(SnapOption::IgnoreScale) && + (!WITHIN_E(mat._11, 1.0) || !WITHIN_E(mat._22, 1.0) || + !WITHIN_E(mat._12, 0.0) || !WITHIN_E(mat._21, 0.0))) { + return false; + } +#undef WITHIN_E + + gfxPoint p1 = UserToDevice(rect.TopLeft()); + gfxPoint p2 = UserToDevice(rect.TopRight()); + gfxPoint p3 = UserToDevice(rect.BottomRight()); + + // Check that the rectangle is axis-aligned. For an axis-aligned rectangle, + // two opposite corners define the entire rectangle. So check if + // the axis-aligned rectangle with opposite corners p1 and p3 + // define an axis-aligned rectangle whose other corners are p2 and p4. + // We actually only need to check one of p2 and p4, since an affine + // transform maps parallelograms to parallelograms. + if (!(p2 == gfxPoint(p1.x, p3.y) || p2 == gfxPoint(p3.x, p1.y))) { + return false; + } + + if (aOptions.contains(SnapOption::PrioritizeSize)) { + // Snap the dimensions of the rect, to minimize distortion; only after that + // will we snap its position. In particular, this guarantees that a square + // remains square after snapping, which may not be the case if each edge is + // independently snapped to device pixels. + + // Use the same rounding approach as gfx::BasePoint::Round. + rect.SizeTo(std::floor(rect.width + 0.5), std::floor(rect.height + 0.5)); + + // Find the top-left corner based on the original center and the snapped + // size, then snap this new corner to the grid. + gfxPoint center = (p1 + p3) / 2; + gfxPoint topLeft = center - gfxPoint(rect.width / 2.0, rect.height / 2.0); + topLeft.Round(); + rect.MoveTo(topLeft); + } else { + p1.Round(); + p3.Round(); + rect.MoveTo(gfxPoint(std::min(p1.x, p3.x), std::min(p1.y, p3.y))); + rect.SizeTo(gfxSize(std::max(p1.x, p3.x) - rect.X(), + std::max(p1.y, p3.y) - rect.Y())); + } + + return true; +} + +bool gfxContext::UserToDevicePixelSnapped(gfxPoint& pt, + bool ignoreScale) const { + if (mDT->GetUserData(&sDisablePixelSnapping)) { + return false; + } + + // if we're not at 1.0 scale, don't snap, unless we're + // ignoring the scale. If we're not -just- a scale, + // never snap. + const gfxFloat epsilon = 0.0000001; +#define WITHIN_E(a, b) (fabs((a) - (b)) < epsilon) + Matrix mat = mAzureState.transform; + if (!ignoreScale && (!WITHIN_E(mat._11, 1.0) || !WITHIN_E(mat._22, 1.0) || + !WITHIN_E(mat._12, 0.0) || !WITHIN_E(mat._21, 0.0))) { + return false; + } +#undef WITHIN_E + + pt = UserToDevice(pt); + pt.Round(); + return true; +} + +void gfxContext::SetDash(const Float* dashes, int ndash, Float offset, + Float devPxScale) { + CURRENTSTATE_CHANGED() + + mAzureState.dashPattern.SetLength(ndash); + for (int i = 0; i < ndash; i++) { + mAzureState.dashPattern[i] = dashes[i] * devPxScale; + } + mAzureState.strokeOptions.mDashLength = ndash; + mAzureState.strokeOptions.mDashOffset = offset * devPxScale; + mAzureState.strokeOptions.mDashPattern = + ndash ? mAzureState.dashPattern.Elements() : nullptr; +} + +bool gfxContext::CurrentDash(FallibleTArray<Float>& dashes, + Float* offset) const { + if (mAzureState.strokeOptions.mDashLength == 0 || + !dashes.Assign(mAzureState.dashPattern, fallible)) { + return false; + } + + *offset = mAzureState.strokeOptions.mDashOffset; + + return true; +} + +// clipping +void gfxContext::Clip(const Rect& rect) { + AzureState::PushedClip clip = {nullptr, rect, mAzureState.transform}; + mAzureState.pushedClips.AppendElement(clip); + mDT->PushClipRect(rect); + NewPath(); +} + +void gfxContext::Clip(Path* aPath) { + mDT->PushClip(aPath); + AzureState::PushedClip clip = {aPath, Rect(), mAzureState.transform}; + mAzureState.pushedClips.AppendElement(clip); +} + +void gfxContext::Clip() { + if (mPathIsRect) { + MOZ_ASSERT(!mTransformChanged); + + AzureState::PushedClip clip = {nullptr, mRect, mAzureState.transform}; + mAzureState.pushedClips.AppendElement(clip); + mDT->PushClipRect(mRect); + } else { + EnsurePath(); + mDT->PushClip(mPath); + AzureState::PushedClip clip = {mPath, Rect(), mAzureState.transform}; + mAzureState.pushedClips.AppendElement(clip); + } +} + +gfxRect gfxContext::GetClipExtents(ClipExtentsSpace aSpace) const { + Rect rect = GetAzureDeviceSpaceClipBounds(); + + if (rect.IsZeroArea()) { + return gfxRect(0, 0, 0, 0); + } + + if (aSpace == eUserSpace) { + Matrix mat = mAzureState.transform; + mat.Invert(); + rect = mat.TransformBounds(rect); + } + + return ThebesRect(rect); +} + +bool gfxContext::ExportClip(ClipExporter& aExporter) const { + ForAllClips([&](const AzureState::PushedClip& aClip) -> void { + gfx::Matrix transform = aClip.transform; + transform.PostTranslate(-GetDeviceOffset()); + + aExporter.BeginClip(transform); + if (aClip.path) { + aClip.path->StreamToSink(&aExporter); + } else { + aExporter.MoveTo(aClip.rect.TopLeft()); + aExporter.LineTo(aClip.rect.TopRight()); + aExporter.LineTo(aClip.rect.BottomRight()); + aExporter.LineTo(aClip.rect.BottomLeft()); + aExporter.Close(); + } + aExporter.EndClip(); + }); + + return true; +} + +// rendering sources + +bool gfxContext::GetDeviceColor(DeviceColor& aColorOut) const { + if (mAzureState.pattern) { + return mAzureState.pattern->GetSolidColor(aColorOut); + } + + aColorOut = mAzureState.color; + return true; +} + +already_AddRefed<gfxPattern> gfxContext::GetPattern() const { + RefPtr<gfxPattern> pat; + + if (mAzureState.pattern) { + pat = mAzureState.pattern; + } else { + pat = new gfxPattern(mAzureState.color); + } + return pat.forget(); +} + +void gfxContext::Paint(Float alpha) const { + AUTO_PROFILER_LABEL("gfxContext::Paint", GRAPHICS); + + Matrix mat = mDT->GetTransform(); + mat.Invert(); + Rect paintRect = mat.TransformBounds(Rect(Point(0, 0), Size(mDT->GetSize()))); + + mDT->FillRect(paintRect, PatternFromState(this), DrawOptions(alpha, GetOp())); +} + +#ifdef MOZ_DUMP_PAINTING +void gfxContext::WriteAsPNG(const char* aFile) { + gfxUtils::WriteAsPNG(mDT, aFile); +} + +void gfxContext::DumpAsDataURI() { gfxUtils::DumpAsDataURI(mDT); } + +void gfxContext::CopyAsDataURI() { gfxUtils::CopyAsDataURI(mDT); } +#endif + +void gfxContext::EnsurePath() { + if (mPathBuilder) { + mPath = mPathBuilder->Finish(); + mPathBuilder = nullptr; + } + + if (mPath) { + if (mTransformChanged) { + Matrix mat = mAzureState.transform; + mat.Invert(); + mat = mPathTransform * mat; + mPathBuilder = mPath->TransformedCopyToBuilder(mat); + mPath = mPathBuilder->Finish(); + mPathBuilder = nullptr; + + mTransformChanged = false; + } + return; + } + + EnsurePathBuilder(); + mPath = mPathBuilder->Finish(); + mPathBuilder = nullptr; +} + +void gfxContext::EnsurePathBuilder() { + if (mPathBuilder && !mTransformChanged) { + return; + } + + if (mPath) { + if (!mTransformChanged) { + mPathBuilder = mPath->CopyToBuilder(); + mPath = nullptr; + } else { + Matrix invTransform = mAzureState.transform; + invTransform.Invert(); + Matrix toNewUS = mPathTransform * invTransform; + mPathBuilder = mPath->TransformedCopyToBuilder(toNewUS); + } + return; + } + + DebugOnly<PathBuilder*> oldPath = mPathBuilder.get(); + + if (!mPathBuilder) { + mPathBuilder = mDT->CreatePathBuilder(FillRule::FILL_WINDING); + + if (mPathIsRect) { + mPathBuilder->MoveTo(mRect.TopLeft()); + mPathBuilder->LineTo(mRect.TopRight()); + mPathBuilder->LineTo(mRect.BottomRight()); + mPathBuilder->LineTo(mRect.BottomLeft()); + mPathBuilder->Close(); + } + } + + if (mTransformChanged) { + // This could be an else if since this should never happen when + // mPathBuilder is nullptr and mPath is nullptr. But this way we can + // assert if all the state is as expected. + MOZ_ASSERT(oldPath); + MOZ_ASSERT(!mPathIsRect); + + Matrix invTransform = mAzureState.transform; + invTransform.Invert(); + Matrix toNewUS = mPathTransform * invTransform; + + RefPtr<Path> path = mPathBuilder->Finish(); + if (!path) { + gfxCriticalError() + << "gfxContext::EnsurePathBuilder failed in PathBuilder::Finish"; + } + mPathBuilder = path->TransformedCopyToBuilder(toNewUS); + } + + mPathIsRect = false; +} + +CompositionOp gfxContext::GetOp() const { + if (mAzureState.op != CompositionOp::OP_SOURCE) { + return mAzureState.op; + } + + if (mAzureState.pattern) { + if (mAzureState.pattern->IsOpaque()) { + return CompositionOp::OP_OVER; + } else { + return CompositionOp::OP_SOURCE; + } + } else { + if (mAzureState.color.a > 0.999) { + return CompositionOp::OP_OVER; + } else { + return CompositionOp::OP_SOURCE; + } + } +} + +/* SVG font code can change the transform after having set the pattern on the + * context. When the pattern is set it is in user space, if the transform is + * changed after doing so the pattern needs to be converted back into userspace. + * We just store the old pattern transform here so that we only do the work + * needed here if the pattern is actually used. + * We need to avoid doing this when this ChangeTransform comes from a restore, + * since the current pattern and the current transform are both part of the + * state we know the new mAzureState's values are valid. But if we assume + * a change they might become invalid since patternTransformChanged is part of + * the state and might be false for the restored AzureState. + */ +void gfxContext::ChangeTransform(const Matrix& aNewMatrix, + bool aUpdatePatternTransform) { + if (aUpdatePatternTransform && (mAzureState.pattern) && + !mAzureState.patternTransformChanged) { + mAzureState.patternTransform = GetDTTransform(); + mAzureState.patternTransformChanged = true; + } + + if (mPathIsRect) { + Matrix invMatrix = aNewMatrix; + + invMatrix.Invert(); + + Matrix toNewUS = mAzureState.transform * invMatrix; + + if (toNewUS.IsRectilinear()) { + mRect = toNewUS.TransformBounds(mRect); + mRect.NudgeToIntegers(); + } else { + mPathBuilder = mDT->CreatePathBuilder(FillRule::FILL_WINDING); + + mPathBuilder->MoveTo(toNewUS.TransformPoint(mRect.TopLeft())); + mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.TopRight())); + mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.BottomRight())); + mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.BottomLeft())); + mPathBuilder->Close(); + + mPathIsRect = false; + } + + // No need to consider the transform changed now! + mTransformChanged = false; + } else if ((mPath || mPathBuilder) && !mTransformChanged) { + mTransformChanged = true; + mPathTransform = mAzureState.transform; + } + + mAzureState.transform = aNewMatrix; + + mDT->SetTransform(GetDTTransform()); +} + +Rect gfxContext::GetAzureDeviceSpaceClipBounds() const { + Rect rect(mAzureState.deviceOffset.x + Float(mDT->GetRect().x), + mAzureState.deviceOffset.y + Float(mDT->GetRect().y), + Float(mDT->GetSize().width), Float(mDT->GetSize().height)); + ForAllClips([&](const AzureState::PushedClip& aClip) -> void { + if (aClip.path) { + rect.IntersectRect(rect, aClip.path->GetBounds(aClip.transform)); + } else { + rect.IntersectRect(rect, aClip.transform.TransformBounds(aClip.rect)); + } + }); + + return rect; +} + +template <typename F> +void gfxContext::ForAllClips(F&& aLambda) const { + for (const auto& state : mSavedStates) { + for (const auto& clip : state.pushedClips) { + aLambda(clip); + } + } + for (const auto& clip : mAzureState.pushedClips) { + aLambda(clip); + } +} diff --git a/gfx/thebes/gfxContext.h b/gfx/thebes/gfxContext.h new file mode 100644 index 0000000000..eaf2a11c45 --- /dev/null +++ b/gfx/thebes/gfxContext.h @@ -0,0 +1,809 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_CONTEXT_H +#define GFX_CONTEXT_H + +#include "gfx2DGlue.h" +#include "gfxPattern.h" +#include "gfxUtils.h" +#include "nsTArray.h" + +#include "mozilla/EnumSet.h" +#include "mozilla/gfx/2D.h" + +typedef struct _cairo cairo_t; +class GlyphBufferAzure; + +namespace mozilla { +namespace gfx { +struct RectCornerRadii; +} // namespace gfx +namespace layout { +class TextDrawTarget; +} // namespace layout +} // namespace mozilla + +class ClipExporter; + +/* This class lives on the stack and allows gfxContext users to easily, and + * performantly get a gfx::Pattern to use for drawing in their current context. + */ +class PatternFromState { + public: + explicit PatternFromState(const gfxContext* aContext) + : mContext(aContext), mPattern(nullptr) {} + ~PatternFromState() { + if (mPattern) { + mPattern->~Pattern(); + } + } + + operator mozilla::gfx::Pattern&(); + + private: + mozilla::AlignedStorage2<mozilla::gfx::ColorPattern> mColorPattern; + + const gfxContext* mContext; + mozilla::gfx::Pattern* mPattern; +}; + +/** + * This is the main class for doing actual drawing. It is initialized using + * a surface and can be drawn on. It manages various state information like + * a current transformation matrix (CTM), a current path, current color, + * etc. + * + * All drawing happens by creating a path and then stroking or filling it. + * The functions like Rectangle and Arc do not do any drawing themselves. + * When a path is drawn (stroked or filled), it is filled/stroked with a + * pattern set by SetPattern or SetColor. + * + * Note that the gfxContext takes coordinates in device pixels, + * as opposed to app units. + */ +class gfxContext final { +#ifdef DEBUG +# define CURRENTSTATE_CHANGED() mAzureState.mContentChanged = true; +#else +# define CURRENTSTATE_CHANGED() +#endif + + typedef mozilla::gfx::BackendType BackendType; + typedef mozilla::gfx::CapStyle CapStyle; + typedef mozilla::gfx::CompositionOp CompositionOp; + typedef mozilla::gfx::DeviceColor DeviceColor; + typedef mozilla::gfx::DrawOptions DrawOptions; + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::JoinStyle JoinStyle; + typedef mozilla::gfx::FillRule FillRule; + typedef mozilla::gfx::Float Float; + typedef mozilla::gfx::Matrix Matrix; + typedef mozilla::gfx::Path Path; + typedef mozilla::gfx::Pattern Pattern; + typedef mozilla::gfx::Point Point; + typedef mozilla::gfx::Rect Rect; + typedef mozilla::gfx::RectCornerRadii RectCornerRadii; + typedef mozilla::gfx::Size Size; + + public: + /** + * Initialize this context from a DrawTarget, which must be non-null. + * Strips any transform from aTarget, unless aPreserveTransform is true. + * aTarget will be flushed in the gfxContext's destructor. + */ + MOZ_NONNULL(2) + explicit gfxContext(DrawTarget* aTarget, const Point& aDeviceOffset = Point()) + : mDT(aTarget) { + mAzureState.deviceOffset = aDeviceOffset; + mDT->SetTransform(GetDTTransform()); + } + + MOZ_NONNULL(2) + gfxContext(DrawTarget* aTarget, bool aPreserveTransform) : mDT(aTarget) { + if (aPreserveTransform) { + SetMatrix(aTarget->GetTransform()); + } else { + mDT->SetTransform(GetDTTransform()); + } + } + + ~gfxContext(); + + /** + * Initialize this context from a DrawTarget. + * Strips any transform from aTarget. + * aTarget will be flushed in the gfxContext's destructor. + * If aTarget is null or invalid, nullptr is returned. The caller + * is responsible for handling this scenario as appropriate. + */ + static mozilla::UniquePtr<gfxContext> CreateOrNull(DrawTarget* aTarget); + + DrawTarget* GetDrawTarget() const { return mDT; } + + /** + * Returns the DrawTarget if it's actually a TextDrawTarget. + */ + mozilla::layout::TextDrawTarget* GetTextDrawer() const; + + /** + ** State + **/ + // XXX document exactly what bits are saved + void Save(); + void Restore(); + + /** + ** Paths & Drawing + **/ + + /** + * Fill the current path according to the current settings. + * + * Does not consume the current path. + */ + void Fill() { Fill(PatternFromState(this)); } + void Fill(const Pattern& aPattern); + + /** + * Forgets the current path. + */ + void NewPath() { + mPath = nullptr; + mPathBuilder = nullptr; + mPathIsRect = false; + mTransformChanged = false; + } + + /** + * Returns the current path. + */ + already_AddRefed<Path> GetPath() { + EnsurePath(); + RefPtr<Path> path(mPath); + return path.forget(); + } + + /** + * Sets the given path as the current path. + */ + void SetPath(Path* path) { + MOZ_ASSERT(path->GetBackendType() == mDT->GetBackendType() || + path->GetBackendType() == BackendType::RECORDING || + (mDT->GetBackendType() == BackendType::DIRECT2D1_1 && + path->GetBackendType() == BackendType::DIRECT2D)); + mPath = path; + mPathBuilder = nullptr; + mPathIsRect = false; + mTransformChanged = false; + } + + /** + * Draws the rectangle given by rect. + */ + void Rectangle(const gfxRect& rect) { return Rectangle(rect, false); } + void SnappedRectangle(const gfxRect& rect) { return Rectangle(rect, true); } + + private: + void Rectangle(const gfxRect& rect, bool snapToPixels); + + public: + /** + ** Transformation Matrix manipulation + **/ + + /** + * Post-multiplies 'other' onto the current CTM, i.e. this + * matrix's transformation will take place before the previously set + * transformations. + */ + void Multiply(const gfxMatrix& aMatrix) { Multiply(ToMatrix(aMatrix)); } + void Multiply(const Matrix& aOther) { + CURRENTSTATE_CHANGED() + ChangeTransform(aOther * mAzureState.transform); + } + + /** + * Replaces the current transformation matrix with matrix. + */ + void SetMatrix(const Matrix& aMatrix) { + CURRENTSTATE_CHANGED() + ChangeTransform(aMatrix); + } + void SetMatrixDouble(const gfxMatrix& aMatrix) { + SetMatrix(ToMatrix(aMatrix)); + } + + void SetCrossProcessPaintScale(float aScale) { + MOZ_ASSERT(mCrossProcessPaintScale == 1.0f, + "Should only be initialized once"); + mCrossProcessPaintScale = aScale; + } + + float GetCrossProcessPaintScale() const { return mCrossProcessPaintScale; } + + /** + * Returns the current transformation matrix. + */ + Matrix CurrentMatrix() const { return mAzureState.transform; } + gfxMatrix CurrentMatrixDouble() const { + return ThebesMatrix(CurrentMatrix()); + } + + /** + * Converts a point from device to user coordinates using the inverse + * transformation matrix. + */ + gfxPoint DeviceToUser(const gfxPoint& aPoint) const { + return ThebesPoint( + mAzureState.transform.Inverse().TransformPoint(ToPoint(aPoint))); + } + + /** + * Converts a size from device to user coordinates. This does not apply + * translation components of the matrix. + */ + Size DeviceToUser(const Size& aSize) const { + return mAzureState.transform.Inverse().TransformSize(aSize); + } + + /** + * Converts a rectangle from device to user coordinates; this has the + * same effect as using DeviceToUser on both the rectangle's point and + * size. + */ + gfxRect DeviceToUser(const gfxRect& aRect) const { + return ThebesRect( + mAzureState.transform.Inverse().TransformBounds(ToRect(aRect))); + } + + /** + * Converts a point from user to device coordinates using the transformation + * matrix. + */ + gfxPoint UserToDevice(const gfxPoint& aPoint) const { + return ThebesPoint(mAzureState.transform.TransformPoint(ToPoint(aPoint))); + } + + /** + * Converts a size from user to device coordinates. This does not apply + * translation components of the matrix. + */ + Size UserToDevice(const Size& aSize) const { + const auto& mtx = mAzureState.transform; + return Size(aSize.width * mtx._11 + aSize.height * mtx._12, + aSize.width * mtx._21 + aSize.height * mtx._22); + } + + /** + * Converts a rectangle from user to device coordinates. The + * resulting rectangle is the minimum device-space rectangle that + * encloses the user-space rectangle given. + */ + gfxRect UserToDevice(const gfxRect& rect) const { + return ThebesRect(mAzureState.transform.TransformBounds(ToRect(rect))); + } + + /** + * Takes the given rect and tries to align it to device pixels. If + * this succeeds, the method will return true, and the rect will + * be in device coordinates (already transformed by the CTM). If it + * fails, the method will return false, and the rect will not be + * changed. + * + * aOptions parameter: + * If IgnoreScale is set, then snapping will take place even if the CTM + * has a scale applied. Snapping never takes place if there is a rotation + * in the CTM. + * + * If PrioritizeSize is set, the rect's dimensions will first be snapped + * and then its position aligned to device pixels, rather than snapping + * the position of each edge independently. + */ + enum class SnapOption : uint8_t { + IgnoreScale = 1, + PrioritizeSize = 2, + }; + using SnapOptions = mozilla::EnumSet<SnapOption>; + bool UserToDevicePixelSnapped(gfxRect& rect, SnapOptions aOptions = {}) const; + + /** + * Takes the given point and tries to align it to device pixels. If + * this succeeds, the method will return true, and the point will + * be in device coordinates (already transformed by the CTM). If it + * fails, the method will return false, and the point will not be + * changed. + * + * If ignoreScale is true, then snapping will take place even if + * the CTM has a scale applied. Snapping never takes place if + * there is a rotation in the CTM. + */ + bool UserToDevicePixelSnapped(gfxPoint& pt, bool ignoreScale = false) const; + + /** + ** Painting sources + **/ + + /** + * Set a solid color to use for drawing. This color is in the device color + * space and is not transformed. + */ + void SetDeviceColor(const DeviceColor& aColor) { + CURRENTSTATE_CHANGED() + mAzureState.pattern = nullptr; + mAzureState.color = aColor; + } + + /** + * Gets the current color. It's returned in the device color space. + * returns false if there is something other than a color + * set as the current source (pattern, surface, etc) + */ + bool GetDeviceColor(DeviceColor& aColorOut) const; + + /** + * Returns true if color is neither opaque nor transparent (i.e. alpha is not + * 0 or 1), and false otherwise. If true, aColorOut is set on output. + */ + bool HasNonOpaqueNonTransparentColor(DeviceColor& aColorOut) const { + return GetDeviceColor(aColorOut) && 0.f < aColorOut.a && aColorOut.a < 1.f; + } + + /** + * Set a solid color in the sRGB color space to use for drawing. + * If CMS is not enabled, the color is treated as a device-space color + * and this call is identical to SetDeviceColor(). + */ + void SetColor(const mozilla::gfx::sRGBColor& aColor) { + CURRENTSTATE_CHANGED() + mAzureState.pattern = nullptr; + mAzureState.color = ToDeviceColor(aColor); + } + + /** + * Uses a pattern for drawing. + */ + void SetPattern(gfxPattern* pattern) { + CURRENTSTATE_CHANGED() + mAzureState.patternTransformChanged = false; + mAzureState.pattern = pattern; + } + + /** + * Get the source pattern (solid color, normal pattern, surface, etc) + */ + already_AddRefed<gfxPattern> GetPattern() const; + + /** + ** Painting + **/ + /** + * Paints the current source surface/pattern everywhere in the current + * clip region. + */ + void Paint(Float alpha = 1.0) const; + + /** + ** Line Properties + **/ + + // Set the dash pattern, applying devPxScale to convert passed-in lengths + // to device pixels (used by the SVGUtils::SetupStrokeGeometry caller, + // which has the desired dash pattern in CSS px). + void SetDash(const Float* dashes, int ndash, Float offset, Float devPxScale); + + // Return true if dashing is set, false if it's not enabled or the + // context is in an error state. |offset| can be nullptr to mean + // "don't care". + bool CurrentDash(FallibleTArray<Float>& dashes, Float* offset) const; + + /** + * Sets the line width that's used for line drawing. + */ + void SetLineWidth(Float width) { + CURRENTSTATE_CHANGED() + mAzureState.strokeOptions.mLineWidth = width; + } + + /** + * Returns the currently set line width. + * + * @see SetLineWidth + */ + Float CurrentLineWidth() const { + return mAzureState.strokeOptions.mLineWidth; + } + + /** + * Sets the line caps, i.e. how line endings are drawn. + */ + void SetLineCap(CapStyle cap) { + CURRENTSTATE_CHANGED() + mAzureState.strokeOptions.mLineCap = cap; + } + CapStyle CurrentLineCap() const { return mAzureState.strokeOptions.mLineCap; } + + /** + * Sets the line join, i.e. how the connection between two lines is + * drawn. + */ + void SetLineJoin(JoinStyle join) { + CURRENTSTATE_CHANGED() + mAzureState.strokeOptions.mLineJoin = join; + } + JoinStyle CurrentLineJoin() const { + return mAzureState.strokeOptions.mLineJoin; + } + + void SetMiterLimit(Float limit) { + CURRENTSTATE_CHANGED() + mAzureState.strokeOptions.mMiterLimit = limit; + } + Float CurrentMiterLimit() const { + return mAzureState.strokeOptions.mMiterLimit; + } + + /** + * Sets the operator used for all further drawing. The operator affects + * how drawing something will modify the destination. For example, the + * OVER operator will do alpha blending of source and destination, while + * SOURCE will replace the destination with the source. + */ + void SetOp(CompositionOp aOp) { + CURRENTSTATE_CHANGED() + mAzureState.op = aOp; + } + CompositionOp CurrentOp() const { return mAzureState.op; } + + void SetAntialiasMode(mozilla::gfx::AntialiasMode aMode) { + CURRENTSTATE_CHANGED() + mAzureState.aaMode = aMode; + } + mozilla::gfx::AntialiasMode CurrentAntialiasMode() const { + return mAzureState.aaMode; + } + + /** + ** Clipping + **/ + + /** + * Clips all further drawing to the current path. + * This does not consume the current path. + */ + void Clip(); + + /** + * Helper functions that will create a rect path and call Clip(). + * Any current path will be destroyed by these functions! + */ + void Clip(const gfxRect& aRect) { Clip(ToRect(aRect)); } + void Clip(const Rect& rect); // will clip to a rect + void SnappedClip(const gfxRect& rect); // snap rect and clip to the result + void Clip(Path* aPath); + + void PopClip() { + MOZ_ASSERT(!mAzureState.pushedClips.IsEmpty()); + mAzureState.pushedClips.RemoveLastElement(); + mDT->PopClip(); + } + + enum ClipExtentsSpace { + eUserSpace = 0, + eDeviceSpace = 1, + }; + + /** + * According to aSpace, this function will return the current bounds of + * the clip region in user space or device space. + */ + gfxRect GetClipExtents(ClipExtentsSpace aSpace = eUserSpace) const; + + /** + * Exports the current clip using the provided exporter. + */ + bool ExportClip(ClipExporter& aExporter) const; + + /** + * Groups + */ + void PushGroupForBlendBack(gfxContentType content, Float aOpacity = 1.0f, + mozilla::gfx::SourceSurface* aMask = nullptr, + const Matrix& aMaskTransform = Matrix()) const { + mDT->PushLayer(content == gfxContentType::COLOR, aOpacity, aMask, + aMaskTransform); + } + + void PopGroupAndBlend() const { mDT->PopLayer(); } + + Point GetDeviceOffset() const { return mAzureState.deviceOffset; } + void SetDeviceOffset(const Point& aOffset) { + mAzureState.deviceOffset = aOffset; + } + +#ifdef MOZ_DUMP_PAINTING + /** + * Debug functions to encode the current surface as a PNG and export it. + */ + + /** + * Writes a binary PNG file. + */ + void WriteAsPNG(const char* aFile); + + /** + * Write as a PNG encoded Data URL to stdout. + */ + void DumpAsDataURI(); + + /** + * Copy a PNG encoded Data URL to the clipboard. + */ + void CopyAsDataURI(); +#endif + + private: + friend class PatternFromState; + friend class GlyphBufferAzure; + + typedef mozilla::gfx::sRGBColor sRGBColor; + typedef mozilla::gfx::StrokeOptions StrokeOptions; + typedef mozilla::gfx::PathBuilder PathBuilder; + typedef mozilla::gfx::SourceSurface SourceSurface; + + struct AzureState { + AzureState() + : op(CompositionOp::OP_OVER), + color(0, 0, 0, 1.0f), + aaMode(mozilla::gfx::AntialiasMode::SUBPIXEL), + patternTransformChanged(false) +#ifdef DEBUG + , + mContentChanged(false) +#endif + { + } + + CompositionOp op; + DeviceColor color; + RefPtr<gfxPattern> pattern; + Matrix transform; + struct PushedClip { + RefPtr<Path> path; + Rect rect; + Matrix transform; + }; + CopyableTArray<PushedClip> pushedClips; + CopyableTArray<Float> dashPattern; + StrokeOptions strokeOptions; + mozilla::gfx::AntialiasMode aaMode; + bool patternTransformChanged; + Matrix patternTransform; + // This is used solely for using minimal intermediate surface size. + Point deviceOffset; +#ifdef DEBUG + // Whether the content of this AzureState changed after construction. + bool mContentChanged; +#endif + }; + + // This ensures mPath contains a valid path (in user space!) + void EnsurePath(); + // This ensures mPathBuilder contains a valid PathBuilder (in user space!) + void EnsurePathBuilder(); + CompositionOp GetOp() const; + void ChangeTransform(const Matrix& aNewMatrix, + bool aUpdatePatternTransform = true); + Rect GetAzureDeviceSpaceClipBounds() const; + Matrix GetDTTransform() const { + Matrix mat = mAzureState.transform; + mat.PostTranslate(-mAzureState.deviceOffset); + return mat; + } + + bool mPathIsRect = false; + bool mTransformChanged = false; + Matrix mPathTransform; + Rect mRect; + RefPtr<PathBuilder> mPathBuilder; + RefPtr<Path> mPath; + AzureState mAzureState; + nsTArray<AzureState> mSavedStates; + + // Iterate over all clips in the saved and current states, calling aLambda + // with each of them. + template <typename F> + void ForAllClips(F&& aLambda) const; + + const AzureState& CurrentState() const { return mAzureState; } + + RefPtr<DrawTarget> const mDT; + float mCrossProcessPaintScale = 1.0f; + +#ifdef DEBUG +# undef CURRENTSTATE_CHANGED +#endif +}; + +/** + * Sentry helper class for functions with multiple return points that need to + * call Save() on a gfxContext and have Restore() called automatically on the + * gfxContext before they return. + */ +class MOZ_STACK_CLASS gfxContextAutoSaveRestore final { + public: + gfxContextAutoSaveRestore() : mContext(nullptr) {} + + explicit gfxContextAutoSaveRestore(gfxContext* aContext) + : mContext(aContext) { + mContext->Save(); + } + + ~gfxContextAutoSaveRestore() { Restore(); } + + void SetContext(gfxContext* aContext) { + MOZ_ASSERT(!mContext, "no context?"); + mContext = aContext; + mContext->Save(); + } + + void EnsureSaved(gfxContext* aContext) { + MOZ_ASSERT(!mContext || mContext == aContext, "wrong context"); + if (!mContext) { + mContext = aContext; + mContext->Save(); + } + } + + void Restore() { + if (mContext) { + mContext->Restore(); + mContext = nullptr; + } + } + + private: + gfxContext* mContext; +}; + +/** + * Sentry helper class for functions with multiple return points that need to + * back up the current matrix of a context and have it automatically restored + * before they return. + */ +class MOZ_STACK_CLASS gfxContextMatrixAutoSaveRestore final { + public: + gfxContextMatrixAutoSaveRestore() : mContext(nullptr) {} + + explicit gfxContextMatrixAutoSaveRestore(gfxContext* aContext) + : mContext(aContext), mMatrix(aContext->CurrentMatrix()) {} + + ~gfxContextMatrixAutoSaveRestore() { + if (mContext) { + mContext->SetMatrix(mMatrix); + } + } + + void SetContext(gfxContext* aContext) { + NS_ASSERTION(!mContext, "Not going to restore the matrix on some context!"); + mContext = aContext; + mMatrix = aContext->CurrentMatrix(); + } + + void Restore() { + if (mContext) { + mContext->SetMatrix(mMatrix); + mContext = nullptr; + } + } + + const mozilla::gfx::Matrix& Matrix() { + MOZ_ASSERT(mContext, "mMatrix doesn't contain a useful matrix"); + return mMatrix; + } + + bool HasMatrix() const { return !!mContext; } + + private: + gfxContext* mContext; + mozilla::gfx::Matrix mMatrix; +}; + +class MOZ_STACK_CLASS gfxGroupForBlendAutoSaveRestore final { + public: + using Float = mozilla::gfx::Float; + using Matrix = mozilla::gfx::Matrix; + + explicit gfxGroupForBlendAutoSaveRestore(gfxContext* aContext) + : mContext(aContext) {} + + ~gfxGroupForBlendAutoSaveRestore() { + if (mPushedGroup) { + mContext->PopGroupAndBlend(); + } + } + + void PushGroupForBlendBack(gfxContentType aContent, Float aOpacity = 1.0f, + mozilla::gfx::SourceSurface* aMask = nullptr, + const Matrix& aMaskTransform = Matrix()) { + MOZ_ASSERT(!mPushedGroup, "Already called PushGroupForBlendBack once"); + mContext->PushGroupForBlendBack(aContent, aOpacity, aMask, aMaskTransform); + mPushedGroup = true; + } + + private: + gfxContext* mContext; + bool mPushedGroup = false; +}; + +class MOZ_STACK_CLASS gfxClipAutoSaveRestore final { + public: + using Rect = mozilla::gfx::Rect; + + explicit gfxClipAutoSaveRestore(gfxContext* aContext) : mContext(aContext) {} + + void Clip(const gfxRect& aRect) { Clip(ToRect(aRect)); } + + void Clip(const Rect& aRect) { + MOZ_ASSERT(!mClipped, "Already called Clip once"); + mContext->Clip(aRect); + mClipped = true; + } + + void TransformedClip(const gfxMatrix& aTransform, const gfxRect& aRect) { + MOZ_ASSERT(!mClipped, "Already called Clip once"); + if (aTransform.IsSingular()) { + return; + } + gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(mContext); + mContext->Multiply(aTransform); + mContext->Clip(aRect); + mClipped = true; + } + + ~gfxClipAutoSaveRestore() { + if (mClipped) { + mContext->PopClip(); + } + } + + private: + gfxContext* mContext; + bool mClipped = false; +}; + +class MOZ_STACK_CLASS DrawTargetAutoDisableSubpixelAntialiasing final { + public: + typedef mozilla::gfx::DrawTarget DrawTarget; + + DrawTargetAutoDisableSubpixelAntialiasing(DrawTarget* aDT, bool aDisable) + : mSubpixelAntialiasingEnabled(false) { + if (aDisable) { + mDT = aDT; + mSubpixelAntialiasingEnabled = mDT->GetPermitSubpixelAA(); + mDT->SetPermitSubpixelAA(false); + } + } + ~DrawTargetAutoDisableSubpixelAntialiasing() { + if (mDT) { + mDT->SetPermitSubpixelAA(mSubpixelAntialiasingEnabled); + } + } + + private: + RefPtr<DrawTarget> mDT; + bool mSubpixelAntialiasingEnabled; +}; + +/* This interface should be implemented to handle exporting the clip from a + * context. + */ +class ClipExporter : public mozilla::gfx::PathSink { + public: + virtual void BeginClip(const mozilla::gfx::Matrix& aMatrix) = 0; + virtual void EndClip() = 0; +}; + +#endif /* GFX_CONTEXT_H */ diff --git a/gfx/thebes/gfxCoreTextShaper.cpp b/gfx/thebes/gfxCoreTextShaper.cpp new file mode 100644 index 0000000000..c0a1c83875 --- /dev/null +++ b/gfx/thebes/gfxCoreTextShaper.cpp @@ -0,0 +1,651 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" +#include "gfxCoreTextShaper.h" +#include "gfxMacFont.h" +#include "gfxFontUtils.h" +#include "gfxTextRun.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/ScaledFontMac.h" +#include "mozilla/UniquePtrExtensions.h" + +#include <algorithm> + +#include <dlfcn.h> + +using namespace mozilla; +using namespace mozilla::gfx; + +// standard font descriptors that we construct the first time they're needed +CTFontDescriptorRef gfxCoreTextShaper::sFeaturesDescriptor[kMaxFontInstances]; + +// Helper to create a CFDictionary with the right attributes for shaping our +// text, including imposing the given directionality. +CFDictionaryRef gfxCoreTextShaper::CreateAttrDict(bool aRightToLeft) { + // Because we always shape unidirectional runs, and may have applied + // directional overrides, we want to force a direction rather than + // allowing CoreText to do its own unicode-based bidi processing. + SInt16 dirOverride = kCTWritingDirectionOverride | + (aRightToLeft ? kCTWritingDirectionRightToLeft + : kCTWritingDirectionLeftToRight); + CFNumberRef dirNumber = + ::CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt16Type, &dirOverride); + CFArrayRef dirArray = ::CFArrayCreate( + kCFAllocatorDefault, (const void**)&dirNumber, 1, &kCFTypeArrayCallBacks); + ::CFRelease(dirNumber); + CFTypeRef attrs[] = {kCTFontAttributeName, kCTWritingDirectionAttributeName}; + CFTypeRef values[] = {mCTFont[0], dirArray}; + CFDictionaryRef attrDict = ::CFDictionaryCreate( + kCFAllocatorDefault, attrs, values, ArrayLength(attrs), + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + ::CFRelease(dirArray); + return attrDict; +} + +gfxCoreTextShaper::gfxCoreTextShaper(gfxMacFont* aFont) + : gfxFontShaper(aFont), + mAttributesDictLTR(nullptr), + mAttributesDictRTL(nullptr) { + for (size_t i = 0; i < kMaxFontInstances; i++) { + mCTFont[i] = nullptr; + } + // Create our default CTFontRef + mCTFont[0] = CreateCTFontWithFeatures( + aFont->GetAdjustedSize(), GetFeaturesDescriptor(kDefaultFeatures)); +} + +gfxCoreTextShaper::~gfxCoreTextShaper() { + if (mAttributesDictLTR) { + ::CFRelease(mAttributesDictLTR); + } + if (mAttributesDictRTL) { + ::CFRelease(mAttributesDictRTL); + } + for (size_t i = 0; i < kMaxFontInstances; i++) { + if (mCTFont[i]) { + ::CFRelease(mCTFont[i]); + } + } +} + +static bool IsBuggyIndicScript(intl::Script aScript) { + return aScript == intl::Script::BENGALI || aScript == intl::Script::KANNADA || + aScript == intl::Script::ORIYA || aScript == intl::Script::KHMER; +} + +bool gfxCoreTextShaper::ShapeText(DrawTarget* aDrawTarget, + const char16_t* aText, uint32_t aOffset, + uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, + RoundingFlags aRounding, + gfxShapedText* aShapedText) { + // Create a CFAttributedString with text and style info, so we can use + // CoreText to lay it out. + bool isRightToLeft = aShapedText->IsRightToLeft(); + const UniChar* text = reinterpret_cast<const UniChar*>(aText); + + CFStringRef stringObj = ::CFStringCreateWithCharactersNoCopy( + kCFAllocatorDefault, text, aLength, kCFAllocatorNull); + + // Figure out whether we should try to set the AAT small-caps feature: + // examine OpenType tags for the requested style, and see if 'smcp' is + // among them. + const gfxFontStyle* style = mFont->GetStyle(); + gfxFontEntry* entry = mFont->GetFontEntry(); + auto handleFeatureTag = [](uint32_t aTag, uint32_t aValue, + void* aUserArg) -> void { + if (aTag == HB_TAG('s', 'm', 'c', 'p') && aValue) { + *static_cast<bool*>(aUserArg) = true; + } + }; + bool addSmallCaps = false; + MergeFontFeatures(style, entry->mFeatureSettings, false, entry->FamilyName(), + false, handleFeatureTag, &addSmallCaps); + + // Get an attributes dictionary suitable for shaping text in the + // current direction, creating it if necessary. + CFDictionaryRef attrObj = + isRightToLeft ? mAttributesDictRTL : mAttributesDictLTR; + if (!attrObj) { + attrObj = CreateAttrDict(isRightToLeft); + (isRightToLeft ? mAttributesDictRTL : mAttributesDictLTR) = attrObj; + } + + FeatureFlags featureFlags = kDefaultFeatures; + if (IsBuggyIndicScript(aScript)) { + // To work around buggy Indic AAT fonts shipped with OS X, + // we re-enable the Line Initial Smart Swashes feature that is needed + // for "split vowels" to work in at least Bengali and Kannada fonts. + // Affected fonts include Bangla MN, Bangla Sangam MN, Kannada MN, + // Kannada Sangam MN. See bugs 686225, 728557, 953231, 1145515. + // Also applies to Oriya and Khmer, see bug 1370927 and bug 1403166. + featureFlags |= kIndicFeatures; + } + if (aShapedText->DisableLigatures()) { + // For letterspacing (or maybe other situations) we need to make + // a copy of the CTFont with the ligature feature disabled. + featureFlags |= kDisableLigatures; + } + if (addSmallCaps) { + featureFlags |= kAddSmallCaps; + } + + // For the disabled-ligature, buggy-indic-font or small-caps case, replace + // the default CTFont in the attribute dictionary with a tweaked version. + CFMutableDictionaryRef mutableAttr = nullptr; + if (featureFlags != 0) { + if (!mCTFont[featureFlags]) { + mCTFont[featureFlags] = CreateCTFontWithFeatures( + mFont->GetAdjustedSize(), GetFeaturesDescriptor(featureFlags)); + } + mutableAttr = + ::CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 2, attrObj); + ::CFDictionaryReplaceValue(mutableAttr, kCTFontAttributeName, + mCTFont[featureFlags]); + attrObj = mutableAttr; + } + + // Now we can create an attributed string + CFAttributedStringRef attrStringObj = + ::CFAttributedStringCreate(kCFAllocatorDefault, stringObj, attrObj); + ::CFRelease(stringObj); + + // Create the CoreText line from our string, then we're done with it + CTLineRef line = ::CTLineCreateWithAttributedString(attrStringObj); + ::CFRelease(attrStringObj); + + // and finally retrieve the glyph data and store into the gfxTextRun + CFArrayRef glyphRuns = ::CTLineGetGlyphRuns(line); + uint32_t numRuns = ::CFArrayGetCount(glyphRuns); + + // Iterate through the glyph runs. + bool success = true; + for (uint32_t runIndex = 0; runIndex < numRuns; runIndex++) { + CTRunRef aCTRun = (CTRunRef)::CFArrayGetValueAtIndex(glyphRuns, runIndex); + CFRange range = ::CTRunGetStringRange(aCTRun); + CFDictionaryRef runAttr = ::CTRunGetAttributes(aCTRun); + if (runAttr != attrObj) { + // If Core Text manufactured a new dictionary, this may indicate + // unexpected font substitution. In that case, we fail (and fall + // back to harfbuzz shaping)... + const void* font1 = ::CFDictionaryGetValue(attrObj, kCTFontAttributeName); + const void* font2 = ::CFDictionaryGetValue(runAttr, kCTFontAttributeName); + if (font1 != font2) { + // ...except that if the fallback was only for a variation + // selector or join control that is otherwise unsupported, + // we just ignore it. + if (range.length == 1) { + char16_t ch = aText[range.location]; + if (gfxFontUtils::IsJoinControl(ch) || + gfxFontUtils::IsVarSelector(ch)) { + continue; + } + } + NS_WARNING("unexpected font fallback in Core Text"); + success = false; + break; + } + } + if (SetGlyphsFromRun(aShapedText, aOffset, aLength, aCTRun) != NS_OK) { + success = false; + break; + } + } + + if (mutableAttr) { + ::CFRelease(mutableAttr); + } + ::CFRelease(line); + + return success; +} + +#define SMALL_GLYPH_RUN \ + 128 // preallocated size of our auto arrays for per-glyph data; + // some testing indicates that 90%+ of glyph runs will fit + // without requiring a separate allocation + +nsresult gfxCoreTextShaper::SetGlyphsFromRun(gfxShapedText* aShapedText, + uint32_t aOffset, uint32_t aLength, + CTRunRef aCTRun) { + typedef gfxShapedText::CompressedGlyph CompressedGlyph; + + int32_t direction = aShapedText->IsRightToLeft() ? -1 : 1; + + int32_t numGlyphs = ::CTRunGetGlyphCount(aCTRun); + if (numGlyphs == 0) { + return NS_OK; + } + + int32_t wordLength = aLength; + + // character offsets get really confusing here, as we have to keep track of + // (a) the text in the actual textRun we're constructing + // (c) the string that was handed to CoreText, which contains the text of + // the font run + // (d) the CTRun currently being processed, which may be a sub-run of the + // CoreText line + + // get the source string range within the CTLine's text + CFRange stringRange = ::CTRunGetStringRange(aCTRun); + // skip the run if it is entirely outside the actual range of the font run + if (stringRange.location + stringRange.length <= 0 || + stringRange.location >= wordLength) { + return NS_OK; + } + + // retrieve the laid-out glyph data from the CTRun + UniquePtr<CGGlyph[]> glyphsArray; + UniquePtr<CGPoint[]> positionsArray; + UniquePtr<CFIndex[]> glyphToCharArray; + const CGGlyph* glyphs = nullptr; + const CGPoint* positions = nullptr; + const CFIndex* glyphToChar = nullptr; + + // Testing indicates that CTRunGetGlyphsPtr (almost?) always succeeds, + // and so allocating a new array and copying data with CTRunGetGlyphs + // will be extremely rare. + // If this were not the case, we could use an AutoTArray<> to + // try and avoid the heap allocation for small runs. + // It's possible that some future change to CoreText will mean that + // CTRunGetGlyphsPtr fails more often; if this happens, AutoTArray<> + // may become an attractive option. + glyphs = ::CTRunGetGlyphsPtr(aCTRun); + if (!glyphs) { + glyphsArray = MakeUniqueFallible<CGGlyph[]>(numGlyphs); + if (!glyphsArray) { + return NS_ERROR_OUT_OF_MEMORY; + } + ::CTRunGetGlyphs(aCTRun, ::CFRangeMake(0, 0), glyphsArray.get()); + glyphs = glyphsArray.get(); + } + + positions = ::CTRunGetPositionsPtr(aCTRun); + if (!positions) { + positionsArray = MakeUniqueFallible<CGPoint[]>(numGlyphs); + if (!positionsArray) { + return NS_ERROR_OUT_OF_MEMORY; + } + ::CTRunGetPositions(aCTRun, ::CFRangeMake(0, 0), positionsArray.get()); + positions = positionsArray.get(); + } + + // Remember that the glyphToChar indices relate to the CoreText line, + // not to the beginning of the textRun, the font run, + // or the stringRange of the glyph run + glyphToChar = ::CTRunGetStringIndicesPtr(aCTRun); + if (!glyphToChar) { + glyphToCharArray = MakeUniqueFallible<CFIndex[]>(numGlyphs); + if (!glyphToCharArray) { + return NS_ERROR_OUT_OF_MEMORY; + } + ::CTRunGetStringIndices(aCTRun, ::CFRangeMake(0, 0), + glyphToCharArray.get()); + glyphToChar = glyphToCharArray.get(); + } + + double runWidth = ::CTRunGetTypographicBounds(aCTRun, ::CFRangeMake(0, 0), + nullptr, nullptr, nullptr); + + AutoTArray<gfxShapedText::DetailedGlyph, 1> detailedGlyphs; + CompressedGlyph* charGlyphs = aShapedText->GetCharacterGlyphs() + aOffset; + + // CoreText gives us the glyphindex-to-charindex mapping, which relates each + // glyph to a source text character; we also need the charindex-to-glyphindex + // mapping to find the glyph for a given char. Note that some chars may not + // map to any glyph (ligature continuations), and some may map to several + // glyphs (eg Indic split vowels). We set the glyph index to NO_GLYPH for + // chars that have no associated glyph, and we record the last glyph index for + // cases where the char maps to several glyphs, so that our clumping will + // include all the glyph fragments for the character. + + // The charToGlyph array is indexed by char position within the stringRange of + // the glyph run. + + static const int32_t NO_GLYPH = -1; + AutoTArray<int32_t, SMALL_GLYPH_RUN> charToGlyphArray; + if (!charToGlyphArray.SetLength(stringRange.length, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + int32_t* charToGlyph = charToGlyphArray.Elements(); + for (int32_t offset = 0; offset < stringRange.length; ++offset) { + charToGlyph[offset] = NO_GLYPH; + } + for (int32_t i = 0; i < numGlyphs; ++i) { + int32_t loc = glyphToChar[i] - stringRange.location; + if (loc >= 0 && loc < stringRange.length) { + charToGlyph[loc] = i; + } + } + + // Find character and glyph clumps that correspond, allowing for ligatures, + // indic reordering, split glyphs, etc. + // + // The idea is that we'll find a character sequence starting at the first char + // of stringRange, and extend it until it includes the character associated + // with the first glyph; we also extend it as long as there are "holes" in the + // range of glyphs. So we will eventually have a contiguous sequence of + // characters, starting at the beginning of the range, that map to a + // contiguous sequence of glyphs, starting at the beginning of the glyph + // array. That's a clump; then we update the starting positions and repeat. + // + // NB: In the case of RTL layouts, we iterate over the stringRange in reverse. + // + + // This may find characters that fall outside the range 0:wordLength, + // so we won't necessarily use everything we find here. + + bool isRightToLeft = aShapedText->IsRightToLeft(); + int32_t glyphStart = + 0; // looking for a clump that starts at this glyph index + int32_t charStart = + isRightToLeft + ? stringRange.length - 1 + : 0; // and this char index (in the stringRange of the glyph run) + + while (glyphStart < + numGlyphs) { // keep finding groups until all glyphs are accounted for + bool inOrder = true; + int32_t charEnd = glyphToChar[glyphStart] - stringRange.location; + NS_WARNING_ASSERTION(charEnd >= 0 && charEnd < stringRange.length, + "glyph-to-char mapping points outside string range"); + // clamp charEnd to the valid range of the string + charEnd = std::max(charEnd, 0); + charEnd = std::min(charEnd, int32_t(stringRange.length)); + + int32_t glyphEnd = glyphStart; + int32_t charLimit = isRightToLeft ? -1 : stringRange.length; + do { + // This is normally executed once for each iteration of the outer loop, + // but in unusual cases where the character/glyph association is complex, + // the initial character range might correspond to a non-contiguous + // glyph range with "holes" in it. If so, we will repeat this loop to + // extend the character range until we have a contiguous glyph sequence. + NS_ASSERTION((direction > 0 && charEnd < charLimit) || + (direction < 0 && charEnd > charLimit), + "no characters left in range?"); + charEnd += direction; + while (charEnd != charLimit && charToGlyph[charEnd] == NO_GLYPH) { + charEnd += direction; + } + + // find the maximum glyph index covered by the clump so far + if (isRightToLeft) { + for (int32_t i = charStart; i > charEnd; --i) { + if (charToGlyph[i] != NO_GLYPH) { + // update extent of glyph range + glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1); + } + } + } else { + for (int32_t i = charStart; i < charEnd; ++i) { + if (charToGlyph[i] != NO_GLYPH) { + // update extent of glyph range + glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1); + } + } + } + + if (glyphEnd == glyphStart + 1) { + // for the common case of a single-glyph clump, we can skip the + // following checks + break; + } + + if (glyphEnd == glyphStart) { + // no glyphs, try to extend the clump + continue; + } + + // check whether all glyphs in the range are associated with the + // characters in our clump; if not, we have a discontinuous range, and + // should extend it unless we've reached the end of the text + bool allGlyphsAreWithinCluster = true; + int32_t prevGlyphCharIndex = charStart; + for (int32_t i = glyphStart; i < glyphEnd; ++i) { + int32_t glyphCharIndex = glyphToChar[i] - stringRange.location; + if (isRightToLeft) { + if (glyphCharIndex > charStart || glyphCharIndex <= charEnd) { + allGlyphsAreWithinCluster = false; + break; + } + if (glyphCharIndex > prevGlyphCharIndex) { + inOrder = false; + } + prevGlyphCharIndex = glyphCharIndex; + } else { + if (glyphCharIndex < charStart || glyphCharIndex >= charEnd) { + allGlyphsAreWithinCluster = false; + break; + } + if (glyphCharIndex < prevGlyphCharIndex) { + inOrder = false; + } + prevGlyphCharIndex = glyphCharIndex; + } + } + if (allGlyphsAreWithinCluster) { + break; + } + } while (charEnd != charLimit); + + NS_WARNING_ASSERTION(glyphStart < glyphEnd, + "character/glyph clump contains no glyphs!"); + if (glyphStart == glyphEnd) { + ++glyphStart; // make progress - avoid potential infinite loop + charStart = charEnd; + continue; + } + + NS_WARNING_ASSERTION(charStart != charEnd, + "character/glyph clump contains no characters!"); + if (charStart == charEnd) { + glyphStart = glyphEnd; // this is bad - we'll discard the glyph(s), + // as there's nowhere to attach them + continue; + } + + // Now charStart..charEnd is a ligature clump, corresponding to + // glyphStart..glyphEnd; Set baseCharIndex to the char we'll actually attach + // the glyphs to (1st of ligature), and endCharIndex to the limit (position + // beyond the last char), adjusting for the offset of the stringRange + // relative to the textRun. + int32_t baseCharIndex, endCharIndex; + if (isRightToLeft) { + while (charEnd >= 0 && charToGlyph[charEnd] == NO_GLYPH) { + charEnd--; + } + baseCharIndex = charEnd + stringRange.location + 1; + endCharIndex = charStart + stringRange.location + 1; + } else { + while (charEnd < stringRange.length && charToGlyph[charEnd] == NO_GLYPH) { + charEnd++; + } + baseCharIndex = charStart + stringRange.location; + endCharIndex = charEnd + stringRange.location; + } + + // Then we check if the clump falls outside our actual string range; if so, + // just go to the next. + if (endCharIndex <= 0 || baseCharIndex >= wordLength) { + glyphStart = glyphEnd; + charStart = charEnd; + continue; + } + // Ensure we won't try to go beyond the valid length of the word's text + baseCharIndex = std::max(baseCharIndex, 0); + endCharIndex = std::min(endCharIndex, wordLength); + + // Now we're ready to set the glyph info in the textRun; measure the glyph + // width of the first (perhaps only) glyph, to see if it is "Simple" + int32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit(); + double toNextGlyph; + if (glyphStart < numGlyphs - 1) { + toNextGlyph = positions[glyphStart + 1].x - positions[glyphStart].x; + } else { + toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x; + } + int32_t advance = int32_t(toNextGlyph * appUnitsPerDevUnit); + + // Check if it's a simple one-to-one mapping + int32_t glyphsInClump = glyphEnd - glyphStart; + if (glyphsInClump == 1 && + gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyphs[glyphStart]) && + gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) && + charGlyphs[baseCharIndex].IsClusterStart() && + positions[glyphStart].y == 0.0) { + charGlyphs[baseCharIndex].SetSimpleGlyph(advance, glyphs[glyphStart]); + } else { + // collect all glyphs in a list to be assigned to the first char; + // there must be at least one in the clump, and we already measured its + // advance, hence the placement of the loop-exit test and the measurement + // of the next glyph + while (true) { + gfxTextRun::DetailedGlyph* details = detailedGlyphs.AppendElement(); + details->mGlyphID = glyphs[glyphStart]; + details->mOffset.y = -positions[glyphStart].y * appUnitsPerDevUnit; + details->mAdvance = advance; + if (++glyphStart >= glyphEnd) { + break; + } + if (glyphStart < numGlyphs - 1) { + toNextGlyph = positions[glyphStart + 1].x - positions[glyphStart].x; + } else { + toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x; + } + advance = int32_t(toNextGlyph * appUnitsPerDevUnit); + } + + aShapedText->SetDetailedGlyphs(aOffset + baseCharIndex, + detailedGlyphs.Length(), + detailedGlyphs.Elements()); + + detailedGlyphs.Clear(); + } + + // the rest of the chars in the group are ligature continuations, no + // associated glyphs + while (++baseCharIndex != endCharIndex && baseCharIndex < wordLength) { + CompressedGlyph& shapedTextGlyph = charGlyphs[baseCharIndex]; + NS_ASSERTION(!shapedTextGlyph.IsSimpleGlyph(), + "overwriting a simple glyph"); + shapedTextGlyph.SetComplex(inOrder && shapedTextGlyph.IsClusterStart(), + false); + } + + glyphStart = glyphEnd; + charStart = charEnd; + } + + return NS_OK; +} + +#undef SMALL_GLYPH_RUN + +// Construct the font attribute descriptor that we'll apply by default when +// creating a CTFontRef. This will turn off line-edge swashes by default, +// because we don't know the actual line breaks when doing glyph shaping. + +// We also cache feature descriptors for shaping with disabled ligatures, and +// for buggy Indic AAT font workarounds, created on an as-needed basis. + +#define MAX_FEATURES 5 // max used by any of our Get*Descriptor functions + +CTFontDescriptorRef gfxCoreTextShaper::CreateFontFeaturesDescriptor( + const std::pair<SInt16, SInt16>* aFeatures, size_t aCount) { + MOZ_ASSERT(aCount <= MAX_FEATURES); + + CFDictionaryRef featureSettings[MAX_FEATURES]; + + for (size_t i = 0; i < aCount; i++) { + CFNumberRef type = ::CFNumberCreate( + kCFAllocatorDefault, kCFNumberSInt16Type, &aFeatures[i].first); + CFNumberRef selector = ::CFNumberCreate( + kCFAllocatorDefault, kCFNumberSInt16Type, &aFeatures[i].second); + + CFTypeRef keys[] = {kCTFontFeatureTypeIdentifierKey, + kCTFontFeatureSelectorIdentifierKey}; + CFTypeRef values[] = {type, selector}; + featureSettings[i] = ::CFDictionaryCreate( + kCFAllocatorDefault, (const void**)keys, (const void**)values, + ArrayLength(keys), &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + ::CFRelease(selector); + ::CFRelease(type); + } + + CFArrayRef featuresArray = + ::CFArrayCreate(kCFAllocatorDefault, (const void**)featureSettings, + aCount, // not ArrayLength(featureSettings), as we + // may not have used all the allocated slots + &kCFTypeArrayCallBacks); + + for (size_t i = 0; i < aCount; i++) { + ::CFRelease(featureSettings[i]); + } + + const CFTypeRef attrKeys[] = {kCTFontFeatureSettingsAttribute}; + const CFTypeRef attrValues[] = {featuresArray}; + CFDictionaryRef attributesDict = ::CFDictionaryCreate( + kCFAllocatorDefault, (const void**)attrKeys, (const void**)attrValues, + ArrayLength(attrKeys), &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + ::CFRelease(featuresArray); + + CTFontDescriptorRef descriptor = + ::CTFontDescriptorCreateWithAttributes(attributesDict); + ::CFRelease(attributesDict); + + return descriptor; +} + +CTFontDescriptorRef gfxCoreTextShaper::GetFeaturesDescriptor( + FeatureFlags aFeatureFlags) { + MOZ_ASSERT(aFeatureFlags < kMaxFontInstances); + if (!sFeaturesDescriptor[aFeatureFlags]) { + typedef std::pair<SInt16, SInt16> FeatT; + AutoTArray<FeatT, MAX_FEATURES> features; + features.AppendElement( + FeatT(kSmartSwashType, kLineFinalSwashesOffSelector)); + if ((aFeatureFlags & kIndicFeatures) == 0) { + features.AppendElement( + FeatT(kSmartSwashType, kLineInitialSwashesOffSelector)); + } + if (aFeatureFlags & kAddSmallCaps) { + features.AppendElement(FeatT(kLetterCaseType, kSmallCapsSelector)); + features.AppendElement( + FeatT(kLowerCaseType, kLowerCaseSmallCapsSelector)); + } + if (aFeatureFlags & kDisableLigatures) { + features.AppendElement( + FeatT(kLigaturesType, kCommonLigaturesOffSelector)); + } + MOZ_ASSERT(features.Length() <= MAX_FEATURES); + sFeaturesDescriptor[aFeatureFlags] = + CreateFontFeaturesDescriptor(features.Elements(), features.Length()); + } + return sFeaturesDescriptor[aFeatureFlags]; +} + +CTFontRef gfxCoreTextShaper::CreateCTFontWithFeatures( + CGFloat aSize, CTFontDescriptorRef aDescriptor) { + const gfxFontEntry* fe = mFont->GetFontEntry(); + bool isInstalledFont = !fe->IsUserFont() || fe->IsLocalUserFont(); + CGFontRef cgFont = static_cast<gfxMacFont*>(mFont)->GetCGFontRef(); + return CreateCTFontFromCGFontWithVariations(cgFont, aSize, isInstalledFont, + aDescriptor); +} + +void gfxCoreTextShaper::Shutdown() // [static] +{ + for (size_t i = 0; i < kMaxFontInstances; i++) { + if (sFeaturesDescriptor[i] != nullptr) { + ::CFRelease(sFeaturesDescriptor[i]); + sFeaturesDescriptor[i] = nullptr; + } + } +} diff --git a/gfx/thebes/gfxCoreTextShaper.h b/gfx/thebes/gfxCoreTextShaper.h new file mode 100644 index 0000000000..53f24ba3ba --- /dev/null +++ b/gfx/thebes/gfxCoreTextShaper.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 GFX_CORETEXTSHAPER_H +#define GFX_CORETEXTSHAPER_H + +#include "gfxFont.h" + +#include <ApplicationServices/ApplicationServices.h> + +class gfxMacFont; + +class gfxCoreTextShaper : public gfxFontShaper { + public: + explicit gfxCoreTextShaper(gfxMacFont* aFont); + + virtual ~gfxCoreTextShaper(); + + bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, RoundingFlags aRounding, + gfxShapedText* aShapedText) override; + + // clean up static objects that may have been cached + static void Shutdown(); + + // Flags used to track what AAT features should be enabled on the Core Text + // font instance. (Internal; only public so that we can use the + // MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS macro below.) + enum FeatureFlags : uint8_t { + kDefaultFeatures = 0x00, + // bit flags for non-default feature settings we might need + // to use, which will require separate font instances + kDisableLigatures = 0x01, + kAddSmallCaps = 0x02, + kIndicFeatures = 0x04, + + // number of font instances, indexed by OR-ing the flags above + kMaxFontInstances = 8 + }; + + protected: + CTFontRef mCTFont[kMaxFontInstances]; + + // attributes for shaping text with LTR or RTL directionality + CFDictionaryRef mAttributesDictLTR; + CFDictionaryRef mAttributesDictRTL; + + nsresult SetGlyphsFromRun(gfxShapedText* aShapedText, uint32_t aOffset, + uint32_t aLength, CTRunRef aCTRun); + + CTFontRef CreateCTFontWithFeatures(CGFloat aSize, + CTFontDescriptorRef aDescriptor); + + CFDictionaryRef CreateAttrDict(bool aRightToLeft); + CFDictionaryRef CreateAttrDictWithoutDirection(); + + static CTFontDescriptorRef CreateFontFeaturesDescriptor( + const std::pair<SInt16, SInt16>* aFeatures, size_t aCount); + + static CTFontDescriptorRef GetFeaturesDescriptor(FeatureFlags aFeatureFlags); + + // cached font descriptors, created the first time they're needed + static CTFontDescriptorRef sFeaturesDescriptor[kMaxFontInstances]; +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(gfxCoreTextShaper::FeatureFlags) + +#endif /* GFX_CORETEXTSHAPER_H */ diff --git a/gfx/thebes/gfxDWriteCommon.cpp b/gfx/thebes/gfxDWriteCommon.cpp new file mode 100644 index 0000000000..ee81f15680 --- /dev/null +++ b/gfx/thebes/gfxDWriteCommon.cpp @@ -0,0 +1,204 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxDWriteCommon.h" + +#include <unordered_map> + +#include "mozilla/Atomics.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/gfx/Logging.h" + +class gfxDWriteFontFileStream; + +static mozilla::StaticMutex sFontFileStreamsMutex MOZ_UNANNOTATED; +static uint64_t sNextFontFileKey = 0; +static std::unordered_map<uint64_t, gfxDWriteFontFileStream*> sFontFileStreams; + +IDWriteFontFileLoader* gfxDWriteFontFileLoader::mInstance = nullptr; + +class gfxDWriteFontFileStream final : public IDWriteFontFileStream { + public: + /** + * Used by the FontFileLoader to create a new font stream, + * this font stream is created from data in memory. The memory + * passed may be released after object creation, it will be + * copied internally. + * + * @param aData Font data + */ + gfxDWriteFontFileStream(const uint8_t* aData, uint32_t aLength, + uint64_t aFontFileKey); + ~gfxDWriteFontFileStream(); + + // IUnknown interface + IFACEMETHOD(QueryInterface)(IID const& iid, OUT void** ppObject) { + if (iid == __uuidof(IDWriteFontFileStream)) { + *ppObject = static_cast<IDWriteFontFileStream*>(this); + return S_OK; + } else if (iid == __uuidof(IUnknown)) { + *ppObject = static_cast<IUnknown*>(this); + return S_OK; + } else { + return E_NOINTERFACE; + } + } + + IFACEMETHOD_(ULONG, AddRef)() { + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); + return ++mRefCnt; + } + + IFACEMETHOD_(ULONG, Release)() { + MOZ_ASSERT(0 != mRefCnt, "dup release"); + uint32_t count = --mRefCnt; + if (count == 0) { + // Avoid locking unless necessary. Verify the refcount hasn't changed + // while locked. Delete within the scope of the lock when zero. + mozilla::StaticMutexAutoLock lock(sFontFileStreamsMutex); + if (0 != mRefCnt) { + return mRefCnt; + } + delete this; + } + return count; + } + + // IDWriteFontFileStream methods + virtual HRESULT STDMETHODCALLTYPE + ReadFileFragment(void const** fragmentStart, UINT64 fileOffset, + UINT64 fragmentSize, OUT void** fragmentContext); + + virtual void STDMETHODCALLTYPE ReleaseFileFragment(void* fragmentContext); + + virtual HRESULT STDMETHODCALLTYPE GetFileSize(OUT UINT64* fileSize); + + virtual HRESULT STDMETHODCALLTYPE GetLastWriteTime(OUT UINT64* lastWriteTime); + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return mData.ShallowSizeOfExcludingThis(mallocSizeOf); + } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf); + } + + private: + FallibleTArray<uint8_t> mData; + mozilla::Atomic<uint32_t> mRefCnt; + uint64_t mFontFileKey; +}; + +gfxDWriteFontFileStream::gfxDWriteFontFileStream(const uint8_t* aData, + uint32_t aLength, + uint64_t aFontFileKey) + : mFontFileKey(aFontFileKey) { + // If this fails, mData will remain empty. That's OK: GetFileSize() + // will then return 0, etc., and the font just won't load. + if (!mData.AppendElements(aData, aLength, mozilla::fallible_t())) { + NS_WARNING("Failed to store data in gfxDWriteFontFileStream"); + } +} + +gfxDWriteFontFileStream::~gfxDWriteFontFileStream() { + sFontFileStreams.erase(mFontFileKey); +} + +HRESULT STDMETHODCALLTYPE +gfxDWriteFontFileStream::GetFileSize(UINT64* fileSize) { + *fileSize = mData.Length(); + return S_OK; +} + +HRESULT STDMETHODCALLTYPE +gfxDWriteFontFileStream::GetLastWriteTime(UINT64* lastWriteTime) { + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE gfxDWriteFontFileStream::ReadFileFragment( + const void** fragmentStart, UINT64 fileOffset, UINT64 fragmentSize, + void** fragmentContext) { + // We are required to do bounds checking. + if (fileOffset + fragmentSize > (UINT64)mData.Length()) { + return E_FAIL; + } + // We should be alive for the duration of this. + *fragmentStart = &mData[fileOffset]; + *fragmentContext = nullptr; + return S_OK; +} + +void STDMETHODCALLTYPE +gfxDWriteFontFileStream::ReleaseFileFragment(void* fragmentContext) {} + +HRESULT STDMETHODCALLTYPE gfxDWriteFontFileLoader::CreateStreamFromKey( + const void* fontFileReferenceKey, UINT32 fontFileReferenceKeySize, + IDWriteFontFileStream** fontFileStream) { + if (!fontFileReferenceKey || !fontFileStream) { + return E_POINTER; + } + + mozilla::StaticMutexAutoLock lock(sFontFileStreamsMutex); + uint64_t fontFileKey = *static_cast<const uint64_t*>(fontFileReferenceKey); + auto found = sFontFileStreams.find(fontFileKey); + if (found == sFontFileStreams.end()) { + *fontFileStream = nullptr; + return E_FAIL; + } + + found->second->AddRef(); + *fontFileStream = found->second; + return S_OK; +} + +/* static */ +HRESULT +gfxDWriteFontFileLoader::CreateCustomFontFile( + const uint8_t* aFontData, uint32_t aLength, IDWriteFontFile** aFontFile, + IDWriteFontFileStream** aFontFileStream) { + MOZ_ASSERT(aFontFile); + MOZ_ASSERT(aFontFileStream); + + RefPtr<IDWriteFactory> factory = mozilla::gfx::Factory::GetDWriteFactory(); + if (!factory) { + gfxCriticalError() + << "Failed to get DWrite Factory in CreateCustomFontFile."; + return E_FAIL; + } + + sFontFileStreamsMutex.Lock(); + uint64_t fontFileKey = sNextFontFileKey++; + RefPtr<gfxDWriteFontFileStream> ffsRef = + new gfxDWriteFontFileStream(aFontData, aLength, fontFileKey); + sFontFileStreams[fontFileKey] = ffsRef; + sFontFileStreamsMutex.Unlock(); + + RefPtr<IDWriteFontFile> fontFile; + HRESULT hr = factory->CreateCustomFontFileReference( + &fontFileKey, sizeof(fontFileKey), Instance(), getter_AddRefs(fontFile)); + if (FAILED(hr)) { + NS_WARNING("Failed to load font file from data!"); + return hr; + } + + fontFile.forget(aFontFile); + ffsRef.forget(aFontFileStream); + + return S_OK; +} + +size_t gfxDWriteFontFileLoader::SizeOfIncludingThis( + mozilla::MallocSizeOf mallocSizeOf) const { + size_t sizes = mallocSizeOf(this); + + // We are a singleton type that is effective owner of sFontFileStreams. + MOZ_ASSERT(this == mInstance); + for (const auto& entry : sFontFileStreams) { + gfxDWriteFontFileStream* fileStream = entry.second; + sizes += fileStream->SizeOfIncludingThis(mallocSizeOf); + } + + return sizes; +} diff --git a/gfx/thebes/gfxDWriteCommon.h b/gfx/thebes/gfxDWriteCommon.h new file mode 100644 index 0000000000..8daf02fd14 --- /dev/null +++ b/gfx/thebes/gfxDWriteCommon.h @@ -0,0 +1,161 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_DWRITECOMMON_H +#define GFX_DWRITECOMMON_H + +// Mozilla includes +#include "mozilla/MemoryReporting.h" +#include "mozilla/FontPropertyTypes.h" +#include "nscore.h" +#include "nsCOMPtr.h" +#include "cairo-features.h" +#include "gfxFontConstants.h" +#include "nsTArray.h" +#include "gfxWindowsPlatform.h" + +#include <windows.h> +#include <dwrite.h> + +#define GFX_CLEARTYPE_PARAMS "gfx.font_rendering.cleartype_params." +#define GFX_CLEARTYPE_PARAMS_GAMMA "gfx.font_rendering.cleartype_params.gamma" +#define GFX_CLEARTYPE_PARAMS_CONTRAST \ + "gfx.font_rendering.cleartype_params.enhanced_contrast" +#define GFX_CLEARTYPE_PARAMS_LEVEL \ + "gfx.font_rendering.cleartype_params.cleartype_level" +#define GFX_CLEARTYPE_PARAMS_STRUCTURE \ + "gfx.font_rendering.cleartype_params.pixel_structure" +#define GFX_CLEARTYPE_PARAMS_MODE \ + "gfx.font_rendering.cleartype_params.rendering_mode" + +#define DISPLAY1_REGISTRY_KEY \ + HKEY_CURRENT_USER, L"Software\\Microsoft\\Avalon.Graphics\\DISPLAY1" + +#define ENHANCED_CONTRAST_VALUE_NAME L"EnhancedContrastLevel" + +// FIXME: This shouldn't look at constants probably. +static inline DWRITE_FONT_STRETCH DWriteFontStretchFromStretch( + mozilla::FontStretch aStretch) { + if (aStretch == mozilla::FontStretch::ULTRA_CONDENSED) { + return DWRITE_FONT_STRETCH_ULTRA_CONDENSED; + } + if (aStretch == mozilla::FontStretch::EXTRA_CONDENSED) { + return DWRITE_FONT_STRETCH_EXTRA_CONDENSED; + } + if (aStretch == mozilla::FontStretch::CONDENSED) { + return DWRITE_FONT_STRETCH_CONDENSED; + } + if (aStretch == mozilla::FontStretch::SEMI_CONDENSED) { + return DWRITE_FONT_STRETCH_SEMI_CONDENSED; + } + if (aStretch == mozilla::FontStretch::NORMAL) { + return DWRITE_FONT_STRETCH_NORMAL; + } + if (aStretch == mozilla::FontStretch::SEMI_EXPANDED) { + return DWRITE_FONT_STRETCH_SEMI_EXPANDED; + } + if (aStretch == mozilla::FontStretch::EXPANDED) { + return DWRITE_FONT_STRETCH_EXPANDED; + } + if (aStretch == mozilla::FontStretch::EXTRA_EXPANDED) { + return DWRITE_FONT_STRETCH_EXTRA_EXPANDED; + } + if (aStretch == mozilla::FontStretch::ULTRA_EXPANDED) { + return DWRITE_FONT_STRETCH_ULTRA_EXPANDED; + } + return DWRITE_FONT_STRETCH_UNDEFINED; +} + +static inline mozilla::FontStretch FontStretchFromDWriteStretch( + DWRITE_FONT_STRETCH aStretch) { + switch (aStretch) { + case DWRITE_FONT_STRETCH_ULTRA_CONDENSED: + return mozilla::FontStretch::ULTRA_CONDENSED; + case DWRITE_FONT_STRETCH_EXTRA_CONDENSED: + return mozilla::FontStretch::EXTRA_CONDENSED; + case DWRITE_FONT_STRETCH_CONDENSED: + return mozilla::FontStretch::CONDENSED; + case DWRITE_FONT_STRETCH_SEMI_CONDENSED: + return mozilla::FontStretch::SEMI_CONDENSED; + case DWRITE_FONT_STRETCH_NORMAL: + return mozilla::FontStretch::NORMAL; + case DWRITE_FONT_STRETCH_SEMI_EXPANDED: + return mozilla::FontStretch::SEMI_EXPANDED; + case DWRITE_FONT_STRETCH_EXPANDED: + return mozilla::FontStretch::EXPANDED; + case DWRITE_FONT_STRETCH_EXTRA_EXPANDED: + return mozilla::FontStretch::EXTRA_EXPANDED; + case DWRITE_FONT_STRETCH_ULTRA_EXPANDED: + return mozilla::FontStretch::ULTRA_EXPANDED; + default: + return mozilla::FontStretch::NORMAL; + } +} + +class gfxDWriteFontFileLoader : public IDWriteFontFileLoader { + public: + gfxDWriteFontFileLoader() {} + + // IUnknown interface + IFACEMETHOD(QueryInterface)(IID const& iid, OUT void** ppObject) { + if (iid == __uuidof(IDWriteFontFileLoader)) { + *ppObject = static_cast<IDWriteFontFileLoader*>(this); + return S_OK; + } else if (iid == __uuidof(IUnknown)) { + *ppObject = static_cast<IUnknown*>(this); + return S_OK; + } else { + return E_NOINTERFACE; + } + } + + IFACEMETHOD_(ULONG, AddRef)() { return 1; } + + IFACEMETHOD_(ULONG, Release)() { return 1; } + + // IDWriteFontFileLoader methods + /** + * Important! Note the key here -has- to be a pointer to a uint64_t. + */ + virtual HRESULT STDMETHODCALLTYPE CreateStreamFromKey( + void const* fontFileReferenceKey, UINT32 fontFileReferenceKeySize, + OUT IDWriteFontFileStream** fontFileStream); + + /** + * Gets the singleton loader instance. Note that when using this font + * loader, the key must be a pointer to a unint64_t. + */ + static IDWriteFontFileLoader* Instance() { + if (!mInstance) { + mInstance = new gfxDWriteFontFileLoader(); + mozilla::gfx::Factory::GetDWriteFactory()->RegisterFontFileLoader( + mInstance); + } + return mInstance; + } + + /** + * Creates a IDWriteFontFile and IDWriteFontFileStream from aFontData. + * The data from aFontData will be copied internally, so the caller + * is free to dispose of it once this method returns. + * + * @param aFontData the font data for the custom font file + * @param aLength length of the font data + * @param aFontFile out param for the created font file + * @param aFontFileStream out param for the corresponding stream + * @return HRESULT of internal calls + */ + static HRESULT CreateCustomFontFile(const uint8_t* aFontData, + uint32_t aLength, + IDWriteFontFile** aFontFile, + IDWriteFontFileStream** aFontFileStream); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + private: + static IDWriteFontFileLoader* mInstance; +}; + +#endif /* GFX_DWRITECOMMON_H */ diff --git a/gfx/thebes/gfxDWriteFontList.cpp b/gfx/thebes/gfxDWriteFontList.cpp new file mode 100644 index 0000000000..b77058b359 --- /dev/null +++ b/gfx/thebes/gfxDWriteFontList.cpp @@ -0,0 +1,2644 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/intl/OSPreferences.h" + +#include "gfxDWriteFontList.h" +#include "gfxDWriteFonts.h" +#include "nsUnicharUtils.h" +#include "nsPresContext.h" +#include "nsServiceManagerUtils.h" +#include "nsCharSeparatedTokenizer.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/Telemetry.h" +#include "mozilla/WindowsProcessMitigations.h" +#include "mozilla/WindowsVersion.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsAppDirectoryServiceDefs.h" + +#include "gfxGDIFontList.h" +#include "gfxRect.h" +#include "SharedFontList-impl.h" + +#include "harfbuzz/hb.h" + +#include "StandardFonts-win10.inc" + +using namespace mozilla; +using namespace mozilla::gfx; +using mozilla::intl::OSPreferences; + +#define LOG_FONTLIST(args) \ + MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug, args) +#define LOG_FONTLIST_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug) + +#define LOG_FONTINIT(args) \ + MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug, args) +#define LOG_FONTINIT_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug) + +#define LOG_CMAPDATA_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_cmapdata), LogLevel::Debug) + +static __inline void BuildKeyNameFromFontName(nsACString& aName) { + ToLowerCase(aName); +} + +//////////////////////////////////////////////////////////////////////////////// +// gfxDWriteFontFamily + +gfxDWriteFontFamily::~gfxDWriteFontFamily() {} + +static bool GetNameAsUtf8(nsACString& aName, IDWriteLocalizedStrings* aStrings, + UINT32 aIndex) { + AutoTArray<WCHAR, 32> name; + UINT32 length; + HRESULT hr = aStrings->GetStringLength(aIndex, &length); + if (FAILED(hr)) { + return false; + } + if (!name.SetLength(length + 1, fallible)) { + return false; + } + hr = aStrings->GetString(aIndex, name.Elements(), length + 1); + if (FAILED(hr)) { + return false; + } + aName.Truncate(); + AppendUTF16toUTF8( + Substring(reinterpret_cast<const char16_t*>(name.Elements()), + name.Length() - 1), + aName); + return true; +} + +static bool GetEnglishOrFirstName(nsACString& aName, + IDWriteLocalizedStrings* aStrings) { + UINT32 englishIdx = 0; + BOOL exists; + HRESULT hr = aStrings->FindLocaleName(L"en-us", &englishIdx, &exists); + if (FAILED(hr) || !exists) { + // Use 0 index if english is not found. + englishIdx = 0; + } + return GetNameAsUtf8(aName, aStrings, englishIdx); +} + +static HRESULT GetDirectWriteFontName(IDWriteFont* aFont, + nsACString& aFontName) { + HRESULT hr; + + RefPtr<IDWriteLocalizedStrings> names; + hr = aFont->GetFaceNames(getter_AddRefs(names)); + if (FAILED(hr)) { + return hr; + } + + if (!GetEnglishOrFirstName(aFontName, names)) { + return E_FAIL; + } + + return S_OK; +} + +#define FULLNAME_ID DWRITE_INFORMATIONAL_STRING_FULL_NAME +#define PSNAME_ID DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_NAME + +// for use in reading postscript or fullname +static HRESULT GetDirectWriteFaceName(IDWriteFont* aFont, + DWRITE_INFORMATIONAL_STRING_ID aWhichName, + nsACString& aFontName) { + HRESULT hr; + + BOOL exists; + RefPtr<IDWriteLocalizedStrings> infostrings; + hr = aFont->GetInformationalStrings(aWhichName, getter_AddRefs(infostrings), + &exists); + if (FAILED(hr) || !exists) { + return E_FAIL; + } + + if (!GetEnglishOrFirstName(aFontName, infostrings)) { + return E_FAIL; + } + + return S_OK; +} + +void gfxDWriteFontFamily::FindStyleVariationsLocked( + FontInfoData* aFontInfoData) { + HRESULT hr; + if (mHasStyles) { + return; + } + + mHasStyles = true; + + gfxPlatformFontList* fp = gfxPlatformFontList::PlatformFontList(); + + bool skipFaceNames = + mFaceNamesInitialized || !fp->NeedFullnamePostscriptNames(); + bool fontInfoShouldHaveFaceNames = !mFaceNamesInitialized && + fp->NeedFullnamePostscriptNames() && + aFontInfoData; + + for (UINT32 i = 0; i < mDWFamily->GetFontCount(); i++) { + RefPtr<IDWriteFont> font; + hr = mDWFamily->GetFont(i, getter_AddRefs(font)); + if (FAILED(hr)) { + // This should never happen. + NS_WARNING("Failed to get existing font from family."); + continue; + } + + if (font->GetSimulations() != DWRITE_FONT_SIMULATIONS_NONE) { + // We don't want these in the font list; we'll apply simulations + // on the fly when appropriate. + continue; + } + + // name + nsCString fullID(mName); + nsAutoCString faceName; + hr = GetDirectWriteFontName(font, faceName); + if (FAILED(hr)) { + continue; + } + fullID.Append(' '); + fullID.Append(faceName); + + // Ignore italic style's "Meiryo" because "Meiryo (Bold) Italic" has + // non-italic style glyphs as Japanese characters. However, using it + // causes serious problem if web pages wants some elements to be + // different style from others only with font-style. For example, + // <em> and <i> should be rendered as italic in the default style. + if (fullID.EqualsLiteral("Meiryo Italic") || + fullID.EqualsLiteral("Meiryo Bold Italic")) { + continue; + } + + gfxDWriteFontEntry* fe = + new gfxDWriteFontEntry(fullID, font, mIsSystemFontFamily); + fe->SetForceGDIClassic(mForceGDIClassic); + + fe->SetupVariationRanges(); + + AddFontEntryLocked(fe); + + // postscript/fullname if needed + nsAutoCString psname, fullname; + if (fontInfoShouldHaveFaceNames) { + aFontInfoData->GetFaceNames(fe->Name(), fullname, psname); + if (!fullname.IsEmpty()) { + fp->AddFullname(fe, fullname); + } + if (!psname.IsEmpty()) { + fp->AddPostscriptName(fe, psname); + } + } else if (!skipFaceNames) { + hr = GetDirectWriteFaceName(font, PSNAME_ID, psname); + if (FAILED(hr)) { + skipFaceNames = true; + } else if (psname.Length() > 0) { + fp->AddPostscriptName(fe, psname); + } + + hr = GetDirectWriteFaceName(font, FULLNAME_ID, fullname); + if (FAILED(hr)) { + skipFaceNames = true; + } else if (fullname.Length() > 0) { + fp->AddFullname(fe, fullname); + } + } + + if (LOG_FONTLIST_ENABLED()) { + nsAutoCString weightString; + fe->Weight().ToString(weightString); + LOG_FONTLIST( + ("(fontlist) added (%s) to family (%s)" + " with style: %s weight: %s stretch: %d psname: %s fullname: %s", + fe->Name().get(), Name().get(), + (fe->IsItalic()) ? "italic" + : (fe->IsOblique() ? "oblique" : "normal"), + weightString.get(), fe->Stretch().AsScalar(), psname.get(), + fullname.get())); + } + } + + // assume that if no error, all postscript/fullnames were initialized + if (!skipFaceNames) { + mFaceNamesInitialized = true; + } + + if (!mAvailableFonts.Length()) { + NS_WARNING("Family with no font faces in it."); + } + + if (mIsBadUnderlineFamily) { + SetBadUnderlineFonts(); + } + + CheckForSimpleFamily(); + if (mIsSimpleFamily) { + for (auto& f : mAvailableFonts) { + if (f) { + static_cast<gfxDWriteFontEntry*>(f.get())->mMayUseGDIAccess = true; + } + } + } +} + +void gfxDWriteFontFamily::ReadFaceNames(gfxPlatformFontList* aPlatformFontList, + bool aNeedFullnamePostscriptNames, + FontInfoData* aFontInfoData) { + // if all needed names have already been read, skip + if (mOtherFamilyNamesInitialized && + (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) { + return; + } + + // If we've been passed a FontInfoData, we skip the DWrite implementation + // here and fall back to the generic code which will use that info. + if (!aFontInfoData) { + // DirectWrite version of this will try to read + // postscript/fullnames via DirectWrite API + FindStyleVariations(); + } + + // fallback to looking up via name table + if (!mOtherFamilyNamesInitialized || !mFaceNamesInitialized) { + gfxFontFamily::ReadFaceNames(aPlatformFontList, + aNeedFullnamePostscriptNames, aFontInfoData); + } +} + +void gfxDWriteFontFamily::LocalizedName(nsACString& aLocalizedName) { + aLocalizedName = Name(); // just return canonical name in case of failure + + if (!mDWFamily) { + return; + } + + HRESULT hr; + nsAutoCString locale; + // We use system locale here because it's what user expects to see. + // See bug 1349454 for details. + RefPtr<OSPreferences> osprefs = OSPreferences::GetInstanceAddRefed(); + if (!osprefs) { + return; + } + osprefs->GetSystemLocale(locale); + + RefPtr<IDWriteLocalizedStrings> names; + + hr = mDWFamily->GetFamilyNames(getter_AddRefs(names)); + if (FAILED(hr)) { + return; + } + UINT32 idx = 0; + BOOL exists; + hr = + names->FindLocaleName(NS_ConvertUTF8toUTF16(locale).get(), &idx, &exists); + if (FAILED(hr)) { + return; + } + if (!exists) { + // Use english is localized is not found. + hr = names->FindLocaleName(L"en-us", &idx, &exists); + if (FAILED(hr)) { + return; + } + if (!exists) { + // Use 0 index if english is not found. + idx = 0; + } + } + AutoTArray<WCHAR, 32> famName; + UINT32 length; + + hr = names->GetStringLength(idx, &length); + if (FAILED(hr)) { + return; + } + + if (!famName.SetLength(length + 1, fallible)) { + // Eeep - running out of memory. Unlikely to end well. + return; + } + + hr = names->GetString(idx, famName.Elements(), length + 1); + if (FAILED(hr)) { + return; + } + + aLocalizedName = NS_ConvertUTF16toUTF8((const char16_t*)famName.Elements(), + famName.Length() - 1); +} + +bool gfxDWriteFontFamily::IsSymbolFontFamily() const { + // Just check the first font in the family + if (mDWFamily->GetFontCount() > 0) { + RefPtr<IDWriteFont> font; + if (SUCCEEDED(mDWFamily->GetFont(0, getter_AddRefs(font)))) { + return font->IsSymbolFont(); + } + } + return false; +} + +void gfxDWriteFontFamily::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + gfxFontFamily::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); + // TODO: + // This doesn't currently account for |mDWFamily| +} + +void gfxDWriteFontFamily::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +//////////////////////////////////////////////////////////////////////////////// +// gfxDWriteFontEntry + +gfxFontEntry* gfxDWriteFontEntry::Clone() const { + MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!"); + gfxDWriteFontEntry* fe = new gfxDWriteFontEntry(Name(), mFont); + fe->mWeightRange = mWeightRange; + fe->mStretchRange = mStretchRange; + fe->mStyleRange = mStyleRange; + return fe; +} + +gfxDWriteFontEntry::~gfxDWriteFontEntry() {} + +static bool UsingArabicOrHebrewScriptSystemLocale() { + LANGID langid = PRIMARYLANGID(::GetSystemDefaultLangID()); + switch (langid) { + case LANG_ARABIC: + case LANG_DARI: + case LANG_PASHTO: + case LANG_PERSIAN: + case LANG_SINDHI: + case LANG_UIGHUR: + case LANG_URDU: + case LANG_HEBREW: + return true; + default: + return false; + } +} + +nsresult gfxDWriteFontEntry::CopyFontTable(uint32_t aTableTag, + nsTArray<uint8_t>& aBuffer) { + gfxDWriteFontList* pFontList = gfxDWriteFontList::PlatformFontList(); + const uint32_t tagBE = NativeEndian::swapToBigEndian(aTableTag); + + // Don't use GDI table loading for symbol fonts or for + // italic fonts in Arabic-script system locales because of + // potential cmap discrepancies, see bug 629386. + // Ditto for Hebrew, bug 837498. + if (mFont && mMayUseGDIAccess && pFontList->UseGDIFontTableAccess() && + !(!IsUpright() && UsingArabicOrHebrewScriptSystemLocale()) && + !mFont->IsSymbolFont()) { + LOGFONTW logfont = {0}; + if (InitLogFont(mFont, &logfont)) { + AutoDC dc; + AutoSelectFont font(dc.GetDC(), &logfont); + if (font.IsValid()) { + uint32_t tableSize = ::GetFontData(dc.GetDC(), tagBE, 0, nullptr, 0); + if (tableSize != GDI_ERROR) { + if (aBuffer.SetLength(tableSize, fallible)) { + ::GetFontData(dc.GetDC(), tagBE, 0, aBuffer.Elements(), + aBuffer.Length()); + return NS_OK; + } + return NS_ERROR_OUT_OF_MEMORY; + } + } + } + } + + RefPtr<IDWriteFontFace> fontFace; + nsresult rv = CreateFontFace(getter_AddRefs(fontFace)); + if (NS_FAILED(rv)) { + return rv; + } + + uint8_t* tableData; + uint32_t len; + void* tableContext = nullptr; + BOOL exists; + HRESULT hr = fontFace->TryGetFontTable(tagBE, (const void**)&tableData, &len, + &tableContext, &exists); + if (FAILED(hr) || !exists) { + return NS_ERROR_FAILURE; + } + + if (aBuffer.SetLength(len, fallible)) { + memcpy(aBuffer.Elements(), tableData, len); + rv = NS_OK; + } else { + rv = NS_ERROR_OUT_OF_MEMORY; + } + + if (tableContext) { + fontFace->ReleaseFontTable(&tableContext); + } + + return rv; +} + +// Access to font tables packaged in hb_blob_t form + +// object attached to the Harfbuzz blob, used to release +// the table when the blob is destroyed +class FontTableRec { + public: + FontTableRec(IDWriteFontFace* aFontFace, void* aContext) + : mFontFace(aFontFace), mContext(aContext) { + MOZ_COUNT_CTOR(FontTableRec); + } + + ~FontTableRec() { + MOZ_COUNT_DTOR(FontTableRec); + mFontFace->ReleaseFontTable(mContext); + } + + private: + RefPtr<IDWriteFontFace> mFontFace; + void* mContext; +}; + +static void DestroyBlobFunc(void* aUserData) { + FontTableRec* ftr = static_cast<FontTableRec*>(aUserData); + delete ftr; +} + +hb_blob_t* gfxDWriteFontEntry::GetFontTable(uint32_t aTag) { + // try to avoid potentially expensive DWrite call if we haven't actually + // created the font face yet, by using the gfxFontEntry method that will + // use CopyFontTable and then cache the data + if (!mFontFace) { + return gfxFontEntry::GetFontTable(aTag); + } + + const void* data; + UINT32 size; + void* context; + BOOL exists; + HRESULT hr = mFontFace->TryGetFontTable(NativeEndian::swapToBigEndian(aTag), + &data, &size, &context, &exists); + if (SUCCEEDED(hr) && exists) { + FontTableRec* ftr = new FontTableRec(mFontFace, context); + return hb_blob_create(static_cast<const char*>(data), size, + HB_MEMORY_MODE_READONLY, ftr, DestroyBlobFunc); + } + + return nullptr; +} + +nsresult gfxDWriteFontEntry::ReadCMAP(FontInfoData* aFontInfoData) { + AUTO_PROFILER_LABEL("gfxDWriteFontEntry::ReadCMAP", GRAPHICS); + + // attempt this once, if errors occur leave a blank cmap + if (mCharacterMap || mShmemCharacterMap) { + return NS_OK; + } + + RefPtr<gfxCharacterMap> charmap; + nsresult rv; + + uint32_t uvsOffset = 0; + if (aFontInfoData && + (charmap = GetCMAPFromFontInfo(aFontInfoData, uvsOffset))) { + rv = NS_OK; + } else { + uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p'); + charmap = new gfxCharacterMap(); + AutoTable cmapTable(this, kCMAP); + + if (cmapTable) { + uint32_t cmapLen; + const uint8_t* cmapData = reinterpret_cast<const uint8_t*>( + hb_blob_get_data(cmapTable, &cmapLen)); + rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, *charmap, uvsOffset); + } else { + rv = NS_ERROR_NOT_AVAILABLE; + } + } + mUVSOffset.exchange(uvsOffset); + + bool setCharMap = true; + if (NS_SUCCEEDED(rv)) { + // Bug 969504: exclude U+25B6 from Segoe UI family, because it's used + // by sites to represent a "Play" icon, but the glyph in Segoe UI Light + // and Semibold on Windows 7 is too thin. (Ditto for leftward U+25C0.) + // Fallback to Segoe UI Symbol is preferred. + if (FamilyName().EqualsLiteral("Segoe UI")) { + charmap->clear(0x25b6); + charmap->clear(0x25c0); + } + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + fontlist::FontList* sharedFontList = pfl->SharedFontList(); + if (!IsUserFont() && mShmemFace) { + mShmemFace->SetCharacterMap(sharedFontList, charmap, mShmemFamily); + if (TrySetShmemCharacterMap()) { + setCharMap = false; + } + } else { + charmap = pfl->FindCharMap(charmap); + } + mHasCmapTable = true; + } else { + // if error occurred, initialize to null cmap + charmap = new gfxCharacterMap(); + mHasCmapTable = false; + } + if (setCharMap) { + // Temporarily retain charmap, until the shared version is + // ready for use. + if (mCharacterMap.compareExchange(nullptr, charmap.get())) { + charmap.get()->AddRef(); + } + } + + LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %zu hash: %8.8x%s\n", + mName.get(), charmap->SizeOfIncludingThis(moz_malloc_size_of), + charmap->mHash, mCharacterMap == charmap ? " new" : "")); + if (LOG_CMAPDATA_ENABLED()) { + char prefix[256]; + SprintfLiteral(prefix, "(cmapdata) name: %.220s", mName.get()); + charmap->Dump(prefix, eGfxLog_cmapdata); + } + + return rv; +} + +bool gfxDWriteFontEntry::HasVariations() { + if (mHasVariationsInitialized) { + return mHasVariations; + } + mHasVariationsInitialized = true; + mHasVariations = false; + + if (!gfxPlatform::HasVariationFontSupport()) { + return mHasVariations; + } + + if (!mFontFace) { + // CreateFontFace will initialize the mFontFace field, and also + // mFontFace5 if available on the current DWrite version. + RefPtr<IDWriteFontFace> fontFace; + if (NS_FAILED(CreateFontFace(getter_AddRefs(fontFace)))) { + return mHasVariations; + } + } + if (mFontFace5) { + mHasVariations = mFontFace5->HasVariations(); + } + return mHasVariations; +} + +void gfxDWriteFontEntry::GetVariationAxes( + nsTArray<gfxFontVariationAxis>& aAxes) { + if (!HasVariations()) { + return; + } + // HasVariations() will have ensured the mFontFace5 interface is available; + // so we can get an IDWriteFontResource and ask it for the axis info. + RefPtr<IDWriteFontResource> resource; + HRESULT hr = mFontFace5->GetFontResource(getter_AddRefs(resource)); + if (FAILED(hr) || !resource) { + return; + } + + uint32_t count = resource->GetFontAxisCount(); + AutoTArray<DWRITE_FONT_AXIS_VALUE, 4> defaultValues; + AutoTArray<DWRITE_FONT_AXIS_RANGE, 4> ranges; + defaultValues.SetLength(count); + ranges.SetLength(count); + resource->GetDefaultFontAxisValues(defaultValues.Elements(), count); + resource->GetFontAxisRanges(ranges.Elements(), count); + for (uint32_t i = 0; i < count; ++i) { + gfxFontVariationAxis axis; + MOZ_ASSERT(ranges[i].axisTag == defaultValues[i].axisTag); + DWRITE_FONT_AXIS_ATTRIBUTES attrs = resource->GetFontAxisAttributes(i); + if (attrs & DWRITE_FONT_AXIS_ATTRIBUTES_HIDDEN) { + continue; + } + if (!(attrs & DWRITE_FONT_AXIS_ATTRIBUTES_VARIABLE)) { + continue; + } + // Extract the 4 chars of the tag from DWrite's packed version, + // and reassemble them in the order we use for TRUETYPE_TAG. + uint32_t t = defaultValues[i].axisTag; + axis.mTag = TRUETYPE_TAG(t & 0xff, (t >> 8) & 0xff, (t >> 16) & 0xff, + (t >> 24) & 0xff); + // Try to get a human-friendly name (may not be present) + RefPtr<IDWriteLocalizedStrings> names; + resource->GetAxisNames(i, getter_AddRefs(names)); + if (names) { + GetEnglishOrFirstName(axis.mName, names); + } + axis.mMinValue = ranges[i].minValue; + axis.mMaxValue = ranges[i].maxValue; + axis.mDefaultValue = defaultValues[i].value; + aAxes.AppendElement(axis); + } +} + +void gfxDWriteFontEntry::GetVariationInstances( + nsTArray<gfxFontVariationInstance>& aInstances) { + gfxFontUtils::GetVariationData(this, nullptr, &aInstances); +} + +gfxFont* gfxDWriteFontEntry::CreateFontInstance( + const gfxFontStyle* aFontStyle) { + // We use the DirectWrite bold simulation for installed fonts, but NOT for + // webfonts; those will use multi-strike synthetic bold instead. + bool useBoldSim = false; + if (aFontStyle->NeedsSyntheticBold(this)) { + switch (StaticPrefs::gfx_font_rendering_directwrite_bold_simulation()) { + case 0: // never use the DWrite simulation + break; + case 1: // use DWrite simulation for installed fonts except COLR fonts, + // but not webfonts + useBoldSim = + !mIsDataUserFont && !HasFontTable(TRUETYPE_TAG('C', 'O', 'L', 'R')); + break; + default: // always use DWrite bold simulation, except for COLR fonts + useBoldSim = !HasFontTable(TRUETYPE_TAG('C', 'O', 'L', 'R')); + break; + } + } + DWRITE_FONT_SIMULATIONS sims = + useBoldSim ? DWRITE_FONT_SIMULATIONS_BOLD : DWRITE_FONT_SIMULATIONS_NONE; + ThreadSafeWeakPtr<UnscaledFontDWrite>& unscaledFontPtr = + useBoldSim ? mUnscaledFontBold : mUnscaledFont; + RefPtr<UnscaledFontDWrite> unscaledFont(unscaledFontPtr); + if (!unscaledFont) { + RefPtr<IDWriteFontFace> fontFace; + nsresult rv = + CreateFontFace(getter_AddRefs(fontFace), nullptr, sims, nullptr); + if (NS_FAILED(rv)) { + return nullptr; + } + // Only pass in the underlying IDWriteFont if the unscaled font doesn't + // reflect a data font. This signals whether or not we can safely query + // a descriptor to represent the font for various transport use-cases. + unscaledFont = + new UnscaledFontDWrite(fontFace, !mIsDataUserFont ? mFont : nullptr); + unscaledFontPtr = unscaledFont; + } + RefPtr<IDWriteFontFace> fontFace; + if (HasVariations()) { + // Get the variation settings needed to instantiate the fontEntry + // for a particular fontStyle. + AutoTArray<gfxFontVariation, 4> vars; + GetVariationsForStyle(vars, *aFontStyle); + + if (!vars.IsEmpty()) { + nsresult rv = + CreateFontFace(getter_AddRefs(fontFace), aFontStyle, sims, &vars); + if (NS_FAILED(rv)) { + return nullptr; + } + } + } + return new gfxDWriteFont(unscaledFont, this, aFontStyle, fontFace); +} + +nsresult gfxDWriteFontEntry::CreateFontFace( + IDWriteFontFace** aFontFace, const gfxFontStyle* aFontStyle, + DWRITE_FONT_SIMULATIONS aSimulations, + const nsTArray<gfxFontVariation>* aVariations) { + // Convert an OpenType font tag from our uint32_t representation + // (as constructed by TRUETYPE_TAG(...)) to the order DWrite wants. + auto makeDWriteAxisTag = [](uint32_t aTag) { + return DWRITE_MAKE_FONT_AXIS_TAG((aTag >> 24) & 0xff, (aTag >> 16) & 0xff, + (aTag >> 8) & 0xff, aTag & 0xff); + }; + + MOZ_SEH_TRY { + // initialize mFontFace if this hasn't been done before + if (!mFontFace) { + HRESULT hr; + if (mFont) { + hr = mFont->CreateFontFace(getter_AddRefs(mFontFace)); + } else if (mFontFile) { + IDWriteFontFile* fontFile = mFontFile.get(); + hr = Factory::GetDWriteFactory()->CreateFontFace( + mFaceType, 1, &fontFile, 0, DWRITE_FONT_SIMULATIONS_NONE, + getter_AddRefs(mFontFace)); + } else { + MOZ_ASSERT_UNREACHABLE("invalid font entry"); + return NS_ERROR_FAILURE; + } + if (FAILED(hr)) { + return NS_ERROR_FAILURE; + } + // Also get the IDWriteFontFace5 interface if we're running on a + // sufficiently new DWrite version where it is available. + if (mFontFace) { + mFontFace->QueryInterface(__uuidof(IDWriteFontFace5), + (void**)getter_AddRefs(mFontFace5)); + if (!mVariationSettings.IsEmpty()) { + // If the font entry has variations specified, mFontFace5 will + // be a distinct face that has the variations applied. + RefPtr<IDWriteFontResource> resource; + HRESULT hr = mFontFace5->GetFontResource(getter_AddRefs(resource)); + if (SUCCEEDED(hr) && resource) { + AutoTArray<DWRITE_FONT_AXIS_VALUE, 4> fontAxisValues; + for (const auto& v : mVariationSettings) { + DWRITE_FONT_AXIS_VALUE axisValue = {makeDWriteAxisTag(v.mTag), + v.mValue}; + fontAxisValues.AppendElement(axisValue); + } + resource->CreateFontFace( + mFontFace->GetSimulations(), fontAxisValues.Elements(), + fontAxisValues.Length(), getter_AddRefs(mFontFace5)); + } + } + } + } + + // Do we need to modify DWrite simulations from what mFontFace has? + bool needSimulations = + (aSimulations & DWRITE_FONT_SIMULATIONS_BOLD) && + !(mFontFace->GetSimulations() & DWRITE_FONT_SIMULATIONS_BOLD); + + // If the IDWriteFontFace5 interface is available, we can try using + // IDWriteFontResource to create a new modified face. + if (mFontFace5 && (HasVariations() || needSimulations)) { + RefPtr<IDWriteFontResource> resource; + HRESULT hr = mFontFace5->GetFontResource(getter_AddRefs(resource)); + if (SUCCEEDED(hr) && resource) { + AutoTArray<DWRITE_FONT_AXIS_VALUE, 4> fontAxisValues; + + // Copy variation settings to DWrite's type. + if (aVariations) { + for (const auto& v : *aVariations) { + DWRITE_FONT_AXIS_VALUE axisValue = {makeDWriteAxisTag(v.mTag), + v.mValue}; + fontAxisValues.AppendElement(axisValue); + } + } + + IDWriteFontFace5* ff5; + resource->CreateFontFace(aSimulations, fontAxisValues.Elements(), + fontAxisValues.Length(), &ff5); + if (ff5) { + *aFontFace = ff5; + return NS_OK; + } + } + } + + // Do we need to add DWrite simulations to the face? + if (needSimulations) { + // if so, we need to return not mFontFace itself but a version that + // has the Bold simulation - unfortunately, old DWrite doesn't provide + // a simple API for this + UINT32 numberOfFiles = 0; + if (FAILED(mFontFace->GetFiles(&numberOfFiles, nullptr))) { + return NS_ERROR_FAILURE; + } + AutoTArray<IDWriteFontFile*, 1> files; + files.AppendElements(numberOfFiles); + if (FAILED(mFontFace->GetFiles(&numberOfFiles, files.Elements()))) { + return NS_ERROR_FAILURE; + } + HRESULT hr = Factory::GetDWriteFactory()->CreateFontFace( + mFontFace->GetType(), numberOfFiles, files.Elements(), + mFontFace->GetIndex(), aSimulations, aFontFace); + for (UINT32 i = 0; i < numberOfFiles; ++i) { + files[i]->Release(); + } + return FAILED(hr) ? NS_ERROR_FAILURE : NS_OK; + } + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + gfxCriticalNote << "Exception occurred creating font face for " + << mName.get(); + } + + // no simulation: we can just add a reference to mFontFace5 (if present) + // or mFontFace (otherwise) and return that + if (mFontFace5) { + *aFontFace = mFontFace5; + } else { + *aFontFace = mFontFace; + } + (*aFontFace)->AddRef(); + return NS_OK; +} + +bool gfxDWriteFontEntry::InitLogFont(IDWriteFont* aFont, LOGFONTW* aLogFont) { + HRESULT hr; + + BOOL isInSystemCollection; + IDWriteGdiInterop* gdi = + gfxDWriteFontList::PlatformFontList()->GetGDIInterop(); + hr = gdi->ConvertFontToLOGFONT(aFont, aLogFont, &isInSystemCollection); + // If the font is not in the system collection, GDI will be unable to + // select it and load its tables, so we return false here to indicate + // failure, and let CopyFontTable fall back to DWrite native methods. + return (SUCCEEDED(hr) && isInSystemCollection); +} + +bool gfxDWriteFontEntry::IsCJKFont() { + if (mIsCJK != UNINITIALIZED_VALUE) { + return mIsCJK; + } + + mIsCJK = false; + + const uint32_t kOS2Tag = TRUETYPE_TAG('O', 'S', '/', '2'); + gfxFontUtils::AutoHBBlob blob(GetFontTable(kOS2Tag)); + if (!blob) { + return mIsCJK; + } + + uint32_t len; + const OS2Table* os2 = + reinterpret_cast<const OS2Table*>(hb_blob_get_data(blob, &len)); + // ulCodePageRange bit definitions for the CJK codepages, + // from http://www.microsoft.com/typography/otspec/os2.htm#cpr + const uint32_t CJK_CODEPAGE_BITS = + (1 << 17) | // codepage 932 - JIS/Japan + (1 << 18) | // codepage 936 - Chinese (simplified) + (1 << 19) | // codepage 949 - Korean Wansung + (1 << 20) | // codepage 950 - Chinese (traditional) + (1 << 21); // codepage 1361 - Korean Johab + if (len >= offsetof(OS2Table, sxHeight)) { + if ((uint32_t(os2->codePageRange1) & CJK_CODEPAGE_BITS) != 0) { + mIsCJK = true; + } + } + + return mIsCJK; +} + +void gfxDWriteFontEntry::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + gfxFontEntry::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); + // TODO: + // This doesn't currently account for the |mFont| and |mFontFile| members +} + +void gfxDWriteFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +//////////////////////////////////////////////////////////////////////////////// +// gfxDWriteFontList + +gfxDWriteFontList::gfxDWriteFontList() : mForceGDIClassicMaxFontSize(0.0) { + CheckFamilyList(kBaseFonts); + CheckFamilyList(kLangPackFonts); +} + +// bug 602792 - CJK systems default to large CJK fonts which cause excessive +// I/O strain during cold startup due to dwrite caching bugs. Default to +// Arial to avoid this. + +FontFamily gfxDWriteFontList::GetDefaultFontForPlatform( + nsPresContext* aPresContext, const gfxFontStyle* aStyle, + nsAtom* aLanguage) { + // try Arial first + FontFamily ff; + ff = FindFamily(aPresContext, "Arial"_ns); + if (!ff.IsNull()) { + return ff; + } + + // otherwise, use local default + NONCLIENTMETRICSW ncm; + ncm.cbSize = sizeof(ncm); + BOOL status = + ::SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0); + + if (status) { + ff = FindFamily(aPresContext, + NS_ConvertUTF16toUTF8(ncm.lfMessageFont.lfFaceName)); + } + + return ff; +} + +gfxFontEntry* gfxDWriteFontList::LookupLocalFont( + nsPresContext* aPresContext, const nsACString& aFontName, + WeightRange aWeightForEntry, StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) { + AutoLock lock(mLock); + + if (SharedFontList()) { + return LookupInSharedFaceNameList(aPresContext, aFontName, aWeightForEntry, + aStretchForEntry, aStyleForEntry); + } + + gfxFontEntry* lookup; + + lookup = LookupInFaceNameLists(aFontName); + if (!lookup) { + return nullptr; + } + + gfxDWriteFontEntry* dwriteLookup = static_cast<gfxDWriteFontEntry*>(lookup); + gfxDWriteFontEntry* fe = + new gfxDWriteFontEntry(lookup->Name(), dwriteLookup->mFont, + aWeightForEntry, aStretchForEntry, aStyleForEntry); + fe->SetForceGDIClassic(dwriteLookup->GetForceGDIClassic()); + return fe; +} + +gfxFontEntry* gfxDWriteFontList::MakePlatformFont( + const nsACString& aFontName, WeightRange aWeightForEntry, + StretchRange aStretchForEntry, SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, uint32_t aLength) { + RefPtr<IDWriteFontFileStream> fontFileStream; + RefPtr<IDWriteFontFile> fontFile; + HRESULT hr = gfxDWriteFontFileLoader::CreateCustomFontFile( + aFontData, aLength, getter_AddRefs(fontFile), + getter_AddRefs(fontFileStream)); + free((void*)aFontData); + NS_ASSERTION(SUCCEEDED(hr), "Failed to create font file reference"); + if (FAILED(hr)) { + return nullptr; + } + + nsAutoString uniqueName; + nsresult rv = gfxFontUtils::MakeUniqueUserFontName(uniqueName); + NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to make unique user font name"); + if (NS_FAILED(rv)) { + return nullptr; + } + + BOOL isSupported; + DWRITE_FONT_FILE_TYPE fileType; + UINT32 numFaces; + + auto entry = MakeUnique<gfxDWriteFontEntry>( + NS_ConvertUTF16toUTF8(uniqueName), fontFile, fontFileStream, + aWeightForEntry, aStretchForEntry, aStyleForEntry); + + hr = fontFile->Analyze(&isSupported, &fileType, &entry->mFaceType, &numFaces); + NS_ASSERTION(SUCCEEDED(hr), "IDWriteFontFile::Analyze failed"); + if (FAILED(hr)) { + return nullptr; + } + NS_ASSERTION(isSupported, "Unsupported font file"); + if (!isSupported) { + return nullptr; + } + NS_ASSERTION(numFaces == 1, "Font file does not contain exactly 1 face"); + if (numFaces != 1) { + // We don't know how to deal with 0 faces either. + return nullptr; + } + + return entry.release(); +} + +bool gfxDWriteFontList::UseGDIFontTableAccess() const { + // Using GDI font table access for DWrite is controlled by a pref, but also we + // must be able to make win32k calls. + return mGDIFontTableAccess && !IsWin32kLockedDown(); +} + +static void GetPostScriptNameFromNameTable(IDWriteFontFace* aFace, + nsCString& aName) { + const auto kNAME = + NativeEndian::swapToBigEndian(TRUETYPE_TAG('n', 'a', 'm', 'e')); + const char* data; + UINT32 size; + void* context; + BOOL exists; + if (SUCCEEDED(aFace->TryGetFontTable(kNAME, (const void**)&data, &size, + &context, &exists)) && + exists) { + if (NS_FAILED(gfxFontUtils::ReadCanonicalName( + data, size, gfxFontUtils::NAME_ID_POSTSCRIPT, aName))) { + aName.Truncate(0); + } + aFace->ReleaseFontTable(context); + } +} + +gfxFontEntry* gfxDWriteFontList::CreateFontEntry( + fontlist::Face* aFace, const fontlist::Family* aFamily) { + IDWriteFontCollection* collection = +#ifdef MOZ_BUNDLED_FONTS + aFamily->IsBundled() ? mBundledFonts : mSystemFonts; +#else + mSystemFonts; +#endif + RefPtr<IDWriteFontFamily> family; + bool foundExpectedFamily = false; + const nsCString& familyName = + aFamily->DisplayName().AsString(SharedFontList()); + + // The DirectWrite calls here might throw exceptions, e.g. in case of disk + // errors when trying to read the font file. + MOZ_SEH_TRY { + if (aFamily->Index() < collection->GetFontFamilyCount()) { + HRESULT hr = + collection->GetFontFamily(aFamily->Index(), getter_AddRefs(family)); + // Check that the family name is what we expected; if not, fall back to + // search by name. It's sad we have to do this, but it is possible for + // Windows to have given different versions of the system font collection + // to the parent and child processes. + if (SUCCEEDED(hr) && family) { + RefPtr<IDWriteLocalizedStrings> names; + hr = family->GetFamilyNames(getter_AddRefs(names)); + if (SUCCEEDED(hr) && names) { + nsAutoCString name; + if (GetEnglishOrFirstName(name, names)) { + foundExpectedFamily = name.Equals(familyName); + } + } + } + } + if (!foundExpectedFamily) { + // Try to get family by name instead of index (to deal with the case of + // collection mismatch). + UINT32 index; + BOOL exists; + NS_ConvertUTF8toUTF16 name16(familyName); + HRESULT hr = collection->FindFamilyName( + reinterpret_cast<const WCHAR*>(name16.BeginReading()), &index, + &exists); + if (FAILED(hr) || !exists || index == UINT_MAX || + FAILED(collection->GetFontFamily(index, getter_AddRefs(family))) || + !family) { + return nullptr; + } + } + + // Retrieve the required face by index within the family. + RefPtr<IDWriteFont> font; + if (FAILED(family->GetFont(aFace->mIndex, getter_AddRefs(font))) || !font) { + return nullptr; + } + + // Retrieve the psName from the font, so we can check we've found the + // expected face. + nsAutoCString psName; + if (FAILED(GetDirectWriteFaceName(font, PSNAME_ID, psName))) { + RefPtr<IDWriteFontFace> dwFontFace; + if (SUCCEEDED(font->CreateFontFace(getter_AddRefs(dwFontFace)))) { + GetPostScriptNameFromNameTable(dwFontFace, psName); + } + } + + // If it doesn't match, DirectWrite must have shuffled the order of faces + // returned for the family; search by name as a fallback. + nsCString faceName = aFace->mDescriptor.AsString(SharedFontList()); + if (psName != faceName) { + gfxWarning() << "Face name mismatch for index " << aFace->mIndex + << " in family " << familyName.get() << ": expected " + << faceName.get() << ", found " << psName.get(); + for (uint32_t i = 0; i < family->GetFontCount(); ++i) { + if (i == aFace->mIndex) { + continue; // this was the face we already tried + } + if (FAILED(family->GetFont(i, getter_AddRefs(font))) || !font) { + return nullptr; // this font family is broken! + } + if (FAILED(GetDirectWriteFaceName(font, PSNAME_ID, psName))) { + RefPtr<IDWriteFontFace> dwFontFace; + if (SUCCEEDED(font->CreateFontFace(getter_AddRefs(dwFontFace)))) { + GetPostScriptNameFromNameTable(dwFontFace, psName); + } + } + if (psName == faceName) { + break; + } + } + } + if (psName != faceName) { + return nullptr; + } + + auto fe = new gfxDWriteFontEntry(faceName, font, !aFamily->IsBundled()); + fe->InitializeFrom(aFace, aFamily); + fe->mForceGDIClassic = aFamily->IsForceClassic(); + fe->mMayUseGDIAccess = aFamily->IsSimple(); + return fe; + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + gfxCriticalNote << "Exception occurred while creating font entry for " + << familyName.get(); + } + return nullptr; +} + +FontVisibility gfxDWriteFontList::GetVisibilityForFamily( + const nsACString& aName) const { + if (FamilyInList(aName, kBaseFonts)) { + return FontVisibility::Base; + } + if (FamilyInList(aName, kLangPackFonts)) { + return FontVisibility::LangPack; + } + return FontVisibility::User; +} + +void gfxDWriteFontList::AppendFamiliesFromCollection( + IDWriteFontCollection* aCollection, + nsTArray<fontlist::Family::InitData>& aFamilies, + const nsTArray<nsCString>* aForceClassicFams) { + auto allFacesUltraBold = [](IDWriteFontFamily* aFamily) -> bool { + for (UINT32 i = 0; i < aFamily->GetFontCount(); i++) { + RefPtr<IDWriteFont> font; + HRESULT hr = aFamily->GetFont(i, getter_AddRefs(font)); + if (FAILED(hr)) { + NS_WARNING("Failed to get existing font from family."); + continue; + } + nsAutoCString faceName; + hr = GetDirectWriteFontName(font, faceName); + if (FAILED(hr)) { + continue; + } + if (faceName.Find("Ultra Bold"_ns) == kNotFound) { + return false; + } + } + return true; + }; + + nsAutoCString locale; + OSPreferences::GetInstance()->GetSystemLocale(locale); + ToLowerCase(locale); + NS_ConvertUTF8toUTF16 loc16(locale); + + for (unsigned i = 0; i < aCollection->GetFontFamilyCount(); ++i) { + RefPtr<IDWriteFontFamily> family; + aCollection->GetFontFamily(i, getter_AddRefs(family)); + RefPtr<IDWriteLocalizedStrings> localizedNames; + HRESULT hr = family->GetFamilyNames(getter_AddRefs(localizedNames)); + if (FAILED(hr)) { + gfxWarning() << "Failed to get names for font-family " << i; + continue; + } + + auto addFamily = [&](const nsACString& name, FontVisibility visibility, + bool altLocale = false) { + nsAutoCString key; + key = name; + BuildKeyNameFromFontName(key); + bool bad = mBadUnderlineFamilyNames.ContainsSorted(key); + bool classic = + aForceClassicFams && aForceClassicFams->ContainsSorted(key); + aFamilies.AppendElement(fontlist::Family::InitData( + key, name, i, visibility, aCollection != mSystemFonts, bad, classic, + altLocale)); + }; + + auto visibilityForName = [&](const nsACString& aName) -> FontVisibility { + // 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 (aName.EqualsLiteral("Gill Sans") && allFacesUltraBold(family)) { + return FontVisibility::Hidden; + } + // Bundled fonts are always available, so only system fonts are checked + // against the standard font names list. + return aCollection == mSystemFonts ? GetVisibilityForFamily(aName) + : FontVisibility::Base; + }; + + unsigned count = localizedNames->GetCount(); + if (count == 1) { + // This is the common case: the great majority of fonts only provide an + // en-US family name. + nsAutoCString name; + if (!GetNameAsUtf8(name, localizedNames, 0)) { + gfxWarning() << "GetNameAsUtf8 failed for index 0 in font-family " << i; + continue; + } + addFamily(name, visibilityForName(name)); + } else { + AutoTArray<nsCString, 4> names; + int sysLocIndex = -1; + FontVisibility visibility = FontVisibility::User; + for (unsigned index = 0; index < count; ++index) { + nsAutoCString name; + if (!GetNameAsUtf8(name, localizedNames, index)) { + gfxWarning() << "GetNameAsUtf8 failed for index " << index + << " in font-family " << i; + continue; + } + if (names.Contains(name)) { + continue; + } + if (sysLocIndex == -1) { + WCHAR buf[32]; + if (SUCCEEDED(localizedNames->GetLocaleName(index, buf, 32))) { + if (loc16.Equals(buf)) { + sysLocIndex = names.Length(); + } + } + } + names.AppendElement(name); + // We give the family the least-restrictive visibility of all its + // localized names, so that the used visibility will not depend on + // locale; with the exception that if any name is explicitly Hidden, + // this hides the family as a whole. + if (visibility != FontVisibility::Hidden) { + FontVisibility v = visibilityForName(name); + if (v == FontVisibility::Hidden) { + visibility = FontVisibility::Hidden; + } else { + visibility = std::min(visibility, v); + } + } + } + // 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], visibility, + index != static_cast<unsigned>(sysLocIndex)); + } + } + } +} + +void gfxDWriteFontList::GetFacesInitDataForFamily( + const fontlist::Family* aFamily, nsTArray<fontlist::Face::InitData>& aFaces, + bool aLoadCmaps) const { + IDWriteFontCollection* collection = +#ifdef MOZ_BUNDLED_FONTS + aFamily->IsBundled() ? mBundledFonts : mSystemFonts; +#else + mSystemFonts; +#endif + if (!collection) { + return; + } + RefPtr<IDWriteFontFamily> family; + collection->GetFontFamily(aFamily->Index(), getter_AddRefs(family)); + for (unsigned i = 0; i < family->GetFontCount(); ++i) { + RefPtr<IDWriteFont> dwFont; + family->GetFont(i, getter_AddRefs(dwFont)); + if (!dwFont || dwFont->GetSimulations() != DWRITE_FONT_SIMULATIONS_NONE) { + continue; + } + DWRITE_FONT_STYLE dwstyle = dwFont->GetStyle(); + // Ignore italic styles of Meiryo because "Meiryo (Bold) Italic" has + // non-italic style glyphs as Japanese characters. However, using it + // causes serious problem if web pages wants some elements to be + // different style from others only with font-style. For example, + // <em> and <i> should be rendered as italic in the default style. + if (dwstyle != DWRITE_FONT_STYLE_NORMAL && + aFamily->Key().AsString(SharedFontList()).EqualsLiteral("meiryo")) { + continue; + } + WeightRange weight(FontWeight::FromInt(dwFont->GetWeight())); + StretchRange stretch(FontStretchFromDWriteStretch(dwFont->GetStretch())); + // Try to read PSName as a unique face identifier; if this fails we'll get + // it directly from the 'name' table, and if that also fails we consider + // the face unusable. + MOZ_SEH_TRY { + nsAutoCString name; + RefPtr<gfxCharacterMap> charmap; + if (FAILED(GetDirectWriteFaceName(dwFont, PSNAME_ID, name)) || + aLoadCmaps) { + RefPtr<IDWriteFontFace> dwFontFace; + if (SUCCEEDED(dwFont->CreateFontFace(getter_AddRefs(dwFontFace)))) { + if (name.IsEmpty()) { + GetPostScriptNameFromNameTable(dwFontFace, name); + } + const auto kCMAP = + NativeEndian::swapToBigEndian(TRUETYPE_TAG('c', 'm', 'a', 'p')); + const char* data; + UINT32 size; + void* context; + BOOL exists; + if (aLoadCmaps) { + if (SUCCEEDED(dwFontFace->TryGetFontTable( + kCMAP, (const void**)&data, &size, &context, &exists)) && + exists) { + charmap = new gfxCharacterMap(); + uint32_t offset; + gfxFontUtils::ReadCMAP((const uint8_t*)data, size, *charmap, + offset); + dwFontFace->ReleaseFontTable(context); + } + } + } + } + if (name.IsEmpty()) { + gfxWarning() << "Failed to get name for face " << i << " in family " + << aFamily->Key().AsString(SharedFontList()).get(); + continue; + } + SlantStyleRange slant( + dwstyle == DWRITE_FONT_STYLE_NORMAL ? FontSlantStyle::NORMAL + : dwstyle == DWRITE_FONT_STYLE_ITALIC ? FontSlantStyle::ITALIC + : FontSlantStyle::OBLIQUE); + aFaces.AppendElement(fontlist::Face::InitData{ + name, uint16_t(i), false, weight, stretch, slant, charmap}); + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + // Exception (e.g. disk i/o error) occurred when DirectWrite tried to use + // the font resource. We'll just skip the bad face. + gfxCriticalNote << "Exception occurred reading faces for " + << aFamily->Key().AsString(SharedFontList()).get(); + } + } +} + +bool gfxDWriteFontList::ReadFaceNames(const fontlist::Family* aFamily, + const fontlist::Face* aFace, + nsCString& aPSName, + nsCString& aFullName) { + IDWriteFontCollection* collection = +#ifdef MOZ_BUNDLED_FONTS + aFamily->IsBundled() ? mBundledFonts : mSystemFonts; +#else + mSystemFonts; +#endif + RefPtr<IDWriteFontFamily> family; + if (FAILED(collection->GetFontFamily(aFamily->Index(), + getter_AddRefs(family)))) { + MOZ_ASSERT_UNREACHABLE("failed to get font-family"); + return false; + } + RefPtr<IDWriteFont> dwFont; + if (FAILED(family->GetFont(aFace->mIndex, getter_AddRefs(dwFont)))) { + MOZ_ASSERT_UNREACHABLE("failed to get font from family"); + return false; + } + HRESULT ps = GetDirectWriteFaceName(dwFont, PSNAME_ID, aPSName); + HRESULT full = GetDirectWriteFaceName(dwFont, FULLNAME_ID, aFullName); + if (FAILED(ps) || FAILED(full) || aPSName.IsEmpty() || aFullName.IsEmpty()) { + // We'll return true if either name was found, false if both fail. + // Note that on older Win7 systems, GetDirectWriteFaceName may "succeed" + // but return an empty string, so we have to check for non-empty strings + // to be sure we actually got a usable name. + + // Initialize result to true if either name was already found. + bool result = (SUCCEEDED(ps) && !aPSName.IsEmpty()) || + (SUCCEEDED(full) && !aFullName.IsEmpty()); + RefPtr<IDWriteFontFace> dwFontFace; + if (FAILED(dwFont->CreateFontFace(getter_AddRefs(dwFontFace)))) { + NS_WARNING("failed to create font face"); + return result; + } + void* context; + const char* data; + UINT32 size; + BOOL exists; + if (FAILED(dwFontFace->TryGetFontTable( + NativeEndian::swapToBigEndian(TRUETYPE_TAG('n', 'a', 'm', 'e')), + (const void**)&data, &size, &context, &exists)) || + !exists) { + NS_WARNING("failed to get name table"); + return result; + } + MOZ_SEH_TRY { + // Try to read the name table entries, and ensure result is true if either + // one succeeds. + if (FAILED(ps) || aPSName.IsEmpty()) { + if (NS_SUCCEEDED(gfxFontUtils::ReadCanonicalName( + data, size, gfxFontUtils::NAME_ID_POSTSCRIPT, aPSName))) { + result = true; + } else { + NS_WARNING("failed to read psname"); + } + } + if (FAILED(full) || aFullName.IsEmpty()) { + if (NS_SUCCEEDED(gfxFontUtils::ReadCanonicalName( + data, size, gfxFontUtils::NAME_ID_FULL, aFullName))) { + result = true; + } else { + NS_WARNING("failed to read fullname"); + } + } + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + gfxCriticalNote << "Exception occurred reading face names for " + << aFamily->Key().AsString(SharedFontList()).get(); + } + if (dwFontFace && context) { + dwFontFace->ReleaseFontTable(context); + } + return result; + } + return true; +} + +void gfxDWriteFontList::ReadFaceNamesForFamily( + fontlist::Family* aFamily, bool aNeedFullnamePostscriptNames) { + if (!aFamily->IsInitialized()) { + if (!InitializeFamily(aFamily)) { + return; + } + } + IDWriteFontCollection* collection = +#ifdef MOZ_BUNDLED_FONTS + aFamily->IsBundled() ? mBundledFonts : mSystemFonts; +#else + mSystemFonts; +#endif + RefPtr<IDWriteFontFamily> family; + if (FAILED(collection->GetFontFamily(aFamily->Index(), + getter_AddRefs(family)))) { + return; + } + fontlist::FontList* list = SharedFontList(); + const fontlist::Pointer* facePtrs = aFamily->Faces(list); + nsAutoCString familyName(aFamily->DisplayName().AsString(list)); + nsAutoCString key(aFamily->Key().AsString(list)); + + MOZ_SEH_TRY { + // Read PS-names and fullnames of the faces, and any alternate family names + // (either localizations or legacy subfamily names) + for (unsigned i = 0; i < aFamily->NumFaces(); ++i) { + auto* face = facePtrs[i].ToPtr<fontlist::Face>(list); + if (!face) { + continue; + } + RefPtr<IDWriteFont> dwFont; + if (FAILED(family->GetFont(face->mIndex, getter_AddRefs(dwFont)))) { + continue; + } + RefPtr<IDWriteFontFace> dwFontFace; + if (FAILED(dwFont->CreateFontFace(getter_AddRefs(dwFontFace)))) { + continue; + } + + const char* data; + UINT32 size; + void* context; + BOOL exists; + if (FAILED(dwFontFace->TryGetFontTable( + NativeEndian::swapToBigEndian(TRUETYPE_TAG('n', 'a', 'm', 'e')), + (const void**)&data, &size, &context, &exists)) || + !exists) { + continue; + } + + AutoTArray<nsCString, 4> otherFamilyNames; + gfxFontUtils::ReadOtherFamilyNamesForFace(familyName, data, size, + otherFamilyNames, false); + for (const auto& alias : otherFamilyNames) { + nsAutoCString key(alias); + ToLowerCase(key); + auto aliasData = mAliasTable.GetOrInsertNew(key); + aliasData->InitFromFamily(aFamily, familyName); + aliasData->mFaces.AppendElement(facePtrs[i]); + } + + nsAutoCString psname, fullname; + // Bug 1854090: don't load PSname if the family name ends with ".tmp", + // as some PDF-related software appears to pollute the font collection + // with spurious re-encoded versions of standard fonts like Arial, fails + // to alter the PSname, and thus can result in garbled rendering because + // the wrong resource may be found via src:local(...). + if (!StringEndsWith(key, ".tmp"_ns)) { + if (NS_SUCCEEDED(gfxFontUtils::ReadCanonicalName( + data, size, gfxFontUtils::NAME_ID_POSTSCRIPT, psname))) { + ToLowerCase(psname); + mLocalNameTable.InsertOrUpdate( + psname, fontlist::LocalFaceRec::InitData(key, i)); + } + } + if (NS_SUCCEEDED(gfxFontUtils::ReadCanonicalName( + data, size, gfxFontUtils::NAME_ID_FULL, fullname))) { + ToLowerCase(fullname); + if (fullname != psname) { + mLocalNameTable.InsertOrUpdate( + fullname, fontlist::LocalFaceRec::InitData(key, i)); + } + } + + dwFontFace->ReleaseFontTable(context); + } + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + gfxCriticalNote << "Exception occurred reading names for " + << familyName.get(); + } +} + +enum DWriteInitError { + errGDIInterop = 1, + errSystemFontCollection = 2, + errNoFonts = 3 +}; + +void gfxDWriteFontList::InitSharedFontListForPlatform() { + mGDIFontTableAccess = Preferences::GetBool( + "gfx.font_rendering.directwrite.use_gdi_table_loading", false); + mForceGDIClassicMaxFontSize = Preferences::GetInt( + "gfx.font_rendering.cleartype_params.force_gdi_classic_max_size", + mForceGDIClassicMaxFontSize); + + mSubstitutions.Clear(); + mNonExistingFonts.Clear(); + + RefPtr<IDWriteFactory> factory = Factory::GetDWriteFactory(); + HRESULT hr = factory->GetGdiInterop(getter_AddRefs(mGDIInterop)); + if (FAILED(hr)) { + Telemetry::Accumulate(Telemetry::DWRITEFONT_INIT_PROBLEM, + uint32_t(errGDIInterop)); + mSharedFontList.reset(nullptr); + return; + } + + mSystemFonts = Factory::GetDWriteSystemFonts(true); + NS_ASSERTION(mSystemFonts != nullptr, "GetSystemFontCollection failed!"); + if (!mSystemFonts) { + Telemetry::Accumulate(Telemetry::DWRITEFONT_INIT_PROBLEM, + uint32_t(errSystemFontCollection)); + mSharedFontList.reset(nullptr); + return; + } +#ifdef MOZ_BUNDLED_FONTS + // We activate bundled fonts if the pref is > 0 (on) or < 0 (auto), only an + // explicit value of 0 (off) will disable them. + TimeStamp start1 = TimeStamp::Now(); + if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) { + mBundledFonts = CreateBundledFontsCollection(factory); + } + TimeStamp end1 = TimeStamp::Now(); +#endif + + if (XRE_IsParentProcess()) { + nsAutoCString classicFamilies; + AutoTArray<nsCString, 16> forceClassicFams; + nsresult rv = Preferences::GetCString( + "gfx.font_rendering.cleartype_params.force_gdi_classic_for_families", + classicFamilies); + if (NS_SUCCEEDED(rv)) { + for (auto name : + nsCCharSeparatedTokenizer(classicFamilies, ',').ToRange()) { + BuildKeyNameFromFontName(name); + forceClassicFams.AppendElement(name); + } + forceClassicFams.Sort(); + } + nsTArray<fontlist::Family::InitData> families; + AppendFamiliesFromCollection(mSystemFonts, families, &forceClassicFams); +#ifdef MOZ_BUNDLED_FONTS + if (mBundledFonts) { + TimeStamp start2 = TimeStamp::Now(); + AppendFamiliesFromCollection(mBundledFonts, families); + TimeStamp end2 = TimeStamp::Now(); + Telemetry::Accumulate( + Telemetry::FONTLIST_BUNDLEDFONTS_ACTIVATE, + (end1 - start1).ToMilliseconds() + (end2 - start2).ToMilliseconds()); + } +#endif + SharedFontList()->SetFamilyNames(families); + GetPrefsAndStartLoader(); + } + + if (!SharedFontList()->Initialized()) { + return; + } + + GetDirectWriteSubstitutes(); + GetFontSubstitutes(); +} + +nsresult gfxDWriteFontList::InitFontListForPlatform() { + LARGE_INTEGER frequency; // ticks per second + LARGE_INTEGER t1, t2, t3, t4, t5; // ticks + double elapsedTime, upTime; + char nowTime[256], nowDate[256]; + + if (LOG_FONTINIT_ENABLED()) { + GetTimeFormatA(LOCALE_INVARIANT, TIME_FORCE24HOURFORMAT, nullptr, nullptr, + nowTime, 256); + GetDateFormatA(LOCALE_INVARIANT, 0, nullptr, nullptr, nowDate, 256); + upTime = (double)GetTickCount(); + } + QueryPerformanceFrequency(&frequency); + QueryPerformanceCounter(&t1); // start + + HRESULT hr; + mGDIFontTableAccess = Preferences::GetBool( + "gfx.font_rendering.directwrite.use_gdi_table_loading", false); + + mFontSubstitutes.Clear(); + mNonExistingFonts.Clear(); + + RefPtr<IDWriteFactory> factory = Factory::GetDWriteFactory(); + + hr = factory->GetGdiInterop(getter_AddRefs(mGDIInterop)); + if (FAILED(hr)) { + Telemetry::Accumulate(Telemetry::DWRITEFONT_INIT_PROBLEM, + uint32_t(errGDIInterop)); + return NS_ERROR_FAILURE; + } + + QueryPerformanceCounter(&t2); // base-class/interop initialization + + mSystemFonts = Factory::GetDWriteSystemFonts(true); + NS_ASSERTION(mSystemFonts != nullptr, "GetSystemFontCollection failed!"); + + if (!mSystemFonts) { + Telemetry::Accumulate(Telemetry::DWRITEFONT_INIT_PROBLEM, + uint32_t(errSystemFontCollection)); + return NS_ERROR_FAILURE; + } + +#ifdef MOZ_BUNDLED_FONTS + // Get bundled fonts before the system collection, so that in the case of + // duplicate names, we have recorded the family as bundled (and therefore + // available regardless of visibility settings). + // We activate bundled fonts if the pref is > 0 (on) or < 0 (auto), only an + // explicit value of 0 (off) will disable them. + if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) { + TimeStamp start = TimeStamp::Now(); + mBundledFonts = CreateBundledFontsCollection(factory); + if (mBundledFonts) { + GetFontsFromCollection(mBundledFonts); + } + TimeStamp end = TimeStamp::Now(); + Telemetry::Accumulate(Telemetry::FONTLIST_BUNDLEDFONTS_ACTIVATE, + (end - start).ToMilliseconds()); + } +#endif + const uint32_t kBundledCount = mFontFamilies.Count(); + + QueryPerformanceCounter(&t3); // system font collection + + GetFontsFromCollection(mSystemFonts); + + // if no fonts found, something is out of whack, bail and use GDI backend + NS_ASSERTION(mFontFamilies.Count() > kBundledCount, + "no fonts found in the system fontlist -- holy crap batman!"); + if (mFontFamilies.Count() == kBundledCount) { + Telemetry::Accumulate(Telemetry::DWRITEFONT_INIT_PROBLEM, + uint32_t(errNoFonts)); + return NS_ERROR_FAILURE; + } + + QueryPerformanceCounter(&t4); // iterate over system fonts + + mOtherFamilyNamesInitialized = true; + GetFontSubstitutes(); + + // bug 642093 - DirectWrite does not support old bitmap (.fon) + // font files, but a few of these such as "Courier" and "MS Sans Serif" + // are frequently specified in shoddy CSS, without appropriate fallbacks. + // By mapping these to TrueType equivalents, we provide better consistency + // with both pre-DW systems and with IE9, which appears to do the same. + GetDirectWriteSubstitutes(); + + // bug 551313 - DirectWrite creates a Gill Sans family out of + // poorly named members of the Gill Sans MT family containing + // only Ultra Bold weights. This causes big problems for pages + // using Gill Sans which is usually only available on OSX + + nsAutoCString nameGillSans("Gill Sans"); + nsAutoCString nameGillSansMT("Gill Sans MT"); + BuildKeyNameFromFontName(nameGillSans); + BuildKeyNameFromFontName(nameGillSansMT); + + gfxFontFamily* gillSansFamily = mFontFamilies.GetWeak(nameGillSans); + gfxFontFamily* gillSansMTFamily = mFontFamilies.GetWeak(nameGillSansMT); + + if (gillSansFamily && gillSansMTFamily) { + gillSansFamily->FindStyleVariations(); + + gillSansFamily->ReadLock(); + const auto& faces = gillSansFamily->GetFontList(); + + bool allUltraBold = true; + for (const auto& face : faces) { + // does the face have 'Ultra Bold' in the name? + if (face->Name().Find("Ultra Bold"_ns) == -1) { + allUltraBold = false; + break; + } + } + + // if all the Gill Sans faces are Ultra Bold ==> move faces + // for Gill Sans into Gill Sans MT family + if (allUltraBold) { + // add faces to Gill Sans MT + for (const auto& face : faces) { + // change the entry's family name to match its adoptive family + face->mFamilyName = gillSansMTFamily->Name(); + gillSansMTFamily->AddFontEntry(face); + + if (LOG_FONTLIST_ENABLED()) { + nsAutoCString weightString; + face->Weight().ToString(weightString); + LOG_FONTLIST( + ("(fontlist) moved (%s) to family (%s)" + " with style: %s weight: %s stretch: %d", + face->Name().get(), gillSansMTFamily->Name().get(), + (face->IsItalic()) ? "italic" + : (face->IsOblique() ? "oblique" : "normal"), + weightString.get(), face->Stretch().AsScalar())); + } + } + gillSansFamily->ReadUnlock(); + + // remove Gill Sans + mFontFamilies.Remove(nameGillSans); + } else { + gillSansFamily->ReadUnlock(); + } + } + + nsAutoCString classicFamilies; + nsresult rv = Preferences::GetCString( + "gfx.font_rendering.cleartype_params.force_gdi_classic_for_families", + classicFamilies); + if (NS_SUCCEEDED(rv)) { + nsCCharSeparatedTokenizer tokenizer(classicFamilies, ','); + while (tokenizer.hasMoreTokens()) { + nsAutoCString name(tokenizer.nextToken()); + BuildKeyNameFromFontName(name); + gfxFontFamily* family = mFontFamilies.GetWeak(name); + if (family) { + static_cast<gfxDWriteFontFamily*>(family)->SetForceGDIClassic(true); + } + } + } + mForceGDIClassicMaxFontSize = Preferences::GetInt( + "gfx.font_rendering.cleartype_params.force_gdi_classic_max_size", + mForceGDIClassicMaxFontSize); + + GetPrefsAndStartLoader(); + + QueryPerformanceCounter(&t5); // misc initialization + + if (LOG_FONTINIT_ENABLED()) { + // determine dwrite version + nsAutoString dwriteVers; + gfxWindowsPlatform::GetDLLVersion(L"dwrite.dll", dwriteVers); + LOG_FONTINIT(("(fontinit) Start: %s %s\n", nowDate, nowTime)); + LOG_FONTINIT(("(fontinit) Uptime: %9.3f s\n", upTime / 1000)); + LOG_FONTINIT(("(fontinit) dwrite version: %s\n", + NS_ConvertUTF16toUTF8(dwriteVers).get())); + } + + elapsedTime = (t5.QuadPart - t1.QuadPart) * 1000.0 / frequency.QuadPart; + Telemetry::Accumulate(Telemetry::DWRITEFONT_DELAYEDINITFONTLIST_TOTAL, + elapsedTime); + Telemetry::Accumulate(Telemetry::DWRITEFONT_DELAYEDINITFONTLIST_COUNT, + mSystemFonts->GetFontFamilyCount()); + LOG_FONTINIT(( + "(fontinit) Total time in InitFontList: %9.3f ms (families: %d, %s)\n", + elapsedTime, mSystemFonts->GetFontFamilyCount(), + (mGDIFontTableAccess ? "gdi table access" : "dwrite table access"))); + + elapsedTime = (t2.QuadPart - t1.QuadPart) * 1000.0 / frequency.QuadPart; + LOG_FONTINIT( + ("(fontinit) --- base/interop obj initialization init: %9.3f ms\n", + elapsedTime)); + + elapsedTime = (t3.QuadPart - t2.QuadPart) * 1000.0 / frequency.QuadPart; + Telemetry::Accumulate(Telemetry::DWRITEFONT_DELAYEDINITFONTLIST_COLLECT, + elapsedTime); + LOG_FONTINIT( + ("(fontinit) --- GetSystemFontCollection: %9.3f ms\n", elapsedTime)); + + elapsedTime = (t4.QuadPart - t3.QuadPart) * 1000.0 / frequency.QuadPart; + LOG_FONTINIT( + ("(fontinit) --- iterate over families: %9.3f ms\n", elapsedTime)); + + elapsedTime = (t5.QuadPart - t4.QuadPart) * 1000.0 / frequency.QuadPart; + LOG_FONTINIT( + ("(fontinit) --- misc initialization: %9.3f ms\n", elapsedTime)); + + return NS_OK; +} + +void gfxDWriteFontList::GetFontsFromCollection( + IDWriteFontCollection* aCollection) { + for (UINT32 i = 0; i < aCollection->GetFontFamilyCount(); i++) { + RefPtr<IDWriteFontFamily> family; + aCollection->GetFontFamily(i, getter_AddRefs(family)); + + RefPtr<IDWriteLocalizedStrings> names; + HRESULT hr = family->GetFamilyNames(getter_AddRefs(names)); + if (FAILED(hr)) { + continue; + } + + nsAutoCString name; + if (!GetEnglishOrFirstName(name, names)) { + continue; + } + nsAutoCString familyName( + name); // keep a copy before we lowercase it as a key + + BuildKeyNameFromFontName(name); + + RefPtr<gfxFontFamily> fam; + + if (mFontFamilies.GetWeak(name)) { + continue; + } + + FontVisibility visibility = aCollection == mSystemFonts + ? GetVisibilityForFamily(familyName) + : FontVisibility::Base; + + fam = new gfxDWriteFontFamily(familyName, visibility, family, + aCollection == mSystemFonts); + if (!fam) { + continue; + } + + if (mBadUnderlineFamilyNames.ContainsSorted(name)) { + fam->SetBadUnderlineFamily(); + } + mFontFamilies.InsertOrUpdate(name, RefPtr{fam}); + + // now add other family name localizations, if present + uint32_t nameCount = names->GetCount(); + uint32_t nameIndex; + + if (nameCount > 1) { + UINT32 englishIdx = 0; + BOOL exists; + // if this fails/doesn't exist, we'll have used name index 0, + // so that's the one we'll want to skip here + names->FindLocaleName(L"en-us", &englishIdx, &exists); + AutoTArray<nsCString, 4> otherFamilyNames; + for (nameIndex = 0; nameIndex < nameCount; nameIndex++) { + UINT32 nameLen; + AutoTArray<WCHAR, 32> localizedName; + + // only add other names + if (nameIndex == englishIdx) { + continue; + } + + hr = names->GetStringLength(nameIndex, &nameLen); + if (FAILED(hr)) { + continue; + } + + if (!localizedName.SetLength(nameLen + 1, fallible)) { + continue; + } + + hr = names->GetString(nameIndex, localizedName.Elements(), nameLen + 1); + if (FAILED(hr)) { + continue; + } + + NS_ConvertUTF16toUTF8 locName(localizedName.Elements()); + + if (!familyName.Equals(locName)) { + otherFamilyNames.AppendElement(locName); + } + } + if (!otherFamilyNames.IsEmpty()) { + AddOtherFamilyNames(fam, otherFamilyNames); + } + } + + // at this point, all family names have been read in + fam->SetOtherFamilyNamesInitialized(); + } +} + +static void RemoveCharsetFromFontSubstitute(nsACString& aName) { + int32_t comma = aName.FindChar(','); + if (comma >= 0) aName.Truncate(comma); +} + +#define MAX_VALUE_NAME 512 +#define MAX_VALUE_DATA 512 + +nsresult gfxDWriteFontList::GetFontSubstitutes() { + HKEY hKey; + DWORD i, rv, lenAlias, lenActual, valueType; + WCHAR aliasName[MAX_VALUE_NAME]; + WCHAR actualName[MAX_VALUE_DATA]; + + if (RegOpenKeyExW( + HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\FontSubstitutes", + 0, KEY_READ, &hKey) != ERROR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + for (i = 0, rv = ERROR_SUCCESS; rv != ERROR_NO_MORE_ITEMS; i++) { + aliasName[0] = 0; + lenAlias = ArrayLength(aliasName); + actualName[0] = 0; + lenActual = sizeof(actualName); + rv = RegEnumValueW(hKey, i, aliasName, &lenAlias, nullptr, &valueType, + (LPBYTE)actualName, &lenActual); + + if (rv != ERROR_SUCCESS || valueType != REG_SZ || lenAlias == 0) { + continue; + } + + if (aliasName[0] == WCHAR('@')) { + continue; + } + + NS_ConvertUTF16toUTF8 substituteName((char16_t*)aliasName); + NS_ConvertUTF16toUTF8 actualFontName((char16_t*)actualName); + RemoveCharsetFromFontSubstitute(substituteName); + BuildKeyNameFromFontName(substituteName); + RemoveCharsetFromFontSubstitute(actualFontName); + BuildKeyNameFromFontName(actualFontName); + if (SharedFontList()) { + // Skip substitution if the original font is available, unless the option + // to apply substitutions unconditionally is enabled. + if (!StaticPrefs::gfx_windows_font_substitutes_always_AtStartup()) { + // Font substitutions are recorded for the canonical family names; we + // don't need FindFamily to consider localized aliases when searching. + if (SharedFontList()->FindFamily(substituteName, + /*aPrimaryNameOnly*/ true)) { + continue; + } + } + if (SharedFontList()->FindFamily(actualFontName, + /*aPrimaryNameOnly*/ true)) { + mSubstitutions.InsertOrUpdate(substituteName, + MakeUnique<nsCString>(actualFontName)); + } else if (mSubstitutions.Get(actualFontName)) { + mSubstitutions.InsertOrUpdate( + substituteName, + MakeUnique<nsCString>(*mSubstitutions.Get(actualFontName))); + } else { + mNonExistingFonts.AppendElement(substituteName); + } + } else { + gfxFontFamily* ff; + if (!actualFontName.IsEmpty() && + (ff = mFontFamilies.GetWeak(actualFontName))) { + mFontSubstitutes.InsertOrUpdate(substituteName, RefPtr{ff}); + } else { + mNonExistingFonts.AppendElement(substituteName); + } + } + } + return NS_OK; +} + +struct FontSubstitution { + const char* aliasName; + const char* actualName; +}; + +static const FontSubstitution sDirectWriteSubs[] = { + {"MS Sans Serif", "Microsoft Sans Serif"}, + {"MS Serif", "Times New Roman"}, + {"Courier", "Courier New"}, + {"Small Fonts", "Arial"}, + {"Roman", "Times New Roman"}, + {"Script", "Mistral"}}; + +void gfxDWriteFontList::GetDirectWriteSubstitutes() { + for (uint32_t i = 0; i < ArrayLength(sDirectWriteSubs); ++i) { + const FontSubstitution& sub(sDirectWriteSubs[i]); + nsAutoCString substituteName(sub.aliasName); + BuildKeyNameFromFontName(substituteName); + if (SharedFontList()) { + // Skip substitution if the original font is available, unless the option + // to apply substitutions unconditionally is enabled. + if (!StaticPrefs::gfx_windows_font_substitutes_always_AtStartup()) { + // We don't need FindFamily to consider localized aliases when searching + // for the DirectWrite substitutes, we know the canonical names. + if (SharedFontList()->FindFamily(substituteName, + /*aPrimaryNameOnly*/ true)) { + continue; + } + } + nsAutoCString actualFontName(sub.actualName); + BuildKeyNameFromFontName(actualFontName); + if (SharedFontList()->FindFamily(actualFontName, + /*aPrimaryNameOnly*/ true)) { + mSubstitutions.InsertOrUpdate(substituteName, + MakeUnique<nsCString>(actualFontName)); + } else { + mNonExistingFonts.AppendElement(substituteName); + } + } else { + if (nullptr != mFontFamilies.GetWeak(substituteName)) { + // don't do the substitution if user actually has a usable font + // with this name installed + continue; + } + nsAutoCString actualFontName(sub.actualName); + BuildKeyNameFromFontName(actualFontName); + gfxFontFamily* ff; + if (nullptr != (ff = mFontFamilies.GetWeak(actualFontName))) { + mFontSubstitutes.InsertOrUpdate(substituteName, RefPtr{ff}); + } else { + mNonExistingFonts.AppendElement(substituteName); + } + } + } +} + +bool gfxDWriteFontList::FindAndAddFamiliesLocked( + nsPresContext* aPresContext, StyleGenericFontFamily aGeneric, + const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput, + FindFamiliesFlags aFlags, gfxFontStyle* aStyle, nsAtom* aLanguage, + gfxFloat aDevToCssSize) { + nsAutoCString keyName(aFamily); + BuildKeyNameFromFontName(keyName); + + if (SharedFontList()) { + nsACString* subst = mSubstitutions.Get(keyName); + if (subst) { + keyName = *subst; + } + } else { + gfxFontFamily* ff = mFontSubstitutes.GetWeak(keyName); + FontVisibility level = + aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User; + if (ff && IsVisibleToCSS(*ff, level)) { + aOutput->AppendElement(FamilyAndGeneric(ff, aGeneric)); + return true; + } + } + + if (mNonExistingFonts.Contains(keyName)) { + return false; + } + + return gfxPlatformFontList::FindAndAddFamiliesLocked( + aPresContext, aGeneric, keyName, aOutput, aFlags, aStyle, aLanguage, + aDevToCssSize); +} + +void gfxDWriteFontList::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + gfxPlatformFontList::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); + + AutoLock lock(mLock); + + // We are a singleton, so include the font loader singleton's memory. + MOZ_ASSERT(static_cast<const gfxPlatformFontList*>(this) == + gfxPlatformFontList::PlatformFontList()); + gfxDWriteFontFileLoader* loader = static_cast<gfxDWriteFontFileLoader*>( + gfxDWriteFontFileLoader::Instance()); + aSizes->mLoaderSize += loader->SizeOfIncludingThis(aMallocSizeOf); + + aSizes->mFontListSize += + SizeOfFontFamilyTableExcludingThis(mFontSubstitutes, aMallocSizeOf); + + aSizes->mFontListSize += + mNonExistingFonts.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (uint32_t i = 0; i < mNonExistingFonts.Length(); ++i) { + aSizes->mFontListSize += + mNonExistingFonts[i].SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } +} + +void gfxDWriteFontList::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +static HRESULT GetFamilyName(IDWriteFont* aFont, nsCString& aFamilyName) { + HRESULT hr; + RefPtr<IDWriteFontFamily> family; + + // clean out previous value + aFamilyName.Truncate(); + + hr = aFont->GetFontFamily(getter_AddRefs(family)); + if (FAILED(hr)) { + return hr; + } + + RefPtr<IDWriteLocalizedStrings> familyNames; + + hr = family->GetFamilyNames(getter_AddRefs(familyNames)); + if (FAILED(hr)) { + return hr; + } + + if (!GetEnglishOrFirstName(aFamilyName, familyNames)) { + return E_FAIL; + } + + return S_OK; +} + +// bug 705594 - the method below doesn't actually do any "drawing", it's only +// used to invoke the DirectWrite layout engine to determine the fallback font +// for a given character. + +IFACEMETHODIMP DWriteFontFallbackRenderer::DrawGlyphRun( + void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, + DWRITE_MEASURING_MODE measuringMode, DWRITE_GLYPH_RUN const* glyphRun, + DWRITE_GLYPH_RUN_DESCRIPTION const* glyphRunDescription, + IUnknown* clientDrawingEffect) { + if (!mSystemFonts) { + return E_FAIL; + } + + HRESULT hr = S_OK; + + RefPtr<IDWriteFont> font; + hr = mSystemFonts->GetFontFromFontFace(glyphRun->fontFace, + getter_AddRefs(font)); + if (FAILED(hr)) { + return hr; + } + + // copy the family name + hr = GetFamilyName(font, mFamilyName); + if (FAILED(hr)) { + return hr; + } + + // Arial is used as the default fallback font + // so if it matches ==> no font found + if (mFamilyName.EqualsLiteral("Arial")) { + mFamilyName.Truncate(); + return E_FAIL; + } + return hr; +} + +gfxFontEntry* gfxDWriteFontList::PlatformGlobalFontFallback( + nsPresContext* aPresContext, const uint32_t aCh, Script aRunScript, + const gfxFontStyle* aMatchStyle, FontFamily& aMatchedFamily) { + HRESULT hr; + + RefPtr<IDWriteFactory> dwFactory = Factory::GetDWriteFactory(); + if (!dwFactory) { + return nullptr; + } + + // initialize fallback renderer + if (!mFallbackRenderer) { + mFallbackRenderer = new DWriteFontFallbackRenderer(dwFactory); + } + if (!mFallbackRenderer->IsValid()) { + return nullptr; + } + + // initialize text format + if (!mFallbackFormat) { + hr = dwFactory->CreateTextFormat( + L"Arial", nullptr, DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL, + DWRITE_FONT_STRETCH_NORMAL, 72.0f, L"en-us", + getter_AddRefs(mFallbackFormat)); + if (FAILED(hr)) { + return nullptr; + } + } + + // set up string with fallback character + wchar_t str[16]; + uint32_t strLen; + + if (IS_IN_BMP(aCh)) { + str[0] = static_cast<wchar_t>(aCh); + str[1] = 0; + strLen = 1; + } else { + str[0] = static_cast<wchar_t>(H_SURROGATE(aCh)); + str[1] = static_cast<wchar_t>(L_SURROGATE(aCh)); + str[2] = 0; + strLen = 2; + } + + // set up layout + RefPtr<IDWriteTextLayout> fallbackLayout; + + hr = dwFactory->CreateTextLayout(str, strLen, mFallbackFormat, 200.0f, 200.0f, + getter_AddRefs(fallbackLayout)); + if (FAILED(hr)) { + return nullptr; + } + + // call the draw method to invoke the DirectWrite layout functions + // which determine the fallback font + MOZ_SEH_TRY { + hr = fallbackLayout->Draw(nullptr, mFallbackRenderer, 50.0f, 50.0f); + if (FAILED(hr)) { + return nullptr; + } + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + gfxCriticalNote << "Exception occurred during DWrite font fallback"; + return nullptr; + } + + FontFamily family = + FindFamily(aPresContext, mFallbackRenderer->FallbackFamilyName()); + if (!family.IsNull()) { + gfxFontEntry* fontEntry = nullptr; + if (family.mShared) { + auto face = + family.mShared->FindFaceForStyle(SharedFontList(), *aMatchStyle); + if (face) { + fontEntry = GetOrCreateFontEntry(face, family.mShared); + } + } else { + fontEntry = family.mUnshared->FindFontForStyle(*aMatchStyle); + } + if (fontEntry && fontEntry->HasCharacter(aCh)) { + aMatchedFamily = family; + return fontEntry; + } + Telemetry::Accumulate(Telemetry::BAD_FALLBACK_FONT, true); + } + + return nullptr; +} + +// used to load system-wide font info on off-main thread +class DirectWriteFontInfo : public FontInfoData { + public: + DirectWriteFontInfo(bool aLoadOtherNames, bool aLoadFaceNames, + bool aLoadCmaps, IDWriteFontCollection* aSystemFonts +#ifdef MOZ_BUNDLED_FONTS + , + IDWriteFontCollection* aBundledFonts +#endif + ) + : FontInfoData(aLoadOtherNames, aLoadFaceNames, aLoadCmaps), + mSystemFonts(aSystemFonts) +#ifdef MOZ_BUNDLED_FONTS + , + mBundledFonts(aBundledFonts) +#endif + { + } + + virtual ~DirectWriteFontInfo() = default; + + // loads font data for all members of a given family + virtual void LoadFontFamilyData(const nsACString& aFamilyName); + + private: + RefPtr<IDWriteFontCollection> mSystemFonts; +#ifdef MOZ_BUNDLED_FONTS + RefPtr<IDWriteFontCollection> mBundledFonts; +#endif +}; + +void DirectWriteFontInfo::LoadFontFamilyData(const nsACString& aFamilyName) { + // lookup the family + NS_ConvertUTF8toUTF16 famName(aFamilyName); + + HRESULT hr; + BOOL exists = false; + + uint32_t index; + RefPtr<IDWriteFontFamily> family; + hr = mSystemFonts->FindFamilyName((const wchar_t*)famName.get(), &index, + &exists); + if (SUCCEEDED(hr) && exists) { + mSystemFonts->GetFontFamily(index, getter_AddRefs(family)); + if (!family) { + return; + } + } + +#ifdef MOZ_BUNDLED_FONTS + if (!family && mBundledFonts) { + hr = mBundledFonts->FindFamilyName((const wchar_t*)famName.get(), &index, + &exists); + if (SUCCEEDED(hr) && exists) { + mBundledFonts->GetFontFamily(index, getter_AddRefs(family)); + } + } +#endif + + if (!family) { + return; + } + + // later versions of DirectWrite support querying the fullname/psname + bool loadFaceNamesUsingDirectWrite = mLoadFaceNames; + + for (uint32_t i = 0; i < family->GetFontCount(); i++) { + // get the font + RefPtr<IDWriteFont> dwFont; + hr = family->GetFont(i, getter_AddRefs(dwFont)); + if (FAILED(hr)) { + // This should never happen. + NS_WARNING("Failed to get existing font from family."); + continue; + } + + if (dwFont->GetSimulations() != DWRITE_FONT_SIMULATIONS_NONE) { + // We don't want these in the font list; we'll apply simulations + // on the fly when appropriate. + continue; + } + + mLoadStats.fonts++; + + // get the name of the face + nsCString fullID(aFamilyName); + nsAutoCString fontName; + hr = GetDirectWriteFontName(dwFont, fontName); + if (FAILED(hr)) { + continue; + } + fullID.Append(' '); + fullID.Append(fontName); + + FontFaceData fontData; + bool haveData = true; + RefPtr<IDWriteFontFace> dwFontFace; + + if (mLoadFaceNames) { + // try to load using DirectWrite first + if (loadFaceNamesUsingDirectWrite) { + hr = + GetDirectWriteFaceName(dwFont, PSNAME_ID, fontData.mPostscriptName); + if (FAILED(hr)) { + loadFaceNamesUsingDirectWrite = false; + } + hr = GetDirectWriteFaceName(dwFont, FULLNAME_ID, fontData.mFullName); + if (FAILED(hr)) { + loadFaceNamesUsingDirectWrite = false; + } + } + + // if DirectWrite read fails, load directly from name table + if (!loadFaceNamesUsingDirectWrite) { + hr = dwFont->CreateFontFace(getter_AddRefs(dwFontFace)); + if (SUCCEEDED(hr)) { + uint32_t kNAME = + NativeEndian::swapToBigEndian(TRUETYPE_TAG('n', 'a', 'm', 'e')); + const char* nameData; + BOOL exists; + void* ctx; + uint32_t nameSize; + + hr = dwFontFace->TryGetFontTable(kNAME, (const void**)&nameData, + &nameSize, &ctx, &exists); + if (SUCCEEDED(hr) && nameData && nameSize > 0) { + MOZ_SEH_TRY { + gfxFontUtils::ReadCanonicalName(nameData, nameSize, + gfxFontUtils::NAME_ID_FULL, + fontData.mFullName); + gfxFontUtils::ReadCanonicalName(nameData, nameSize, + gfxFontUtils::NAME_ID_POSTSCRIPT, + fontData.mPostscriptName); + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + gfxCriticalNote << "Exception occurred reading names for " + << PromiseFlatCString(aFamilyName).get(); + } + dwFontFace->ReleaseFontTable(ctx); + } + } + } + + haveData = + !fontData.mPostscriptName.IsEmpty() || !fontData.mFullName.IsEmpty(); + if (haveData) { + mLoadStats.facenames++; + } + } + + // cmaps + if (mLoadCmaps) { + if (!dwFontFace) { + hr = dwFont->CreateFontFace(getter_AddRefs(dwFontFace)); + if (!SUCCEEDED(hr)) { + continue; + } + } + + uint32_t kCMAP = + NativeEndian::swapToBigEndian(TRUETYPE_TAG('c', 'm', 'a', 'p')); + const uint8_t* cmapData; + BOOL exists; + void* ctx; + uint32_t cmapSize; + + hr = dwFontFace->TryGetFontTable(kCMAP, (const void**)&cmapData, + &cmapSize, &ctx, &exists); + + if (SUCCEEDED(hr) && exists) { + bool cmapLoaded = false; + RefPtr<gfxCharacterMap> charmap = new gfxCharacterMap(); + uint32_t offset; + MOZ_SEH_TRY { + if (cmapData && cmapSize > 0 && + NS_SUCCEEDED(gfxFontUtils::ReadCMAP(cmapData, cmapSize, *charmap, + offset))) { + fontData.mCharacterMap = charmap; + fontData.mUVSOffset = offset; + cmapLoaded = true; + mLoadStats.cmaps++; + } + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + gfxCriticalNote << "Exception occurred reading cmaps for " + << PromiseFlatCString(aFamilyName).get(); + } + dwFontFace->ReleaseFontTable(ctx); + haveData = haveData || cmapLoaded; + } + } + + // if have data, load + if (haveData) { + mFontFaceData.InsertOrUpdate(fullID, fontData); + } + } +} + +already_AddRefed<FontInfoData> gfxDWriteFontList::CreateFontInfoData() { + bool loadCmaps = !UsesSystemFallback() || + gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback(); + + RefPtr<DirectWriteFontInfo> fi = new DirectWriteFontInfo( + false, NeedFullnamePostscriptNames(), loadCmaps, mSystemFonts +#ifdef MOZ_BUNDLED_FONTS + , + mBundledFonts +#endif + ); + + return fi.forget(); +} + +gfxFontFamily* gfxDWriteFontList::CreateFontFamily( + const nsACString& aName, FontVisibility aVisibility) const { + return new gfxDWriteFontFamily(aName, aVisibility, nullptr); +} + +#ifdef MOZ_BUNDLED_FONTS + +# define IMPL_QI_FOR_DWRITE(_interface) \ + public: \ + IFACEMETHOD(QueryInterface)(IID const& riid, void** ppvObject) { \ + if (__uuidof(_interface) == riid) { \ + *ppvObject = this; \ + } else if (__uuidof(IUnknown) == riid) { \ + *ppvObject = this; \ + } else { \ + *ppvObject = nullptr; \ + return E_NOINTERFACE; \ + } \ + this->AddRef(); \ + return S_OK; \ + } + +class BundledFontFileEnumerator : public IDWriteFontFileEnumerator { + IMPL_QI_FOR_DWRITE(IDWriteFontFileEnumerator) + + NS_INLINE_DECL_REFCOUNTING(BundledFontFileEnumerator) + + public: + BundledFontFileEnumerator(IDWriteFactory* aFactory, nsIFile* aFontDir); + + IFACEMETHODIMP MoveNext(BOOL* hasCurrentFile); + + IFACEMETHODIMP GetCurrentFontFile(IDWriteFontFile** fontFile); + + private: + BundledFontFileEnumerator() = delete; + BundledFontFileEnumerator(const BundledFontFileEnumerator&) = delete; + BundledFontFileEnumerator& operator=(const BundledFontFileEnumerator&) = + delete; + virtual ~BundledFontFileEnumerator() = default; + + RefPtr<IDWriteFactory> mFactory; + + nsCOMPtr<nsIFile> mFontDir; + nsCOMPtr<nsIDirectoryEnumerator> mEntries; + nsCOMPtr<nsISupports> mCurrent; +}; + +BundledFontFileEnumerator::BundledFontFileEnumerator(IDWriteFactory* aFactory, + nsIFile* aFontDir) + : mFactory(aFactory), mFontDir(aFontDir) { + mFontDir->GetDirectoryEntries(getter_AddRefs(mEntries)); +} + +IFACEMETHODIMP +BundledFontFileEnumerator::MoveNext(BOOL* aHasCurrentFile) { + bool hasMore = false; + if (mEntries) { + if (NS_SUCCEEDED(mEntries->HasMoreElements(&hasMore)) && hasMore) { + if (NS_SUCCEEDED(mEntries->GetNext(getter_AddRefs(mCurrent)))) { + hasMore = true; + } + } + } + *aHasCurrentFile = hasMore; + return S_OK; +} + +IFACEMETHODIMP +BundledFontFileEnumerator::GetCurrentFontFile(IDWriteFontFile** aFontFile) { + nsCOMPtr<nsIFile> file = do_QueryInterface(mCurrent); + if (!file) { + return E_FAIL; + } + nsString path; + if (NS_FAILED(file->GetPath(path))) { + return E_FAIL; + } + return mFactory->CreateFontFileReference((const WCHAR*)path.get(), nullptr, + aFontFile); +} + +class BundledFontLoader : public IDWriteFontCollectionLoader { + IMPL_QI_FOR_DWRITE(IDWriteFontCollectionLoader) + + NS_INLINE_DECL_REFCOUNTING(BundledFontLoader) + + public: + BundledFontLoader() {} + + IFACEMETHODIMP CreateEnumeratorFromKey( + IDWriteFactory* aFactory, const void* aCollectionKey, + UINT32 aCollectionKeySize, + IDWriteFontFileEnumerator** aFontFileEnumerator); + + private: + BundledFontLoader(const BundledFontLoader&) = delete; + BundledFontLoader& operator=(const BundledFontLoader&) = delete; + virtual ~BundledFontLoader() = default; +}; + +IFACEMETHODIMP +BundledFontLoader::CreateEnumeratorFromKey( + IDWriteFactory* aFactory, const void* aCollectionKey, + UINT32 aCollectionKeySize, + IDWriteFontFileEnumerator** aFontFileEnumerator) { + nsIFile* fontDir = *(nsIFile**)aCollectionKey; + *aFontFileEnumerator = new BundledFontFileEnumerator(aFactory, fontDir); + NS_ADDREF(*aFontFileEnumerator); + return S_OK; +} + +already_AddRefed<IDWriteFontCollection> +gfxDWriteFontList::CreateBundledFontsCollection(IDWriteFactory* aFactory) { + nsCOMPtr<nsIFile> localDir; + nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(localDir)); + if (NS_FAILED(rv)) { + return nullptr; + } + if (NS_FAILED(localDir->Append(u"fonts"_ns))) { + return nullptr; + } + bool isDir; + if (NS_FAILED(localDir->IsDirectory(&isDir)) || !isDir) { + return nullptr; + } + + RefPtr<BundledFontLoader> loader = new BundledFontLoader(); + if (FAILED(aFactory->RegisterFontCollectionLoader(loader))) { + return nullptr; + } + + const void* key = localDir.get(); + RefPtr<IDWriteFontCollection> collection; + HRESULT hr = aFactory->CreateCustomFontCollection(loader, &key, sizeof(key), + getter_AddRefs(collection)); + + aFactory->UnregisterFontCollectionLoader(loader); + + if (FAILED(hr)) { + return nullptr; + } else { + return collection.forget(); + } +} + +#endif diff --git a/gfx/thebes/gfxDWriteFontList.h b/gfx/thebes/gfxDWriteFontList.h new file mode 100644 index 0000000000..adf8fdfa5a --- /dev/null +++ b/gfx/thebes/gfxDWriteFontList.h @@ -0,0 +1,492 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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" + +#include "gfxFont.h" +#include "gfxUserFontSet.h" +#include "cairo-win32.h" + +#include "gfxPlatformFontList.h" +#include "gfxPlatform.h" +#include <algorithm> + +#include "mozilla/gfx/UnscaledFontDWrite.h" + +/** + * \brief Class representing directwrite font family. + * + * gfxDWriteFontFamily is a class that describes one of the font families on + * the user's system. It holds each gfxDWriteFontEntry (maps more directly to + * a font face) which holds font type, charset info and character map info. + */ +class gfxDWriteFontFamily final : public gfxFontFamily { + public: + typedef mozilla::FontStretch FontStretch; + typedef mozilla::FontSlantStyle FontSlantStyle; + typedef mozilla::FontWeight FontWeight; + + /** + * Constructs a new DWriteFont Family. + * + * \param aName Name identifying the family + * \param aFamily IDWriteFontFamily object representing the directwrite + * family object. + */ + gfxDWriteFontFamily(const nsACString& aName, FontVisibility aVisibility, + IDWriteFontFamily* aFamily, + bool aIsSystemFontFamily = false) + : gfxFontFamily(aName, aVisibility), + mDWFamily(aFamily), + mIsSystemFontFamily(aIsSystemFontFamily), + mForceGDIClassic(false) {} + virtual ~gfxDWriteFontFamily(); + + void FindStyleVariationsLocked(FontInfoData* aFontInfoData = nullptr) + MOZ_REQUIRES(mLock) final; + + void LocalizedName(nsACString& aLocalizedName) final; + + void ReadFaceNames(gfxPlatformFontList* aPlatformFontList, + bool aNeedFullnamePostscriptNames, + FontInfoData* aFontInfoData = nullptr) final; + + void SetForceGDIClassic(bool aForce) { mForceGDIClassic = aForce; } + + void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const final; + void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const final; + + bool FilterForFontList(nsAtom* aLangGroup, + const nsACString& aGeneric) const final { + return !IsSymbolFontFamily(); + } + + protected: + // helper for FilterForFontList + bool IsSymbolFontFamily() const; + + /** This font family's directwrite fontfamily object */ + RefPtr<IDWriteFontFamily> mDWFamily; + bool mIsSystemFontFamily; + bool mForceGDIClassic; +}; + +/** + * \brief Class representing DirectWrite FontEntry (a unique font style/family) + */ +class gfxDWriteFontEntry final : public gfxFontEntry { + public: + /** + * Constructs a font entry. + * + * \param aFaceName The name of the corresponding font face. + * \param aFont DirectWrite font object + */ + gfxDWriteFontEntry(const nsACString& aFaceName, IDWriteFont* aFont, + bool aIsSystemFont = false) + : gfxFontEntry(aFaceName), + mFont(aFont), + mFontFile(nullptr), + mIsSystemFont(aIsSystemFont), + mForceGDIClassic(false), + mHasVariations(false), + mHasVariationsInitialized(false) { + DWRITE_FONT_STYLE dwriteStyle = aFont->GetStyle(); + FontSlantStyle style = (dwriteStyle == DWRITE_FONT_STYLE_ITALIC + ? FontSlantStyle::ITALIC + : (dwriteStyle == DWRITE_FONT_STYLE_OBLIQUE + ? FontSlantStyle::OBLIQUE + : FontSlantStyle::NORMAL)); + mStyleRange = SlantStyleRange(style); + + mStretchRange = + StretchRange(FontStretchFromDWriteStretch(aFont->GetStretch())); + + int weight = NS_ROUNDUP(aFont->GetWeight() - 50, 100); + weight = mozilla::Clamp(weight, 100, 900); + mWeightRange = WeightRange(FontWeight::FromInt(weight)); + + mIsCJK = UNINITIALIZED_VALUE; + } + + /** + * Constructs a font entry using a font. But with custom font values. + * This is used for creating correct font entries for @font-face with local + * font source. + * + * \param aFaceName The name of the corresponding font face. + * \param aFont DirectWrite font object + * \param aWeight Weight of the font + * \param aStretch Stretch of the font + * \param aStyle italic or oblique of font + */ + gfxDWriteFontEntry(const nsACString& aFaceName, IDWriteFont* aFont, + WeightRange aWeight, StretchRange aStretch, + SlantStyleRange aStyle) + : gfxFontEntry(aFaceName), + mFont(aFont), + mFontFile(nullptr), + mIsSystemFont(false), + mForceGDIClassic(false), + mHasVariations(false), + mHasVariationsInitialized(false) { + mWeightRange = aWeight; + mStretchRange = aStretch; + mStyleRange = aStyle; + mIsLocalUserFont = true; + mIsCJK = UNINITIALIZED_VALUE; + } + + /** + * Constructs a font entry using a font file. + * + * \param aFaceName The name of the corresponding font face. + * \param aFontFile DirectWrite fontfile object + * \param aFontFileStream DirectWrite fontfile stream object + * \param aWeight Weight of the font + * \param aStretch Stretch of the font + * \param aStyle italic or oblique of font + */ + gfxDWriteFontEntry(const nsACString& aFaceName, IDWriteFontFile* aFontFile, + IDWriteFontFileStream* aFontFileStream, + WeightRange aWeight, StretchRange aStretch, + SlantStyleRange aStyle) + : gfxFontEntry(aFaceName), + mFont(nullptr), + mFontFile(aFontFile), + mFontFileStream(aFontFileStream), + mIsSystemFont(false), + mForceGDIClassic(false), + mHasVariations(false), + mHasVariationsInitialized(false) { + mWeightRange = aWeight; + mStretchRange = aStretch; + mStyleRange = aStyle; + mIsDataUserFont = true; + mIsCJK = UNINITIALIZED_VALUE; + } + + gfxFontEntry* Clone() const override; + + virtual ~gfxDWriteFontEntry(); + + hb_blob_t* GetFontTable(uint32_t aTableTag) override; + + nsresult ReadCMAP(FontInfoData* aFontInfoData = nullptr) override; + + bool IsCJKFont(); + + bool HasVariations() override; + void GetVariationAxes(nsTArray<gfxFontVariationAxis>& aAxes) override; + void GetVariationInstances( + nsTArray<gfxFontVariationInstance>& aInstances) override; + + void SetForceGDIClassic(bool aForce) { mForceGDIClassic = aForce; } + bool GetForceGDIClassic() { return mForceGDIClassic; } + + void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const override; + void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const override; + + protected: + friend class gfxDWriteFont; + friend class gfxDWriteFontList; + friend class gfxDWriteFontFamily; + + virtual nsresult CopyFontTable(uint32_t aTableTag, + nsTArray<uint8_t>& aBuffer) override; + + virtual gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle); + + nsresult CreateFontFace( + IDWriteFontFace** aFontFace, const gfxFontStyle* aFontStyle = nullptr, + DWRITE_FONT_SIMULATIONS aSimulations = DWRITE_FONT_SIMULATIONS_NONE, + const nsTArray<gfxFontVariation>* aVariations = nullptr); + + static bool InitLogFont(IDWriteFont* aFont, LOGFONTW* aLogFont); + + /** + * A fontentry only needs to have either of these. If it has both only + * the IDWriteFont will be used. + */ + RefPtr<IDWriteFont> mFont; + RefPtr<IDWriteFontFile> mFontFile; + + // For custom fonts, we hold a reference to the IDWriteFontFileStream for + // for the IDWriteFontFile, so that the data is available. + RefPtr<IDWriteFontFileStream> mFontFileStream; + + // font face corresponding to the mFont/mFontFile *without* any DWrite + // style simulations applied + RefPtr<IDWriteFontFace> mFontFace; + // Extended fontface interface if supported, else null + RefPtr<IDWriteFontFace5> mFontFace5; + + DWRITE_FONT_FACE_TYPE mFaceType; + + int8_t mIsCJK; + bool mIsSystemFont; + bool mForceGDIClassic; + bool mHasVariations; + bool mHasVariationsInitialized; + + // Set to true only if the font belongs to a "simple" family where the + // faces can be reliably identified via a GDI LOGFONT structure. + bool mMayUseGDIAccess = false; + + mozilla::ThreadSafeWeakPtr<mozilla::gfx::UnscaledFontDWrite> mUnscaledFont; + mozilla::ThreadSafeWeakPtr<mozilla::gfx::UnscaledFontDWrite> + mUnscaledFontBold; +}; + +// custom text renderer used to determine the fallback font for a given char +class DWriteFontFallbackRenderer final : public IDWriteTextRenderer { + public: + explicit DWriteFontFallbackRenderer(IDWriteFactory* aFactory) : mRefCount(0) { + HRESULT hr = + aFactory->GetSystemFontCollection(getter_AddRefs(mSystemFonts)); + NS_ASSERTION(SUCCEEDED(hr), "GetSystemFontCollection failed!"); + (void)hr; + } + + ~DWriteFontFallbackRenderer() {} + + // If we don't have an mSystemFonts pointer, this renderer is unusable. + bool IsValid() const { return mSystemFonts; } + + // IDWriteTextRenderer methods + IFACEMETHOD(DrawGlyphRun) + (void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, + DWRITE_MEASURING_MODE measuringMode, DWRITE_GLYPH_RUN const* glyphRun, + DWRITE_GLYPH_RUN_DESCRIPTION const* glyphRunDescription, + IUnknown* clientDrawingEffect); + + IFACEMETHOD(DrawUnderline) + (void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, + DWRITE_UNDERLINE const* underline, IUnknown* clientDrawingEffect) { + return E_NOTIMPL; + } + + IFACEMETHOD(DrawStrikethrough) + (void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, + DWRITE_STRIKETHROUGH const* strikethrough, IUnknown* clientDrawingEffect) { + return E_NOTIMPL; + } + + IFACEMETHOD(DrawInlineObject) + (void* clientDrawingContext, FLOAT originX, FLOAT originY, + IDWriteInlineObject* inlineObject, BOOL isSideways, BOOL isRightToLeft, + IUnknown* clientDrawingEffect) { + return E_NOTIMPL; + } + + // IDWritePixelSnapping methods + + IFACEMETHOD(IsPixelSnappingDisabled) + (void* clientDrawingContext, BOOL* isDisabled) { + *isDisabled = FALSE; + return S_OK; + } + + IFACEMETHOD(GetCurrentTransform) + (void* clientDrawingContext, DWRITE_MATRIX* transform) { + const DWRITE_MATRIX ident = {1.0, 0.0, 0.0, 1.0, 0.0, 0.0}; + *transform = ident; + return S_OK; + } + + IFACEMETHOD(GetPixelsPerDip) + (void* clientDrawingContext, FLOAT* pixelsPerDip) { + *pixelsPerDip = 1.0f; + return S_OK; + } + + // IUnknown methods + + IFACEMETHOD_(unsigned long, AddRef)() { + return InterlockedIncrement(&mRefCount); + } + + IFACEMETHOD_(unsigned long, Release)() { + unsigned long newCount = InterlockedDecrement(&mRefCount); + if (newCount == 0) { + delete this; + return 0; + } + + return newCount; + } + + IFACEMETHOD(QueryInterface)(IID const& riid, void** ppvObject) { + if (__uuidof(IDWriteTextRenderer) == riid) { + *ppvObject = this; + } else if (__uuidof(IDWritePixelSnapping) == riid) { + *ppvObject = this; + } else if (__uuidof(IUnknown) == riid) { + *ppvObject = this; + } else { + *ppvObject = nullptr; + return E_FAIL; + } + + this->AddRef(); + return S_OK; + } + + const nsCString& FallbackFamilyName() { return mFamilyName; } + + protected: + long mRefCount; + RefPtr<IDWriteFontCollection> mSystemFonts; + nsCString mFamilyName; +}; + +class gfxDWriteFontList final : public gfxPlatformFontList { + public: + gfxDWriteFontList(); + virtual ~gfxDWriteFontList() { AutoLock lock(mLock); } + + static gfxDWriteFontList* PlatformFontList() { + return static_cast<gfxDWriteFontList*>( + gfxPlatformFontList::PlatformFontList()); + } + + // initialize font lists + nsresult InitFontListForPlatform() MOZ_REQUIRES(mLock) override; + void InitSharedFontListForPlatform() MOZ_REQUIRES(mLock) override; + + FontVisibility GetVisibilityForFamily(const nsACString& aName) const; + + gfxFontFamily* CreateFontFamily(const nsACString& aName, + FontVisibility aVisibility) const override; + + gfxFontEntry* CreateFontEntry( + mozilla::fontlist::Face* aFace, + const mozilla::fontlist::Family* aFamily) override; + + void ReadFaceNamesForFamily(mozilla::fontlist::Family* aFamily, + bool aNeedFullnamePostscriptNames) + MOZ_REQUIRES(mLock) override; + + bool ReadFaceNames(const mozilla::fontlist::Family* aFamily, + const mozilla::fontlist::Face* aFace, nsCString& aPSName, + nsCString& aFullName) override; + + void GetFacesInitDataForFamily( + const mozilla::fontlist::Family* aFamily, + nsTArray<mozilla::fontlist::Face::InitData>& aFaces, + bool aLoadCmaps) const override; + + gfxFontEntry* LookupLocalFont(nsPresContext* aPresContext, + const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) override; + + gfxFontEntry* MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, + uint32_t aLength) override; + + IDWriteGdiInterop* GetGDIInterop() { return mGDIInterop; } + bool UseGDIFontTableAccess() const; + + bool FindAndAddFamiliesLocked( + nsPresContext* aPresContext, mozilla::StyleGenericFontFamily aGeneric, + const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput, + FindFamiliesFlags aFlags, gfxFontStyle* aStyle = nullptr, + nsAtom* aLanguage = nullptr, gfxFloat aDevToCssSize = 1.0) + MOZ_REQUIRES(mLock) override; + + gfxFloat GetForceGDIClassicMaxFontSize() { + return mForceGDIClassicMaxFontSize; + } + + virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const; + virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const; + + protected: + FontFamily GetDefaultFontForPlatform(nsPresContext* aPresContext, + const gfxFontStyle* aStyle, + nsAtom* aLanguage = nullptr) + MOZ_REQUIRES(mLock) override; + + // attempt to use platform-specific fallback for the given character, + // return null if no usable result found + gfxFontEntry* PlatformGlobalFontFallback(nsPresContext* aPresContext, + const uint32_t aCh, + Script aRunScript, + const gfxFontStyle* aMatchStyle, + FontFamily& aMatchedFamily) + MOZ_REQUIRES(mLock) override; + + private: + friend class gfxDWriteFontFamily; + + nsresult GetFontSubstitutes() MOZ_REQUIRES(mLock); + + void GetDirectWriteSubstitutes() MOZ_REQUIRES(mLock); + + virtual bool UsesSystemFallback() { return true; } + + void GetFontsFromCollection(IDWriteFontCollection* aCollection) + MOZ_REQUIRES(mLock); + + void AppendFamiliesFromCollection( + IDWriteFontCollection* aCollection, + nsTArray<mozilla::fontlist::Family::InitData>& aFamilies, + const nsTArray<nsCString>* aForceClassicFams = nullptr) + MOZ_REQUIRES(mLock); + +#ifdef MOZ_BUNDLED_FONTS + already_AddRefed<IDWriteFontCollection> CreateBundledFontsCollection( + IDWriteFactory* aFactory); +#endif + + /** + * Fonts listed in the registry as substitutes but for which no actual + * font family is found. + */ + nsTArray<nsCString> mNonExistingFonts; + + /** + * Table of font substitutes, we grab this from the registry to get + * alternative font names. + */ + FontFamilyTable mFontSubstitutes; + nsClassHashtable<nsCStringHashKey, nsCString> mSubstitutions; + + virtual already_AddRefed<FontInfoData> CreateFontInfoData(); + + gfxFloat mForceGDIClassicMaxFontSize; + + // whether to use GDI font table access routines + bool mGDIFontTableAccess; + RefPtr<IDWriteGdiInterop> mGDIInterop; + + RefPtr<DWriteFontFallbackRenderer> mFallbackRenderer; + RefPtr<IDWriteTextFormat> mFallbackFormat; + + RefPtr<IDWriteFontCollection> mSystemFonts; +#ifdef MOZ_BUNDLED_FONTS + RefPtr<IDWriteFontCollection> mBundledFonts; +#endif +}; + +#endif /* GFX_DWRITEFONTLIST_H */ diff --git a/gfx/thebes/gfxDWriteFonts.cpp b/gfx/thebes/gfxDWriteFonts.cpp new file mode 100644 index 0000000000..0877d389bc --- /dev/null +++ b/gfx/thebes/gfxDWriteFonts.cpp @@ -0,0 +1,852 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxDWriteFonts.h" + +#include <algorithm> +#include "gfxDWriteFontList.h" +#include "gfxContext.h" +#include "gfxHarfBuzzShaper.h" +#include "gfxTextRun.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/DWriteSettings.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/Preferences.h" + +#include "harfbuzz/hb.h" +#include "mozilla/FontPropertyTypes.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +// Code to determine whether Windows is set to use ClearType font smoothing; +// based on private functions in cairo-win32-font.c + +#ifndef SPI_GETFONTSMOOTHINGTYPE +# define SPI_GETFONTSMOOTHINGTYPE 0x200a +#endif +#ifndef FE_FONTSMOOTHINGCLEARTYPE +# define FE_FONTSMOOTHINGCLEARTYPE 2 +#endif + +// Cleartype can be dynamically enabled/disabled, so we have to allow for +// dynamically updating it. +static BYTE GetSystemTextQuality() { + BOOL font_smoothing; + UINT smoothing_type; + + if (!SystemParametersInfo(SPI_GETFONTSMOOTHING, 0, &font_smoothing, 0)) { + return DEFAULT_QUALITY; + } + + if (font_smoothing) { + if (!SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE, 0, &smoothing_type, + 0)) { + return DEFAULT_QUALITY; + } + + if (smoothing_type == FE_FONTSMOOTHINGCLEARTYPE) { + return CLEARTYPE_QUALITY; + } + + return ANTIALIASED_QUALITY; + } + + return DEFAULT_QUALITY; +} + +#ifndef SPI_GETFONTSMOOTHINGCONTRAST +# define SPI_GETFONTSMOOTHINGCONTRAST 0x200c +#endif + +// "Retrieves a contrast value that is used in ClearType smoothing. Valid +// contrast values are from 1000 to 2200. The default value is 1400." +static FLOAT GetSystemGDIGamma() { + UINT value = 0; + if (!SystemParametersInfo(SPI_GETFONTSMOOTHINGCONTRAST, 0, &value, 0) || + value < 1000 || value > 2200) { + value = 1400; + } + return value / 1000.0f; +} + +//////////////////////////////////////////////////////////////////////////////// +// gfxDWriteFont +gfxDWriteFont::gfxDWriteFont(const RefPtr<UnscaledFontDWrite>& aUnscaledFont, + gfxFontEntry* aFontEntry, + const gfxFontStyle* aFontStyle, + RefPtr<IDWriteFontFace> aFontFace, + AntialiasOption anAAOption) + : gfxFont(aUnscaledFont, aFontEntry, aFontStyle, anAAOption), + mFontFace(aFontFace ? aFontFace : aUnscaledFont->GetFontFace()), + mUseSubpixelPositions(false), + mAllowManualShowGlyphs(true), + mAzureScaledFontUsedClearType(false) { + // If the IDWriteFontFace1 interface is available, we can use that for + // faster glyph width retrieval. + mFontFace->QueryInterface(__uuidof(IDWriteFontFace1), + (void**)getter_AddRefs(mFontFace1)); + // If a fake-bold effect is needed, determine whether we're using DWrite's + // "simulation" or applying our multi-strike "synthetic bold". + if (aFontStyle->NeedsSyntheticBold(aFontEntry)) { + switch (StaticPrefs::gfx_font_rendering_directwrite_bold_simulation()) { + case 0: // never use the DWrite simulation + mApplySyntheticBold = true; + break; + case 1: // use DWrite simulation for installed fonts except COLR fonts, + // but not webfonts + mApplySyntheticBold = + aFontEntry->mIsDataUserFont || + aFontEntry->HasFontTable(TRUETYPE_TAG('C', 'O', 'L', 'R')); + break; + default: // always use DWrite bold simulation, except for COLR fonts + mApplySyntheticBold = + aFontEntry->HasFontTable(TRUETYPE_TAG('C', 'O', 'L', 'R')); + break; + } + } + ComputeMetrics(anAAOption); +} + +gfxDWriteFont::~gfxDWriteFont() { + if (auto* scaledFont = mAzureScaledFontGDI.exchange(nullptr)) { + scaledFont->Release(); + } +} + +/* static */ +bool gfxDWriteFont::InitDWriteSupport() { + if (!Factory::EnsureDWriteFactory()) { + return false; + } + + if (XRE_IsParentProcess()) { + UpdateSystemTextVars(); + } else { + // UpdateClearTypeVars doesn't update the vars in non parent processes, but + // it does set sForceGDIClassicEnabled so we still need to call it. + UpdateClearTypeVars(); + } + DWriteSettings::Initialize(); + + return true; +} + +/* static */ +void gfxDWriteFont::UpdateSystemTextVars() { + MOZ_ASSERT(XRE_IsParentProcess()); + + if (!gfxVars::IsInitialized()) { + return; + } + + BYTE newQuality = GetSystemTextQuality(); + if (gfxVars::SystemTextQuality() != newQuality) { + gfxVars::SetSystemTextQuality(newQuality); + } + + FLOAT newGDIGamma = GetSystemGDIGamma(); + if (gfxVars::SystemGDIGamma() != newGDIGamma) { + gfxVars::SetSystemGDIGamma(newGDIGamma); + } + + UpdateClearTypeVars(); +} + +void gfxDWriteFont::SystemTextQualityChanged() { + // If ClearType status has changed, update our value, + Factory::SetSystemTextQuality(gfxVars::SystemTextQuality()); + // flush cached stuff that depended on the old setting, and force + // reflow everywhere to ensure we are using correct glyph metrics. + gfxPlatform::FlushFontAndWordCaches(); + gfxPlatform::ForceGlobalReflow(gfxPlatform::NeedsReframe::No); +} + +mozilla::Atomic<bool> gfxDWriteFont::sForceGDIClassicEnabled{true}; + +/* static */ +void gfxDWriteFont::UpdateClearTypeVars() { + // We don't force GDI classic if the cleartype rendering mode pref is set to + // something valid. + int32_t renderingModePref = + Preferences::GetInt(GFX_CLEARTYPE_PARAMS_MODE, -1); + if (renderingModePref < 0 || renderingModePref > 5) { + renderingModePref = -1; + } + sForceGDIClassicEnabled = (renderingModePref == -1); + + if (!XRE_IsParentProcess()) { + return; + } + + if (!Factory::GetDWriteFactory()) { + return; + } + + // First set sensible hard coded defaults. + float clearTypeLevel = 1.0f; + float enhancedContrast = 1.0f; + float gamma = 2.2f; + int pixelGeometry = DWRITE_PIXEL_GEOMETRY_RGB; + int renderingMode = DWRITE_RENDERING_MODE_DEFAULT; + + // Override these from DWrite function if available. + RefPtr<IDWriteRenderingParams> defaultRenderingParams; + HRESULT hr = Factory::GetDWriteFactory()->CreateRenderingParams( + getter_AddRefs(defaultRenderingParams)); + if (SUCCEEDED(hr) && defaultRenderingParams) { + clearTypeLevel = defaultRenderingParams->GetClearTypeLevel(); + + // For enhanced contrast, we only use the default if the user has set it + // in the registry (by using the ClearType Tuner). + // XXXbobowen it seems slightly odd that we do this and only for enhanced + // contrast, but this reproduces previous functionality from + // gfxWindowsPlatform::SetupClearTypeParams. + HKEY hKey; + LONG res = RegOpenKeyExW(DISPLAY1_REGISTRY_KEY, 0, KEY_READ, &hKey); + if (res == ERROR_SUCCESS) { + res = RegQueryValueExW(hKey, ENHANCED_CONTRAST_VALUE_NAME, nullptr, + nullptr, nullptr, nullptr); + if (res == ERROR_SUCCESS) { + enhancedContrast = defaultRenderingParams->GetEnhancedContrast(); + } + RegCloseKey(hKey); + } + + gamma = defaultRenderingParams->GetGamma(); + pixelGeometry = defaultRenderingParams->GetPixelGeometry(); + renderingMode = defaultRenderingParams->GetRenderingMode(); + } else { + gfxWarning() << "Failed to create default rendering params"; + } + + // Finally override from prefs if valid values are set. If ClearType is + // turned off we just use the default params, this reproduces the previous + // functionality that was spread across gfxDWriteFont::GetScaledFont and + // gfxWindowsPlatform::SetupClearTypeParams, but it seems odd because the + // default params will still be the ClearType ones although we won't use the + // anti-alias for ClearType because of GetSystemDefaultAAMode. + if (gfxVars::SystemTextQuality() == CLEARTYPE_QUALITY) { + int32_t prefInt = Preferences::GetInt(GFX_CLEARTYPE_PARAMS_LEVEL, -1); + if (prefInt >= 0 && prefInt <= 100) { + clearTypeLevel = float(prefInt / 100.0); + } + + prefInt = Preferences::GetInt(GFX_CLEARTYPE_PARAMS_CONTRAST, -1); + if (prefInt >= 0 && prefInt <= 1000) { + enhancedContrast = float(prefInt / 100.0); + } + + prefInt = Preferences::GetInt(GFX_CLEARTYPE_PARAMS_GAMMA, -1); + if (prefInt >= 1000 && prefInt <= 2200) { + gamma = float(prefInt / 1000.0); + } + + prefInt = Preferences::GetInt(GFX_CLEARTYPE_PARAMS_STRUCTURE, -1); + if (prefInt >= 0 && prefInt <= 2) { + pixelGeometry = prefInt; + } + + // renderingModePref is retrieved and validated above. + if (renderingModePref != -1) { + renderingMode = renderingModePref; + } + } + + if (gfxVars::SystemTextClearTypeLevel() != clearTypeLevel) { + gfxVars::SetSystemTextClearTypeLevel(clearTypeLevel); + } + + if (gfxVars::SystemTextEnhancedContrast() != enhancedContrast) { + gfxVars::SetSystemTextEnhancedContrast(enhancedContrast); + } + + if (gfxVars::SystemTextGamma() != gamma) { + gfxVars::SetSystemTextGamma(gamma); + } + + if (gfxVars::SystemTextPixelGeometry() != pixelGeometry) { + gfxVars::SetSystemTextPixelGeometry(pixelGeometry); + } + + if (gfxVars::SystemTextRenderingMode() != renderingMode) { + gfxVars::SetSystemTextRenderingMode(renderingMode); + } + + // Set cairo dwrite params in the parent process where it might still be + // needed for printing. We use the validated pref int directly for rendering + // mode, because a negative (i.e. not set) rendering mode is also used for + // deciding on forcing GDI in cairo. + cairo_dwrite_set_cleartype_params(gamma, enhancedContrast, clearTypeLevel, + pixelGeometry, renderingModePref); +} + +gfxFont* gfxDWriteFont::CopyWithAntialiasOption( + AntialiasOption anAAOption) const { + auto entry = static_cast<gfxDWriteFontEntry*>(mFontEntry.get()); + RefPtr<UnscaledFontDWrite> unscaledFont = + static_cast<UnscaledFontDWrite*>(mUnscaledFont.get()); + return new gfxDWriteFont(unscaledFont, entry, &mStyle, mFontFace, anAAOption); +} + +bool gfxDWriteFont::GetFakeMetricsForArialBlack( + DWRITE_FONT_METRICS* aFontMetrics) { + gfxFontStyle style(mStyle); + style.weight = FontWeight::FromInt(700); + + gfxFontEntry* fe = gfxPlatformFontList::PlatformFontList()->FindFontForFamily( + nullptr, "Arial"_ns, &style); + if (!fe || fe == mFontEntry) { + return false; + } + + RefPtr<gfxFont> font = fe->FindOrMakeFont(&style); + gfxDWriteFont* dwFont = static_cast<gfxDWriteFont*>(font.get()); + dwFont->mFontFace->GetMetrics(aFontMetrics); + + return true; +} + +void gfxDWriteFont::ComputeMetrics(AntialiasOption anAAOption) { + ::memset(&mMetrics, 0, sizeof(mMetrics)); + + DWRITE_FONT_METRICS fontMetrics; + if (!(mFontEntry->Weight().Min() == FontWeight::FromInt(900) && + mFontEntry->Weight().Max() == FontWeight::FromInt(900) && + !mFontEntry->IsUserFont() && + mFontEntry->Name().EqualsLiteral("Arial Black") && + GetFakeMetricsForArialBlack(&fontMetrics))) { + mFontFace->GetMetrics(&fontMetrics); + } + + if (GetAdjustedSize() > 0.0 && mStyle.sizeAdjust >= 0.0 && + FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) != + FontSizeAdjust::Tag::None) { + // For accurate measurement during the font-size-adjust computations; + // these may be reset later according to the adjusted size. + mUseSubpixelPositions = true; + mFUnitsConvFactor = float(mAdjustedSize / fontMetrics.designUnitsPerEm); + gfxFloat aspect; + switch (FontSizeAdjust::Tag(mStyle.sizeAdjustBasis)) { + default: + MOZ_ASSERT_UNREACHABLE("unhandled sizeAdjustBasis?"); + aspect = 0.0; + break; + case FontSizeAdjust::Tag::ExHeight: + aspect = (gfxFloat)fontMetrics.xHeight / fontMetrics.designUnitsPerEm; + break; + case FontSizeAdjust::Tag::CapHeight: + aspect = (gfxFloat)fontMetrics.capHeight / fontMetrics.designUnitsPerEm; + break; + case FontSizeAdjust::Tag::ChWidth: { + gfxFloat advance = GetCharAdvance('0'); + aspect = advance > 0.0 ? advance / mAdjustedSize : 0.5; + break; + } + case FontSizeAdjust::Tag::IcWidth: + case FontSizeAdjust::Tag::IcHeight: { + bool vertical = FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) == + FontSizeAdjust::Tag::IcHeight; + gfxFloat advance = GetCharAdvance(kWaterIdeograph, vertical); + aspect = advance > 0.0 ? advance / mAdjustedSize : 1.0; + break; + } + } + if (aspect > 0.0) { + // If we created a shaper above (to measure glyphs), discard it so we + // get a new one for the adjusted scaling. + delete mHarfBuzzShaper.exchange(nullptr); + mAdjustedSize = mStyle.GetAdjustedSize(aspect); + } + } + + // Update now that we've adjusted the size if necessary. + mFUnitsConvFactor = float(mAdjustedSize / fontMetrics.designUnitsPerEm); + + // Note that GetMeasuringMode depends on mAdjustedSize + if ((anAAOption == gfxFont::kAntialiasDefault && UsingClearType() && + GetMeasuringMode() == DWRITE_MEASURING_MODE_NATURAL) || + anAAOption == gfxFont::kAntialiasSubpixel) { + mUseSubpixelPositions = true; + // note that this may be reset to FALSE if we determine that a bitmap + // strike is going to be used + } else { + mUseSubpixelPositions = false; + } + + gfxDWriteFontEntry* fe = static_cast<gfxDWriteFontEntry*>(mFontEntry.get()); + if (fe->IsCJKFont() && HasBitmapStrikeForSize(NS_lround(mAdjustedSize))) { + mAdjustedSize = NS_lround(mAdjustedSize); + mUseSubpixelPositions = false; + // if we have bitmaps, we need to tell Cairo NOT to use subpixel AA, + // to avoid the manual-subpixel codepath in cairo-d2d-surface.cpp + // which fails to render bitmap glyphs (see bug 626299). + // This option will be passed to the cairo_dwrite_scaled_font_t + // after creation. + mAllowManualShowGlyphs = false; + } + + mMetrics.xHeight = fontMetrics.xHeight * mFUnitsConvFactor; + mMetrics.capHeight = fontMetrics.capHeight * mFUnitsConvFactor; + + mMetrics.maxAscent = round(fontMetrics.ascent * mFUnitsConvFactor); + mMetrics.maxDescent = round(fontMetrics.descent * mFUnitsConvFactor); + mMetrics.maxHeight = mMetrics.maxAscent + mMetrics.maxDescent; + + mMetrics.emHeight = mAdjustedSize; + mMetrics.emAscent = + mMetrics.emHeight * mMetrics.maxAscent / mMetrics.maxHeight; + mMetrics.emDescent = mMetrics.emHeight - mMetrics.emAscent; + + mMetrics.maxAdvance = mAdjustedSize; + + // try to get the true maxAdvance value from 'hhea' + gfxFontEntry::AutoTable hheaTable(GetFontEntry(), + TRUETYPE_TAG('h', 'h', 'e', 'a')); + if (hheaTable) { + uint32_t len; + const MetricsHeader* hhea = reinterpret_cast<const MetricsHeader*>( + hb_blob_get_data(hheaTable, &len)); + if (len >= sizeof(MetricsHeader)) { + mMetrics.maxAdvance = uint16_t(hhea->advanceWidthMax) * mFUnitsConvFactor; + } + } + + mMetrics.internalLeading = + std::max(mMetrics.maxHeight - mMetrics.emHeight, 0.0); + mMetrics.externalLeading = ceil(fontMetrics.lineGap * mFUnitsConvFactor); + + UINT32 ucs = L' '; + UINT16 glyph; + if (SUCCEEDED(mFontFace->GetGlyphIndices(&ucs, 1, &glyph)) && glyph != 0) { + mSpaceGlyph = glyph; + mMetrics.spaceWidth = MeasureGlyphWidth(glyph); + } else { + mMetrics.spaceWidth = 0; + } + + // try to get aveCharWidth from the OS/2 table, fall back to measuring 'x' + // if the table is not available or if using hinted/pixel-snapped widths + if (mUseSubpixelPositions) { + mMetrics.aveCharWidth = 0; + gfxFontEntry::AutoTable os2Table(GetFontEntry(), + TRUETYPE_TAG('O', 'S', '/', '2')); + if (os2Table) { + uint32_t len; + const OS2Table* os2 = + reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len)); + if (len >= 4) { + // Not checking against sizeof(mozilla::OS2Table) here because older + // versions of the table have different sizes; we only need the first + // two 16-bit fields here. + mMetrics.aveCharWidth = int16_t(os2->xAvgCharWidth) * mFUnitsConvFactor; + } + } + } + + if (mMetrics.aveCharWidth < 1) { + mMetrics.aveCharWidth = GetCharAdvance('x'); + if (mMetrics.aveCharWidth < 1) { + // Let's just assume the X is square. + mMetrics.aveCharWidth = fontMetrics.xHeight * mFUnitsConvFactor; + } + } + + mMetrics.zeroWidth = GetCharAdvance('0'); + + mMetrics.ideographicWidth = GetCharAdvance(kWaterIdeograph); + + mMetrics.underlineOffset = fontMetrics.underlinePosition * mFUnitsConvFactor; + mMetrics.underlineSize = fontMetrics.underlineThickness * mFUnitsConvFactor; + mMetrics.strikeoutOffset = + fontMetrics.strikethroughPosition * mFUnitsConvFactor; + mMetrics.strikeoutSize = + fontMetrics.strikethroughThickness * mFUnitsConvFactor; + + SanitizeMetrics(&mMetrics, GetFontEntry()->mIsBadUnderlineFont); + + if (ApplySyntheticBold()) { + auto delta = GetSyntheticBoldOffset(); + mMetrics.spaceWidth += delta; + mMetrics.aveCharWidth += delta; + mMetrics.maxAdvance += delta; + if (mMetrics.zeroWidth > 0) { + mMetrics.zeroWidth += delta; + } + if (mMetrics.ideographicWidth > 0) { + mMetrics.ideographicWidth += delta; + } + } + +#if 0 + printf("Font: %p (%s) size: %f\n", this, + NS_ConvertUTF16toUTF8(GetName()).get(), mStyle.size); + printf(" emHeight: %f emAscent: %f emDescent: %f\n", mMetrics.emHeight, mMetrics.emAscent, mMetrics.emDescent); + printf(" maxAscent: %f maxDescent: %f maxAdvance: %f\n", mMetrics.maxAscent, mMetrics.maxDescent, mMetrics.maxAdvance); + printf(" internalLeading: %f externalLeading: %f\n", mMetrics.internalLeading, mMetrics.externalLeading); + printf(" spaceWidth: %f aveCharWidth: %f zeroWidth: %f\n", + mMetrics.spaceWidth, mMetrics.aveCharWidth, mMetrics.zeroWidth); + printf(" xHeight: %f capHeight: %f\n", mMetrics.xHeight, mMetrics.capHeight); + printf(" uOff: %f uSize: %f stOff: %f stSize: %f\n", + mMetrics.underlineOffset, mMetrics.underlineSize, mMetrics.strikeoutOffset, mMetrics.strikeoutSize); +#endif +} + +using namespace mozilla; // for AutoSwap_* types + +struct EBLCHeader { + AutoSwap_PRUint32 version; + AutoSwap_PRUint32 numSizes; +}; + +struct SbitLineMetrics { + int8_t ascender; + int8_t descender; + uint8_t widthMax; + int8_t caretSlopeNumerator; + int8_t caretSlopeDenominator; + int8_t caretOffset; + int8_t minOriginSB; + int8_t minAdvanceSB; + int8_t maxBeforeBL; + int8_t minAfterBL; + int8_t pad1; + int8_t pad2; +}; + +struct BitmapSizeTable { + AutoSwap_PRUint32 indexSubTableArrayOffset; + AutoSwap_PRUint32 indexTablesSize; + AutoSwap_PRUint32 numberOfIndexSubTables; + AutoSwap_PRUint32 colorRef; + SbitLineMetrics hori; + SbitLineMetrics vert; + AutoSwap_PRUint16 startGlyphIndex; + AutoSwap_PRUint16 endGlyphIndex; + uint8_t ppemX; + uint8_t ppemY; + uint8_t bitDepth; + uint8_t flags; +}; + +typedef EBLCHeader EBSCHeader; + +struct BitmapScaleTable { + SbitLineMetrics hori; + SbitLineMetrics vert; + uint8_t ppemX; + uint8_t ppemY; + uint8_t substitutePpemX; + uint8_t substitutePpemY; +}; + +bool gfxDWriteFont::HasBitmapStrikeForSize(uint32_t aSize) { + uint8_t* tableData; + uint32_t len; + void* tableContext; + BOOL exists; + HRESULT hr = mFontFace->TryGetFontTable( + DWRITE_MAKE_OPENTYPE_TAG('E', 'B', 'L', 'C'), (const void**)&tableData, + &len, &tableContext, &exists); + if (FAILED(hr)) { + return false; + } + + bool hasStrike = false; + // not really a loop, but this lets us use 'break' to skip out of the block + // as soon as we know the answer, and skips it altogether if the table is + // not present + while (exists) { + if (len < sizeof(EBLCHeader)) { + break; + } + const EBLCHeader* hdr = reinterpret_cast<const EBLCHeader*>(tableData); + if (hdr->version != 0x00020000) { + break; + } + uint32_t numSizes = hdr->numSizes; + if (numSizes > 0xffff) { // sanity-check, prevent overflow below + break; + } + if (len < sizeof(EBLCHeader) + numSizes * sizeof(BitmapSizeTable)) { + break; + } + const BitmapSizeTable* sizeTable = + reinterpret_cast<const BitmapSizeTable*>(hdr + 1); + for (uint32_t i = 0; i < numSizes; ++i, ++sizeTable) { + if (sizeTable->ppemX == aSize && sizeTable->ppemY == aSize) { + // we ignore a strike that contains fewer than 4 glyphs, + // as that probably indicates a font such as Courier New + // that provides bitmaps ONLY for the "shading" characters + // U+2591..2593 + hasStrike = (uint16_t(sizeTable->endGlyphIndex) >= + uint16_t(sizeTable->startGlyphIndex) + 3); + break; + } + } + // if we reach here, we didn't find a strike; unconditionally break + // out of the while-loop block + break; + } + mFontFace->ReleaseFontTable(tableContext); + + if (hasStrike) { + return true; + } + + // if we didn't find a real strike, check if the font calls for scaling + // another bitmap to this size + hr = mFontFace->TryGetFontTable(DWRITE_MAKE_OPENTYPE_TAG('E', 'B', 'S', 'C'), + (const void**)&tableData, &len, &tableContext, + &exists); + if (FAILED(hr)) { + return false; + } + + while (exists) { + if (len < sizeof(EBSCHeader)) { + break; + } + const EBSCHeader* hdr = reinterpret_cast<const EBSCHeader*>(tableData); + if (hdr->version != 0x00020000) { + break; + } + uint32_t numSizes = hdr->numSizes; + if (numSizes > 0xffff) { + break; + } + if (len < sizeof(EBSCHeader) + numSizes * sizeof(BitmapScaleTable)) { + break; + } + const BitmapScaleTable* scaleTable = + reinterpret_cast<const BitmapScaleTable*>(hdr + 1); + for (uint32_t i = 0; i < numSizes; ++i, ++scaleTable) { + if (scaleTable->ppemX == aSize && scaleTable->ppemY == aSize) { + hasStrike = true; + break; + } + } + break; + } + mFontFace->ReleaseFontTable(tableContext); + + return hasStrike; +} + +bool gfxDWriteFont::IsValid() const { return mFontFace != nullptr; } + +IDWriteFontFace* gfxDWriteFont::GetFontFace() { return mFontFace.get(); } + +gfxFont::RunMetrics gfxDWriteFont::Measure(const gfxTextRun* aTextRun, + uint32_t aStart, uint32_t aEnd, + BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, + Spacing* aSpacing, + gfx::ShapedTextFlags aOrientation) { + gfxFont::RunMetrics metrics = + gfxFont::Measure(aTextRun, aStart, aEnd, aBoundingBoxType, aRefDrawTarget, + aSpacing, aOrientation); + + // if aBoundingBoxType is LOOSE_INK_EXTENTS + // and the underlying cairo font may be antialiased, + // we can't trust Windows to have considered all the pixels + // so we need to add "padding" to the bounds. + // (see bugs 475968, 439831, compare also bug 445087) + if (aBoundingBoxType == LOOSE_INK_EXTENTS && + mAntialiasOption != kAntialiasNone && + GetMeasuringMode() == DWRITE_MEASURING_MODE_GDI_CLASSIC && + metrics.mBoundingBox.Width() > 0) { + metrics.mBoundingBox.MoveByX(-aTextRun->GetAppUnitsPerDevUnit()); + metrics.mBoundingBox.SetWidth(metrics.mBoundingBox.Width() + + aTextRun->GetAppUnitsPerDevUnit() * 3); + } + + return metrics; +} + +bool gfxDWriteFont::ProvidesGlyphWidths() const { + return !mUseSubpixelPositions || + (mFontFace->GetSimulations() & DWRITE_FONT_SIMULATIONS_BOLD) || + ((gfxDWriteFontEntry*)(GetFontEntry()))->HasVariations(); +} + +int32_t gfxDWriteFont::GetGlyphWidth(uint16_t aGID) { + if (!mGlyphWidths) { + mGlyphWidths = MakeUnique<nsTHashMap<nsUint32HashKey, int32_t>>(128); + } + + return mGlyphWidths->LookupOrInsertWith( + aGID, [&] { return NS_lround(MeasureGlyphWidth(aGID) * 65536.0); }); +} + +bool gfxDWriteFont::GetForceGDIClassic() const { + return sForceGDIClassicEnabled && + static_cast<gfxDWriteFontEntry*>(mFontEntry.get()) + ->GetForceGDIClassic() && + GetAdjustedSize() <= gfxDWriteFontList::PlatformFontList() + ->GetForceGDIClassicMaxFontSize() && + GetAdjustedSize() >= 6.0; +} + +DWRITE_MEASURING_MODE +gfxDWriteFont::GetMeasuringMode() const { + return DWriteSettings::Get(GetForceGDIClassic()).MeasuringMode(); +} + +gfxFloat gfxDWriteFont::MeasureGlyphWidth(uint16_t aGlyph) { + MOZ_SEH_TRY { + HRESULT hr; + if (mFontFace1) { + int32_t advance; + if (mUseSubpixelPositions) { + hr = mFontFace1->GetDesignGlyphAdvances(1, &aGlyph, &advance, FALSE); + if (SUCCEEDED(hr)) { + return advance * mFUnitsConvFactor; + } + } else { + hr = mFontFace1->GetGdiCompatibleGlyphAdvances( + FLOAT(mAdjustedSize), 1.0f, nullptr, + GetMeasuringMode() == DWRITE_MEASURING_MODE_GDI_NATURAL, FALSE, 1, + &aGlyph, &advance); + if (SUCCEEDED(hr)) { + return NS_lround(advance * mFUnitsConvFactor); + } + } + } else { + DWRITE_GLYPH_METRICS metrics; + if (mUseSubpixelPositions) { + hr = mFontFace->GetDesignGlyphMetrics(&aGlyph, 1, &metrics, FALSE); + if (SUCCEEDED(hr)) { + return metrics.advanceWidth * mFUnitsConvFactor; + } + } else { + hr = mFontFace->GetGdiCompatibleGlyphMetrics( + FLOAT(mAdjustedSize), 1.0f, nullptr, + GetMeasuringMode() == DWRITE_MEASURING_MODE_GDI_NATURAL, &aGlyph, 1, + &metrics, FALSE); + if (SUCCEEDED(hr)) { + return NS_lround(metrics.advanceWidth * mFUnitsConvFactor); + } + } + } + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + // Exception (e.g. disk i/o error) occurred when DirectWrite tried to use + // the font resource; possibly a failing drive or similar hardware issue. + // Mark the font as invalid, and wipe the fontEntry's charmap so that font + // selection will skip it; we'll use a fallback font instead. + mIsValid = false; + GetFontEntry()->mCharacterMap = new gfxCharacterMap(); + GetFontEntry()->mShmemCharacterMap = nullptr; + gfxCriticalError() << "Exception occurred measuring glyph width for " + << GetFontEntry()->Name().get(); + } + return 0.0; +} + +bool gfxDWriteFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, + bool aTight) { + MOZ_SEH_TRY { + DWRITE_GLYPH_METRICS m; + HRESULT hr = mFontFace->GetDesignGlyphMetrics(&aGID, 1, &m, FALSE); + if (FAILED(hr)) { + return false; + } + gfxRect bounds(m.leftSideBearing, m.topSideBearing - m.verticalOriginY, + m.advanceWidth - m.leftSideBearing - m.rightSideBearing, + m.advanceHeight - m.topSideBearing - m.bottomSideBearing); + bounds.Scale(mFUnitsConvFactor); + // GetDesignGlyphMetrics returns 'ideal' glyph metrics, we need to pad to + // account for antialiasing. + if (!aTight && !aBounds->IsEmpty()) { + bounds.Inflate(1.0, 0.0); + } + *aBounds = bounds; + return true; + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + // Exception (e.g. disk i/o error) occurred when DirectWrite tried to use + // the font resource; possibly a failing drive or similar hardware issue. + // Mark the font as invalid, and wipe the fontEntry's charmap so that font + // selection will skip it; we'll use a fallback font instead. + mIsValid = false; + GetFontEntry()->mCharacterMap = new gfxCharacterMap(); + GetFontEntry()->mShmemCharacterMap = nullptr; + gfxCriticalError() << "Exception occurred measuring glyph bounds for " + << GetFontEntry()->Name().get(); + } + return false; +} + +void gfxDWriteFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const { + gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); + if (mGlyphWidths) { + aSizes->mFontInstances += + mGlyphWidths->ShallowSizeOfIncludingThis(aMallocSizeOf); + } +} + +void gfxDWriteFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const { + aSizes->mFontInstances += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +already_AddRefed<ScaledFont> gfxDWriteFont::GetScaledFont( + const TextRunDrawParams& aRunParams) { + bool useClearType = UsingClearType(); + if (mAzureScaledFontUsedClearType != useClearType) { + if (auto* oldScaledFont = mAzureScaledFont.exchange(nullptr)) { + oldScaledFont->Release(); + } + if (auto* oldScaledFont = mAzureScaledFontGDI.exchange(nullptr)) { + oldScaledFont->Release(); + } + } + bool forceGDI = aRunParams.allowGDI && GetForceGDIClassic(); + ScaledFont* scaledFont = forceGDI ? mAzureScaledFontGDI : mAzureScaledFont; + if (scaledFont) { + return do_AddRef(scaledFont); + } + + gfxDWriteFontEntry* fe = static_cast<gfxDWriteFontEntry*>(mFontEntry.get()); + bool useEmbeddedBitmap = + (gfxVars::SystemTextRenderingMode() == DWRITE_RENDERING_MODE_DEFAULT || + forceGDI) && + fe->IsCJKFont() && HasBitmapStrikeForSize(NS_lround(mAdjustedSize)); + + const gfxFontStyle* fontStyle = GetStyle(); + RefPtr<ScaledFont> newScaledFont = Factory::CreateScaledFontForDWriteFont( + mFontFace, fontStyle, GetUnscaledFont(), GetAdjustedSize(), + useEmbeddedBitmap, ApplySyntheticBold(), forceGDI); + if (!newScaledFont) { + return nullptr; + } + InitializeScaledFont(newScaledFont); + + if (forceGDI) { + if (mAzureScaledFontGDI.compareExchange(nullptr, newScaledFont.get())) { + Unused << newScaledFont.forget(); + mAzureScaledFontUsedClearType = useClearType; + } + scaledFont = mAzureScaledFontGDI; + } else { + if (mAzureScaledFont.compareExchange(nullptr, newScaledFont.get())) { + Unused << newScaledFont.forget(); + mAzureScaledFontUsedClearType = useClearType; + } + scaledFont = mAzureScaledFont; + } + return do_AddRef(scaledFont); +} + +bool gfxDWriteFont::ShouldRoundXOffset(cairo_t* aCairo) const { + // show_glyphs is implemented on the font and so is used for all Cairo + // surface types; however, it may pixel-snap depending on the dwrite + // rendering mode + return GetMeasuringMode() != DWRITE_MEASURING_MODE_NATURAL; +} diff --git a/gfx/thebes/gfxDWriteFonts.h b/gfx/thebes/gfxDWriteFonts.h new file mode 100644 index 0000000000..fc01aee6d9 --- /dev/null +++ b/gfx/thebes/gfxDWriteFonts.h @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_WINDOWSDWRITEFONTS_H +#define GFX_WINDOWSDWRITEFONTS_H + +#include "mozilla/Atomics.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/UniquePtr.h" +#include <dwrite_1.h> + +#include "gfxFont.h" +#include "gfxUserFontSet.h" +#include "nsTHashMap.h" +#include "nsHashKeys.h" + +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/UnscaledFontDWrite.h" + +/** + * \brief Class representing a font face for a font entry. + */ +class gfxDWriteFont final : public gfxFont { + public: + gfxDWriteFont(const RefPtr<mozilla::gfx::UnscaledFontDWrite>& aUnscaledFont, + gfxFontEntry* aFontEntry, const gfxFontStyle* aFontStyle, + RefPtr<IDWriteFontFace> aFontFace = nullptr, + AntialiasOption = kAntialiasDefault); + + static bool InitDWriteSupport(); + + // These Update functions update gfxVars with font settings, they must only be + // called in the parent process. + static void UpdateSystemTextVars(); + static void UpdateClearTypeVars(); + + static void SystemTextQualityChanged(); + + gfxFont* CopyWithAntialiasOption(AntialiasOption anAAOption) const override; + + bool AllowSubpixelAA() const override { return mAllowManualShowGlyphs; } + + bool IsValid() const; + + IDWriteFontFace* GetFontFace(); + + /* override Measure to add padding for antialiasing */ + RunMetrics Measure(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd, + BoundingBoxType aBoundingBoxType, + DrawTarget* aDrawTargetForTightBoundingBox, + Spacing* aSpacing, + mozilla::gfx::ShapedTextFlags aOrientation) override; + + bool ProvidesGlyphWidths() const override; + + int32_t GetGlyphWidth(uint16_t aGID) override; + + bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override; + + void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const override; + void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const override; + + FontType GetType() const override { return FONT_TYPE_DWRITE; } + + already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont( + const TextRunDrawParams& aRunParams) override; + + bool ShouldRoundXOffset(cairo_t* aCairo) const override; + + protected: + ~gfxDWriteFont() override; + + const Metrics& GetHorizontalMetrics() const override { return mMetrics; } + + bool GetFakeMetricsForArialBlack(DWRITE_FONT_METRICS* aFontMetrics); + + void ComputeMetrics(AntialiasOption anAAOption); + + bool HasBitmapStrikeForSize(uint32_t aSize); + + gfxFloat MeasureGlyphWidth(uint16_t aGlyph); + + DWRITE_MEASURING_MODE GetMeasuringMode() const; + + static mozilla::Atomic<bool> sForceGDIClassicEnabled; + bool GetForceGDIClassic() const; + + RefPtr<IDWriteFontFace> mFontFace; + RefPtr<IDWriteFontFace1> mFontFace1; // may be unavailable on older DWrite + + Metrics mMetrics; + + // cache of glyph widths in 16.16 fixed-point pixels + mozilla::UniquePtr<nsTHashMap<nsUint32HashKey, int32_t>> mGlyphWidths; + + bool mUseSubpixelPositions; + bool mAllowManualShowGlyphs; + + // Used to record the sUseClearType setting at the time mAzureScaledFont + // was set up, so we can tell if it's stale and needs to be re-created. + mozilla::Atomic<bool> mAzureScaledFontUsedClearType; + + // Cache the GDI version of the ScaledFont so that font keys and other + // meta-data can remain stable even if there is thrashing between GDI and + // non-GDI usage. + mozilla::Atomic<mozilla::gfx::ScaledFont*> mAzureScaledFontGDI; + + bool UsingClearType() { + return mozilla::gfx::gfxVars::SystemTextQuality() == CLEARTYPE_QUALITY; + } +}; + +#endif diff --git a/gfx/thebes/gfxDrawable.cpp b/gfx/thebes/gfxDrawable.cpp new file mode 100644 index 0000000000..de0ea1948a --- /dev/null +++ b/gfx/thebes/gfxDrawable.cpp @@ -0,0 +1,216 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxDrawable.h" +#include "gfxContext.h" +#include "gfxPlatform.h" +#include "gfx2DGlue.h" +#ifdef MOZ_X11 +# include "cairo.h" +# include "gfxXlibSurface.h" +#endif +#include "mozilla/gfx/Logging.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +gfxSurfaceDrawable::gfxSurfaceDrawable(SourceSurface* aSurface, + const IntSize aSize, + const gfxMatrix aTransform) + : gfxDrawable(aSize), mSourceSurface(aSurface), mTransform(aTransform) { + if (!mSourceSurface) { + gfxWarning() << "Creating gfxSurfaceDrawable with null SourceSurface"; + } +} + +bool gfxSurfaceDrawable::DrawWithSamplingRect( + DrawTarget* aDrawTarget, CompositionOp aOp, AntialiasMode aAntialiasMode, + const gfxRect& aFillRect, const gfxRect& aSamplingRect, + ExtendMode aExtendMode, const SamplingFilter aSamplingFilter, + gfxFloat aOpacity) { + if (!mSourceSurface) { + return true; + } + + // When drawing with CLAMP we can expand the sampling rect to the nearest + // pixel without changing the result. + IntRect intRect = + IntRect::RoundOut(aSamplingRect.X(), aSamplingRect.Y(), + aSamplingRect.Width(), aSamplingRect.Height()); + + IntSize size = mSourceSurface->GetSize(); + if (!IntRect(IntPoint(), size).Contains(intRect)) { + return false; + } + + DrawInternal(aDrawTarget, aOp, aAntialiasMode, aFillRect, intRect, + ExtendMode::CLAMP, aSamplingFilter, aOpacity, gfxMatrix()); + return true; +} + +bool gfxSurfaceDrawable::Draw(gfxContext* aContext, const gfxRect& aFillRect, + ExtendMode aExtendMode, + const SamplingFilter aSamplingFilter, + gfxFloat aOpacity, const gfxMatrix& aTransform) + +{ + if (!mSourceSurface) { + return true; + } + + DrawInternal(aContext->GetDrawTarget(), aContext->CurrentOp(), + aContext->CurrentAntialiasMode(), aFillRect, IntRect(), + aExtendMode, aSamplingFilter, aOpacity, aTransform); + return true; +} + +void gfxSurfaceDrawable::DrawInternal( + DrawTarget* aDrawTarget, CompositionOp aOp, AntialiasMode aAntialiasMode, + const gfxRect& aFillRect, const IntRect& aSamplingRect, + ExtendMode aExtendMode, const SamplingFilter aSamplingFilter, + gfxFloat aOpacity, const gfxMatrix& aTransform) { + Matrix patternTransform = ToMatrix(aTransform * mTransform); + patternTransform.Invert(); + + SurfacePattern pattern(mSourceSurface, aExtendMode, patternTransform, + aSamplingFilter, aSamplingRect); + + Rect fillRect = ToRect(aFillRect); + + if (aOp == CompositionOp::OP_SOURCE && aOpacity == 1.0) { + // Emulate cairo operator source which is bound by mask! + aDrawTarget->ClearRect(fillRect); + aDrawTarget->FillRect(fillRect, pattern); + } else { + aDrawTarget->FillRect(fillRect, pattern, + DrawOptions(aOpacity, aOp, aAntialiasMode)); + } +} + +gfxCallbackDrawable::gfxCallbackDrawable(gfxDrawingCallback* aCallback, + const IntSize aSize) + : gfxDrawable(aSize), mCallback(aCallback) {} + +already_AddRefed<gfxSurfaceDrawable> gfxCallbackDrawable::MakeSurfaceDrawable( + gfxContext* aContext, const SamplingFilter aSamplingFilter) { + SurfaceFormat format = gfxPlatform::GetPlatform()->Optimal2DFormatForContent( + gfxContentType::COLOR_ALPHA); + if (!aContext->GetDrawTarget()->CanCreateSimilarDrawTarget(mSize, format)) { + return nullptr; + } + RefPtr<DrawTarget> dt = + aContext->GetDrawTarget()->CreateSimilarDrawTarget(mSize, format); + + if (!dt || !dt->IsValid()) { + return nullptr; + } + + gfxContext ctx(dt); + Draw(&ctx, gfxRect(0, 0, mSize.width, mSize.height), ExtendMode::CLAMP, + aSamplingFilter); + + RefPtr<SourceSurface> surface = dt->Snapshot(); + if (surface) { + RefPtr<gfxSurfaceDrawable> drawable = + new gfxSurfaceDrawable(surface, mSize); + return drawable.forget(); + } + return nullptr; +} + +static bool IsRepeatingExtendMode(ExtendMode aExtendMode) { + switch (aExtendMode) { + case ExtendMode::REPEAT: + case ExtendMode::REPEAT_X: + case ExtendMode::REPEAT_Y: + return true; + default: + return false; + } +} + +bool gfxCallbackDrawable::Draw(gfxContext* aContext, const gfxRect& aFillRect, + ExtendMode aExtendMode, + const SamplingFilter aSamplingFilter, + gfxFloat aOpacity, const gfxMatrix& aTransform) { + if ((IsRepeatingExtendMode(aExtendMode) || aOpacity != 1.0 || + aContext->CurrentOp() != CompositionOp::OP_OVER) && + !mSurfaceDrawable) { + mSurfaceDrawable = MakeSurfaceDrawable(aContext, aSamplingFilter); + } + + if (mSurfaceDrawable) + return mSurfaceDrawable->Draw(aContext, aFillRect, aExtendMode, + aSamplingFilter, aOpacity, aTransform); + + if (mCallback) + return (*mCallback)(aContext, aFillRect, aSamplingFilter, aTransform); + + return false; +} + +gfxPatternDrawable::gfxPatternDrawable(gfxPattern* aPattern, + const IntSize aSize) + : gfxDrawable(aSize), mPattern(aPattern) {} + +gfxPatternDrawable::~gfxPatternDrawable() = default; + +class DrawingCallbackFromDrawable : public gfxDrawingCallback { + public: + explicit DrawingCallbackFromDrawable(gfxDrawable* aDrawable) + : mDrawable(aDrawable) { + NS_ASSERTION(aDrawable, "aDrawable is null!"); + } + + virtual ~DrawingCallbackFromDrawable() = default; + + bool operator()(gfxContext* aContext, const gfxRect& aFillRect, + const SamplingFilter aSamplingFilter, + const gfxMatrix& aTransform = gfxMatrix()) override { + return mDrawable->Draw(aContext, aFillRect, ExtendMode::CLAMP, + aSamplingFilter, 1.0, aTransform); + } + + private: + RefPtr<gfxDrawable> mDrawable; +}; + +already_AddRefed<gfxCallbackDrawable> +gfxPatternDrawable::MakeCallbackDrawable() { + RefPtr<gfxDrawingCallback> callback = new DrawingCallbackFromDrawable(this); + RefPtr<gfxCallbackDrawable> callbackDrawable = + new gfxCallbackDrawable(callback, mSize); + return callbackDrawable.forget(); +} + +bool gfxPatternDrawable::Draw(gfxContext* aContext, const gfxRect& aFillRect, + ExtendMode aExtendMode, + const SamplingFilter aSamplingFilter, + gfxFloat aOpacity, const gfxMatrix& aTransform) { + DrawTarget& aDrawTarget = *aContext->GetDrawTarget(); + + if (!mPattern) return false; + + if (IsRepeatingExtendMode(aExtendMode)) { + // We can't use mPattern directly: We want our repeated tiles to have + // the size mSize, which might not be the case in mPattern. + // So we need to draw mPattern into a surface of size mSize, create + // a pattern from the surface and draw that pattern. + // gfxCallbackDrawable and gfxSurfaceDrawable already know how to do + // those things, so we use them here. Drawing mPattern into the surface + // will happen through this Draw() method with aRepeat = false. + RefPtr<gfxCallbackDrawable> callbackDrawable = MakeCallbackDrawable(); + return callbackDrawable->Draw(aContext, aFillRect, aExtendMode, + aSamplingFilter, aOpacity, aTransform); + } + + gfxMatrix oldMatrix = mPattern->GetMatrix(); + mPattern->SetMatrix(aTransform * oldMatrix); + DrawOptions drawOptions(aOpacity); + aDrawTarget.FillRect(ToRect(aFillRect), *mPattern->GetPattern(&aDrawTarget), + drawOptions); + mPattern->SetMatrix(oldMatrix); + return true; +} diff --git a/gfx/thebes/gfxDrawable.h b/gfx/thebes/gfxDrawable.h new file mode 100644 index 0000000000..b0d47e3691 --- /dev/null +++ b/gfx/thebes/gfxDrawable.h @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_DRAWABLE_H +#define GFX_DRAWABLE_H + +#include "gfxRect.h" +#include "gfxMatrix.h" +#include "gfxTypes.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Types.h" +#include "nsISupportsImpl.h" + +class gfxContext; +class gfxPattern; + +/** + * gfxDrawable + * An Interface representing something that has an intrinsic size and can draw + * itself repeatedly. + */ +class gfxDrawable { + NS_INLINE_DECL_REFCOUNTING(gfxDrawable) + public: + typedef mozilla::gfx::AntialiasMode AntialiasMode; + typedef mozilla::gfx::CompositionOp CompositionOp; + typedef mozilla::gfx::DrawTarget DrawTarget; + + explicit gfxDrawable(const mozilla::gfx::IntSize aSize) : mSize(aSize) {} + + /** + * Draw into aContext filling aFillRect, possibly repeating, using + * aSamplingFilter. aTransform is a userspace to "image"space matrix. For + * example, if Draw draws using a gfxPattern, this is the matrix that should + * be set on the pattern prior to rendering it. + * @return whether drawing was successful + */ + virtual bool Draw(gfxContext* aContext, const gfxRect& aFillRect, + mozilla::gfx::ExtendMode aExtendMode, + const mozilla::gfx::SamplingFilter aSamplingFilter, + gfxFloat aOpacity = 1.0, + const gfxMatrix& aTransform = gfxMatrix()) = 0; + + virtual bool DrawWithSamplingRect( + DrawTarget* aDrawTarget, CompositionOp aOp, AntialiasMode aAntialiasMode, + const gfxRect& aFillRect, const gfxRect& aSamplingRect, + mozilla::gfx::ExtendMode aExtendMode, + const mozilla::gfx::SamplingFilter aSamplingFilter, + gfxFloat aOpacity = 1.0) { + return false; + } + + virtual mozilla::gfx::IntSize Size() { return mSize; } + + protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~gfxDrawable() = default; + + const mozilla::gfx::IntSize mSize; +}; + +/** + * gfxSurfaceDrawable + * A convenience implementation of gfxDrawable for surfaces. + */ +class gfxSurfaceDrawable : public gfxDrawable { + public: + gfxSurfaceDrawable(mozilla::gfx::SourceSurface* aSurface, + const mozilla::gfx::IntSize aSize, + const gfxMatrix aTransform = gfxMatrix()); + virtual ~gfxSurfaceDrawable() = default; + + virtual bool Draw(gfxContext* aContext, const gfxRect& aFillRect, + mozilla::gfx::ExtendMode aExtendMode, + const mozilla::gfx::SamplingFilter aSamplingFilter, + gfxFloat aOpacity = 1.0, + const gfxMatrix& aTransform = gfxMatrix()) override; + + virtual bool DrawWithSamplingRect( + DrawTarget* aDrawTarget, CompositionOp aOp, AntialiasMode aAntialiasMode, + const gfxRect& aFillRect, const gfxRect& aSamplingRect, + mozilla::gfx::ExtendMode aExtendMode, + const mozilla::gfx::SamplingFilter aSamplingFilter, + gfxFloat aOpacity = 1.0) override; + + protected: + void DrawInternal(DrawTarget* aDrawTarget, CompositionOp aOp, + AntialiasMode aAntialiasMode, const gfxRect& aFillRect, + const mozilla::gfx::IntRect& aSamplingRect, + mozilla::gfx::ExtendMode aExtendMode, + const mozilla::gfx::SamplingFilter aSamplingFilter, + gfxFloat aOpacity, + const gfxMatrix& aTransform = gfxMatrix()); + + RefPtr<mozilla::gfx::SourceSurface> mSourceSurface; + const gfxMatrix mTransform; +}; + +/** + * gfxDrawingCallback + * A simple drawing functor. + */ +class gfxDrawingCallback { + NS_INLINE_DECL_REFCOUNTING(gfxDrawingCallback) + protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~gfxDrawingCallback() = default; + + public: + /** + * Draw into aContext filling aFillRect using aSamplingFilter. + * aTransform is a userspace to "image"space matrix. For example, if Draw + * draws using a gfxPattern, this is the matrix that should be set on the + * pattern prior to rendering it. + * @return whether drawing was successful + */ + virtual bool operator()(gfxContext* aContext, const gfxRect& aFillRect, + const mozilla::gfx::SamplingFilter aSamplingFilter, + const gfxMatrix& aTransform = gfxMatrix()) = 0; +}; + +/** + * gfxCallbackDrawable + * A convenience implementation of gfxDrawable for callbacks. + */ +class gfxCallbackDrawable : public gfxDrawable { + public: + gfxCallbackDrawable(gfxDrawingCallback* aCallback, + const mozilla::gfx::IntSize aSize); + virtual ~gfxCallbackDrawable() = default; + + virtual bool Draw(gfxContext* aContext, const gfxRect& aFillRect, + mozilla::gfx::ExtendMode aExtendMode, + const mozilla::gfx::SamplingFilter aSamplingFilter, + gfxFloat aOpacity = 1.0, + const gfxMatrix& aTransform = gfxMatrix()) override; + + protected: + already_AddRefed<gfxSurfaceDrawable> MakeSurfaceDrawable( + gfxContext* aContext, mozilla::gfx::SamplingFilter aSamplingFilter = + mozilla::gfx::SamplingFilter::LINEAR); + + RefPtr<gfxDrawingCallback> mCallback; + RefPtr<gfxSurfaceDrawable> mSurfaceDrawable; +}; + +/** + * gfxPatternDrawable + * A convenience implementation of gfxDrawable for patterns. + */ +class gfxPatternDrawable : public gfxDrawable { + public: + gfxPatternDrawable(gfxPattern* aPattern, const mozilla::gfx::IntSize aSize); + virtual ~gfxPatternDrawable(); + + virtual bool Draw(gfxContext* aContext, const gfxRect& aFillRect, + mozilla::gfx::ExtendMode aExtendMode, + const mozilla::gfx::SamplingFilter aSamplingFilter, + gfxFloat aOpacity = 1.0, + const gfxMatrix& aTransform = gfxMatrix()) override; + + protected: + already_AddRefed<gfxCallbackDrawable> MakeCallbackDrawable(); + + RefPtr<gfxPattern> mPattern; +}; + +#endif /* GFX_DRAWABLE_H */ diff --git a/gfx/thebes/gfxEnv.h b/gfx/thebes/gfxEnv.h new file mode 100644 index 0000000000..a75f26aa97 --- /dev/null +++ b/gfx/thebes/gfxEnv.h @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_ENV_H +#define GFX_ENV_H + +#include "mozilla/Attributes.h" +#include "nsDebug.h" +#include "prenv.h" + +#include <sstream> +#include <string_view> + +// To register the check for an environment variable existence (and not empty), +// add a line in this file using the DECL_GFX_ENV macro. +// +// For example this line in the .h: +// DECL_GFX_ENV(MOZ_GL_SPEW); + +// means that you can call e.g. +// if (gfxEnv::MOZ_GL_SPEW()) { ... } +// if (gfxEnv::MOZ_GL_SPEW().as_str == "2") { ... } +// and that the value will be checked only once, first time we call it, then +// cached. + +struct EnvVal { + std::string_view as_str; + + static auto From(const char* const raw) { + auto ret = EnvVal{}; + + ret.as_str = std::string_view{}; + // Empty string counts as missing. + if (raw) { + ret.as_str = raw; + } + + return ret; + } + + MOZ_IMPLICIT operator bool() const { + return !as_str.empty(); // Warning, this means ENV=0" -> true! + } +}; + +class gfxEnv final { + public: + gfxEnv() = delete; + + static EnvVal Uncached(const char* name) { + const auto raw = PR_GetEnv(name); + const auto ret = EnvVal::From(raw); + if (ret && ret.as_str == "0") { + auto msg = std::stringstream{}; + msg << name << "=" << ret.as_str << " -> true!"; + NS_WARNING(msg.str().c_str()); + } + return ret; + } + +#define DECL_GFX_ENV(Name) \ + static const EnvVal& Name() { \ + static const auto cached = Uncached(#Name); \ + return cached; \ + } + + // This is where DECL_GFX_ENV for each of the environment variables should go. + // We will keep these in an alphabetical order by the environment variable, + // to make it easier to see if a method accessing an entry already exists. + // Just insert yours in the list. + + // OpenGL shader debugging in OGLShaderProgram, in DEBUG only + DECL_GFX_ENV(MOZ_DEBUG_SHADERS) + + // Disabling the crash guard in DriverCrashGuard + DECL_GFX_ENV(MOZ_DISABLE_CRASH_GUARD) + DECL_GFX_ENV(MOZ_FORCE_CRASH_GUARD_NIGHTLY) + + // We force present to work around some Windows bugs - disable that if this + // environment variable is set. + DECL_GFX_ENV(MOZ_DISABLE_FORCE_PRESENT) + + // Together with paint dumping, only when MOZ_DUMP_PAINTING is defined. + // Dumping compositor textures is broken pretty badly. For example, + // on Linux it crashes TextureHost::GetAsSurface() returns null. + // Expect to have to fix things like this if you turn it on. + // Meanwhile, content-side texture dumping + // (conditioned on DebugDumpPainting()) is a good replacement. + DECL_GFX_ENV(MOZ_DUMP_COMPOSITOR_TEXTURES) + + // Dump GLBlitHelper shader source text. + DECL_GFX_ENV(MOZ_DUMP_GLBLITHELPER) + + // Paint dumping, only when MOZ_DUMP_PAINTING is defined. + DECL_GFX_ENV(MOZ_DUMP_PAINT) + DECL_GFX_ENV(MOZ_DUMP_PAINT_ITEMS) + DECL_GFX_ENV(MOZ_DUMP_PAINT_TO_FILE) + + // Force gfxDevCrash to use MOZ_CRASH in Beta and Release + DECL_GFX_ENV(MOZ_GFX_CRASH_MOZ_CRASH) + // Force gfxDevCrash to use telemetry in Nightly and Aurora + DECL_GFX_ENV(MOZ_GFX_CRASH_TELEMETRY) + + // Debugging in GLContext + DECL_GFX_ENV(MOZ_GL_DEBUG) + DECL_GFX_ENV(MOZ_GL_DEBUG_VERBOSE) + DECL_GFX_ENV(MOZ_GL_DEBUG_ABORT_ON_ERROR) + DECL_GFX_ENV(MOZ_GL_RELEASE_ASSERT_CONTEXT_OWNERSHIP) + DECL_GFX_ENV(MOZ_EGL_RELEASE_ASSERT_CONTEXT_OWNERSHIP) + + // Count GL extensions + DECL_GFX_ENV(MOZ_GL_DUMP_EXTS) + + // Very noisy GLContext and GLContextProviderEGL + DECL_GFX_ENV(MOZ_GL_SPEW) + + // Do extra work before and after each GLX call in GLContextProviderGLX + DECL_GFX_ENV(MOZ_GLX_DEBUG) + + // GL compositing on Windows + DECL_GFX_ENV(MOZ_LAYERS_PREFER_EGL) + + // Offscreen GL context for main layer manager + DECL_GFX_ENV(MOZ_LAYERS_PREFER_OFFSCREEN) + + // WebGL workarounds + DECL_GFX_ENV(MOZ_WEBGL_WORKAROUND_FIRST_AFFECTS_INSTANCE_ID) + + // WARNING: + // For readability reasons, please make sure that you've added your new envvar + // to the list above in alphabetical order. + // Please do not just append it to the end of the list! + +#undef DECL_GFX_ENV +}; + +#endif /* GFX_ENV_H */ diff --git a/gfx/thebes/gfxFT2FontBase.cpp b/gfx/thebes/gfxFT2FontBase.cpp new file mode 100644 index 0000000000..923ece60c3 --- /dev/null +++ b/gfx/thebes/gfxFT2FontBase.cpp @@ -0,0 +1,843 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxFT2FontBase.h" +#include "gfxFT2Utils.h" +#include "harfbuzz/hb.h" +#include "mozilla/Likely.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "gfxFontConstants.h" +#include "gfxFontUtils.h" +#include "gfxHarfBuzzShaper.h" +#include <algorithm> +#include <dlfcn.h> + +#include FT_TRUETYPE_TAGS_H +#include FT_TRUETYPE_TABLES_H +#include FT_ADVANCES_H +#include FT_MULTIPLE_MASTERS_H + +#ifndef FT_LOAD_COLOR +# define FT_LOAD_COLOR (1L << 20) +#endif +#ifndef FT_FACE_FLAG_COLOR +# define FT_FACE_FLAG_COLOR (1L << 14) +#endif + +using namespace mozilla; +using namespace mozilla::gfx; + +gfxFT2FontBase::gfxFT2FontBase( + const RefPtr<UnscaledFontFreeType>& aUnscaledFont, + RefPtr<mozilla::gfx::SharedFTFace>&& aFTFace, gfxFontEntry* aFontEntry, + const gfxFontStyle* aFontStyle, int aLoadFlags, bool aEmbolden) + : gfxFont(aUnscaledFont, aFontEntry, aFontStyle, kAntialiasDefault), + mFTFace(std::move(aFTFace)), + mFTLoadFlags(aLoadFlags | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH | + FT_LOAD_COLOR), + mEmbolden(aEmbolden), + mFTSize(0.0) {} + +gfxFT2FontBase::~gfxFT2FontBase() { mFTFace->ForgetLockOwner(this); } + +FT_Face gfxFT2FontBase::LockFTFace() const + MOZ_CAPABILITY_ACQUIRE(mFTFace) MOZ_NO_THREAD_SAFETY_ANALYSIS { + if (!mFTFace->Lock(this)) { + FT_Set_Transform(mFTFace->GetFace(), nullptr, nullptr); + + FT_F26Dot6 charSize = NS_lround(mFTSize * 64.0); + FT_Set_Char_Size(mFTFace->GetFace(), charSize, charSize, 0, 0); + } + return mFTFace->GetFace(); +} + +void gfxFT2FontBase::UnlockFTFace() const + MOZ_CAPABILITY_RELEASE(mFTFace) MOZ_NO_THREAD_SAFETY_ANALYSIS { + mFTFace->Unlock(); +} + +static FT_ULong GetTableSizeFromFTFace(SharedFTFace* aFace, + uint32_t aTableTag) { + if (!aFace) { + return 0; + } + FT_ULong len = 0; + if (FT_Load_Sfnt_Table(aFace->GetFace(), aTableTag, 0, nullptr, &len) != 0) { + return 0; + } + return len; +} + +bool gfxFT2FontEntryBase::FaceHasTable(SharedFTFace* aFace, + uint32_t aTableTag) { + return GetTableSizeFromFTFace(aFace, aTableTag) > 0; +} + +nsresult gfxFT2FontEntryBase::CopyFaceTable(SharedFTFace* aFace, + uint32_t aTableTag, + nsTArray<uint8_t>& aBuffer) { + FT_ULong length = GetTableSizeFromFTFace(aFace, aTableTag); + if (!length) { + return NS_ERROR_NOT_AVAILABLE; + } + if (!aBuffer.SetLength(length, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + if (FT_Load_Sfnt_Table(aFace->GetFace(), aTableTag, 0, aBuffer.Elements(), + &length) != 0) { + aBuffer.Clear(); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +uint32_t gfxFT2FontEntryBase::GetGlyph(uint32_t aCharCode, + gfxFT2FontBase* aFont) { + const uint32_t slotIndex = aCharCode % kNumCmapCacheSlots; + { + // Try to read a cached entry without taking an exclusive lock. + AutoReadLock lock(mLock); + if (mCmapCache) { + const auto& slot = mCmapCache[slotIndex]; + if (slot.mCharCode == aCharCode) { + return slot.mGlyphIndex; + } + } + } + + // Create/update the charcode-to-glyphid cache. + AutoWriteLock lock(mLock); + + // This cache algorithm and size is based on what is done in + // cairo_scaled_font_text_to_glyphs and pango_fc_font_real_get_glyph. I + // think the concept is that adjacent characters probably come mostly from + // one Unicode block. This assumption is probably not so valid with + // scripts with large character sets as used for East Asian languages. + if (!mCmapCache) { + mCmapCache = mozilla::MakeUnique<CmapCacheSlot[]>(kNumCmapCacheSlots); + + // Invalidate slot 0 by setting its char code to something that would + // never end up in slot 0. All other slots are already invalid + // because they have mCharCode = 0 and a glyph for char code 0 will + // always be in the slot 0. + mCmapCache[0].mCharCode = 1; + } + + auto& slot = mCmapCache[slotIndex]; + if (slot.mCharCode != aCharCode) { + slot.mCharCode = aCharCode; + slot.mGlyphIndex = gfxFT2LockedFace(aFont).GetGlyph(aCharCode); + } + return slot.mGlyphIndex; +} + +// aScale is intended for a 16.16 x/y_scale of an FT_Size_Metrics +static inline FT_Long ScaleRoundDesignUnits(FT_Short aDesignMetric, + FT_Fixed aScale) { + FT_Long fixed26dot6 = FT_MulFix(aDesignMetric, aScale); + return ROUND_26_6_TO_INT(fixed26dot6); +} + +// Snap a line to pixels while keeping the center and size of the line as +// close to the original position as possible. +// +// Pango does similar snapping for underline and strikethrough when fonts are +// hinted, but nsCSSRendering::GetTextDecorationRectInternal always snaps the +// top and size of lines. Optimizing the distance between the line and +// baseline is probably good for the gap between text and underline, but +// optimizing the center of the line is better for positioning strikethough. +static void SnapLineToPixels(gfxFloat& aOffset, gfxFloat& aSize) { + gfxFloat snappedSize = std::max(floor(aSize + 0.5), 1.0); + // Correct offset for change in size + gfxFloat offset = aOffset - 0.5 * (aSize - snappedSize); + // Snap offset + aOffset = floor(offset + 0.5); + aSize = snappedSize; +} + +static inline gfxRect ScaleGlyphBounds(const IntRect& aBounds, + gfxFloat aScale) { + return gfxRect(FLOAT_FROM_26_6(aBounds.x) * aScale, + FLOAT_FROM_26_6(aBounds.y) * aScale, + FLOAT_FROM_26_6(aBounds.width) * aScale, + FLOAT_FROM_26_6(aBounds.height) * aScale); +} + +/** + * Get extents for a simple character representable by a single glyph. + * The return value is the glyph id of that glyph or zero if no such glyph + * exists. aWidth/aBounds is only set when this returns a non-zero glyph id. + * This is just for use during initialization, and doesn't use the width cache. + */ +uint32_t gfxFT2FontBase::GetCharExtents(uint32_t aChar, gfxFloat* aWidth, + gfxRect* aBounds) { + FT_UInt gid = GetGlyph(aChar); + int32_t width; + IntRect bounds; + if (gid && GetFTGlyphExtents(gid, aWidth ? &width : nullptr, + aBounds ? &bounds : nullptr)) { + if (aWidth) { + *aWidth = FLOAT_FROM_16_16(width); + } + if (aBounds) { + *aBounds = ScaleGlyphBounds(bounds, GetAdjustedSize() / mFTSize); + } + return gid; + } else { + return 0; + } +} + +/** + * Find the closest available fixed strike size, if applicable, to the + * desired font size. + */ +static double FindClosestSize(FT_Face aFace, double aSize) { + // FT size selection does not actually support sizes smaller than 1 and will + // clamp this internally, regardless of what is requested. Do the clamp here + // instead so that glyph extents/font matrix scaling will compensate it, as + // Cairo normally would. + if (aSize < 1.0) { + aSize = 1.0; + } + if (FT_IS_SCALABLE(aFace)) { + return aSize; + } + double bestDist = DBL_MAX; + FT_Int bestSize = -1; + for (FT_Int i = 0; i < aFace->num_fixed_sizes; i++) { + double dist = aFace->available_sizes[i].y_ppem / 64.0 - aSize; + // If the previous best is smaller than the desired size, prefer + // a bigger size. Otherwise, just choose whatever size is closest. + if (bestDist < 0 ? dist >= bestDist : fabs(dist) <= bestDist) { + bestDist = dist; + bestSize = i; + } + } + if (bestSize < 0) { + return aSize; + } + return aFace->available_sizes[bestSize].y_ppem / 64.0; +} + +void gfxFT2FontBase::InitMetrics() { + mFUnitsConvFactor = 0.0; + + if (MOZ_UNLIKELY(mStyle.AdjustedSizeMustBeZero())) { + memset(&mMetrics, 0, sizeof(mMetrics)); // zero initialize + mSpaceGlyph = GetGlyph(' '); + return; + } + + if (FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) != + FontSizeAdjust::Tag::None && + mStyle.sizeAdjust >= 0.0 && GetAdjustedSize() > 0.0 && mFTSize == 0.0) { + // If font-size-adjust is in effect, we need to get metrics in order to + // determine the aspect ratio, then compute the final adjusted size and + // re-initialize metrics. + // Setting mFTSize nonzero here ensures we will not recurse again; the + // actual value will be overridden by FindClosestSize below. + mFTSize = 1.0; + InitMetrics(); + // Now do the font-size-adjust calculation and set the final size. + gfxFloat aspect; + switch (FontSizeAdjust::Tag(mStyle.sizeAdjustBasis)) { + default: + MOZ_ASSERT_UNREACHABLE("unhandled sizeAdjustBasis?"); + aspect = 0.0; + break; + case FontSizeAdjust::Tag::ExHeight: + aspect = mMetrics.xHeight / mAdjustedSize; + break; + case FontSizeAdjust::Tag::CapHeight: + aspect = mMetrics.capHeight / mAdjustedSize; + break; + case FontSizeAdjust::Tag::ChWidth: + aspect = + mMetrics.zeroWidth > 0.0 ? mMetrics.zeroWidth / mAdjustedSize : 0.5; + break; + case FontSizeAdjust::Tag::IcWidth: + case FontSizeAdjust::Tag::IcHeight: { + bool vertical = FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) == + FontSizeAdjust::Tag::IcHeight; + gfxFloat advance = GetCharAdvance(kWaterIdeograph, vertical); + aspect = advance > 0.0 ? advance / mAdjustedSize : 1.0; + break; + } + } + if (aspect > 0.0) { + // If we created a shaper above (to measure glyphs), discard it so we + // get a new one for the adjusted scaling. + delete mHarfBuzzShaper.exchange(nullptr); + mAdjustedSize = mStyle.GetAdjustedSize(aspect); + // Ensure the FT_Face will be reconfigured for the new size next time we + // need to use it. + mFTFace->ForgetLockOwner(this); + } + } + + // Set mAdjustedSize if it hasn't already been set by a font-size-adjust + // computation. + mAdjustedSize = GetAdjustedSize(); + + // Cairo metrics are normalized to em-space, so that whatever fixed size + // might actually be chosen is factored out. They are then later scaled by + // the font matrix to the target adjusted size. Stash the chosen closest + // size here for later scaling of the metrics. + mFTSize = FindClosestSize(mFTFace->GetFace(), GetAdjustedSize()); + + // Explicitly lock the face so we can release it early before calling + // back into Cairo below. + FT_Face face = LockFTFace(); + + if (MOZ_UNLIKELY(!face)) { + // No face. This unfortunate situation might happen if the font + // file is (re)moved at the wrong time. + const gfxFloat emHeight = GetAdjustedSize(); + mMetrics.emHeight = emHeight; + mMetrics.maxAscent = mMetrics.emAscent = 0.8 * emHeight; + mMetrics.maxDescent = mMetrics.emDescent = 0.2 * emHeight; + mMetrics.maxHeight = emHeight; + mMetrics.internalLeading = 0.0; + mMetrics.externalLeading = 0.2 * emHeight; + const gfxFloat spaceWidth = 0.5 * emHeight; + mMetrics.spaceWidth = spaceWidth; + mMetrics.maxAdvance = spaceWidth; + mMetrics.aveCharWidth = spaceWidth; + mMetrics.zeroWidth = spaceWidth; + mMetrics.ideographicWidth = emHeight; + const gfxFloat xHeight = 0.5 * emHeight; + mMetrics.xHeight = xHeight; + mMetrics.capHeight = mMetrics.maxAscent; + const gfxFloat underlineSize = emHeight / 14.0; + mMetrics.underlineSize = underlineSize; + mMetrics.underlineOffset = -underlineSize; + mMetrics.strikeoutOffset = 0.25 * emHeight; + mMetrics.strikeoutSize = underlineSize; + + SanitizeMetrics(&mMetrics, false); + UnlockFTFace(); + return; + } + + const FT_Size_Metrics& ftMetrics = face->size->metrics; + + mMetrics.maxAscent = FLOAT_FROM_26_6(ftMetrics.ascender); + mMetrics.maxDescent = -FLOAT_FROM_26_6(ftMetrics.descender); + mMetrics.maxAdvance = FLOAT_FROM_26_6(ftMetrics.max_advance); + gfxFloat lineHeight = FLOAT_FROM_26_6(ftMetrics.height); + + gfxFloat emHeight; + // Scale for vertical design metric conversion: pixels per design unit. + // If this remains at 0.0, we can't use metrics from OS/2 etc. + gfxFloat yScale = 0.0; + if (FT_IS_SCALABLE(face)) { + // Prefer FT_Size_Metrics::x_scale to x_ppem as x_ppem does not + // have subpixel accuracy. + // + // FT_Size_Metrics::y_scale is in 16.16 fixed point format. Its + // (fractional) value is a factor that converts vertical metrics from + // design units to units of 1/64 pixels, so that the result may be + // interpreted as pixels in 26.6 fixed point format. + mFUnitsConvFactor = FLOAT_FROM_26_6(FLOAT_FROM_16_16(ftMetrics.x_scale)); + yScale = FLOAT_FROM_26_6(FLOAT_FROM_16_16(ftMetrics.y_scale)); + emHeight = face->units_per_EM * yScale; + } else { // Not scalable. + emHeight = ftMetrics.y_ppem; + // FT_Face doc says units_per_EM and a bunch of following fields + // are "only relevant to scalable outlines". If it's an sfnt, + // we can get units_per_EM from the 'head' table instead; otherwise, + // we don't have a unitsPerEm value so we can't compute/use yScale or + // mFUnitsConvFactor (x scale). + const TT_Header* head = + static_cast<TT_Header*>(FT_Get_Sfnt_Table(face, ft_sfnt_head)); + if (head) { + // Bug 1267909 - Even if the font is not explicitly scalable, + // if the face has color bitmaps, it should be treated as scalable + // and scaled to the desired size. Metrics based on y_ppem need + // to be rescaled for the adjusted size. This makes metrics agree + // with the scales we pass to Cairo for Fontconfig fonts. + if (face->face_flags & FT_FACE_FLAG_COLOR) { + emHeight = GetAdjustedSize(); + gfxFloat adjustScale = emHeight / ftMetrics.y_ppem; + mMetrics.maxAscent *= adjustScale; + mMetrics.maxDescent *= adjustScale; + mMetrics.maxAdvance *= adjustScale; + lineHeight *= adjustScale; + } + gfxFloat emUnit = head->Units_Per_EM; + mFUnitsConvFactor = ftMetrics.x_ppem / emUnit; + yScale = emHeight / emUnit; + } + } + + TT_OS2* os2 = static_cast<TT_OS2*>(FT_Get_Sfnt_Table(face, ft_sfnt_os2)); + + if (os2 && os2->sTypoAscender && yScale > 0.0) { + mMetrics.emAscent = os2->sTypoAscender * yScale; + mMetrics.emDescent = -os2->sTypoDescender * yScale; + FT_Short typoHeight = + os2->sTypoAscender - os2->sTypoDescender + os2->sTypoLineGap; + lineHeight = typoHeight * yScale; + + // If the OS/2 fsSelection USE_TYPO_METRICS bit is set, + // set maxAscent/Descent from the sTypo* fields instead of hhea. + const uint16_t kUseTypoMetricsMask = 1 << 7; + if ((os2->fsSelection & kUseTypoMetricsMask) || + // maxAscent/maxDescent get used for frame heights, and some fonts + // don't have the HHEA table ascent/descent set (bug 279032). + (mMetrics.maxAscent == 0.0 && mMetrics.maxDescent == 0.0)) { + // We use NS_round here to parallel the pixel-rounded values that + // freetype gives us for ftMetrics.ascender/descender. + mMetrics.maxAscent = NS_round(mMetrics.emAscent); + mMetrics.maxDescent = NS_round(mMetrics.emDescent); + } + } else { + mMetrics.emAscent = mMetrics.maxAscent; + mMetrics.emDescent = mMetrics.maxDescent; + } + + // gfxFont::Metrics::underlineOffset is the position of the top of the + // underline. + // + // FT_FaceRec documentation describes underline_position as "the + // center of the underlining stem". This was the original definition + // of the PostScript metric, but in the PostScript table of OpenType + // fonts the metric is "the top of the underline" + // (http://www.microsoft.com/typography/otspec/post.htm), and FreeType + // (up to version 2.3.7) doesn't make any adjustment. + // + // Therefore get the underline position directly from the table + // ourselves when this table exists. Use FreeType's metrics for + // other (including older PostScript) fonts. + if (face->underline_position && face->underline_thickness && yScale > 0.0) { + mMetrics.underlineSize = face->underline_thickness * yScale; + TT_Postscript* post = + static_cast<TT_Postscript*>(FT_Get_Sfnt_Table(face, ft_sfnt_post)); + if (post && post->underlinePosition) { + mMetrics.underlineOffset = post->underlinePosition * yScale; + } else { + mMetrics.underlineOffset = + face->underline_position * yScale + 0.5 * mMetrics.underlineSize; + } + } else { // No underline info. + // Imitate Pango. + mMetrics.underlineSize = emHeight / 14.0; + mMetrics.underlineOffset = -mMetrics.underlineSize; + } + + if (os2 && os2->yStrikeoutSize && os2->yStrikeoutPosition && yScale > 0.0) { + mMetrics.strikeoutSize = os2->yStrikeoutSize * yScale; + mMetrics.strikeoutOffset = os2->yStrikeoutPosition * yScale; + } else { // No strikeout info. + mMetrics.strikeoutSize = mMetrics.underlineSize; + // Use OpenType spec's suggested position for Roman font. + mMetrics.strikeoutOffset = + emHeight * 409.0 / 2048.0 + 0.5 * mMetrics.strikeoutSize; + } + SnapLineToPixels(mMetrics.strikeoutOffset, mMetrics.strikeoutSize); + + if (os2 && os2->sxHeight && yScale > 0.0) { + mMetrics.xHeight = os2->sxHeight * yScale; + } else { + // CSS 2.1, section 4.3.2 Lengths: "In the cases where it is + // impossible or impractical to determine the x-height, a value of + // 0.5em should be used." + mMetrics.xHeight = 0.5 * emHeight; + } + + // aveCharWidth is used for the width of text input elements so be + // liberal rather than conservative in the estimate. + if (os2 && os2->xAvgCharWidth) { + // Round to pixels as this is compared with maxAdvance to guess + // whether this is a fixed width font. + mMetrics.aveCharWidth = + ScaleRoundDesignUnits(os2->xAvgCharWidth, ftMetrics.x_scale); + } else { + mMetrics.aveCharWidth = 0.0; // updated below + } + + if (os2 && os2->sCapHeight && yScale > 0.0) { + mMetrics.capHeight = os2->sCapHeight * yScale; + } else { + mMetrics.capHeight = mMetrics.maxAscent; + } + + // Release the face lock to safely load glyphs with GetCharExtents if + // necessary without recursively locking. + UnlockFTFace(); + + gfxFloat width; + mSpaceGlyph = GetCharExtents(' ', &width); + if (mSpaceGlyph) { + mMetrics.spaceWidth = width; + } else { + mMetrics.spaceWidth = mMetrics.maxAdvance; // guess + } + + if (GetCharExtents('0', &width)) { + mMetrics.zeroWidth = width; + } else { + mMetrics.zeroWidth = -1.0; // indicates not found + } + + if (GetCharExtents(kWaterIdeograph, &width)) { + mMetrics.ideographicWidth = width; + } else { + mMetrics.ideographicWidth = -1.0; + } + + // If we didn't get a usable x-height or cap-height above, try measuring + // specific glyphs. This can be affected by hinting, leading to erratic + // behavior across font sizes and system configuration, so we prefer to + // use the metrics directly from the font if possible. + // Using glyph bounds for x-height or cap-height may not really be right, + // if fonts have fancy swashes etc. For x-height, CSS 2.1 suggests possibly + // using the height of an "o", which may be more consistent across fonts, + // but then curve-overshoot should also be accounted for. + gfxFloat xWidth; + gfxRect xBounds; + if (mMetrics.xHeight == 0.0) { + if (GetCharExtents('x', &xWidth, &xBounds) && xBounds.y < 0.0) { + mMetrics.xHeight = -xBounds.y; + mMetrics.aveCharWidth = std::max(mMetrics.aveCharWidth, xWidth); + } + } + + if (mMetrics.capHeight == 0.0) { + if (GetCharExtents('H', nullptr, &xBounds) && xBounds.y < 0.0) { + mMetrics.capHeight = -xBounds.y; + } + } + + mMetrics.aveCharWidth = std::max(mMetrics.aveCharWidth, mMetrics.zeroWidth); + if (mMetrics.aveCharWidth == 0.0) { + mMetrics.aveCharWidth = mMetrics.spaceWidth; + } + // Apparently hinting can mean that max_advance is not always accurate. + mMetrics.maxAdvance = std::max(mMetrics.maxAdvance, mMetrics.aveCharWidth); + + mMetrics.maxHeight = mMetrics.maxAscent + mMetrics.maxDescent; + + // Make the line height an integer number of pixels so that lines will be + // equally spaced (rather than just being snapped to pixels, some up and + // some down). Layout calculates line height from the emHeight + + // internalLeading + externalLeading, but first each of these is rounded + // to layout units. To ensure that the result is an integer number of + // pixels, round each of the components to pixels. + mMetrics.emHeight = floor(emHeight + 0.5); + + // maxHeight will normally be an integer, but round anyway in case + // FreeType is configured differently. + mMetrics.internalLeading = + floor(mMetrics.maxHeight - mMetrics.emHeight + 0.5); + + // Text input boxes currently don't work well with lineHeight + // significantly less than maxHeight (with Verdana, for example). + lineHeight = floor(std::max(lineHeight, mMetrics.maxHeight) + 0.5); + mMetrics.externalLeading = + lineHeight - mMetrics.internalLeading - mMetrics.emHeight; + + // Ensure emAscent + emDescent == emHeight + gfxFloat sum = mMetrics.emAscent + mMetrics.emDescent; + mMetrics.emAscent = + sum > 0.0 ? mMetrics.emAscent * mMetrics.emHeight / sum : 0.0; + mMetrics.emDescent = mMetrics.emHeight - mMetrics.emAscent; + + SanitizeMetrics(&mMetrics, false); + +#if 0 + // printf("font name: %s %f\n", NS_ConvertUTF16toUTF8(GetName()).get(), GetStyle()->size); + // printf ("pango font %s\n", pango_font_description_to_string (pango_font_describe (font))); + + fprintf (stderr, "Font: %s\n", GetName().get()); + fprintf (stderr, " emHeight: %f emAscent: %f emDescent: %f\n", mMetrics.emHeight, mMetrics.emAscent, mMetrics.emDescent); + fprintf (stderr, " maxAscent: %f maxDescent: %f\n", mMetrics.maxAscent, mMetrics.maxDescent); + fprintf (stderr, " internalLeading: %f externalLeading: %f\n", mMetrics.externalLeading, mMetrics.internalLeading); + fprintf (stderr, " spaceWidth: %f aveCharWidth: %f xHeight: %f\n", mMetrics.spaceWidth, mMetrics.aveCharWidth, mMetrics.xHeight); + fprintf (stderr, " ideographicWidth: %f\n", mMetrics.ideographicWidth); + fprintf (stderr, " uOff: %f uSize: %f stOff: %f stSize: %f\n", mMetrics.underlineOffset, mMetrics.underlineSize, mMetrics.strikeoutOffset, mMetrics.strikeoutSize); +#endif +} + +uint32_t gfxFT2FontBase::GetGlyph(uint32_t unicode, + uint32_t variation_selector) { + if (variation_selector) { + uint32_t id = + gfxFT2LockedFace(this).GetUVSGlyph(unicode, variation_selector); + if (id) { + return id; + } + unicode = gfxFontUtils::GetUVSFallback(unicode, variation_selector); + if (unicode) { + return GetGlyph(unicode); + } + return 0; + } + + return GetGlyph(unicode); +} + +bool gfxFT2FontBase::ShouldRoundXOffset(cairo_t* aCairo) const { + // Force rounding if outputting to a Cairo context or if requested by pref to + // disable subpixel positioning. Otherwise, allow subpixel positioning (no + // rounding) if rendering a scalable outline font with anti-aliasing. + // Monochrome rendering or some bitmap fonts can become too distorted with + // subpixel positioning, so force rounding in those cases. Also be careful not + // to use subpixel positioning if the user requests full hinting via + // Fontconfig, which we detect by checking that neither hinting was disabled + // nor light hinting was requested. Allow pref to force subpixel positioning + // on even if full hinting was requested. + return MOZ_UNLIKELY( + StaticPrefs:: + gfx_text_subpixel_position_force_disabled_AtStartup()) || + aCairo != nullptr || !mFTFace || !FT_IS_SCALABLE(mFTFace->GetFace()) || + (mFTLoadFlags & FT_LOAD_MONOCHROME) || + !((mFTLoadFlags & FT_LOAD_NO_HINTING) || + FT_LOAD_TARGET_MODE(mFTLoadFlags) == FT_RENDER_MODE_LIGHT || + MOZ_UNLIKELY( + StaticPrefs:: + gfx_text_subpixel_position_force_enabled_AtStartup())); +} + +FT_Vector gfxFT2FontBase::GetEmboldenStrength(FT_Face aFace) const { + FT_Vector strength = {0, 0}; + if (!mEmbolden) { + return strength; + } + + // If it's an outline glyph, we'll be using mozilla_glyphslot_embolden_less + // (see gfx/wr/webrender/src/platform/unix/font.rs), so we need to match its + // emboldening strength here. + if (aFace->glyph->format == FT_GLYPH_FORMAT_OUTLINE) { + strength.x = + FT_MulFix(aFace->units_per_EM, aFace->size->metrics.y_scale) / 48; + strength.y = strength.x; + return strength; + } + + // This is the embolden "strength" used by FT_GlyphSlot_Embolden. + strength.x = + FT_MulFix(aFace->units_per_EM, aFace->size->metrics.y_scale) / 24; + strength.y = strength.x; + if (aFace->glyph->format == FT_GLYPH_FORMAT_BITMAP) { + strength.x &= -64; + if (!strength.x) { + strength.x = 64; + } + strength.y &= -64; + } + return strength; +} + +bool gfxFT2FontBase::GetFTGlyphExtents(uint16_t aGID, int32_t* aAdvance, + IntRect* aBounds) { + gfxFT2LockedFace face(this); + MOZ_ASSERT(face.get()); + if (!face.get()) { + // Failed to get the FT_Face? Give up already. + NS_WARNING("failed to get FT_Face!"); + return false; + } + + FT_Int32 flags = mFTLoadFlags; + if (!aBounds) { + flags |= FT_LOAD_ADVANCE_ONLY; + } + + // Whether to disable subpixel positioning + bool roundX = ShouldRoundXOffset(nullptr); + + // Workaround for FT_Load_Glyph not setting linearHoriAdvance for SVG glyphs. + // See https://gitlab.freedesktop.org/freetype/freetype/-/issues/1156. + if (!roundX && + GetFontEntry()->HasFontTable(TRUETYPE_TAG('S', 'V', 'G', ' '))) { + flags &= ~FT_LOAD_COLOR; + } + + if (Factory::LoadFTGlyph(face.get(), aGID, flags) != FT_Err_Ok) { + // FT_Face was somehow broken/invalid? Don't try to access glyph slot. + // This probably shouldn't happen, but does: see bug 1440938. + NS_WARNING("failed to load glyph!"); + return false; + } + + // Whether to interpret hinting settings (i.e. not printing) + bool hintMetrics = ShouldHintMetrics(); + // No hinting disables X and Y hinting. Light disables only X hinting. + bool unhintedY = (mFTLoadFlags & FT_LOAD_NO_HINTING) != 0; + bool unhintedX = + unhintedY || FT_LOAD_TARGET_MODE(mFTLoadFlags) == FT_RENDER_MODE_LIGHT; + + // Normalize out the loaded FT glyph size and then scale to the actually + // desired size, in case these two sizes differ. + gfxFloat extentsScale = GetAdjustedSize() / mFTSize; + + FT_Vector bold = GetEmboldenStrength(face.get()); + + // Due to freetype bug 52683 we MUST use the linearHoriAdvance field when + // dealing with a variation font; also use it for scalable fonts when not + // applying hinting. Otherwise, prefer hinted width from glyph->advance.x. + if (aAdvance) { + FT_Fixed advance; + if (!roundX || FT_HAS_MULTIPLE_MASTERS(face.get())) { + advance = face.get()->glyph->linearHoriAdvance; + } else { + advance = face.get()->glyph->advance.x << 10; // convert 26.6 to 16.16 + } + if (advance) { + advance += bold.x << 10; // convert 26.6 to 16.16 + } + // Hinting was requested, but FT did not apply any hinting to the metrics. + // Round the advance here to approximate hinting as Cairo does. This must + // happen BEFORE we apply the glyph extents scale, just like FT hinting + // would. + if (hintMetrics && roundX && unhintedX) { + advance = (advance + 0x8000) & 0xffff0000u; + } + *aAdvance = NS_lround(advance * extentsScale); + } + + if (aBounds) { + const FT_Glyph_Metrics& metrics = face.get()->glyph->metrics; + FT_F26Dot6 x = metrics.horiBearingX; + FT_F26Dot6 y = -metrics.horiBearingY; + FT_F26Dot6 x2 = x + metrics.width; + FT_F26Dot6 y2 = y + metrics.height; + // Synthetic bold moves the glyph top and right boundaries. + y -= bold.y; + x2 += bold.x; + if (hintMetrics) { + if (roundX && unhintedX) { + x &= -64; + x2 = (x2 + 63) & -64; + } + if (unhintedY) { + y &= -64; + y2 = (y2 + 63) & -64; + } + } + *aBounds = IntRect(x, y, x2 - x, y2 - y); + + // Color fonts may not have reported the right bounds here, if there wasn't + // an outline for the nominal glyph ID. + // In principle we could use COLRFonts::GetColorGlyphBounds to retrieve the + // true bounds of the rendering, but that's more expensive; probably better + // to just use the font-wide ascent/descent as a heuristic that will + // generally ensure everything gets rendered. + if (aBounds->IsEmpty() && + GetFontEntry()->HasFontTable(TRUETYPE_TAG('C', 'O', 'L', 'R'))) { + const auto& fm = GetMetrics(nsFontMetrics::eHorizontal); + // aBounds is stored as FT_F26Dot6, so scale values from `fm` by 64. + aBounds->y = int32_t(-NS_round(fm.maxAscent * 64.0)); + aBounds->height = + int32_t(NS_round((fm.maxAscent + fm.maxDescent) * 64.0)); + aBounds->x = 0; + aBounds->width = + int32_t(aAdvance ? *aAdvance : NS_round(fm.maxAdvance * 64.0)); + } + } + + 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) { + { + // Try to read cached metrics without exclusive locking. + AutoReadLock lock(mLock); + if (mGlyphMetrics) { + if (auto metrics = mGlyphMetrics->Lookup(aGID)) { + return metrics.Data(); + } + } + } + + // We need to create/update the cache. + AutoWriteLock lock(mLock); + if (!mGlyphMetrics) { + mGlyphMetrics = + mozilla::MakeUnique<nsTHashMap<nsUint32HashKey, GlyphMetrics>>(128); + } + + return mGlyphMetrics->LookupOrInsertWith(aGID, [&] { + GlyphMetrics metrics; + IntRect bounds; + if (GetFTGlyphExtents(aGID, &metrics.mAdvance, &bounds)) { + metrics.SetBounds(bounds); + if (aBounds) { + *aBounds = bounds; + } + } + return metrics; + }); +} + +bool gfxFT2FontBase::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, + bool aTight) { + IntRect bounds; + const GlyphMetrics& metrics = GetCachedGlyphMetrics(aGID, &bounds); + if (!metrics.HasValidBounds()) { + return false; + } + // Check if there are cached bounds and use those if available. Otherwise, + // fall back to directly querying the glyph extents. + if (metrics.HasCachedBounds()) { + bounds = metrics.GetBounds(); + } else if (bounds.IsEmpty() && !GetFTGlyphExtents(aGID, nullptr, &bounds)) { + return false; + } + // The bounds are stored unscaled, so must be scaled to the adjusted size. + *aBounds = ScaleGlyphBounds(bounds, GetAdjustedSize() / mFTSize); + return true; +} + +// For variation fonts, figure out the variation coordinates to be applied +// for each axis, in freetype's order (which may not match the order of +// axes in mStyle.variationSettings, so we need to search by axis tag). +/*static*/ +void gfxFT2FontBase::SetupVarCoords( + FT_MM_Var* aMMVar, const nsTArray<gfxFontVariation>& aVariations, + FT_Face aFTFace) { + if (!aMMVar) { + return; + } + + nsTArray<FT_Fixed> coords; + for (unsigned i = 0; i < aMMVar->num_axis; ++i) { + coords.AppendElement(aMMVar->axis[i].def); + for (const auto& v : aVariations) { + if (aMMVar->axis[i].tag == v.mTag) { + FT_Fixed val = v.mValue * 0x10000; + val = std::min(val, aMMVar->axis[i].maximum); + val = std::max(val, aMMVar->axis[i].minimum); + coords[i] = val; + break; + } + } + } + + if (!coords.IsEmpty()) { +#if MOZ_TREE_FREETYPE + FT_Set_Var_Design_Coordinates(aFTFace, coords.Length(), coords.Elements()); +#else + typedef FT_Error (*SetCoordsFunc)(FT_Face, FT_UInt, FT_Fixed*); + static SetCoordsFunc setCoords; + static bool firstTime = true; + if (firstTime) { + firstTime = false; + setCoords = + (SetCoordsFunc)dlsym(RTLD_DEFAULT, "FT_Set_Var_Design_Coordinates"); + } + if (setCoords) { + (*setCoords)(aFTFace, coords.Length(), coords.Elements()); + } +#endif + } +} diff --git a/gfx/thebes/gfxFT2FontBase.h b/gfx/thebes/gfxFT2FontBase.h new file mode 100644 index 0000000000..08a79c2ada --- /dev/null +++ b/gfx/thebes/gfxFT2FontBase.h @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FT2FONTBASE_H +#define GFX_FT2FONTBASE_H + +#include "gfxContext.h" +#include "gfxFont.h" +#include "gfxFontEntry.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/UnscaledFontFreeType.h" +#include "nsTHashMap.h" +#include "nsHashKeys.h" + +class gfxFT2FontBase; + +class gfxFT2FontEntryBase : public gfxFontEntry { + public: + explicit gfxFT2FontEntryBase(const nsACString& aName) : gfxFontEntry(aName) {} + + uint32_t GetGlyph(uint32_t aCharCode, gfxFT2FontBase* aFont); + + static bool FaceHasTable(mozilla::gfx::SharedFTFace*, uint32_t aTableTag); + static nsresult CopyFaceTable(mozilla::gfx::SharedFTFace*, uint32_t aTableTag, + nsTArray<uint8_t>&); + + private: + enum { kNumCmapCacheSlots = 256 }; + + struct CmapCacheSlot { + CmapCacheSlot() : mCharCode(0), mGlyphIndex(0) {} + + uint32_t mCharCode; + uint32_t mGlyphIndex; + }; + + mozilla::UniquePtr<CmapCacheSlot[]> mCmapCache MOZ_GUARDED_BY(mLock); +}; + +class gfxFT2FontBase : public gfxFont { + public: + gfxFT2FontBase( + const RefPtr<mozilla::gfx::UnscaledFontFreeType>& aUnscaledFont, + RefPtr<mozilla::gfx::SharedFTFace>&& aFTFace, gfxFontEntry* aFontEntry, + const gfxFontStyle* aFontStyle, int aLoadFlags, bool aEmbolden); + + uint32_t GetGlyph(uint32_t aCharCode) { + auto* entry = static_cast<gfxFT2FontEntryBase*>(mFontEntry.get()); + return entry->GetGlyph(aCharCode, this); + } + + bool ProvidesGetGlyph() const override { return true; } + virtual uint32_t GetGlyph(uint32_t unicode, + uint32_t variation_selector) override; + + bool ProvidesGlyphWidths() const override { return true; } + int32_t GetGlyphWidth(uint16_t aGID) override { + return GetCachedGlyphMetrics(aGID).mAdvance; + } + + bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override; + + FontType GetType() const override { return FONT_TYPE_FT2; } + + bool ShouldRoundXOffset(cairo_t* aCairo) const override; + + static void SetupVarCoords(FT_MM_Var* aMMVar, + const nsTArray<gfxFontVariation>& aVariations, + FT_Face aFTFace); + + FT_Face LockFTFace() const; + void UnlockFTFace() const; + + private: + uint32_t GetCharExtents(uint32_t aChar, gfxFloat* aWidth, + gfxRect* aBounds = nullptr); + + // Get advance (and optionally bounds) of a single glyph from FreeType, + // and return true, or return false if we failed. + bool GetFTGlyphExtents(uint16_t aGID, int32_t* aWidth, + mozilla::gfx::IntRect* aBounds = nullptr); + + protected: + ~gfxFT2FontBase() override; + void InitMetrics(); + const Metrics& GetHorizontalMetrics() const override { return mMetrics; } + FT_Vector GetEmboldenStrength(FT_Face aFace) const; + + RefPtr<mozilla::gfx::SharedFTFace> mFTFace; + + Metrics mMetrics; + int mFTLoadFlags; + bool mEmbolden; + gfxFloat mFTSize; + + // For variation/multiple-master fonts, this will be an array of the values + // for each axis, as specified by mStyle.variationSettings (or the font's + // default for axes not present in variationSettings). Values here are in + // freetype's 16.16 fixed-point format, and clamped to the valid min/max + // range reported by the face. + nsTArray<FT_Fixed> mCoords; + + // Store cached glyph metrics for reasonably small glyph sizes. The bounds + // are stored unscaled to losslessly compress 26.6 fixed point to an int16_t. + // Larger glyphs are handled directly via GetFTGlyphExtents. + struct GlyphMetrics { + // Set the X coord to INT16_MIN to signal the bounds are invalid, or + // INT16_MAX to signal that the bounds would overflow an int16_t. + enum { INVALID = INT16_MIN, LARGE = INT16_MAX }; + + GlyphMetrics() : mAdvance(0), mX(INVALID), mY(0), mWidth(0), mHeight(0) {} + + bool HasValidBounds() const { return mX != INVALID; } + bool HasCachedBounds() const { return mX != LARGE; } + + // If the bounds can fit in an int16_t, set them. Otherwise, leave the + // bounds invalid to signal that GetFTGlyphExtents should be queried + // directly. + void SetBounds(const mozilla::gfx::IntRect& aBounds) { + if (aBounds.x > INT16_MIN && aBounds.x < INT16_MAX && + aBounds.y > INT16_MIN && aBounds.y < INT16_MAX && + aBounds.width <= UINT16_MAX && aBounds.height <= UINT16_MAX) { + mX = aBounds.x; + mY = aBounds.y; + mWidth = aBounds.width; + mHeight = aBounds.height; + } else { + mX = LARGE; + } + } + + mozilla::gfx::IntRect GetBounds() const { + return mozilla::gfx::IntRect(mX, mY, mWidth, mHeight); + } + + int32_t mAdvance; + int16_t mX; + int16_t mY; + uint16_t mWidth; + uint16_t mHeight; + }; + + const GlyphMetrics& GetCachedGlyphMetrics( + uint16_t aGID, mozilla::gfx::IntRect* aBounds = nullptr); + + mozilla::UniquePtr<nsTHashMap<nsUint32HashKey, GlyphMetrics>> mGlyphMetrics + MOZ_GUARDED_BY(mLock); +}; + +#endif /* GFX_FT2FONTBASE_H */ diff --git a/gfx/thebes/gfxFT2FontList.cpp b/gfx/thebes/gfxFT2FontList.cpp new file mode 100644 index 0000000000..6ff9788dc1 --- /dev/null +++ b/gfx/thebes/gfxFT2FontList.cpp @@ -0,0 +1,1959 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Base64.h" +#include "mozilla/MemoryReporting.h" + +#include "mozilla/dom/ContentChild.h" +#include "gfxAndroidPlatform.h" +#include "mozilla/Omnijar.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsReadableUtils.h" + +#include "nsXULAppAPI.h" +#include <dirent.h> +#include <android/log.h> +#define ALOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gecko", ##args) + +#include "ft2build.h" +#include FT_FREETYPE_H +#include FT_TRUETYPE_TAGS_H +#include FT_TRUETYPE_TABLES_H +#include FT_MULTIPLE_MASTERS_H +#include "cairo-ft.h" + +#include "gfxFT2FontList.h" +#include "gfxFT2Fonts.h" +#include "gfxFT2Utils.h" +#include "gfxUserFontSet.h" +#include "gfxFontUtils.h" +#include "SharedFontList-impl.h" +#include "StandardFonts-android.inc" +#include "harfbuzz/hb-ot.h" // for name ID constants + +#include "nsServiceManagerUtils.h" +#include "nsIGfxInfo.h" +#include "mozilla/Components.h" +#include "nsIObserverService.h" +#include "nsTArray.h" +#include "nsUnicharUtils.h" +#include "nsCRT.h" + +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsMemory.h" +#include "nsPresContext.h" +#include "gfxFontConstants.h" + +#include "mozilla/EndianUtils.h" +#include "mozilla/Preferences.h" +#include "mozilla/scache/StartupCache.h" +#include "mozilla/Telemetry.h" +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/stat.h> + +#ifdef MOZ_WIDGET_ANDROID +# include "AndroidBuild.h" +# include "mozilla/jni/Utils.h" +# include <dlfcn.h> +#endif + +using namespace mozilla; +using namespace mozilla::gfx; + +static LazyLogModule sFontInfoLog("fontInfoLog"); + +#undef LOG +#define LOG(args) MOZ_LOG(sFontInfoLog, mozilla::LogLevel::Debug, args) +#define LOG_ENABLED() MOZ_LOG_TEST(sFontInfoLog, mozilla::LogLevel::Debug) + +static __inline void BuildKeyNameFromFontName(nsACString& aName) { + ToLowerCase(aName); +} + +// Helper to access the FT_Face for a given FT2FontEntry, +// creating a temporary face if the entry does not have one yet. +// This allows us to read font names, tables, etc if necessary +// without permanently instantiating a freetype face and consuming +// memory long-term. +// This may fail (resulting in a null FT_Face), e.g. if it fails to +// allocate memory to uncompress a font from omnijar. +already_AddRefed<SharedFTFace> FT2FontEntry::GetFTFace(bool aCommit) { + if (mFTFace) { + // Create a new reference, and return it. + RefPtr<SharedFTFace> face(mFTFace); + return face.forget(); + } + + NS_ASSERTION(!mFilename.IsEmpty(), + "can't use GetFTFace for fonts without a filename"); + + // A relative path (no initial "/") means this is a resource in + // omnijar, not an installed font on the device. + // The NS_ASSERTIONs here should never fail, as the resource must have + // been read successfully during font-list initialization or we'd never + // have created the font entry. The only legitimate runtime failure + // here would be memory allocation, in which case mFace remains null. + RefPtr<SharedFTFace> face; + if (mFilename[0] != '/') { + RefPtr<nsZipArchive> reader = Omnijar::GetReader(Omnijar::Type::GRE); + nsZipItem* item = reader->GetItem(mFilename.get()); + NS_ASSERTION(item, "failed to find zip entry"); + + uint32_t bufSize = item->RealSize(); + uint8_t* fontDataBuf = static_cast<uint8_t*>(malloc(bufSize)); + if (fontDataBuf) { + nsZipCursor cursor(item, reader, fontDataBuf, bufSize); + cursor.Copy(&bufSize); + NS_ASSERTION(bufSize == item->RealSize(), "error reading bundled font"); + RefPtr<FTUserFontData> ufd = new FTUserFontData(fontDataBuf, bufSize); + face = ufd->CloneFace(mFTFontIndex); + if (!face) { + NS_WARNING("failed to create freetype face"); + return nullptr; + } + } + } else { + RefPtr<FTUserFontData> fd = new FTUserFontData(mFilename.get()); + face = fd->CloneFace(mFTFontIndex); + if (!face) { + NS_WARNING("failed to create freetype face"); + return nullptr; + } + if (FT_Err_Ok != FT_Select_Charmap(face->GetFace(), FT_ENCODING_UNICODE) && + FT_Err_Ok != + FT_Select_Charmap(face->GetFace(), FT_ENCODING_MS_SYMBOL)) { + NS_WARNING("failed to select Unicode or symbol charmap"); + } + } + + if (aCommit) { + if (mFTFace.compareExchange(nullptr, face.get())) { + // The reference we created is now owned by mFTFace. + Unused << face.forget(); + } else { + // We lost a race! Just discard our new face and use the existing one. + } + } + + // Create a new reference, and return it. + face = mFTFace; + return face.forget(); +} + +FTUserFontData* FT2FontEntry::GetUserFontData() { + if (SharedFTFace* face = mFTFace) { + return static_cast<FTUserFontData*>(face->GetData()); + } + return nullptr; +} + +/* + * FT2FontEntry + * gfxFontEntry subclass corresponding to a specific face that can be + * rendered by freetype. This is associated with a face index in a + * file (normally a .ttf/.otf file holding a single face, but in principle + * there could be .ttc files with multiple faces). + * The FT2FontEntry can create the necessary FT_Face on demand, and can + * then create a Cairo font_face and scaled_font for drawing. + */ + +FT2FontEntry::~FT2FontEntry() { + if (mMMVar) { + SharedFTFace* face = mFTFace; + FT_Done_MM_Var(face->GetFace()->glyph->library, mMMVar); + } + if (mFTFace) { + auto face = mFTFace.exchange(nullptr); + NS_IF_RELEASE(face); + } +} + +gfxFontEntry* FT2FontEntry::Clone() const { + MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!"); + FT2FontEntry* fe = new FT2FontEntry(Name()); + fe->mFilename = mFilename; + fe->mFTFontIndex = mFTFontIndex; + fe->mWeightRange = mWeightRange; + fe->mStretchRange = mStretchRange; + fe->mStyleRange = mStyleRange; + return fe; +} + +gfxFont* FT2FontEntry::CreateFontInstance(const gfxFontStyle* aStyle) { + RefPtr<SharedFTFace> face = GetFTFace(true); + if (!face) { + return nullptr; + } + + if (face->GetFace()->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS) { + // Resolve variations from entry (descriptor) and style (property) + AutoTArray<gfxFontVariation, 8> settings; + GetVariationsForStyle(settings, aStyle ? *aStyle : gfxFontStyle()); + + // If variations are present, we will not use our cached mFTFace + // but always create a new one as it will have custom variation + // coordinates applied. + if (!settings.IsEmpty()) { + // Create a separate FT_Face because we need to apply custom + // variation settings to it. + RefPtr<SharedFTFace> varFace; + if (!mFilename.IsEmpty() && mFilename[0] == '/') { + varFace = + Factory::NewSharedFTFace(nullptr, mFilename.get(), mFTFontIndex); + } else { + varFace = face->GetData()->CloneFace(mFTFontIndex); + } + if (varFace) { + gfxFT2FontBase::SetupVarCoords(GetMMVar(), settings, + varFace->GetFace()); + face = std::move(varFace); + } + } + } + + int loadFlags = gfxPlatform::GetPlatform()->FontHintingEnabled() + ? FT_LOAD_DEFAULT + : (FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING); + if (face->GetFace()->face_flags & FT_FACE_FLAG_TRICKY) { + loadFlags &= ~FT_LOAD_NO_AUTOHINT; + } + + RefPtr<UnscaledFontFreeType> unscaledFont(mUnscaledFont); + if (!unscaledFont) { + RefPtr<SharedFTFace> origFace(mFTFace); + unscaledFont = + !mFilename.IsEmpty() && mFilename[0] == '/' + ? new UnscaledFontFreeType(mFilename.BeginReading(), mFTFontIndex, + std::move(origFace)) + : new UnscaledFontFreeType(std::move(origFace)); + mUnscaledFont = unscaledFont; + } + + gfxFont* font = + new gfxFT2Font(unscaledFont, std::move(face), this, aStyle, loadFlags); + return font; +} + +/* static */ +FT2FontEntry* FT2FontEntry::CreateFontEntry( + const nsACString& aFontName, WeightRange aWeight, StretchRange aStretch, + SlantStyleRange aStyle, const uint8_t* aFontData, uint32_t aLength) { + // Ownership of aFontData is passed in here; the fontEntry must + // retain it as long as the FT_Face needs it, and ensure it is + // eventually deleted. + RefPtr<FTUserFontData> ufd = new FTUserFontData(aFontData, aLength); + RefPtr<SharedFTFace> face = ufd->CloneFace(); + if (!face) { + return nullptr; + } + // Create our FT2FontEntry, which inherits the name of the userfont entry + // as it's not guaranteed that the face has valid names (bug 737315) + FT2FontEntry* fe = + FT2FontEntry::CreateFontEntry(aFontName, nullptr, 0, nullptr); + if (fe) { + fe->mFTFace = face.forget().take(); // mFTFace takes ownership. + fe->mStyleRange = aStyle; + fe->mWeightRange = aWeight; + fe->mStretchRange = aStretch; + fe->mIsDataUserFont = true; + } + return fe; +} + +/* static */ +FT2FontEntry* FT2FontEntry::CreateFontEntry(const FontListEntry& aFLE) { + FT2FontEntry* fe = new FT2FontEntry(aFLE.faceName()); + fe->mFilename = aFLE.filepath(); + fe->mFTFontIndex = aFLE.index(); + fe->mWeightRange = WeightRange::FromScalar(aFLE.weightRange()); + fe->mStretchRange = StretchRange::FromScalar(aFLE.stretchRange()); + fe->mStyleRange = SlantStyleRange::FromScalar(aFLE.styleRange()); + return fe; +} + +// Extract font entry properties from an hb_face_t +static void SetPropertiesFromFace(gfxFontEntry* aFontEntry, + const hb_face_t* aFace) { + // OS2 width class to CSS 'stretch' mapping from + // https://docs.microsoft.com/en-gb/typography/opentype/spec/os2#uswidthclass + const float kOS2WidthToStretch[] = { + 100, // (invalid, treat as normal) + 50, // Ultra-condensed + 62.5, // Extra-condensed + 75, // Condensed + 87.5, // Semi-condensed + 100, // Normal + 112.5, // Semi-expanded + 125, // Expanded + 150, // Extra-expanded + 200 // Ultra-expanded + }; + + // Get the macStyle field from the 'head' table + gfxFontUtils::AutoHBBlob headBlob( + hb_face_reference_table(aFace, HB_TAG('h', 'e', 'a', 'd'))); + unsigned int len; + const char* data = hb_blob_get_data(headBlob, &len); + uint16_t style = 0; + if (len >= sizeof(HeadTable)) { + const HeadTable* head = reinterpret_cast<const HeadTable*>(data); + style = head->macStyle; + } + + // Get the OS/2 table for weight & width fields + gfxFontUtils::AutoHBBlob os2blob( + hb_face_reference_table(aFace, HB_TAG('O', 'S', '/', '2'))); + data = hb_blob_get_data(os2blob, &len); + uint16_t os2weight = 400; + float stretch = 100.0; + if (len >= offsetof(OS2Table, fsType)) { + const OS2Table* os2 = reinterpret_cast<const OS2Table*>(data); + os2weight = os2->usWeightClass; + uint16_t os2width = os2->usWidthClass; + if (os2width < ArrayLength(kOS2WidthToStretch)) { + stretch = kOS2WidthToStretch[os2width]; + } + } + + aFontEntry->mStyleRange = SlantStyleRange( + (style & 2) ? FontSlantStyle::ITALIC : FontSlantStyle::NORMAL); + aFontEntry->mWeightRange = WeightRange(FontWeight::FromInt(int(os2weight))); + aFontEntry->mStretchRange = StretchRange(FontStretch::FromFloat(stretch)); + + // For variable fonts, update the style/weight/stretch attributes if the + // corresponding variation axes are present. + aFontEntry->SetupVariationRanges(); +} + +// Used to create the font entry for installed faces on the device, +// when iterating over the fonts directories. +// We use the hb_face_t to retrieve the details needed for the font entry, +// but do *not* save a reference to it, nor create a cairo face. +/* static */ +FT2FontEntry* FT2FontEntry::CreateFontEntry(const nsACString& aName, + const char* aFilename, + uint8_t aIndex, + const hb_face_t* aFace) { + FT2FontEntry* fe = new FT2FontEntry(aName); + fe->mFilename = aFilename; + fe->mFTFontIndex = aIndex; + + if (aFace) { + SetPropertiesFromFace(fe, aFace); + } else { + // If nullptr is passed for aFace, the caller is intending to override + // these attributes anyway. We just set defaults here to be safe. + fe->mStyleRange = SlantStyleRange(FontSlantStyle::NORMAL); + fe->mWeightRange = WeightRange(FontWeight::NORMAL); + fe->mStretchRange = StretchRange(FontStretch::NORMAL); + } + + return fe; +} + +FT2FontEntry* gfxFT2Font::GetFontEntry() { + return static_cast<FT2FontEntry*>(mFontEntry.get()); +} + +// Copied/modified from similar code in gfxMacPlatformFontList.mm: +// Complex scripts will not render correctly unless Graphite or OT +// layout tables are present. +// For OpenType, we also check that the GSUB table supports the relevant +// script tag, to avoid using things like Arial Unicode MS for Lao (it has +// the characters, but lacks OpenType support). + +// TODO: consider whether we should move this to gfxFontEntry and do similar +// cmap-masking on all platforms to avoid using fonts that won't shape +// properly. + +nsresult FT2FontEntry::ReadCMAP(FontInfoData* aFontInfoData) { + if (mCharacterMap || mShmemCharacterMap) { + return NS_OK; + } + + RefPtr<gfxCharacterMap> charmap = new gfxCharacterMap(); + + nsresult rv = NS_ERROR_NOT_AVAILABLE; + uint32_t uvsOffset = 0; + gfxFontUtils::AutoHBBlob cmapBlob(GetFontTable(TTAG_cmap)); + if (cmapBlob) { + unsigned int length; + const char* data = hb_blob_get_data(cmapBlob, &length); + rv = gfxFontUtils::ReadCMAP((const uint8_t*)data, length, *charmap, + uvsOffset); + } + mUVSOffset.exchange(uvsOffset); + + if (NS_SUCCEEDED(rv) && !mIsDataUserFont && !HasGraphiteTables()) { + // For downloadable fonts, trust the author and don't + // try to munge the cmap based on script shaping support. + + // We also assume a Graphite font knows what it's doing, + // and provides whatever shaping is needed for the + // characters it supports, so only check/clear the + // complex-script ranges for non-Graphite fonts + + // for layout support, check for the presence of opentype layout tables + bool hasGSUB = HasFontTable(TRUETYPE_TAG('G', 'S', 'U', 'B')); + + for (const ScriptRange* sr = gfxPlatformFontList::sComplexScriptRanges; + sr->rangeStart; sr++) { + // check to see if the cmap includes complex script codepoints + if (charmap->TestRange(sr->rangeStart, sr->rangeEnd)) { + // We check for GSUB here, as GPOS alone would not be ok. + if (hasGSUB && SupportsScriptInGSUB(sr->tags, sr->numTags)) { + continue; + } + charmap->ClearRange(sr->rangeStart, sr->rangeEnd); + } + } + } + +#ifdef MOZ_WIDGET_ANDROID + // Hack for the SamsungDevanagari font, bug 1012365: + // pretend the font supports U+0972. + if (!charmap->test(0x0972) && charmap->test(0x0905) && + charmap->test(0x0945)) { + charmap->set(0x0972); + } +#endif + + bool setCharMap = true; + if (NS_SUCCEEDED(rv)) { + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + fontlist::FontList* sharedFontList = pfl->SharedFontList(); + if (!IsUserFont() && mShmemFace) { + mShmemFace->SetCharacterMap(sharedFontList, charmap, mShmemFamily); + if (TrySetShmemCharacterMap()) { + setCharMap = false; + } + } else { + charmap = pfl->FindCharMap(charmap); + } + mHasCmapTable = true; + } else { + // if error occurred, initialize to null cmap + charmap = new gfxCharacterMap(); + mHasCmapTable = false; + } + if (setCharMap) { + if (mCharacterMap.compareExchange(nullptr, charmap.get())) { + // We forget rather than addref because we don't use the charmap below. + Unused << charmap.forget(); + } + } + + return rv; +} + +hb_face_t* FT2FontEntry::CreateHBFace() const { + hb_face_t* result = nullptr; + + if (mFilename[0] == '/') { + // An absolute path means a normal file in the filesystem, so we can use + // hb_blob_create_from_file to read it. + gfxFontUtils::AutoHBBlob fileBlob( + hb_blob_create_from_file(mFilename.get())); + if (hb_blob_get_length(fileBlob) > 0) { + result = hb_face_create(fileBlob, mFTFontIndex); + } + } else { + // A relative path means an omnijar resource, which we may need to + // decompress to a temporary buffer. + RefPtr<nsZipArchive> reader = Omnijar::GetReader(Omnijar::Type::GRE); + nsZipItem* item = reader->GetItem(mFilename.get()); + MOZ_ASSERT(item, "failed to find zip entry"); + if (item) { + // TODO(jfkthame): + // Check whether the item is compressed; if not, we could just get a + // pointer without needing to allocate a buffer and copy the data. + // (Currently this configuration isn't used for Gecko on Android.) + uint32_t length = item->RealSize(); + uint8_t* buffer = static_cast<uint8_t*>(malloc(length)); + if (buffer) { + nsZipCursor cursor(item, reader, buffer, length); + cursor.Copy(&length); + MOZ_ASSERT(length == item->RealSize(), "error reading font"); + if (length == item->RealSize()) { + gfxFontUtils::AutoHBBlob blob( + hb_blob_create((const char*)buffer, length, + HB_MEMORY_MODE_READONLY, buffer, free)); + result = hb_face_create(blob, mFTFontIndex); + } + } + } + } + + return result; +} + +bool FT2FontEntry::HasFontTable(uint32_t aTableTag) { + // If we already have a FreeType face, we can just use that. + if (mFTFace) { + RefPtr<SharedFTFace> face = GetFTFace(); + return gfxFT2FontEntryBase::FaceHasTable(face, aTableTag); + } + + { + // If we have a cached set of tables, query that. + AutoReadLock lock(mLock); + if (mAvailableTables.Count() > 0) { + return mAvailableTables.Contains(aTableTag); + } + } + + // If we haven't created a FreeType face already, try to avoid that by + // reading the available table tags via harfbuzz and caching in a hashset. + AutoWriteLock lock(mLock); + if (!mFTFace && !mFilename.IsEmpty()) { + hb_face_t* face = CreateHBFace(); + if (face) { + // Read table tags in batches; 32 should be enough for most fonts in a + // single operation. + const unsigned TAG_BUF_LENGTH = 32; + hb_tag_t tags[TAG_BUF_LENGTH]; + unsigned int startOffset = 0; + unsigned int totalTables = 0; + do { + unsigned int count = TAG_BUF_LENGTH; + // Updates count to the number of table tags actually retrieved + totalTables = hb_face_get_table_tags(face, startOffset, &count, tags); + startOffset += count; + while (count-- > 0) { + mAvailableTables.Insert(tags[count]); + } + } while (startOffset < totalTables); + hb_face_destroy(face); + } else { + // Failed to create the HarfBuzz face! The font is probably broken. + // Put a dummy entry in mAvailableTables so that we don't bother + // re-trying here. + mAvailableTables.Insert(uint32_t(-1)); + } + return mAvailableTables.Contains(aTableTag); + } + + // Last resort: we'll have to create a (temporary) FreeType face to query + // for table presence. + RefPtr<SharedFTFace> face = GetFTFace(); + return gfxFT2FontEntryBase::FaceHasTable(face, aTableTag); +} + +nsresult FT2FontEntry::CopyFontTable(uint32_t aTableTag, + nsTArray<uint8_t>& aBuffer) { + RefPtr<SharedFTFace> face = GetFTFace(); + return gfxFT2FontEntryBase::CopyFaceTable(face, aTableTag, aBuffer); +} + +hb_blob_t* FT2FontEntry::GetFontTable(uint32_t aTableTag) { + if (FTUserFontData* userFontData = GetUserFontData()) { + // If there's a cairo font face, we may be able to return a blob + // that just wraps a range of the attached user font data + if (userFontData->FontData()) { + return gfxFontUtils::GetTableFromFontData(userFontData->FontData(), + aTableTag); + } + } + + // If the FT_Face hasn't been instantiated, try to read table directly + // via harfbuzz API to avoid expensive FT_Face creation. + if (!mFTFace && !mFilename.IsEmpty()) { + hb_face_t* face = CreateHBFace(); + if (face) { + hb_blob_t* result = hb_face_reference_table(face, aTableTag); + hb_face_destroy(face); + return result; + } + } + + // Otherwise, use the default method (which in turn will call our + // implementation of CopyFontTable). + return gfxFontEntry::GetFontTable(aTableTag); +} + +bool FT2FontEntry::HasVariations() { + switch (mHasVariations) { + case HasVariationsState::No: + return false; + case HasVariationsState::Yes: + return true; + case HasVariationsState::Uninitialized: + break; + } + + SharedFTFace* face = mFTFace; + bool hasVariations = + face ? face->GetFace()->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS + : gfxPlatform::HasVariationFontSupport() && + HasFontTable(TRUETYPE_TAG('f', 'v', 'a', 'r')); + mHasVariations = + hasVariations ? HasVariationsState::Yes : HasVariationsState::No; + return hasVariations; +} + +void FT2FontEntry::GetVariationAxes(nsTArray<gfxFontVariationAxis>& aAxes) { + if (!HasVariations()) { + return; + } + if (FT_MM_Var* mmVar = GetMMVar()) { + gfxFT2Utils::GetVariationAxes(mmVar, aAxes); + } +} + +void FT2FontEntry::GetVariationInstances( + nsTArray<gfxFontVariationInstance>& aInstances) { + if (!HasVariations()) { + return; + } + if (FT_MM_Var* mmVar = GetMMVar()) { + gfxFT2Utils::GetVariationInstances(this, mmVar, aInstances); + } +} + +FT_MM_Var* FT2FontEntry::GetMMVar() { + if (mMMVarInitialized) { + return mMMVar; + } + AutoWriteLock lock(mLock); + RefPtr<SharedFTFace> face = GetFTFace(true); + if (!face) { + return nullptr; + } + if (FT_Err_Ok != FT_Get_MM_Var(face->GetFace(), &mMMVar)) { + mMMVar = nullptr; + } + mMMVarInitialized = true; + return mMMVar; +} + +void FT2FontEntry::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + gfxFontEntry::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); + aSizes->mFontListSize += + mFilename.SizeOfExcludingThisIfUnshared(aMallocSizeOf); +} + +void FT2FontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +/* + * FT2FontFamily + * A standard gfxFontFamily; just adds a method used to support sending + * the font list from chrome to content via IPC. + */ + +void FT2FontFamily::AddFacesToFontList(nsTArray<FontListEntry>* aFontList) { + AutoReadLock lock(mLock); + for (int i = 0, n = mAvailableFonts.Length(); i < n; ++i) { + const FT2FontEntry* fe = + static_cast<const FT2FontEntry*>(mAvailableFonts[i].get()); + if (!fe) { + continue; + } + + aFontList->AppendElement(FontListEntry( + Name(), fe->Name(), fe->mFilename, fe->Weight().AsScalar(), + fe->Stretch().AsScalar(), fe->SlantStyle().AsScalar(), fe->mFTFontIndex, + Visibility())); + } +} + +/* + * Startup cache support for the font list: + * We store the list of families and faces, with their style attributes and the + * corresponding font files, in the startup cache. + * This allows us to recreate the gfxFT2FontList collection of families and + * faces without instantiating Freetype faces for each font file (in order to + * find their attributes), leading to significantly quicker startup. + */ + +#define CACHE_KEY "font.cached-list" + +void gfxFT2FontList::CollectInitData(const FontListEntry& aFLE, + const nsCString& aPSName, + const nsCString& aFullName, + StandardFile aStdFile) { + nsAutoCString key(aFLE.familyName()); + BuildKeyNameFromFontName(key); + mFaceInitData + .LookupOrInsertWith( + key, + [&] { + mFamilyInitData.AppendElement(fontlist::Family::InitData{ + key, aFLE.familyName(), fontlist::Family::kNoIndex, + aFLE.visibility()}); + return MakeUnique<nsTArray<fontlist::Face::InitData>>(); + }) + ->AppendElement(fontlist::Face::InitData{ + aFLE.filepath(), aFLE.index(), false, + WeightRange::FromScalar(aFLE.weightRange()), + StretchRange::FromScalar(aFLE.stretchRange()), + SlantStyleRange::FromScalar(aFLE.styleRange())}); + nsAutoCString psname(aPSName), fullname(aFullName); + if (!psname.IsEmpty()) { + ToLowerCase(psname); + mLocalNameTable.InsertOrUpdate( + psname, fontlist::LocalFaceRec::InitData(key, aFLE.filepath())); + } + if (!fullname.IsEmpty()) { + ToLowerCase(fullname); + if (fullname != psname) { + mLocalNameTable.InsertOrUpdate( + fullname, fontlist::LocalFaceRec::InitData(key, aFLE.filepath())); + } + } +} + +class FontNameCache { + public: + // Delimiters used in the cached font-list records we store in startupCache + static const char kFileSep = 0x1c; + static const char kGroupSep = 0x1d; + static const char kRecordSep = 0x1e; + static const char kFieldSep = 0x1f; + + // Separator for font property ranges; we only look for this within a + // field that holds a serialized FontPropertyValue or Range, so there's no + // risk of conflicting with printable characters in font names. + // Note that this must be a character that will terminate strtof() parsing + // of a number. + static const char kRangeSep = ':'; + + // Creates the object but does NOT load the cached data from the startup + // cache; call Init() after creation to do that. + FontNameCache() : mMap(&mOps, sizeof(FNCMapEntry), 0), mWriteNeeded(false) { + // HACK ALERT: it's weird to assign |mOps| after we passed a pointer to + // it to |mMap|'s constructor. A more normal approach here would be to + // have a static |sOps| member. Unfortunately, this mysteriously but + // consistently makes Fennec start-up slower, so we take this + // unorthodox approach instead. It's safe because PLDHashTable's + // constructor doesn't dereference the pointer; it just makes a copy of + // it. + mOps = (PLDHashTableOps){StringHash, HashMatchEntry, MoveEntry, + PLDHashTable::ClearEntryStub, nullptr}; + + MOZ_ASSERT(XRE_IsParentProcess(), + "FontNameCache should only be used in chrome process"); + mCache = mozilla::scache::StartupCache::GetSingleton(); + } + + ~FontNameCache() { WriteCache(); } + + size_t EntryCount() const { return mMap.EntryCount(); } + + void DropStaleEntries() { + for (auto iter = mMap.ConstIter(); !iter.Done(); iter.Next()) { + auto entry = static_cast<FNCMapEntry*>(iter.Get()); + if (!entry->mFileExists) { + iter.Remove(); + } + } + } + + void WriteCache() { + if (!mWriteNeeded || !mCache) { + return; + } + + LOG(("Writing FontNameCache:")); + nsAutoCString buf; + for (auto iter = mMap.ConstIter(); !iter.Done(); iter.Next()) { + auto entry = static_cast<FNCMapEntry*>(iter.Get()); + MOZ_ASSERT(entry->mFileExists); + buf.Append(entry->mFilename); + buf.Append(kGroupSep); + buf.Append(entry->mFaces); + buf.Append(kGroupSep); + buf.AppendInt(entry->mTimestamp); + buf.Append(kGroupSep); + buf.AppendInt(entry->mFilesize); + buf.Append(kFileSep); + } + + LOG(("putting FontNameCache to " CACHE_KEY ", length %zu", + buf.Length() + 1)); + mCache->PutBuffer(CACHE_KEY, UniqueFreePtr<char[]>(ToNewCString(buf)), + buf.Length() + 1); + mWriteNeeded = false; + } + + // This may be called more than once (if we re-load the font list). + void Init() { + if (!mCache) { + return; + } + + uint32_t size; + const char* cur; + if (NS_FAILED(mCache->GetBuffer(CACHE_KEY, &cur, &size))) { + LOG(("no cache of " CACHE_KEY)); + return; + } + + LOG(("got: %u bytes from the cache " CACHE_KEY, size)); + + mMap.Clear(); + mWriteNeeded = false; + + while (const char* fileEnd = strchr(cur, kFileSep)) { + // The cached record for one file is at [cur, fileEnd]. + + // Find end of field that starts at aStart, terminated by kGroupSep or + // end of record. + auto endOfField = [=](const char* aStart) -> const char* { + MOZ_ASSERT(aStart <= fileEnd); + const char* end = static_cast<const char*>( + memchr(aStart, kGroupSep, fileEnd - aStart)); + if (end) { + return end; + } + return fileEnd; + }; + + // Advance aStart and aEnd to indicate the range of the next field and + // return true, or just return false if already at end of record. + auto nextField = [=](const char*& aStart, const char*& aEnd) -> bool { + if (aEnd < fileEnd) { + aStart = aEnd + 1; + aEnd = endOfField(aStart); + return true; + } + return false; + }; + + const char* end = endOfField(cur); + nsCString filename(cur, end - cur); + if (!nextField(cur, end)) { + break; + } + nsCString faceList(cur, end - cur); + if (!nextField(cur, end)) { + break; + } + uint32_t timestamp = strtoul(cur, nullptr, 10); + if (!nextField(cur, end)) { + break; + } + uint32_t filesize = strtoul(cur, nullptr, 10); + + auto mapEntry = + static_cast<FNCMapEntry*>(mMap.Add(filename.get(), fallible)); + if (mapEntry) { + mapEntry->mFilename.Assign(filename); + mapEntry->mTimestamp = timestamp; + mapEntry->mFilesize = filesize; + mapEntry->mFaces.Assign(faceList); + // entries from the startupcache are marked "non-existing" + // until we have confirmed that the file still exists + mapEntry->mFileExists = false; + } + + cur = fileEnd + 1; + } + } + + void GetInfoForFile(const nsCString& aFileName, nsCString& aFaceList, + uint32_t* aTimestamp, uint32_t* aFilesize) { + auto entry = static_cast<FNCMapEntry*>(mMap.Search(aFileName.get())); + if (entry) { + *aTimestamp = entry->mTimestamp; + *aFilesize = entry->mFilesize; + aFaceList.Assign(entry->mFaces); + // this entry does correspond to an existing file + // (although it might not be up-to-date, in which case + // it will get overwritten via CacheFileInfo) + entry->mFileExists = true; + } + } + + void CacheFileInfo(const nsCString& aFileName, const nsCString& aFaceList, + uint32_t aTimestamp, uint32_t aFilesize) { + auto entry = static_cast<FNCMapEntry*>(mMap.Add(aFileName.get(), fallible)); + if (entry) { + entry->mFilename.Assign(aFileName); + entry->mTimestamp = aTimestamp; + entry->mFilesize = aFilesize; + entry->mFaces.Assign(aFaceList); + entry->mFileExists = true; + } + mWriteNeeded = true; + } + + private: + mozilla::scache::StartupCache* mCache; + PLDHashTable mMap; + bool mWriteNeeded; + + PLDHashTableOps mOps; + + struct FNCMapEntry : public PLDHashEntryHdr { + public: + nsCString mFilename; + uint32_t mTimestamp; + uint32_t mFilesize; + nsCString mFaces; + bool mFileExists; + }; + + static PLDHashNumber StringHash(const void* key) { + return HashString(reinterpret_cast<const char*>(key)); + } + + static bool HashMatchEntry(const PLDHashEntryHdr* aHdr, const void* key) { + const FNCMapEntry* entry = static_cast<const FNCMapEntry*>(aHdr); + return entry->mFilename.Equals(reinterpret_cast<const char*>(key)); + } + + static void MoveEntry(PLDHashTable* table, const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo) { + FNCMapEntry* to = static_cast<FNCMapEntry*>(aTo); + const FNCMapEntry* from = static_cast<const FNCMapEntry*>(aFrom); + to->mFilename.Assign(from->mFilename); + to->mTimestamp = from->mTimestamp; + to->mFilesize = from->mFilesize; + to->mFaces.Assign(from->mFaces); + to->mFileExists = from->mFileExists; + } +}; + +/*************************************************************** + * + * gfxFT2FontList + * + */ + +// For Mobile, we use gfxFT2Fonts, and we build the font list by directly +// scanning the system's Fonts directory for OpenType and TrueType files. + +#define JAR_LAST_MODIFED_TIME "jar-last-modified-time" + +class WillShutdownObserver : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + explicit WillShutdownObserver(gfxFT2FontList* aFontList) + : mFontList(aFontList) {} + + void Remove() { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID); + } + mFontList = nullptr; + } + + protected: + virtual ~WillShutdownObserver() = default; + + gfxFT2FontList* mFontList; +}; + +NS_IMPL_ISUPPORTS(WillShutdownObserver, nsIObserver) + +NS_IMETHODIMP +WillShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!nsCRT::strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID)) { + mFontList->WillShutdown(); + } else { + MOZ_ASSERT_UNREACHABLE("unexpected notification topic"); + } + return NS_OK; +} + +gfxFT2FontList::gfxFT2FontList() : mJarModifiedTime(0) { + CheckFamilyList(kBaseFonts_Android); + CheckFamilyList(kBaseFonts_Android5_8); + CheckFamilyList(kBaseFonts_Android9_Higher); + CheckFamilyList(kBaseFonts_Android9_11); + CheckFamilyList(kBaseFonts_Android12_Higher); + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + mObserver = new WillShutdownObserver(this); + obs->AddObserver(mObserver, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false); + } +} + +gfxFT2FontList::~gfxFT2FontList() { + AutoLock lock(mLock); + if (mObserver) { + mObserver->Remove(); + } +} + +bool gfxFT2FontList::AppendFacesFromCachedFaceList(CollectFunc aCollectFace, + const nsCString& aFileName, + const nsCString& aFaceList, + StandardFile aStdFile) { + const char* start = aFaceList.get(); + int count = 0; + + while (const char* recEnd = strchr(start, FontNameCache::kRecordSep)) { + auto endOfField = [=](const char* aStart) -> const char* { + MOZ_ASSERT(aStart <= recEnd); + const char* end = static_cast<const char*>( + memchr(aStart, FontNameCache::kFieldSep, recEnd - aStart)); + if (end) { + return end; + } + return recEnd; + }; + + auto nextField = [=](const char*& aStart, const char*& aEnd) -> bool { + if (aEnd < recEnd) { + aStart = aEnd + 1; + aEnd = endOfField(aStart); + return true; + } + return false; + }; + + const char* end = endOfField(start); + nsAutoCString familyName(start, end - start); + nsAutoCString key(familyName); + ToLowerCase(key); + + if (!nextField(start, end)) { + break; + } + nsAutoCString faceName(start, end - start); + + if (!nextField(start, end)) { + break; + } + uint32_t index = strtoul(start, nullptr, 10); + + if (!nextField(start, end)) { + break; + } + + auto readIntPair = [&](int32_t& aStart, int32_t& aEnd) { + char* limit = nullptr; + aStart = strtol(start, &limit, 10); + if (*limit == FontNameCache::kRangeSep && limit + 1 < end) { + aEnd = strtof(limit + 1, nullptr); + } + }; + + int32_t minStyle, maxStyle; + readIntPair(minStyle, maxStyle); + + if (!nextField(start, end)) { + break; + } + + int32_t minWeight, maxWeight; + readIntPair(minWeight, maxWeight); + + if (!nextField(start, end)) { + break; + } + + int32_t minStretch, maxStretch; + readIntPair(minStretch, maxStretch); + + if (!nextField(start, end)) { + break; + } + nsAutoCString psname(start, end - start); + + if (!nextField(start, end)) { + break; + } + nsAutoCString fullname(start, end - start); + + if (!nextField(start, end)) { + break; + } + FontVisibility visibility = FontVisibility(strtoul(start, nullptr, 10)); + + FontListEntry fle(familyName, faceName, aFileName, + WeightRange(FontWeight::FromRaw(minWeight), + FontWeight::FromRaw(maxWeight)) + .AsScalar(), + StretchRange(FontStretch::FromRaw(minStretch), + FontStretch::FromRaw(maxStretch)) + .AsScalar(), + SlantStyleRange(FontSlantStyle::FromRaw(minStyle), + FontSlantStyle::FromRaw(maxStyle)) + .AsScalar(), + index, visibility); + + aCollectFace(fle, psname, fullname, aStdFile); + count++; + + start = recEnd + 1; + } + + return count > 0; +} + +void FT2FontEntry::AppendToFaceList(nsCString& aFaceList, + const nsACString& aFamilyName, + const nsACString& aPSName, + const nsACString& aFullName, + FontVisibility aVisibility) { + aFaceList.Append(aFamilyName); + aFaceList.Append(FontNameCache::kFieldSep); + aFaceList.Append(Name()); + aFaceList.Append(FontNameCache::kFieldSep); + aFaceList.AppendInt(mFTFontIndex); + aFaceList.Append(FontNameCache::kFieldSep); + aFaceList.AppendInt(SlantStyle().Min().Raw()); + aFaceList.Append(FontNameCache::kRangeSep); + aFaceList.AppendInt(SlantStyle().Max().Raw()); + aFaceList.Append(FontNameCache::kFieldSep); + aFaceList.AppendInt(Weight().Min().Raw()); + aFaceList.Append(FontNameCache::kRangeSep); + aFaceList.AppendInt(Weight().Max().Raw()); + aFaceList.Append(FontNameCache::kFieldSep); + aFaceList.AppendInt(Stretch().Min().Raw()); + aFaceList.Append(FontNameCache::kRangeSep); + aFaceList.AppendInt(Stretch().Max().Raw()); + aFaceList.Append(FontNameCache::kFieldSep); + aFaceList.Append(aPSName); + aFaceList.Append(FontNameCache::kFieldSep); + aFaceList.Append(aFullName); + aFaceList.Append(FontNameCache::kFieldSep); + aFaceList.AppendInt(int(aVisibility)); + aFaceList.Append(FontNameCache::kRecordSep); +} + +void FT2FontEntry::CheckForBrokenFont(gfxFontFamily* aFamily) { + // note if the family is in the "bad underline" blocklist + if (aFamily->IsBadUnderlineFamily()) { + mIsBadUnderlineFont = true; + } + nsAutoCString familyKey(aFamily->Name()); + BuildKeyNameFromFontName(familyKey); + CheckForBrokenFont(familyKey); +} + +void FT2FontEntry::CheckForBrokenFont(const nsACString& aFamilyKey) { + // bug 721719 - set the IgnoreGSUB flag on entries for Roboto + // because of unwanted on-by-default "ae" ligature. + // (See also AppendFaceFromFontListEntry.) + if (aFamilyKey.EqualsLiteral("roboto")) { + mIgnoreGSUB = true; + return; + } + + // bug 706888 - set the IgnoreGSUB flag on the broken version of + // Droid Sans Arabic from certain phones, as identified by the + // font checksum in the 'head' table + if (aFamilyKey.EqualsLiteral("droid sans arabic")) { + RefPtr<SharedFTFace> face = GetFTFace(); + if (face) { + const TT_Header* head = static_cast<const TT_Header*>( + FT_Get_Sfnt_Table(face->GetFace(), ft_sfnt_head)); + if (head && head->CheckSum_Adjust == 0xe445242) { + mIgnoreGSUB = true; + } + } + } +} + +void gfxFT2FontList::AppendFacesFromBlob( + const nsCString& aFileName, StandardFile aStdFile, hb_blob_t* aBlob, + FontNameCache* aCache, uint32_t aTimestamp, uint32_t aFilesize) { + nsCString newFaceList; + uint32_t numFaces = 1; + unsigned int length; + const char* data = hb_blob_get_data(aBlob, &length); + // Check for a possible TrueType Collection + if (length >= sizeof(TTCHeader)) { + const TTCHeader* ttc = reinterpret_cast<const TTCHeader*>(data); + if (ttc->ttcTag == TRUETYPE_TAG('t', 't', 'c', 'f')) { + numFaces = ttc->numFonts; + } + } + for (unsigned int index = 0; index < numFaces; index++) { + hb_face_t* face = hb_face_create(aBlob, index); + if (face != hb_face_get_empty()) { + AddFaceToList(aFileName, index, aStdFile, face, newFaceList); + } + hb_face_destroy(face); + } + if (aCache && !newFaceList.IsEmpty()) { + aCache->CacheFileInfo(aFileName, newFaceList, aTimestamp, aFilesize); + } +} + +void gfxFT2FontList::AppendFacesFromFontFile(const nsCString& aFileName, + FontNameCache* aCache, + StandardFile aStdFile) { + nsCString cachedFaceList; + uint32_t filesize = 0, timestamp = 0; + if (aCache) { + aCache->GetInfoForFile(aFileName, cachedFaceList, ×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) { + auto* pfl = PlatformFontList(); + pfl->mLock.AssertCurrentThreadIn(); + pfl->AppendFaceFromFontListEntry(aFLE, aStdFile); + }; + CollectFunc shared = [](const FontListEntry& aFLE, const nsCString& aPSName, + const nsCString& aFullName, StandardFile aStdFile) { + PlatformFontList()->CollectInitData(aFLE, aPSName, aFullName, aStdFile); + }; + if (AppendFacesFromCachedFaceList(SharedFontList() ? shared : unshared, + aFileName, cachedFaceList, aStdFile)) { + LOG(("using cached font info for %s", aFileName.get())); + return; + } + } + + gfxFontUtils::AutoHBBlob fileBlob(hb_blob_create_from_file(aFileName.get())); + if (hb_blob_get_length(fileBlob) > 0) { + LOG(("reading font info via harfbuzz for %s", aFileName.get())); + AppendFacesFromBlob(aFileName, aStdFile, fileBlob, + 0 == statRetval ? aCache : nullptr, s.st_mtime, + s.st_size); + } +} + +void gfxFT2FontList::FindFontsInOmnijar(FontNameCache* aCache) { + bool jarChanged = false; + + mozilla::scache::StartupCache* cache = + mozilla::scache::StartupCache::GetSingleton(); + const char* cachedModifiedTimeBuf; + uint32_t longSize; + if (cache && + NS_SUCCEEDED(cache->GetBuffer(JAR_LAST_MODIFED_TIME, + &cachedModifiedTimeBuf, &longSize)) && + longSize == sizeof(int64_t)) { + nsCOMPtr<nsIFile> jarFile = Omnijar::GetPath(Omnijar::Type::GRE); + jarFile->GetLastModifiedTime(&mJarModifiedTime); + if (mJarModifiedTime > LittleEndian::readInt64(cachedModifiedTimeBuf)) { + jarChanged = true; + } + } + + static const char* sJarSearchPaths[] = { + "res/fonts/*.ttf$", + }; + RefPtr<nsZipArchive> reader = Omnijar::GetReader(Omnijar::Type::GRE); + for (unsigned i = 0; i < ArrayLength(sJarSearchPaths); i++) { + nsZipFind* find; + if (NS_SUCCEEDED(reader->FindInit(sJarSearchPaths[i], &find))) { + const char* path; + uint16_t len; + while (NS_SUCCEEDED(find->FindNext(&path, &len))) { + nsCString entryName(path, len); + AppendFacesFromOmnijarEntry(reader, entryName, aCache, jarChanged); + } + delete find; + } + } +} + +using Device = nsIGfxInfo::FontVisibilityDeviceDetermination; +FontVisibility gfxFT2FontList::GetVisibilityForFamily( + const nsACString& aName) const { + static Device fontVisibilityDevice = Device::Unassigned; + if (fontVisibilityDevice == Device::Unassigned) { + nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); + NS_ENSURE_SUCCESS( + gfxInfo->GetFontVisibilityDetermination(&fontVisibilityDevice), + FontVisibility::Unknown); + } + + if (fontVisibilityDevice == Device::Android_Unknown_Release_Version || + fontVisibilityDevice == Device::Android_Unknown_Peloton || + fontVisibilityDevice == Device::Android_Unknown_vbox || + fontVisibilityDevice == Device::Android_Unknown_mitv || + fontVisibilityDevice == Device::Android_Chromebook || + fontVisibilityDevice == Device::Android_Amazon) { + return FontVisibility::Unknown; + } + + // Sanity Check + if (fontVisibilityDevice != Device::Android_sub_9 && + fontVisibilityDevice != Device::Android_9_11 && + fontVisibilityDevice != Device::Android_12_plus) { + return FontVisibility::Unknown; + } + + if (FamilyInList(aName, kBaseFonts_Android)) { + return FontVisibility::Base; + } + + if (fontVisibilityDevice == Device::Android_sub_9) { + if (FamilyInList(aName, kBaseFonts_Android5_8)) { + return FontVisibility::Base; + } + } else { + if (FamilyInList(aName, kBaseFonts_Android9_Higher)) { + return FontVisibility::Base; + } + + if (fontVisibilityDevice == Device::Android_9_11) { + if (FamilyInList(aName, kBaseFonts_Android9_11)) { + return FontVisibility::Base; + } + } else { + if (FamilyInList(aName, kBaseFonts_Android12_Higher)) { + return FontVisibility::Base; + } + } + } + + return FontVisibility::User; +} + +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 = GetVisibilityForFamily(familyName); + + nsAutoCString psname; + GetName(aFace, HB_OT_NAME_ID_POSTSCRIPT_NAME, psname); + + if (SharedFontList()) { + FontListEntry fle(familyName, fe->Name(), fe->mFilename, + fe->Weight().AsScalar(), fe->Stretch().AsScalar(), + fe->SlantStyle().AsScalar(), fe->mFTFontIndex, + visibility); + CollectInitData(fle, psname, fullname, aStdFile); + } else { + RefPtr<gfxFontFamily> family = + mFontFamilies.LookupOrInsertWith(familyKey, [&] { + auto family = MakeRefPtr<FT2FontFamily>(familyName, visibility); + if (mSkipSpaceLookupCheckFamilies.Contains(familyKey)) { + family->SetSkipSpaceFeatureCheck(true); + } + if (mBadUnderlineFamilyNames.ContainsSorted(familyKey)) { + family->SetBadUnderlineFamily(); + } + return family; + }); + family->AddFontEntry(fe); + fe->CheckForBrokenFont(family); + } + + fe->AppendToFaceList(aFaceList, familyName, psname, fullname, visibility); + if (LOG_ENABLED()) { + nsAutoCString weightString; + fe->Weight().ToString(weightString); + nsAutoCString stretchString; + fe->Stretch().ToString(stretchString); + LOG( + ("(fontinit) added (%s) to family (%s)" + " with style: %s weight: %s stretch: %s", + fe->Name().get(), familyName.get(), + fe->IsItalic() ? "italic" : "normal", weightString.get(), + stretchString.get())); + } + } +} + +void gfxFT2FontList::AppendFacesFromOmnijarEntry(nsZipArchive* aArchive, + const nsCString& aEntryName, + FontNameCache* aCache, + bool aJarChanged) { + nsCString faceList; + if (aCache && !aJarChanged) { + uint32_t filesize, timestamp; + aCache->GetInfoForFile(aEntryName, faceList, ×tamp, &filesize); + if (faceList.Length() > 0) { + CollectFunc unshared = + [](const FontListEntry& aFLE, const nsCString& aPSName, + const nsCString& aFullName, StandardFile aStdFile) { + auto* pfl = PlatformFontList(); + pfl->mLock.AssertCurrentThreadIn(); + pfl->AppendFaceFromFontListEntry(aFLE, aStdFile); + }; + CollectFunc shared = [](const FontListEntry& aFLE, + const nsCString& aPSName, + const nsCString& aFullName, + StandardFile aStdFile) { + PlatformFontList()->CollectInitData(aFLE, aPSName, aFullName, aStdFile); + }; + if (AppendFacesFromCachedFaceList(SharedFontList() ? shared : unshared, + aEntryName, faceList, kStandard)) { + return; + } + } + } + + nsZipItem* item = aArchive->GetItem(aEntryName.get()); + NS_ASSERTION(item, "failed to find zip entry"); + + uint32_t bufSize = item->RealSize(); + + // We use fallible allocation here; if there's not enough RAM, we'll simply + // ignore the bundled fonts and fall back to the device's installed fonts. + char* buffer = static_cast<char*>(malloc(bufSize)); + if (!buffer) { + return; + } + + nsZipCursor cursor(item, aArchive, (uint8_t*)buffer, bufSize); + uint8_t* data = cursor.Copy(&bufSize); + MOZ_ASSERT(data && bufSize == item->RealSize(), "error reading bundled font"); + if (!data) { + return; + } + + gfxFontUtils::AutoHBBlob blob( + hb_blob_create(buffer, bufSize, HB_MEMORY_MODE_READONLY, buffer, free)); + AppendFacesFromBlob(aEntryName, kStandard, blob, aCache, 0, bufSize); +} + +// Called on each family after all fonts are added to the list; +// if aSortFaces is true this will sort faces to give priority to "standard" +// font files. +void FT2FontFamily::FinalizeMemberList(bool aSortFaces) { + AutoWriteLock lock(mLock); + + SetHasStyles(true); + + if (aSortFaces) { + SortAvailableFonts(); + } + CheckForSimpleFamily(); +} + +void gfxFT2FontList::FindFonts() { + MOZ_ASSERT(XRE_IsParentProcess()); + + // Chrome process: get the cached list (if any) + if (!mFontNameCache) { + mFontNameCache = MakeUnique<FontNameCache>(); + } + mFontNameCache->Init(); + +#if defined(MOZ_WIDGET_ANDROID) + // Android API 29+ provides system font and font matcher API for native code. + typedef void* (*_ASystemFontIterator_open)(); + typedef void* (*_ASystemFontIterator_next)(void*); + typedef void (*_ASystemFontIterator_close)(void*); + typedef const char* (*_AFont_getFontFilePath)(const void*); + typedef void (*_AFont_close)(void*); + + static _ASystemFontIterator_open systemFontIterator_open = nullptr; + static _ASystemFontIterator_next systemFontIterator_next = nullptr; + static _ASystemFontIterator_close systemFontIterator_close = nullptr; + static _AFont_getFontFilePath font_getFontFilePath = nullptr; + static _AFont_close font_close = nullptr; + + static bool firstTime = true; + + nsAutoCString androidFontsRoot = [&] { + // ANDROID_ROOT is the root of the android system, typically /system; + // font files are in /$ANDROID_ROOT/fonts/ + nsAutoCString root; + char* androidRoot = PR_GetEnv("ANDROID_ROOT"); + if (androidRoot) { + root = androidRoot; + } else { + root = "/system"_ns; + } + root.AppendLiteral("/fonts"); + return root; + }(); + + if (firstTime) { + if (jni::GetAPIVersion() >= 29) { + void* handle = dlopen("libandroid.so", RTLD_LAZY | RTLD_LOCAL); + MOZ_ASSERT(handle); + + systemFontIterator_open = + (_ASystemFontIterator_open)dlsym(handle, "ASystemFontIterator_open"); + systemFontIterator_next = + (_ASystemFontIterator_next)dlsym(handle, "ASystemFontIterator_next"); + systemFontIterator_close = (_ASystemFontIterator_close)dlsym( + handle, "ASystemFontIterator_close"); + font_getFontFilePath = + (_AFont_getFontFilePath)dlsym(handle, "AFont_getFontFilePath"); + font_close = (_AFont_close)dlsym(handle, "AFont_close"); + + if (NS_WARN_IF(!systemFontIterator_next) || + NS_WARN_IF(!systemFontIterator_close) || + NS_WARN_IF(!font_getFontFilePath) || NS_WARN_IF(!font_close)) { + // Since any functions aren't resolved, use old way to enumerate fonts. + systemFontIterator_open = nullptr; + } + } + firstTime = false; + } + + bool useSystemFontAPI = !!systemFontIterator_open; + + if (useSystemFontAPI && + !StaticPrefs:: + gfx_font_list_use_font_match_api_force_enabled_AtStartup()) { + // OPPO, realme and OnePlus device seem to crash when using font match API + // (Bug 1787551). + nsCString manufacturer = java::sdk::Build::MANUFACTURER()->ToCString(); + if (manufacturer.EqualsLiteral("OPPO") || + manufacturer.EqualsLiteral("realme") || + manufacturer.EqualsLiteral("OnePlus")) { + useSystemFontAPI = false; + } + } + + if (useSystemFontAPI) { + void* iter = systemFontIterator_open(); + if (iter) { + void* font = systemFontIterator_next(iter); + while (font) { + nsDependentCString path(font_getFontFilePath(font)); + AppendFacesFromFontFile(path, mFontNameCache.get(), kStandard); + font_close(font); + font = systemFontIterator_next(iter); + } + + if (!StaticPrefs::gfx_font_rendering_colr_v1_enabled()) { + // We turn off COLRv1 fonts support. Newer android versions have + // COLRv1 emoji font, and a legacy and hidden CBDT font we understand, + // so try to find NotoColorEmojiLegacy.ttf explicitly for now. + nsAutoCString legacyEmojiFont(androidFontsRoot); + legacyEmojiFont.Append("/NotoColorEmojiLegacy.ttf"); + AppendFacesFromFontFile(legacyEmojiFont, mFontNameCache.get(), + kStandard); + } + + systemFontIterator_close(iter); + } else { + useSystemFontAPI = false; + } + } + + if (!useSystemFontAPI) +#endif + { + FindFontsInDir(androidFontsRoot, mFontNameCache.get()); + } + + // Look for fonts stored in omnijar, unless we're on a low-memory + // device where we don't want to spend the RAM to decompress them. + // (Prefs may disable this, or force-enable it even with low memory.) + if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() > 0 || + (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() < 0 && + !nsMemory::IsLowMemoryPlatform())) { + TimeStamp start = TimeStamp::Now(); + FindFontsInOmnijar(mFontNameCache.get()); + TimeStamp end = TimeStamp::Now(); + Telemetry::Accumulate(Telemetry::FONTLIST_BUNDLEDFONTS_ACTIVATE, + (end - start).ToMilliseconds()); + } + + // Look for downloaded fonts in a profile-agnostic "fonts" directory. + nsCOMPtr<nsIProperties> dirSvc = + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID); + if (dirSvc) { + nsCOMPtr<nsIFile> appDir; + nsresult rv = dirSvc->Get(NS_XPCOM_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(appDir)); + if (NS_SUCCEEDED(rv)) { + appDir->AppendNative("fonts"_ns); + nsCString localPath; + if (NS_SUCCEEDED(appDir->GetNativePath(localPath))) { + FindFontsInDir(localPath, mFontNameCache.get()); + } + } + } + + // look for locally-added fonts in a "fonts" subdir of the profile + nsCOMPtr<nsIFile> localDir; + nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, + getter_AddRefs(localDir)); + if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(localDir->Append(u"fonts"_ns))) { + nsCString localPath; + rv = localDir->GetNativePath(localPath); + if (NS_SUCCEEDED(rv)) { + FindFontsInDir(localPath, mFontNameCache.get()); + } + } + + mFontNameCache->DropStaleEntries(); + if (!mFontNameCache->EntryCount()) { + // if we can't find any usable fonts, we are doomed! + MOZ_CRASH("No font files found"); + } + + // Write out FontCache data if needed + WriteCache(); +} + +void gfxFT2FontList::WriteCache() { + if (mFontNameCache) { + mFontNameCache->WriteCache(); + } + mozilla::scache::StartupCache* cache = + mozilla::scache::StartupCache::GetSingleton(); + if (cache && mJarModifiedTime > 0) { + const size_t bufSize = sizeof(mJarModifiedTime); + auto buf = UniqueFreePtr<char[]>( + reinterpret_cast<char*>(malloc(sizeof(char) * bufSize))); + LittleEndian::writeInt64(buf.get(), mJarModifiedTime); + + LOG(("WriteCache: putting Jar, length %zu", bufSize)); + cache->PutBuffer(JAR_LAST_MODIFED_TIME, std::move(buf), bufSize); + } + LOG(("Done with writecache")); +} + +void gfxFT2FontList::FindFontsInDir(const nsCString& aDir, + FontNameCache* aFNC) { + static const char* sStandardFonts[] = {"DroidSans.ttf", + "DroidSans-Bold.ttf", + "DroidSerif-Regular.ttf", + "DroidSerif-Bold.ttf", + "DroidSerif-Italic.ttf", + "DroidSerif-BoldItalic.ttf", + "DroidSansMono.ttf", + "DroidSansArabic.ttf", + "DroidSansHebrew.ttf", + "DroidSansThai.ttf", + "MTLmr3m.ttf", + "MTLc3m.ttf", + "NanumGothic.ttf", + "NotoColorEmoji.ttf", + "NotoColorEmojiFlags.ttf", + "NotoColorEmojiLegacy.ttf", + "DroidSansJapanese.ttf", + "DroidSansFallback.ttf"}; + + DIR* d = opendir(aDir.get()); + if (!d) { + return; + } + + struct dirent* ent = nullptr; + while ((ent = readdir(d)) != nullptr) { + const char* ext = strrchr(ent->d_name, '.'); + if (!ext) { + continue; + } + if (strcasecmp(ext, ".ttf") == 0 || strcasecmp(ext, ".otf") == 0 || + strcasecmp(ext, ".woff") == 0 || strcasecmp(ext, ".ttc") == 0) { + bool isStdFont = false; + for (unsigned int i = 0; i < ArrayLength(sStandardFonts) && !isStdFont; + i++) { + isStdFont = strcmp(sStandardFonts[i], ent->d_name) == 0; + } + + nsAutoCString s(aDir); + s.Append('/'); + s.Append(ent->d_name); + + // Add the face(s) from this file to our font list; + // note that if we have cached info for this file in fnc, + // and the file is unchanged, we won't actually need to read it. + // If the file is new/changed, this will update the FontNameCache. + AppendFacesFromFontFile(s, aFNC, isStdFont ? kStandard : kUnknown); + } + } + + closedir(d); +} + +void gfxFT2FontList::AppendFaceFromFontListEntry(const FontListEntry& aFLE, + StandardFile aStdFile) { + FT2FontEntry* fe = FT2FontEntry::CreateFontEntry(aFLE); + if (fe) { + nsAutoCString key(aFLE.familyName()); + BuildKeyNameFromFontName(key); + fe->mStandardFace = (aStdFile == kStandard); + RefPtr<gfxFontFamily> family = mFontFamilies.LookupOrInsertWith(key, [&] { + auto family = + MakeRefPtr<FT2FontFamily>(aFLE.familyName(), aFLE.visibility()); + if (mSkipSpaceLookupCheckFamilies.Contains(key)) { + family->SetSkipSpaceFeatureCheck(true); + } + if (mBadUnderlineFamilyNames.ContainsSorted(key)) { + family->SetBadUnderlineFamily(); + } + return family; + }); + family->AddFontEntry(fe); + + fe->CheckForBrokenFont(family); + } +} + +void gfxFT2FontList::ReadSystemFontList(dom::SystemFontList* aList) { + AutoLock lock(mLock); + for (const auto& entry : mFontFamilies) { + auto family = static_cast<FT2FontFamily*>(entry.GetData().get()); + family->AddFacesToFontList(&aList->entries()); + } +} + +static void LoadSkipSpaceLookupCheck( + nsTHashSet<nsCString>& aSkipSpaceLookupCheck) { + AutoTArray<nsCString, 5> skiplist; + gfxFontUtils::GetPrefsFontList( + "font.whitelist.skip_default_features_space_check", skiplist); + uint32_t numFonts = skiplist.Length(); + for (uint32_t i = 0; i < numFonts; i++) { + ToLowerCase(skiplist[i]); + aSkipSpaceLookupCheck.Insert(skiplist[i]); + } +} + +nsresult gfxFT2FontList::InitFontListForPlatform() { + LoadSkipSpaceLookupCheck(mSkipSpaceLookupCheckFamilies); + + if (XRE_IsParentProcess()) { + // This will populate/update mFontNameCache and store it in the + // startupCache for future startups. + FindFonts(); + + // Finalize the families by sorting faces into standard order + // and marking "simple" families. + for (const auto& entry : mFontFamilies) { + auto* family = static_cast<FT2FontFamily*>(entry.GetData().get()); + family->FinalizeMemberList(/* aSortFaces */ true); + } + + return NS_OK; + } + + // Content process: use font list passed from the chrome process via + // the GetXPCOMProcessAttributes message. + auto& fontList = dom::ContentChild::GetSingleton()->SystemFontList(); + for (FontListEntry& fle : fontList.entries()) { + // We don't need to identify "standard" font files here, + // as the faces are already sorted. + AppendFaceFromFontListEntry(fle, kUnknown); + } + + // We don't need to sort faces (because they were already sorted by the + // chrome process, so we just maintain the existing order) + for (const auto& entry : mFontFamilies) { + auto* family = static_cast<FT2FontFamily*>(entry.GetData().get()); + family->FinalizeMemberList(/* aSortFaces */ false); + } + + LOG(("got font list from chrome process: %" PRIdPTR " faces in %" PRIu32 + " families", + fontList.entries().Length(), mFontFamilies.Count())); + fontList.entries().Clear(); + + return NS_OK; +} + +void gfxFT2FontList::InitSharedFontListForPlatform() { + if (!XRE_IsParentProcess()) { + // Content processes will access the shared-memory data created by the + // parent, so don't need to scan for available fonts themselves. + return; + } + + // This will populate mFontNameCache with entries for all the available font + // files, and record them in mFamilies (unshared list) or mFamilyInitData and + // mFaceInitData (shared font list). + FindFonts(); + + mozilla::fontlist::FontList* list = SharedFontList(); + list->SetFamilyNames(mFamilyInitData); + + auto families = list->Families(); + for (uint32_t i = 0; i < mFamilyInitData.Length(); i++) { + auto faceList = mFaceInitData.Get(mFamilyInitData[i].mKey); + MOZ_ASSERT(faceList); + families[i].AddFaces(list, *faceList); + } + + mFamilyInitData.Clear(); + mFaceInitData.Clear(); +} + +gfxFontEntry* gfxFT2FontList::CreateFontEntry(fontlist::Face* aFace, + const fontlist::Family* aFamily) { + fontlist::FontList* list = SharedFontList(); + nsAutoCString desc(aFace->mDescriptor.AsString(list)); + FT2FontEntry* fe = + FT2FontEntry::CreateFontEntry(desc, desc.get(), aFace->mIndex, nullptr); + fe->InitializeFrom(aFace, aFamily); + fe->CheckForBrokenFont(aFamily->Key().AsString(list)); + return fe; +} + +// called for each family name, based on the assumption that the +// first part of the full name is the family name + +gfxFontEntry* gfxFT2FontList::LookupLocalFont(nsPresContext* aPresContext, + const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) { + AutoLock lock(mLock); + + if (SharedFontList()) { + return LookupInSharedFaceNameList(aPresContext, aFontName, aWeightForEntry, + aStretchForEntry, aStyleForEntry); + } + + // walk over list of names + FT2FontEntry* fontEntry = nullptr; + FontVisibility level = + aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User; + + for (const RefPtr<gfxFontFamily>& fontFamily : mFontFamilies.Values()) { + if (!IsVisibleToCSS(*fontFamily, level)) { + continue; + } + + // Check family name, based on the assumption that the + // first part of the full name is the family name + + // does the family name match up to the length of the family name? + const nsCString& family = fontFamily->Name(); + + const nsAutoCString fullNameFamily( + Substring(aFontName, 0, family.Length())); + + // if so, iterate over faces in this family to see if there is a match + if (family.Equals(fullNameFamily, nsCaseInsensitiveCStringComparator)) { + gfxFontEntry* fe = + fontFamily->FindFont(aFontName, nsCaseInsensitiveCStringComparator); + if (fe) { + fontEntry = static_cast<FT2FontEntry*>(fe); + goto searchDone; + } + } + } + +searchDone: + if (!fontEntry) { + return nullptr; + } + + // Clone the font entry so that we can then set its style descriptors + // from the userfont entry rather than the actual font. + + // Ensure existence of mFTFace in the original entry + RefPtr<SharedFTFace> face = fontEntry->GetFTFace(true); + if (!face) { + return nullptr; + } + + FT2FontEntry* fe = FT2FontEntry::CreateFontEntry( + fontEntry->Name(), fontEntry->mFilename.get(), fontEntry->mFTFontIndex, + nullptr); + if (fe) { + fe->mStyleRange = aStyleForEntry; + fe->mWeightRange = aWeightForEntry; + fe->mStretchRange = aStretchForEntry; + fe->mIsLocalUserFont = true; + } + + return fe; +} + +FontFamily gfxFT2FontList::GetDefaultFontForPlatform( + nsPresContext* aPresContext, const gfxFontStyle* aStyle, + nsAtom* aLanguage) { + FontFamily ff; +#if defined(MOZ_WIDGET_ANDROID) + ff = FindFamily(aPresContext, "Roboto"_ns); + if (ff.IsNull()) { + ff = FindFamily(aPresContext, "Droid Sans"_ns); + } +#endif + /* TODO: what about Qt or other platforms that may use this? */ + return ff; +} + +gfxFontEntry* gfxFT2FontList::MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, + uint32_t aLength) { + // The FT2 font needs the font data to persist, so we do NOT free it here + // but instead pass ownership to the font entry. + // Deallocation will happen later, when the font face is destroyed. + return FT2FontEntry::CreateFontEntry(aFontName, aWeightForEntry, + aStretchForEntry, aStyleForEntry, + aFontData, aLength); +} + +gfxFontFamily* gfxFT2FontList::CreateFontFamily( + const nsACString& aName, FontVisibility aVisibility) const { + return new FT2FontFamily(aName, aVisibility); +} + +void gfxFT2FontList::WillShutdown() { + LOG(("WillShutdown")); + WriteCache(); + mFontNameCache = nullptr; +} diff --git a/gfx/thebes/gfxFT2FontList.h b/gfx/thebes/gfxFT2FontList.h new file mode 100644 index 0000000000..895b1172d4 --- /dev/null +++ b/gfx/thebes/gfxFT2FontList.h @@ -0,0 +1,264 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FT2FONTLIST_H +#define GFX_FT2FONTLIST_H + +#include "mozilla/MemoryReporting.h" +#include "gfxFT2FontBase.h" +#include "gfxPlatformFontList.h" +#include "nsTHashSet.h" + +namespace mozilla { +namespace dom { +class SystemFontListEntry; +}; +namespace gfx { +class FTUserFontData; +}; +}; // namespace mozilla + +class FontNameCache; +typedef struct FT_FaceRec_* FT_Face; +class nsZipArchive; +class WillShutdownObserver; + +class FT2FontEntry final : public gfxFT2FontEntryBase { + friend class gfxFT2FontList; + + using FontListEntry = mozilla::dom::SystemFontListEntry; + using FTUserFontData = mozilla::gfx::FTUserFontData; + + public: + explicit FT2FontEntry(const nsACString& aFaceName) + : gfxFT2FontEntryBase(aFaceName), mFTFontIndex(0) {} + + ~FT2FontEntry(); + + gfxFontEntry* Clone() const override; + + const nsCString& GetName() const { return Name(); } + + // create a font entry for a downloaded font + static FT2FontEntry* CreateFontEntry( + const nsACString& aFontName, WeightRange aWeight, StretchRange aStretch, + SlantStyleRange aStyle, const uint8_t* aFontData, uint32_t aLength); + + // create a font entry representing an installed font, identified by + // a FontListEntry; the freetype and cairo faces will not be instantiated + // until actually needed + static FT2FontEntry* CreateFontEntry(const FontListEntry& aFLE); + + // Create a font entry with the given name; if it is an installed font, + // also record the filename and index. + // If a non-null harfbuzz face is passed, also set style/weight/stretch + // properties of the entry from the values in the face. + static FT2FontEntry* CreateFontEntry(const nsACString& aName, + const char* aFilename, uint8_t aIndex, + const hb_face_t* aFace); + + gfxFont* CreateFontInstance(const gfxFontStyle* aStyle) override; + + nsresult ReadCMAP(FontInfoData* aFontInfoData = nullptr) override; + + hb_blob_t* GetFontTable(uint32_t aTableTag) override; + + bool HasFontTable(uint32_t aTableTag) override; + nsresult CopyFontTable(uint32_t aTableTag, nsTArray<uint8_t>&) override; + + bool HasVariations() override; + void GetVariationAxes( + nsTArray<gfxFontVariationAxis>& aVariationAxes) override; + void GetVariationInstances( + nsTArray<gfxFontVariationInstance>& aInstances) override; + + // Check for various kinds of brokenness, and set flags on the entry + // accordingly so that we avoid using bad font tables + void CheckForBrokenFont(gfxFontFamily* aFamily); + void CheckForBrokenFont(const nsACString& aFamilyKey); + + already_AddRefed<mozilla::gfx::SharedFTFace> GetFTFace(bool aCommit = false); + FTUserFontData* GetUserFontData(); + + FT_MM_Var* GetMMVar() override; + + // Get a harfbuzz face for this font, if possible. The caller is responsible + // to destroy the face when no longer needed. + // This may be a bit expensive, so avoid calling multiple times if the same + // face can be re-used for several purposes instead. + hb_face_t* CreateHBFace() const; + + /** + * Append this face's metadata to aFaceList for storage in the FontNameCache + * (for faster startup). + * The aPSName and aFullName parameters here can in principle be empty, + * but if they are missing for a given face then src:local() lookups will + * not be able to find it when the shared font list is in use. + */ + void AppendToFaceList(nsCString& aFaceList, const nsACString& aFamilyName, + const nsACString& aPSName, const nsACString& aFullName, + FontVisibility aVisibility); + + void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const override; + void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const override; + + // Strong reference (addref'd), but held in an atomic ptr rather than a + // normal RefPtr. + mozilla::Atomic<mozilla::gfx::SharedFTFace*> mFTFace; + + FT_MM_Var* mMMVar = nullptr; + + nsCString mFilename; + uint8_t mFTFontIndex; + + mozilla::ThreadSafeWeakPtr<mozilla::gfx::UnscaledFontFreeType> mUnscaledFont; + + nsTHashSet<uint32_t> mAvailableTables; + + enum class HasVariationsState : int8_t { + Uninitialized = -1, + No = 0, + Yes = 1, + }; + std::atomic<HasVariationsState> mHasVariations = + HasVariationsState::Uninitialized; + + bool mMMVarInitialized = false; +}; + +class FT2FontFamily final : public gfxFontFamily { + using FontListEntry = mozilla::dom::SystemFontListEntry; + + public: + explicit FT2FontFamily(const nsACString& aName, FontVisibility aVisibility) + : gfxFontFamily(aName, aVisibility) {} + + // Append this family's faces to the IPC fontlist + void AddFacesToFontList(nsTArray<FontListEntry>* aFontList); + + void FinalizeMemberList(bool aSortFaces); +}; + +class gfxFT2FontList final : public gfxPlatformFontList { + using FontListEntry = mozilla::dom::SystemFontListEntry; + + public: + gfxFT2FontList(); + virtual ~gfxFT2FontList(); + + gfxFontEntry* CreateFontEntry( + mozilla::fontlist::Face* aFace, + const mozilla::fontlist::Family* aFamily) override; + + gfxFontEntry* LookupLocalFont(nsPresContext* aPresContext, + const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) override; + + gfxFontEntry* MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, + uint32_t aLength) override; + + void WriteCache(); + + void ReadSystemFontList(mozilla::dom::SystemFontList*); + + static gfxFT2FontList* PlatformFontList() { + return static_cast<gfxFT2FontList*>( + gfxPlatformFontList::PlatformFontList()); + } + + gfxFontFamily* CreateFontFamily(const nsACString& aName, + FontVisibility aVisibility) const override; + + void WillShutdown(); + + protected: + typedef enum { kUnknown, kStandard } StandardFile; + + // initialize font lists + nsresult InitFontListForPlatform() MOZ_REQUIRES(mLock) override; + + FontVisibility GetVisibilityForFamily(const nsACString& aName) const; + + void AppendFaceFromFontListEntry(const FontListEntry& aFLE, + StandardFile aStdFile) MOZ_REQUIRES(mLock); + + void AppendFacesFromBlob(const nsCString& aFileName, StandardFile aStdFile, + hb_blob_t* aBlob, FontNameCache* aCache, + uint32_t aTimestamp, uint32_t aFilesize) + MOZ_REQUIRES(mLock); + + void AppendFacesFromFontFile(const nsCString& aFileName, + FontNameCache* aCache, StandardFile aStdFile) + MOZ_REQUIRES(mLock); + + void AppendFacesFromOmnijarEntry(nsZipArchive* aReader, + const nsCString& aEntryName, + FontNameCache* aCache, bool aJarChanged) + MOZ_REQUIRES(mLock); + + void InitSharedFontListForPlatform() MOZ_REQUIRES(mLock) override; + void CollectInitData(const FontListEntry& aFLE, const nsCString& aPSName, + const nsCString& aFullName, StandardFile aStdFile); + + /** + * Callback passed to AppendFacesFromCachedFaceList to collect family/face + * information in either the unshared or shared list we're building. + */ + typedef void (*CollectFunc)(const FontListEntry& aFLE, + const nsCString& aPSName, + const nsCString& aFullName, + StandardFile aStdFile); + + /** + * Append faces from the face-list record for a specific file. + * aCollectFace is a callback that will store the face(s) in either the + * unshared mFontFamilies list or the mFamilyInitData/mFaceInitData tables + * that will be used to initialize the shared list. + * Returns true if it is able to read at least one face entry; false if no + * usable face entry was found. + */ + bool AppendFacesFromCachedFaceList(CollectFunc aCollectFace, + const nsCString& aFileName, + const nsCString& aFaceList, + StandardFile aStdFile) MOZ_REQUIRES(mLock); + + void AddFaceToList(const nsCString& aEntryName, uint32_t aIndex, + StandardFile aStdFile, hb_face_t* aFace, + nsCString& aFaceList) MOZ_REQUIRES(mLock); + + void FindFonts() MOZ_REQUIRES(mLock); + + void FindFontsInOmnijar(FontNameCache* aCache) MOZ_REQUIRES(mLock); + + void FindFontsInDir(const nsCString& aDir, FontNameCache* aFNC) + MOZ_REQUIRES(mLock); + + FontFamily GetDefaultFontForPlatform(nsPresContext* aPresContext, + const gfxFontStyle* aStyle, + nsAtom* aLanguage = nullptr) + MOZ_REQUIRES(mLock) override; + + nsTHashSet<nsCString> mSkipSpaceLookupCheckFamilies; + + private: + mozilla::UniquePtr<FontNameCache> mFontNameCache; + int64_t mJarModifiedTime; + RefPtr<WillShutdownObserver> mObserver; + + nsTArray<mozilla::fontlist::Family::InitData> mFamilyInitData; + nsClassHashtable<nsCStringHashKey, + nsTArray<mozilla::fontlist::Face::InitData>> + mFaceInitData; +}; + +#endif /* GFX_FT2FONTLIST_H */ diff --git a/gfx/thebes/gfxFT2Fonts.cpp b/gfx/thebes/gfxFT2Fonts.cpp new file mode 100644 index 0000000000..9cc3a4bfd5 --- /dev/null +++ b/gfx/thebes/gfxFT2Fonts.cpp @@ -0,0 +1,243 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(MOZ_WIDGET_GTK) +# include "gfxPlatformGtk.h" +# define gfxToolkitPlatform gfxPlatformGtk +#elif defined(XP_WIN) +# include "gfxWindowsPlatform.h" +# define gfxToolkitPlatform gfxWindowsPlatform +#elif defined(ANDROID) +# include "gfxAndroidPlatform.h" +# define gfxToolkitPlatform gfxAndroidPlatform +#endif + +#include "gfxTypes.h" +#include "gfxFT2Fonts.h" +#include "gfxFT2FontBase.h" +#include "gfxFT2Utils.h" +#include "gfxFT2FontList.h" +#include "gfxTextRun.h" +#include <locale.h> +#include "nsGkAtoms.h" +#include "nsTArray.h" +#include "nsCRT.h" +#include "nsXULAppAPI.h" + +#include "mozilla/Logging.h" +#include "prinit.h" + +#include "mozilla/MemoryReporting.h" +#include "mozilla/Preferences.h" +#include "mozilla/gfx/2D.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +/** + * gfxFT2Font + */ + +bool gfxFT2Font::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, + RoundingFlags aRounding, + gfxShapedText* aShapedText) { + if (!gfxFont::ShapeText(aDrawTarget, aText, aOffset, aLength, aScript, + aLanguage, aVertical, aRounding, aShapedText)) { + // harfbuzz must have failed(?!), just render raw glyphs + AddRange(aText, aOffset, aLength, aShapedText); + PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical, + aShapedText); + } + + return true; +} + +void gfxFT2Font::AddRange(const char16_t* aText, uint32_t aOffset, + uint32_t aLength, gfxShapedText* aShapedText) { + typedef gfxShapedText::CompressedGlyph CompressedGlyph; + + const uint32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit(); + // we'll pass this in/figure it out dynamically, but at this point there can + // be only one face. + gfxFT2LockedFace faceLock(this); + FT_Face face = faceLock.get(); + + CompressedGlyph* charGlyphs = aShapedText->GetCharacterGlyphs(); + + const gfxFT2Font::CachedGlyphData *cgd = nullptr, *cgdNext = nullptr; + + FT_UInt spaceGlyph = GetSpaceGlyph(); + + for (uint32_t i = 0; i < aLength; i++, aOffset++) { + char16_t ch = aText[i]; + + if (ch == 0) { + // treat this null byte as a missing glyph, don't create a glyph for it + aShapedText->SetMissingGlyph(aOffset, 0, this); + continue; + } + + NS_ASSERTION(!gfxFontGroup::IsInvalidChar(ch), "Invalid char detected"); + + if (cgdNext) { + cgd = cgdNext; + cgdNext = nullptr; + } else { + cgd = GetGlyphDataForChar(face, ch); + } + + FT_UInt gid = cgd->glyphIndex; + int32_t advance = 0; + + if (gid == 0) { + advance = -1; // trigger the missing glyphs case below + } else { + // find next character and its glyph -- in case they exist + // and exist in the current font face -- to compute kerning + char16_t chNext = 0; + FT_UInt gidNext = 0; + FT_Pos lsbDeltaNext = 0; + + if (FT_HAS_KERNING(face) && i + 1 < aLength) { + chNext = aText[i + 1]; + if (chNext != 0) { + cgdNext = GetGlyphDataForChar(face, chNext); + gidNext = cgdNext->glyphIndex; + if (gidNext && gidNext != spaceGlyph) + lsbDeltaNext = cgdNext->lsbDelta; + } + } + + advance = cgd->xAdvance; + + // now add kerning to the current glyph's advance + if (chNext && gidNext) { + FT_Vector kerning; + kerning.x = 0; + FT_Get_Kerning(face, gid, gidNext, FT_KERNING_DEFAULT, &kerning); + advance += kerning.x; + if (cgd->rsbDelta - lsbDeltaNext >= 32) { + advance -= 64; + } else if (cgd->rsbDelta - lsbDeltaNext < -32) { + advance += 64; + } + } + + // convert 26.6 fixed point to app units + // round rather than truncate to nearest pixel + // because these advances are often scaled + advance = ((advance * appUnitsPerDevUnit + 32) >> 6); + } + + if (advance >= 0 && CompressedGlyph::IsSimpleAdvance(advance) && + CompressedGlyph::IsSimpleGlyphID(gid)) { + charGlyphs[aOffset].SetSimpleGlyph(advance, gid); + } else if (gid == 0) { + // gid = 0 only happens when the glyph is missing from the font + aShapedText->SetMissingGlyph(aOffset, ch, this); + } else { + gfxTextRun::DetailedGlyph details; + details.mGlyphID = gid; + NS_ASSERTION(details.mGlyphID == gid, + "Seriously weird glyph ID detected!"); + details.mAdvance = advance; + aShapedText->SetDetailedGlyphs(aOffset, 1, &details); + } + } +} + +gfxFT2Font::gfxFT2Font(const RefPtr<UnscaledFontFreeType>& aUnscaledFont, + RefPtr<mozilla::gfx::SharedFTFace>&& aFTFace, + FT2FontEntry* aFontEntry, const gfxFontStyle* aFontStyle, + int aLoadFlags) + : gfxFT2FontBase(aUnscaledFont, std::move(aFTFace), aFontEntry, aFontStyle, + aLoadFlags, aFontStyle->NeedsSyntheticBold(aFontEntry)), + mCharGlyphCache(32) { + NS_ASSERTION(mFontEntry, + "Unable to find font entry for font. Something is whack."); + InitMetrics(); +} + +gfxFT2Font::~gfxFT2Font() {} + +already_AddRefed<ScaledFont> gfxFT2Font::GetScaledFont( + const TextRunDrawParams& aRunParams) { + if (ScaledFont* scaledFont = mAzureScaledFont) { + return do_AddRef(scaledFont); + } + + RefPtr<ScaledFont> newScaledFont = Factory::CreateScaledFontForFreeTypeFont( + GetUnscaledFont(), GetAdjustedSize(), mFTFace, + GetStyle()->NeedsSyntheticBold(GetFontEntry())); + if (!newScaledFont) { + return nullptr; + } + + InitializeScaledFont(newScaledFont); + + if (mAzureScaledFont.compareExchange(nullptr, newScaledFont.get())) { + Unused << newScaledFont.forget(); + } + ScaledFont* scaledFont = mAzureScaledFont; + return do_AddRef(scaledFont); +} + +bool gfxFT2Font::ShouldHintMetrics() const { + return !gfxPlatform::GetPlatform()->RequiresLinearZoom(); +} + +void gfxFT2Font::FillGlyphDataForChar(FT_Face face, uint32_t ch, + CachedGlyphData* gd) { + if (!face->charmap || (face->charmap->encoding != FT_ENCODING_UNICODE && + face->charmap->encoding != FT_ENCODING_MS_SYMBOL)) { + if (FT_Err_Ok != FT_Select_Charmap(face, FT_ENCODING_UNICODE) && + FT_Err_Ok != FT_Select_Charmap(face, FT_ENCODING_MS_SYMBOL)) { + NS_WARNING("failed to select Unicode or symbol charmap!"); + } + } + FT_UInt gid = FT_Get_Char_Index(face, ch); + + if (gid == 0) { + // this font doesn't support this char! + NS_ASSERTION(gid != 0, + "We don't have a glyph, but font indicated that it supported " + "this char in tables?"); + gd->glyphIndex = 0; + return; + } + + FT_Error err = Factory::LoadFTGlyph(face, gid, mFTLoadFlags); + + if (err) { + // hmm, this is weird, we failed to load a glyph that we had? + NS_WARNING("Failed to load glyph that we got from Get_Char_index"); + + gd->glyphIndex = 0; + return; + } + + gd->glyphIndex = gid; + gd->lsbDelta = face->glyph->lsb_delta; + gd->rsbDelta = face->glyph->rsb_delta; + gd->xAdvance = face->glyph->advance.x; + if (gd->xAdvance) { + gd->xAdvance += GetEmboldenStrength(face).x; + } +} + +void gfxFT2Font::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const { + gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); + aSizes->mFontInstances += + mCharGlyphCache.ShallowSizeOfExcludingThis(aMallocSizeOf); +} + +void gfxFT2Font::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const { + aSizes->mFontInstances += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} diff --git a/gfx/thebes/gfxFT2Fonts.h b/gfx/thebes/gfxFT2Fonts.h new file mode 100644 index 0000000000..3b164c711e --- /dev/null +++ b/gfx/thebes/gfxFT2Fonts.h @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FT2FONTS_H +#define GFX_FT2FONTS_H + +#include "mozilla/MemoryReporting.h" +#include "gfxTypes.h" +#include "gfxFont.h" +#include "gfxFT2FontBase.h" +#include "gfxContext.h" +#include "gfxFontUtils.h" +#include "gfxUserFontSet.h" + +class FT2FontEntry; + +class gfxFT2Font final : public gfxFT2FontBase { + public: // new functions + gfxFT2Font(const RefPtr<mozilla::gfx::UnscaledFontFreeType>& aUnscaledFont, + RefPtr<mozilla::gfx::SharedFTFace>&& aFTFace, + FT2FontEntry* aFontEntry, const gfxFontStyle* aFontStyle, + int aLoadFlags); + + FT2FontEntry* GetFontEntry(); + + already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont( + const TextRunDrawParams& aRunParams) override; + + bool ShouldHintMetrics() const override; + + void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const override; + void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const override; + + protected: + ~gfxFT2Font() override; + + struct CachedGlyphData { + CachedGlyphData() : glyphIndex(0xffffffffU) {} + + explicit CachedGlyphData(uint32_t gid) : glyphIndex(gid) {} + + uint32_t glyphIndex; + int32_t lsbDelta; + int32_t rsbDelta; + int32_t xAdvance; + }; + + const CachedGlyphData* GetGlyphDataForChar(FT_Face aFace, uint32_t ch) { + CharGlyphMapEntryType* entry = mCharGlyphCache.PutEntry(ch); + + if (!entry) return nullptr; + + if (entry->GetData().glyphIndex == 0xffffffffU) { + // this is a new entry, fill it + FillGlyphDataForChar(aFace, ch, entry->GetModifiableData()); + } + + return &entry->GetData(); + } + + bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, RoundingFlags aRounding, + gfxShapedText* aShapedText) override; + + void FillGlyphDataForChar(FT_Face face, uint32_t ch, CachedGlyphData* gd); + + void AddRange(const char16_t* aText, uint32_t aOffset, uint32_t aLength, + gfxShapedText* aShapedText); + + typedef nsBaseHashtableET<nsUint32HashKey, CachedGlyphData> + CharGlyphMapEntryType; + typedef nsTHashtable<CharGlyphMapEntryType> CharGlyphMap; + CharGlyphMap mCharGlyphCache; +}; + +#endif /* GFX_FT2FONTS_H */ diff --git a/gfx/thebes/gfxFT2Utils.cpp b/gfx/thebes/gfxFT2Utils.cpp new file mode 100644 index 0000000000..94403c9dc1 --- /dev/null +++ b/gfx/thebes/gfxFT2Utils.cpp @@ -0,0 +1,157 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxFT2FontBase.h" +#include "gfxFT2Utils.h" +#include "mozilla/Likely.h" + +#ifdef USE_FC_FREETYPE +# include <fontconfig/fcfreetype.h> +#endif + +#include "ft2build.h" +#include FT_MULTIPLE_MASTERS_H + +#include "prlink.h" + +uint32_t gfxFT2LockedFace::GetGlyph(uint32_t aCharCode) { + if (MOZ_UNLIKELY(!mFace)) return 0; + +#ifdef USE_FC_FREETYPE + // FcFreeTypeCharIndex will search starting from the most recently + // selected charmap. This can cause non-determistic behavior when more + // than one charmap supports a character but with different glyphs, as + // with older versions of MS Gothic, for example. Always prefer a Unicode + // charmap, if there is one; failing that, try MS_SYMBOL. + // (FcFreeTypeCharIndex usually does the appropriate Unicode conversion, + // but some fonts have non-Roman glyphs for FT_ENCODING_APPLE_ROMAN + // characters.) + if (!mFace->charmap || (mFace->charmap->encoding != FT_ENCODING_UNICODE && + mFace->charmap->encoding != FT_ENCODING_MS_SYMBOL)) { + if (FT_Err_Ok != FT_Select_Charmap(mFace, FT_ENCODING_UNICODE) && + FT_Err_Ok != FT_Select_Charmap(mFace, FT_ENCODING_MS_SYMBOL)) { + NS_WARNING("failed to select Unicode or symbol charmap"); + } + } + + uint32_t gid = FcFreeTypeCharIndex(mFace, aCharCode); +#else + uint32_t gid = FT_Get_Char_Index(mFace, aCharCode); +#endif + if (!gid && mFace->charmap && + mFace->charmap->encoding == FT_ENCODING_MS_SYMBOL) { + if (auto pua = gfxFontUtils::MapLegacySymbolFontCharToPUA(aCharCode)) { + gid = FT_Get_Char_Index(mFace, pua); + } + } + return gid; +} + +typedef FT_UInt (*GetCharVariantFunction)(FT_Face face, FT_ULong charcode, + FT_ULong variantSelector); + +uint32_t gfxFT2LockedFace::GetUVSGlyph(uint32_t aCharCode, + uint32_t aVariantSelector) { + MOZ_ASSERT(aVariantSelector, "aVariantSelector should not be NULL"); + + if (MOZ_UNLIKELY(!mFace)) return 0; + + // This function is available from FreeType 2.3.6 (June 2008). + static CharVariantFunction sGetCharVariantPtr = FindCharVariantFunction(); + if (!sGetCharVariantPtr) return 0; + +#ifdef USE_FC_FREETYPE + // FcFreeTypeCharIndex may have changed the selected charmap. + // FT_Face_GetCharVariantIndex needs a unicode charmap. + if (!mFace->charmap || mFace->charmap->encoding != FT_ENCODING_UNICODE) { + FT_Select_Charmap(mFace, FT_ENCODING_UNICODE); + } +#endif + + return (*sGetCharVariantPtr)(mFace, aCharCode, aVariantSelector); +} + +gfxFT2LockedFace::CharVariantFunction +gfxFT2LockedFace::FindCharVariantFunction() { + // This function is available from FreeType 2.3.6 (June 2008). + PRLibrary* lib = nullptr; + CharVariantFunction function = reinterpret_cast<CharVariantFunction>( + PR_FindFunctionSymbolAndLibrary("FT_Face_GetCharVariantIndex", &lib)); + if (!lib) { + return nullptr; + } + + FT_Int major; + FT_Int minor; + FT_Int patch; + FT_Library_Version(mFace->glyph->library, &major, &minor, &patch); + + // Versions 2.4.0 to 2.4.3 crash if configured with + // FT_CONFIG_OPTION_OLD_INTERNALS. Presence of the symbol FT_Alloc + // indicates FT_CONFIG_OPTION_OLD_INTERNALS. + if (major == 2 && minor == 4 && patch < 4 && + PR_FindFunctionSymbol(lib, "FT_Alloc")) { + function = nullptr; + } + + // Decrement the reference count incremented in + // PR_FindFunctionSymbolAndLibrary. + PR_UnloadLibrary(lib); + + return function; +} + +/*static*/ +void gfxFT2Utils::GetVariationAxes(const FT_MM_Var* aMMVar, + nsTArray<gfxFontVariationAxis>& aAxes) { + MOZ_ASSERT(aAxes.IsEmpty()); + if (!aMMVar) { + return; + } + aAxes.SetCapacity(aMMVar->num_axis); + for (unsigned i = 0; i < aMMVar->num_axis; i++) { + const auto& a = aMMVar->axis[i]; + gfxFontVariationAxis axis; + axis.mMinValue = a.minimum / 65536.0; + axis.mMaxValue = a.maximum / 65536.0; + axis.mDefaultValue = a.def / 65536.0; + axis.mTag = a.tag; + axis.mName = a.name; + aAxes.AppendElement(axis); + } +} + +/*static*/ +void gfxFT2Utils::GetVariationInstances( + gfxFontEntry* aFontEntry, const FT_MM_Var* aMMVar, + nsTArray<gfxFontVariationInstance>& aInstances) { + MOZ_ASSERT(aInstances.IsEmpty()); + if (!aMMVar) { + return; + } + gfxFontUtils::AutoHBBlob nameTable( + aFontEntry->GetFontTable(TRUETYPE_TAG('n', 'a', 'm', 'e'))); + if (!nameTable) { + return; + } + aInstances.SetCapacity(aMMVar->num_namedstyles); + for (unsigned i = 0; i < aMMVar->num_namedstyles; i++) { + const auto& ns = aMMVar->namedstyle[i]; + gfxFontVariationInstance inst; + nsresult rv = + gfxFontUtils::ReadCanonicalName(nameTable, ns.strid, inst.mName); + if (NS_FAILED(rv)) { + continue; + } + inst.mValues.SetCapacity(aMMVar->num_axis); + for (unsigned j = 0; j < aMMVar->num_axis; j++) { + gfxFontVariationValue value; + value.mAxis = aMMVar->axis[j].tag; + value.mValue = ns.coords[j] / 65536.0; + inst.mValues.AppendElement(value); + } + aInstances.AppendElement(inst); + } +} diff --git a/gfx/thebes/gfxFT2Utils.h b/gfx/thebes/gfxFT2Utils.h new file mode 100644 index 0000000000..37e930c743 --- /dev/null +++ b/gfx/thebes/gfxFT2Utils.h @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FT2UTILS_H +#define GFX_FT2UTILS_H + +#include "cairo-ft.h" +#include "gfxFT2FontBase.h" +#include "mozilla/Likely.h" + +// Rounding and truncation functions for a FreeType fixed point number +// (FT26Dot6) stored in a 32bit integer with high 26 bits for the integer +// part and low 6 bits for the fractional part. +#define FLOAT_FROM_26_6(x) ((x) / 64.0) +#define FLOAT_FROM_16_16(x) ((x) / 65536.0) +#define ROUND_26_6_TO_INT(x) ((x) >= 0 ? ((32 + (x)) >> 6) : -((32 - (x)) >> 6)) + +typedef struct FT_FaceRec_* FT_Face; + +/** + * BEWARE: Recursively locking with gfxFT2LockedFace is not supported. + * Do not instantiate gfxFT2LockedFace within the scope of another instance. + * Do not attempt to call into Cairo within the scope of gfxFT2LockedFace, + * as that may accidentally try to re-lock the face within Cairo itself + * and thus deadlock. + */ +class MOZ_STACK_CLASS gfxFT2LockedFace { + public: + explicit gfxFT2LockedFace(const gfxFT2FontBase* aFont) + : mGfxFont(aFont), mFace(aFont->LockFTFace()) {} + ~gfxFT2LockedFace() { + if (mFace) { + mGfxFont->UnlockFTFace(); + } + } + + FT_Face get() { return mFace; }; + + /** + * Get the glyph id for a Unicode character representable by a single + * glyph, or return zero if there is no such glyph. This does no caching, + * so you probably want gfxFcFont::GetGlyph. + */ + uint32_t GetGlyph(uint32_t aCharCode); + /** + * Returns 0 if there is no variation selector cmap subtable. + */ + uint32_t GetUVSGlyph(uint32_t aCharCode, uint32_t aVariantSelector); + + protected: + typedef FT_UInt (*CharVariantFunction)(FT_Face face, FT_ULong charcode, + FT_ULong variantSelector); + CharVariantFunction FindCharVariantFunction(); + + const gfxFT2FontBase* MOZ_NON_OWNING_REF mGfxFont; // owned by caller + FT_Face mFace; +}; + +// A couple of FreeType-based utilities shared by gfxFontconfigFontEntry +// and FT2FontEntry. + +typedef struct FT_MM_Var_ FT_MM_Var; + +class gfxFT2Utils { + public: + static void GetVariationAxes(const FT_MM_Var* aMMVar, + nsTArray<gfxFontVariationAxis>& aAxes); + + static void GetVariationInstances( + gfxFontEntry* aFontEntry, const FT_MM_Var* aMMVar, + nsTArray<gfxFontVariationInstance>& aInstances); +}; + +#endif /* GFX_FT2UTILS_H */ diff --git a/gfx/thebes/gfxFailure.h b/gfx/thebes/gfxFailure.h new file mode 100644 index 0000000000..04192a5a6e --- /dev/null +++ b/gfx/thebes/gfxFailure.h @@ -0,0 +1,24 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef gfxFailure_h_ +#define gfxFailure_h_ + +#include "nsString.h" +#include "nsIGfxInfo.h" +#include "nsServiceManagerUtils.h" + +namespace mozilla { +namespace gfx { +inline void LogFailure(const nsCString& failure) { + nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1"); + gfxInfo->LogFailure(failure); +} +} // namespace gfx +} // namespace mozilla + +#endif // gfxFailure_h_ diff --git a/gfx/thebes/gfxFcPlatformFontList.cpp b/gfx/thebes/gfxFcPlatformFontList.cpp new file mode 100644 index 0000000000..60a8f9894e --- /dev/null +++ b/gfx/thebes/gfxFcPlatformFontList.cpp @@ -0,0 +1,2971 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Logging.h" + +#include "gfxFcPlatformFontList.h" +#include "gfxFont.h" +#include "gfxFontConstants.h" +#include "gfxFT2Utils.h" +#include "gfxPlatform.h" +#include "nsPresContext.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/Preferences.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimeStamp.h" +#include "nsGkAtoms.h" +#include "nsIConsoleService.h" +#include "nsIGfxInfo.h" +#include "mozilla/Components.h" +#include "nsString.h" +#include "nsStringFwd.h" +#include "nsUnicodeProperties.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsXULAppAPI.h" +#include "SharedFontList-impl.h" +#include "StandardFonts-linux.inc" +#include "mozilla/intl/Locale.h" + +#include "mozilla/gfx/HelpersCairo.h" + +#include <cairo-ft.h> +#include <fontconfig/fcfreetype.h> +#include <fontconfig/fontconfig.h> +#include <harfbuzz/hb.h> +#include <dlfcn.h> +#include <unistd.h> + +#ifdef MOZ_WIDGET_GTK +# include <gdk/gdk.h> +# include <gtk/gtk.h> +# include "gfxPlatformGtk.h" +# include "mozilla/WidgetUtilsGtk.h" +#endif + +#ifdef MOZ_X11 +# include "mozilla/X11Util.h" +#endif + +#if defined(MOZ_SANDBOX) && defined(XP_LINUX) +# include "mozilla/SandboxBrokerPolicyFactory.h" +# include "mozilla/SandboxSettings.h" +#endif + +#include FT_MULTIPLE_MASTERS_H + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::unicode; +using namespace mozilla::intl; + +#ifndef FC_POSTSCRIPT_NAME +# define FC_POSTSCRIPT_NAME "postscriptname" /* String */ +#endif +#ifndef FC_VARIABLE +# define FC_VARIABLE "variable" /* Bool */ +#endif + +#define PRINTING_FC_PROPERTY "gfx.printing" + +#define LOG_FONTLIST(args) \ + MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug, args) +#define LOG_FONTLIST_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug) +#define LOG_CMAPDATA_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_cmapdata), LogLevel::Debug) + +static const FcChar8* ToFcChar8Ptr(const char* aStr) { + return reinterpret_cast<const FcChar8*>(aStr); +} + +static const char* ToCharPtr(const FcChar8* aStr) { + return reinterpret_cast<const char*>(aStr); +} + +// canonical name ==> first en name or first name if no en name +// This is the required logic for fullname lookups as per CSS3 Fonts spec. +static uint32_t FindCanonicalNameIndex(FcPattern* aFont, + const char* aLangField) { + uint32_t n = 0, en = 0; + FcChar8* lang; + while (FcPatternGetString(aFont, aLangField, n, &lang) == FcResultMatch) { + // look for 'en' or variants, en-US, en-JP etc. + uint32_t len = strlen(ToCharPtr(lang)); + bool enPrefix = (strncmp(ToCharPtr(lang), "en", 2) == 0); + if (enPrefix && (len == 2 || (len > 2 && aLangField[2] == '-'))) { + en = n; + break; + } + n++; + } + return en; +} + +static void GetFaceNames(FcPattern* aFont, const nsACString& aFamilyName, + nsACString& aPostscriptName, nsACString& aFullname) { + // get the Postscript name + FcChar8* psname; + if (FcPatternGetString(aFont, FC_POSTSCRIPT_NAME, 0, &psname) == + FcResultMatch) { + aPostscriptName = ToCharPtr(psname); + } + + // get the canonical fullname (i.e. en name or first name) + uint32_t en = FindCanonicalNameIndex(aFont, FC_FULLNAMELANG); + FcChar8* fullname; + if (FcPatternGetString(aFont, FC_FULLNAME, en, &fullname) == FcResultMatch) { + aFullname = ToCharPtr(fullname); + } + + // if have fullname, done + if (!aFullname.IsEmpty()) { + return; + } + + // otherwise, set the fullname to family + style name [en] and use that + aFullname = aFamilyName; + + // figure out the en style name + en = FindCanonicalNameIndex(aFont, FC_STYLELANG); + nsAutoCString style; + FcChar8* stylename = nullptr; + FcPatternGetString(aFont, FC_STYLE, en, &stylename); + if (stylename) { + style = ToCharPtr(stylename); + } + + if (!style.IsEmpty() && !style.EqualsLiteral("Regular")) { + aFullname.Append(' '); + aFullname.Append(style); + } +} + +static FontWeight MapFcWeight(int aFcWeight) { + if (aFcWeight <= (FC_WEIGHT_THIN + FC_WEIGHT_EXTRALIGHT) / 2) { + return FontWeight::FromInt(100); + } + if (aFcWeight <= (FC_WEIGHT_EXTRALIGHT + FC_WEIGHT_LIGHT) / 2) { + return FontWeight::FromInt(200); + } + if (aFcWeight <= (FC_WEIGHT_LIGHT + FC_WEIGHT_BOOK) / 2) { + return FontWeight::FromInt(300); + } + if (aFcWeight <= (FC_WEIGHT_REGULAR + FC_WEIGHT_MEDIUM) / 2) { + // This includes FC_WEIGHT_BOOK + return FontWeight::FromInt(400); + } + if (aFcWeight <= (FC_WEIGHT_MEDIUM + FC_WEIGHT_DEMIBOLD) / 2) { + return FontWeight::FromInt(500); + } + if (aFcWeight <= (FC_WEIGHT_DEMIBOLD + FC_WEIGHT_BOLD) / 2) { + return FontWeight::FromInt(600); + } + if (aFcWeight <= (FC_WEIGHT_BOLD + FC_WEIGHT_EXTRABOLD) / 2) { + return FontWeight::FromInt(700); + } + if (aFcWeight <= (FC_WEIGHT_EXTRABOLD + FC_WEIGHT_BLACK) / 2) { + return FontWeight::FromInt(800); + } + if (aFcWeight <= FC_WEIGHT_BLACK) { + return FontWeight::FromInt(900); + } + + // including FC_WEIGHT_EXTRABLACK + return FontWeight::FromInt(901); +} + +// TODO(emilio, jfkthame): I think this can now be more fine-grained. +static FontStretch MapFcWidth(int aFcWidth) { + if (aFcWidth <= (FC_WIDTH_ULTRACONDENSED + FC_WIDTH_EXTRACONDENSED) / 2) { + return FontStretch::ULTRA_CONDENSED; + } + if (aFcWidth <= (FC_WIDTH_EXTRACONDENSED + FC_WIDTH_CONDENSED) / 2) { + return FontStretch::EXTRA_CONDENSED; + } + if (aFcWidth <= (FC_WIDTH_CONDENSED + FC_WIDTH_SEMICONDENSED) / 2) { + return FontStretch::CONDENSED; + } + if (aFcWidth <= (FC_WIDTH_SEMICONDENSED + FC_WIDTH_NORMAL) / 2) { + return FontStretch::SEMI_CONDENSED; + } + if (aFcWidth <= (FC_WIDTH_NORMAL + FC_WIDTH_SEMIEXPANDED) / 2) { + return FontStretch::NORMAL; + } + if (aFcWidth <= (FC_WIDTH_SEMIEXPANDED + FC_WIDTH_EXPANDED) / 2) { + return FontStretch::SEMI_EXPANDED; + } + if (aFcWidth <= (FC_WIDTH_EXPANDED + FC_WIDTH_EXTRAEXPANDED) / 2) { + return FontStretch::EXPANDED; + } + if (aFcWidth <= (FC_WIDTH_EXTRAEXPANDED + FC_WIDTH_ULTRAEXPANDED) / 2) { + return FontStretch::EXTRA_EXPANDED; + } + return FontStretch::ULTRA_EXPANDED; +} + +static void GetFontProperties(FcPattern* aFontPattern, WeightRange* aWeight, + StretchRange* aStretch, + SlantStyleRange* aSlantStyle, + uint16_t* aSize = nullptr) { + // weight + int weight; + if (FcPatternGetInteger(aFontPattern, FC_WEIGHT, 0, &weight) != + FcResultMatch) { + weight = FC_WEIGHT_REGULAR; + } + *aWeight = WeightRange(MapFcWeight(weight)); + + // width + int width; + if (FcPatternGetInteger(aFontPattern, FC_WIDTH, 0, &width) != FcResultMatch) { + width = FC_WIDTH_NORMAL; + } + *aStretch = StretchRange(MapFcWidth(width)); + + // italic + int slant; + if (FcPatternGetInteger(aFontPattern, FC_SLANT, 0, &slant) != FcResultMatch) { + slant = FC_SLANT_ROMAN; + } + if (slant == FC_SLANT_OBLIQUE) { + *aSlantStyle = SlantStyleRange(FontSlantStyle::OBLIQUE); + } else if (slant > 0) { + *aSlantStyle = SlantStyleRange(FontSlantStyle::ITALIC); + } + + if (aSize) { + // pixel size, or zero if scalable + FcBool scalable; + if (FcPatternGetBool(aFontPattern, FC_SCALABLE, 0, &scalable) == + FcResultMatch && + scalable) { + *aSize = 0; + } else { + double size; + if (FcPatternGetDouble(aFontPattern, FC_PIXEL_SIZE, 0, &size) == + FcResultMatch) { + *aSize = uint16_t(NS_round(size)); + } else { + *aSize = 0; + } + } + } +} + +void gfxFontconfigFontEntry::GetUserFontFeatures(FcPattern* aPattern) { + int fontFeaturesNum = 0; + char* s; + hb_feature_t tmpFeature; + while (FcResultMatch == FcPatternGetString(aPattern, "fontfeatures", + fontFeaturesNum, (FcChar8**)&s)) { + bool ret = hb_feature_from_string(s, -1, &tmpFeature); + if (ret) { + mFeatureSettings.AppendElement( + (gfxFontFeature){tmpFeature.tag, tmpFeature.value}); + } + fontFeaturesNum++; + } +} + +gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsACString& aFaceName, + FcPattern* aFontPattern, + bool aIgnoreFcCharmap) + : gfxFT2FontEntryBase(aFaceName), + mFontPattern(aFontPattern), + mFTFaceInitialized(false), + mIgnoreFcCharmap(aIgnoreFcCharmap) { + GetFontProperties(aFontPattern, &mWeightRange, &mStretchRange, &mStyleRange); + GetUserFontFeatures(mFontPattern); +} + +gfxFontEntry* gfxFontconfigFontEntry::Clone() const { + MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!"); + return new gfxFontconfigFontEntry(Name(), mFontPattern, mIgnoreFcCharmap); +} + +static already_AddRefed<FcPattern> CreatePatternForFace(FT_Face aFace) { + // Use fontconfig to fill out the pattern from the FTFace. + // The "file" argument cannot be nullptr (in fontconfig-2.6.0 at + // least). The dummy file passed here is removed below. + // + // When fontconfig scans the system fonts, FcConfigGetBlanks(nullptr) + // is passed as the "blanks" argument, which provides that unexpectedly + // blank glyphs are elided. Here, however, we pass nullptr for + // "blanks", effectively assuming that, if the font has a blank glyph, + // then the author intends any associated character to be rendered + // blank. + RefPtr<FcPattern> pattern = + dont_AddRef(FcFreeTypeQueryFace(aFace, ToFcChar8Ptr(""), 0, nullptr)); + // given that we have a FT_Face, not really sure this is possible... + if (!pattern) { + pattern = dont_AddRef(FcPatternCreate()); + } + FcPatternDel(pattern, FC_FILE); + FcPatternDel(pattern, FC_INDEX); + + // Make a new pattern and store the face in it so that cairo uses + // that when creating a cairo font face. + FcPatternAddFTFace(pattern, FC_FT_FACE, aFace); + + return pattern.forget(); +} + +static already_AddRefed<SharedFTFace> CreateFaceForPattern( + FcPattern* aPattern) { + FcChar8* filename; + if (FcPatternGetString(aPattern, FC_FILE, 0, &filename) != FcResultMatch) { + return nullptr; + } + int index; + if (FcPatternGetInteger(aPattern, FC_INDEX, 0, &index) != FcResultMatch) { + index = 0; // default to 0 if not found in pattern + } + return Factory::NewSharedFTFace(nullptr, ToCharPtr(filename), index); +} + +gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsACString& aFaceName, + WeightRange aWeight, + StretchRange aStretch, + SlantStyleRange aStyle, + RefPtr<SharedFTFace>&& aFace) + : gfxFT2FontEntryBase(aFaceName), + mFontPattern(CreatePatternForFace(aFace->GetFace())), + mFTFace(aFace.forget().take()), + mFTFaceInitialized(true), + mIgnoreFcCharmap(true) { + mWeightRange = aWeight; + mStyleRange = aStyle; + mStretchRange = aStretch; + mIsDataUserFont = true; +} + +gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsACString& aFaceName, + FcPattern* aFontPattern, + WeightRange aWeight, + StretchRange aStretch, + SlantStyleRange aStyle) + : gfxFT2FontEntryBase(aFaceName), + mFontPattern(aFontPattern), + mFTFaceInitialized(false) { + mWeightRange = aWeight; + mStyleRange = aStyle; + mStretchRange = aStretch; + mIsLocalUserFont = true; + + // The proper setting of mIgnoreFcCharmap is tricky for fonts loaded + // via src:local()... + // If the local font happens to come from the application fontset, + // we want to set it to true so that color/svg fonts will work even + // if the default glyphs are blank; but if the local font is a non- + // sfnt face (e.g. legacy type 1) then we need to set it to false + // because our cmap-reading code will fail and we depend on FT+Fc to + // determine the coverage. + // We set the flag here, but may flip it the first time TestCharacterMap + // is called, at which point we'll look to see whether a 'cmap' is + // actually present in the font. + mIgnoreFcCharmap = true; + + GetUserFontFeatures(mFontPattern); +} + +typedef FT_Error (*GetVarFunc)(FT_Face, FT_MM_Var**); +typedef FT_Error (*DoneVarFunc)(FT_Library, FT_MM_Var*); +static GetVarFunc sGetVar; +static DoneVarFunc sDoneVar; +static bool sInitializedVarFuncs = false; + +static void InitializeVarFuncs() { + if (sInitializedVarFuncs) { + return; + } + sInitializedVarFuncs = true; +#if MOZ_TREE_FREETYPE + sGetVar = &FT_Get_MM_Var; + sDoneVar = &FT_Done_MM_Var; +#else + sGetVar = (GetVarFunc)dlsym(RTLD_DEFAULT, "FT_Get_MM_Var"); + sDoneVar = (DoneVarFunc)dlsym(RTLD_DEFAULT, "FT_Done_MM_Var"); +#endif +} + +gfxFontconfigFontEntry::~gfxFontconfigFontEntry() { + if (mMMVar) { + // Prior to freetype 2.9, there was no specific function to free the + // FT_MM_Var record, and the docs just said to use free(). + // InitializeVarFuncs must have been called in order for mMMVar to be + // non-null here, so we don't need to do it again. + if (sDoneVar) { + auto ftFace = GetFTFace(); + MOZ_ASSERT(ftFace, "How did mMMVar get set without a face?"); + (*sDoneVar)(ftFace->GetFace()->glyph->library, mMMVar); + } else { + free(mMMVar); + } + } + if (mFTFaceInitialized) { + auto face = mFTFace.exchange(nullptr); + NS_IF_RELEASE(face); + } +} + +nsresult gfxFontconfigFontEntry::ReadCMAP(FontInfoData* aFontInfoData) { + // attempt this once, if errors occur leave a blank cmap + if (mCharacterMap) { + return NS_OK; + } + + RefPtr<gfxCharacterMap> charmap; + nsresult rv; + + uint32_t uvsOffset = 0; + if (aFontInfoData && + (charmap = GetCMAPFromFontInfo(aFontInfoData, uvsOffset))) { + rv = NS_OK; + } else { + uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p'); + charmap = new gfxCharacterMap(); + AutoTable cmapTable(this, kCMAP); + + if (cmapTable) { + uint32_t cmapLen; + const uint8_t* cmapData = reinterpret_cast<const uint8_t*>( + hb_blob_get_data(cmapTable, &cmapLen)); + rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, *charmap, uvsOffset); + } else { + rv = NS_ERROR_NOT_AVAILABLE; + } + } + mUVSOffset.exchange(uvsOffset); + + bool setCharMap = true; + if (NS_SUCCEEDED(rv)) { + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + fontlist::FontList* sharedFontList = pfl->SharedFontList(); + if (!IsUserFont() && mShmemFace) { + mShmemFace->SetCharacterMap(sharedFontList, charmap, mShmemFamily); + if (TrySetShmemCharacterMap()) { + setCharMap = false; + } + } else { + charmap = pfl->FindCharMap(charmap); + } + mHasCmapTable = true; + } else { + // if error occurred, initialize to null cmap + charmap = new gfxCharacterMap(); + mHasCmapTable = false; + } + if (setCharMap) { + if (mCharacterMap.compareExchange(nullptr, charmap.get())) { + charmap.get()->AddRef(); + } + } + + LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %zu hash: %8.8x%s\n", + mName.get(), charmap->SizeOfIncludingThis(moz_malloc_size_of), + charmap->mHash, mCharacterMap == charmap ? " new" : "")); + if (LOG_CMAPDATA_ENABLED()) { + char prefix[256]; + SprintfLiteral(prefix, "(cmapdata) name: %.220s", mName.get()); + charmap->Dump(prefix, eGfxLog_cmapdata); + } + + return rv; +} + +static bool HasChar(FcPattern* aFont, FcChar32 aCh) { + FcCharSet* charset = nullptr; + FcPatternGetCharSet(aFont, FC_CHARSET, 0, &charset); + return charset && FcCharSetHasChar(charset, aCh); +} + +bool gfxFontconfigFontEntry::TestCharacterMap(uint32_t aCh) { + // For user fonts, or for fonts bundled with the app (which might include + // color/svg glyphs where the default glyphs may be blank, and thus confuse + // fontconfig/freetype's char map checking), we instead check the cmap + // directly for character coverage. + if (mIgnoreFcCharmap) { + // If it does not actually have a cmap, switch our strategy to use + // fontconfig's charmap after all (except for data fonts, which must + // always have a cmap to have passed OTS validation). + if (!mIsDataUserFont && !HasFontTable(TRUETYPE_TAG('c', 'm', 'a', 'p'))) { + mIgnoreFcCharmap = false; + // ...and continue with HasChar() below. + } else { + return gfxFontEntry::TestCharacterMap(aCh); + } + } + // otherwise (for system fonts), use the charmap in the pattern + return HasChar(mFontPattern, aCh); +} + +bool gfxFontconfigFontEntry::HasFontTable(uint32_t aTableTag) { + if (FTUserFontData* ufd = GetUserFontData()) { + if (ufd->FontData()) { + return !!gfxFontUtils::FindTableDirEntry(ufd->FontData(), aTableTag); + } + } + return gfxFT2FontEntryBase::FaceHasTable(GetFTFace(), aTableTag); +} + +hb_blob_t* gfxFontconfigFontEntry::GetFontTable(uint32_t aTableTag) { + // for data fonts, read directly from the font data + if (FTUserFontData* ufd = GetUserFontData()) { + if (ufd->FontData()) { + return gfxFontUtils::GetTableFromFontData(ufd->FontData(), aTableTag); + } + } + + return gfxFontEntry::GetFontTable(aTableTag); +} + +double gfxFontconfigFontEntry::GetAspect(uint8_t aSizeAdjustBasis) { + using FontSizeAdjust = gfxFont::FontSizeAdjust; + if (FontSizeAdjust::Tag(aSizeAdjustBasis) == FontSizeAdjust::Tag::ExHeight || + FontSizeAdjust::Tag(aSizeAdjustBasis) == FontSizeAdjust::Tag::CapHeight) { + // try to compute aspect from OS/2 metrics if available + AutoTable os2Table(this, TRUETYPE_TAG('O', 'S', '/', '2')); + if (os2Table) { + uint16_t upem = UnitsPerEm(); + if (upem != kInvalidUPEM) { + uint32_t len; + const auto* os2 = + reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len)); + if (uint16_t(os2->version) >= 2) { + // XXX(jfkthame) Other implementations don't have the check for + // values <= 0.1em; should we drop that here? Just require it to be + // a positive number? + if (FontSizeAdjust::Tag(aSizeAdjustBasis) == + FontSizeAdjust::Tag::ExHeight) { + if (len >= offsetof(OS2Table, sxHeight) + sizeof(int16_t) && + int16_t(os2->sxHeight) > 0.1 * upem) { + return double(int16_t(os2->sxHeight)) / upem; + } + } + if (FontSizeAdjust::Tag(aSizeAdjustBasis) == + FontSizeAdjust::Tag::CapHeight) { + if (len >= offsetof(OS2Table, sCapHeight) + sizeof(int16_t) && + int16_t(os2->sCapHeight) > 0.1 * upem) { + return double(int16_t(os2->sCapHeight)) / upem; + } + } + } + } + } + } + + // create a font to calculate the requested aspect + gfxFontStyle s; + s.size = 256.0; // pick large size to reduce hinting artifacts + RefPtr<gfxFont> font = FindOrMakeFont(&s); + if (font) { + const gfxFont::Metrics& metrics = + font->GetMetrics(nsFontMetrics::eHorizontal); + if (metrics.emHeight == 0) { + return 0; + } + switch (FontSizeAdjust::Tag(aSizeAdjustBasis)) { + case FontSizeAdjust::Tag::ExHeight: + return metrics.xHeight / metrics.emHeight; + case FontSizeAdjust::Tag::CapHeight: + return metrics.capHeight / metrics.emHeight; + case FontSizeAdjust::Tag::ChWidth: + return metrics.zeroWidth > 0 ? metrics.zeroWidth / metrics.emHeight + : 0.5; + case FontSizeAdjust::Tag::IcWidth: + case FontSizeAdjust::Tag::IcHeight: { + bool vertical = FontSizeAdjust::Tag(aSizeAdjustBasis) == + FontSizeAdjust::Tag::IcHeight; + gfxFloat advance = + font->GetCharAdvance(gfxFont::kWaterIdeograph, vertical); + return advance > 0 ? advance / metrics.emHeight : 1.0; + } + default: + break; + } + } + + MOZ_ASSERT_UNREACHABLE("failed to compute size-adjust aspect"); + return 0.5; +} + +static void PrepareFontOptions(FcPattern* aPattern, int* aOutLoadFlags, + unsigned int* aOutSynthFlags) { + int loadFlags = FT_LOAD_DEFAULT; + unsigned int synthFlags = 0; + + // xxx - taken from the gfxFontconfigFonts code, needs to be reviewed + + FcBool printing; + if (FcPatternGetBool(aPattern, PRINTING_FC_PROPERTY, 0, &printing) != + FcResultMatch) { + printing = FcFalse; + } + + // Font options are set explicitly here to improve cairo's caching + // behavior and to record the relevant parts of the pattern so that + // the pattern can be released. + // + // Most font_options have already been set as defaults on the FcPattern + // with cairo_ft_font_options_substitute(), then user and system + // fontconfig configurations were applied. The resulting font_options + // have been recorded on the face during + // cairo_ft_font_face_create_for_pattern(). + // + // None of the settings here cause this scaled_font to behave any + // differently from how it would behave if it were created from the same + // face with default font_options. + // + // We set options explicitly so that the same scaled_font will be found in + // the cairo_scaled_font_map when cairo loads glyphs from a context with + // the same font_face, font_matrix, ctm, and surface font_options. + // + // Unfortunately, _cairo_scaled_font_keys_equal doesn't know about the + // font_options on the cairo_ft_font_face, and doesn't consider default + // option values to not match any explicit values. + // + // Even after cairo_set_scaled_font is used to set font_options for the + // cairo context, when cairo looks for a scaled_font for the context, it + // will look for a font with some option values from the target surface if + // any values are left default on the context font_options. If this + // scaled_font is created with default font_options, cairo will not find + // it. + // + // The one option not recorded in the pattern is hint_metrics, which will + // affect glyph metrics. The default behaves as CAIRO_HINT_METRICS_ON. + // We should be considering the font_options of the surface on which this + // font will be used, but currently we don't have different gfxFonts for + // different surface font_options, so we'll create a font suitable for the + // Screen. Image and xlib surfaces default to CAIRO_HINT_METRICS_ON. + + // The remaining options have been recorded on the pattern and the face. + // _cairo_ft_options_merge has some logic to decide which options from the + // scaled_font or from the cairo_ft_font_face take priority in the way the + // font behaves. + // + // In the majority of cases, _cairo_ft_options_merge uses the options from + // the cairo_ft_font_face, so sometimes it is not so important which + // values are set here so long as they are not defaults, but we'll set + // them to the exact values that we expect from the font, to be consistent + // and to protect against changes in cairo. + // + // In some cases, _cairo_ft_options_merge uses some options from the + // scaled_font's font_options rather than options on the + // cairo_ft_font_face (from fontconfig). + // https://bugs.freedesktop.org/show_bug.cgi?id=11838 + // + // Surface font options were set on the pattern in + // cairo_ft_font_options_substitute. If fontconfig has changed the + // hint_style then that is what the user (or distribution) wants, so we + // use the setting from the FcPattern. + // + // Fallback values here mirror treatment of defaults in cairo-ft-font.c. + FcBool hinting = FcFalse; + if (FcPatternGetBool(aPattern, FC_HINTING, 0, &hinting) != FcResultMatch) { + hinting = FcTrue; + } + + int fc_hintstyle = FC_HINT_NONE; + if (!printing && hinting && + FcPatternGetInteger(aPattern, FC_HINT_STYLE, 0, &fc_hintstyle) != + FcResultMatch) { + fc_hintstyle = FC_HINT_FULL; + } + switch (fc_hintstyle) { + case FC_HINT_NONE: + loadFlags = FT_LOAD_NO_HINTING; + break; + case FC_HINT_SLIGHT: + loadFlags = FT_LOAD_TARGET_LIGHT; + break; + } + + FcBool fc_antialias; + if (FcPatternGetBool(aPattern, FC_ANTIALIAS, 0, &fc_antialias) != + FcResultMatch) { + fc_antialias = FcTrue; + } + if (!fc_antialias) { + if (fc_hintstyle != FC_HINT_NONE) { + loadFlags = FT_LOAD_TARGET_MONO; + } + loadFlags |= FT_LOAD_MONOCHROME; + } else if (fc_hintstyle == FC_HINT_FULL) { + int fc_rgba; + if (FcPatternGetInteger(aPattern, FC_RGBA, 0, &fc_rgba) != FcResultMatch) { + fc_rgba = FC_RGBA_UNKNOWN; + } + switch (fc_rgba) { + case FC_RGBA_RGB: + case FC_RGBA_BGR: + loadFlags = FT_LOAD_TARGET_LCD; + break; + case FC_RGBA_VRGB: + case FC_RGBA_VBGR: + loadFlags = FT_LOAD_TARGET_LCD_V; + break; + } + } + + if (!FcPatternAllowsBitmaps(aPattern, fc_antialias != FcFalse, + fc_hintstyle != FC_HINT_NONE)) { + loadFlags |= FT_LOAD_NO_BITMAP; + } + + FcBool autohint; + if (FcPatternGetBool(aPattern, FC_AUTOHINT, 0, &autohint) == FcResultMatch && + autohint) { + loadFlags |= FT_LOAD_FORCE_AUTOHINT; + } + + FcBool embolden; + if (FcPatternGetBool(aPattern, FC_EMBOLDEN, 0, &embolden) == FcResultMatch && + embolden) { + synthFlags |= CAIRO_FT_SYNTHESIZE_BOLD; + } + + *aOutLoadFlags = loadFlags; + *aOutSynthFlags = synthFlags; +} + +#ifdef MOZ_X11 +static bool GetXftInt(Display* aDisplay, const char* aName, int* aResult) { + if (!aDisplay) { + return false; + } + char* value = XGetDefault(aDisplay, "Xft", aName); + if (!value) { + return false; + } + if (FcNameConstant(const_cast<FcChar8*>(ToFcChar8Ptr(value)), aResult)) { + return true; + } + char* end; + *aResult = strtol(value, &end, 0); + if (end != value) { + return true; + } + return false; +} +#endif + +static void PreparePattern(FcPattern* aPattern, bool aIsPrinterFont) { + FcConfigSubstitute(nullptr, aPattern, FcMatchPattern); + + // This gets cairo_font_options_t for the Screen. We should have + // different font options for printing (no hinting) but we are not told + // what we are measuring for. + // + // If cairo adds support for lcd_filter, gdk will not provide the default + // setting for that option. We could get the default setting by creating + // an xlib surface once, recording its font_options, and then merging the + // gdk options. + // + // Using an xlib surface would also be an option to get Screen font + // options for non-GTK X11 toolkits, but less efficient than using GDK to + // pick up dynamic changes. + if (aIsPrinterFont) { + cairo_font_options_t* options = cairo_font_options_create(); + cairo_font_options_set_hint_style(options, CAIRO_HINT_STYLE_NONE); + cairo_font_options_set_antialias(options, CAIRO_ANTIALIAS_GRAY); + cairo_ft_font_options_substitute(options, aPattern); + cairo_font_options_destroy(options); + FcPatternAddBool(aPattern, PRINTING_FC_PROPERTY, FcTrue); +#ifdef MOZ_WIDGET_GTK + } else { + gfxFcPlatformFontList::PlatformFontList()->SubstituteSystemFontOptions( + aPattern); +#endif // MOZ_WIDGET_GTK + } + + FcDefaultSubstitute(aPattern); +} + +void gfxFontconfigFontEntry::UnscaledFontCache::MoveToFront(size_t aIndex) { + if (aIndex > 0) { + ThreadSafeWeakPtr<UnscaledFontFontconfig> front = + std::move(mUnscaledFonts[aIndex]); + for (size_t i = aIndex; i > 0; i--) { + mUnscaledFonts[i] = std::move(mUnscaledFonts[i - 1]); + } + mUnscaledFonts[0] = std::move(front); + } +} + +already_AddRefed<UnscaledFontFontconfig> +gfxFontconfigFontEntry::UnscaledFontCache::Lookup(const std::string& aFile, + uint32_t aIndex) { + for (size_t i = 0; i < kNumEntries; i++) { + RefPtr<UnscaledFontFontconfig> entry(mUnscaledFonts[i]); + if (entry && entry->GetFile() == aFile && entry->GetIndex() == aIndex) { + MoveToFront(i); + return entry.forget(); + } + } + return nullptr; +} + +static inline gfxFloat SizeForStyle(gfxFontconfigFontEntry* aEntry, + const gfxFontStyle& aStyle) { + return StyleFontSizeAdjust::Tag(aStyle.sizeAdjustBasis) != + StyleFontSizeAdjust::Tag::None + ? aStyle.GetAdjustedSize(aEntry->GetAspect(aStyle.sizeAdjustBasis)) + : aStyle.size * aEntry->mSizeAdjust; +} + +static double ChooseFontSize(gfxFontconfigFontEntry* aEntry, + const gfxFontStyle& aStyle) { + double requestedSize = SizeForStyle(aEntry, aStyle); + double bestDist = -1.0; + double bestSize = requestedSize; + double size; + int v = 0; + while (FcPatternGetDouble(aEntry->GetPattern(), FC_PIXEL_SIZE, v, &size) == + FcResultMatch) { + ++v; + double dist = fabs(size - requestedSize); + if (bestDist < 0.0 || dist < bestDist) { + bestDist = dist; + bestSize = size; + } + } + // If the font has bitmaps but wants to be scaled, then let it scale. + if (bestSize >= 0.0) { + FcBool scalable; + if (FcPatternGetBool(aEntry->GetPattern(), FC_SCALABLE, 0, &scalable) == + FcResultMatch && + scalable) { + return requestedSize; + } + } + return bestSize; +} + +gfxFont* gfxFontconfigFontEntry::CreateFontInstance( + const gfxFontStyle* aFontStyle) { + RefPtr<FcPattern> pattern = dont_AddRef(FcPatternCreate()); + if (!pattern) { + NS_WARNING("Failed to create Fontconfig pattern for font instance"); + return nullptr; + } + + double size = ChooseFontSize(this, *aFontStyle); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, size); + + RefPtr<SharedFTFace> face = GetFTFace(); + if (!face) { + NS_WARNING("Failed to get FreeType face for pattern"); + return nullptr; + } + if (HasVariations()) { + // For variation fonts, we create a new FT_Face here so that + // variation coordinates from the style can be applied without + // affecting other font instances created from the same entry + // (font resource). + // For user fonts: create a new FT_Face from the font data, and then make + // a pattern from that. + // For system fonts: create a new FT_Face and store it in a copy of the + // original mFontPattern. + RefPtr<SharedFTFace> varFace = face->GetData() + ? face->GetData()->CloneFace() + : CreateFaceForPattern(mFontPattern); + if (varFace) { + AutoTArray<gfxFontVariation, 8> settings; + GetVariationsForStyle(settings, *aFontStyle); + gfxFT2FontBase::SetupVarCoords(GetMMVar(), settings, varFace->GetFace()); + face = std::move(varFace); + } + } + + PreparePattern(pattern, aFontStyle->printerFont); + RefPtr<FcPattern> renderPattern = + dont_AddRef(FcFontRenderPrepare(nullptr, pattern, mFontPattern)); + if (!renderPattern) { + NS_WARNING("Failed to prepare Fontconfig pattern for font instance"); + return nullptr; + } + + if (aFontStyle->NeedsSyntheticBold(this)) { + FcPatternAddBool(renderPattern, FC_EMBOLDEN, FcTrue); + } + + // will synthetic oblique be applied using a transform? + if (IsUpright() && !aFontStyle->style.IsNormal() && + aFontStyle->allowSyntheticStyle) { + // disable embedded bitmaps (mimics behavior in 90-synthetic.conf) + FcPatternDel(renderPattern, FC_EMBEDDED_BITMAP); + FcPatternAddBool(renderPattern, FC_EMBEDDED_BITMAP, FcFalse); + } + + int loadFlags; + unsigned int synthFlags; + PrepareFontOptions(renderPattern, &loadFlags, &synthFlags); + + std::string file; + int index = 0; + if (!face->GetData()) { + const FcChar8* fcFile; + if (FcPatternGetString(renderPattern, FC_FILE, 0, + const_cast<FcChar8**>(&fcFile)) != FcResultMatch || + FcPatternGetInteger(renderPattern, FC_INDEX, 0, &index) != + FcResultMatch) { + NS_WARNING("No file in Fontconfig pattern for font instance"); + return nullptr; + } + file = ToCharPtr(fcFile); + } + + RefPtr<UnscaledFontFontconfig> unscaledFont; + { + AutoReadLock lock(mLock); + unscaledFont = mUnscaledFontCache.Lookup(file, index); + } + + if (!unscaledFont) { + AutoWriteLock lock(mLock); + // Here, we use the original mFTFace, not a potential clone with variation + // settings applied. + auto ftFace = GetFTFace(); + unscaledFont = ftFace->GetData() ? new UnscaledFontFontconfig(ftFace) + : new UnscaledFontFontconfig( + std::move(file), index, ftFace); + mUnscaledFontCache.Add(unscaledFont); + } + + gfxFont* newFont = new gfxFontconfigFont( + unscaledFont, std::move(face), renderPattern, size, this, aFontStyle, + loadFlags, (synthFlags & CAIRO_FT_SYNTHESIZE_BOLD) != 0); + + return newFont; +} + +SharedFTFace* gfxFontconfigFontEntry::GetFTFace() { + if (!mFTFaceInitialized) { + RefPtr<SharedFTFace> face = CreateFaceForPattern(mFontPattern); + if (face) { + if (mFTFace.compareExchange(nullptr, face.get())) { + Unused << face.forget(); // The reference is now owned by mFTFace. + mFTFaceInitialized = true; + } else { + // We lost a race to set mFTFace! Just discard our new face. + } + } + } + return mFTFace; +} + +FTUserFontData* gfxFontconfigFontEntry::GetUserFontData() { + auto face = GetFTFace(); + if (face && face->GetData()) { + return static_cast<FTUserFontData*>(face->GetData()); + } + return nullptr; +} + +bool gfxFontconfigFontEntry::HasVariations() { + // If the answer is already cached, just return it. + switch (mHasVariations) { + case HasVariationsState::No: + return false; + case HasVariationsState::Yes: + return true; + case HasVariationsState::Uninitialized: + break; + } + + // Figure out whether we have variations, and record in mHasVariations. + // (It doesn't matter if we race with another thread to set this; the result + // will be the same.) + + if (!gfxPlatform::HasVariationFontSupport()) { + mHasVariations = HasVariationsState::No; + return false; + } + + // For installed fonts, query the fontconfig pattern rather than paying + // the cost of loading a FT_Face that we otherwise might never need. + if (!IsUserFont() || IsLocalUserFont()) { + FcBool variable; + if ((FcPatternGetBool(mFontPattern, FC_VARIABLE, 0, &variable) == + FcResultMatch) && + variable) { + mHasVariations = HasVariationsState::Yes; + return true; + } + } else { + if (auto ftFace = GetFTFace()) { + if (ftFace->GetFace()->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS) { + mHasVariations = HasVariationsState::Yes; + return true; + } + } + } + + mHasVariations = HasVariationsState::No; + return false; +} + +FT_MM_Var* gfxFontconfigFontEntry::GetMMVar() { + { + AutoReadLock lock(mLock); + if (mMMVarInitialized) { + return mMMVar; + } + } + + AutoWriteLock lock(mLock); + + mMMVarInitialized = true; + InitializeVarFuncs(); + if (!sGetVar) { + return nullptr; + } + auto ftFace = GetFTFace(); + if (!ftFace) { + return nullptr; + } + if (FT_Err_Ok != (*sGetVar)(ftFace->GetFace(), &mMMVar)) { + mMMVar = nullptr; + } + return mMMVar; +} + +void gfxFontconfigFontEntry::GetVariationAxes( + nsTArray<gfxFontVariationAxis>& aAxes) { + if (!HasVariations()) { + return; + } + gfxFT2Utils::GetVariationAxes(GetMMVar(), aAxes); +} + +void gfxFontconfigFontEntry::GetVariationInstances( + nsTArray<gfxFontVariationInstance>& aInstances) { + if (!HasVariations()) { + return; + } + gfxFT2Utils::GetVariationInstances(this, GetMMVar(), aInstances); +} + +nsresult gfxFontconfigFontEntry::CopyFontTable(uint32_t aTableTag, + nsTArray<uint8_t>& aBuffer) { + NS_ASSERTION(!mIsDataUserFont, + "data fonts should be reading tables directly from memory"); + return gfxFT2FontEntryBase::CopyFaceTable(GetFTFace(), aTableTag, aBuffer); +} + +void gfxFontconfigFontFamily::FindStyleVariationsLocked( + FontInfoData* aFontInfoData) { + if (mHasStyles) { + return; + } + + // add font entries for each of the faces + uint32_t numFonts = mFontPatterns.Length(); + NS_ASSERTION(numFonts, "font family containing no faces!!"); + uint32_t numRegularFaces = 0; + for (uint32_t i = 0; i < numFonts; i++) { + FcPattern* face = mFontPatterns[i]; + + // figure out the psname/fullname and choose which to use as the facename + nsAutoCString psname, fullname; + GetFaceNames(face, mName, psname, fullname); + const nsAutoCString& faceName = !psname.IsEmpty() ? psname : fullname; + + gfxFontconfigFontEntry* fontEntry = + new gfxFontconfigFontEntry(faceName, face, mContainsAppFonts); + + if (gfxPlatform::HasVariationFontSupport()) { + fontEntry->SetupVariationRanges(); + } + + AddFontEntryLocked(fontEntry); + + if (fontEntry->IsNormalStyle()) { + numRegularFaces++; + } + + if (LOG_FONTLIST_ENABLED()) { + nsAutoCString weightString; + fontEntry->Weight().ToString(weightString); + nsAutoCString stretchString; + fontEntry->Stretch().ToString(stretchString); + nsAutoCString styleString; + fontEntry->SlantStyle().ToString(styleString); + LOG_FONTLIST( + ("(fontlist) added (%s) to family (%s)" + " with style: %s weight: %s stretch: %s" + " psname: %s fullname: %s", + fontEntry->Name().get(), Name().get(), styleString.get(), + weightString.get(), stretchString.get(), psname.get(), + fullname.get())); + } + } + + // somewhat arbitrary, but define a family with two or more regular + // faces as a family for which intra-family fallback should be used + if (numRegularFaces > 1) { + mCheckForFallbackFaces = true; + } + mFaceNamesInitialized = true; + mFontPatterns.Clear(); + SetHasStyles(true); + + CheckForSimpleFamily(); +} + +void gfxFontconfigFontFamily::AddFontPattern(FcPattern* aFontPattern, + bool aSingleName) { + NS_ASSERTION( + !mHasStyles, + "font patterns must not be added to already enumerated families"); + + FcBool outline; + if (FcPatternGetBool(aFontPattern, FC_OUTLINE, 0, &outline) != + FcResultMatch || + !outline) { + mHasNonScalableFaces = true; + + FcBool scalable; + if (FcPatternGetBool(aFontPattern, FC_SCALABLE, 0, &scalable) == + FcResultMatch && + scalable) { + mForceScalable = true; + } + } + + if (aSingleName) { + mFontPatterns.InsertElementAt(mUniqueNameFaceCount++, aFontPattern); + } else { + mFontPatterns.AppendElement(aFontPattern); + } +} + +static const double kRejectDistance = 10000.0; + +// Calculate a distance score representing the size disparity between the +// requested style's size and the font entry's size. +static double SizeDistance(gfxFontconfigFontEntry* aEntry, + const gfxFontStyle& aStyle, bool aForceScalable) { + double requestedSize = SizeForStyle(aEntry, aStyle); + double bestDist = -1.0; + double size; + int v = 0; + while (FcPatternGetDouble(aEntry->GetPattern(), FC_PIXEL_SIZE, v, &size) == + FcResultMatch) { + ++v; + double dist = fabs(size - requestedSize); + if (bestDist < 0.0 || dist < bestDist) { + bestDist = dist; + } + } + if (bestDist < 0.0) { + // No size means scalable + return -1.0; + } else if (aForceScalable || 5.0 * bestDist < requestedSize) { + // fontconfig prefers a matching family or lang to pixelsize of bitmap + // fonts. CSS suggests a tolerance of 20% on pixelsize. + return bestDist; + } else { + // Reject any non-scalable fonts that are not within tolerance. + return kRejectDistance; + } +} + +void gfxFontconfigFontFamily::FindAllFontsForStyle( + const gfxFontStyle& aFontStyle, nsTArray<gfxFontEntry*>& aFontEntryList, + bool aIgnoreSizeTolerance) { + gfxFontFamily::FindAllFontsForStyle(aFontStyle, aFontEntryList, + aIgnoreSizeTolerance); + + if (!mHasNonScalableFaces) { + return; + } + + // Iterate over the the available fonts while compacting any groups + // of unscalable fonts with matching styles into a single entry + // corresponding to the closest available size. If the closest + // available size is rejected for being outside tolerance, then the + // entire group will be skipped. + size_t skipped = 0; + gfxFontconfigFontEntry* bestEntry = nullptr; + double bestDist = -1.0; + for (size_t i = 0; i < aFontEntryList.Length(); i++) { + gfxFontconfigFontEntry* entry = + static_cast<gfxFontconfigFontEntry*>(aFontEntryList[i]); + double dist = + SizeDistance(entry, aFontStyle, mForceScalable || aIgnoreSizeTolerance); + // If the entry is scalable or has a style that does not match + // the group of unscalable fonts, then start a new group. + if (dist < 0.0 || !bestEntry || bestEntry->Stretch() != entry->Stretch() || + bestEntry->Weight() != entry->Weight() || + bestEntry->SlantStyle() != entry->SlantStyle()) { + // If the best entry in this group is still outside the tolerance, + // then skip the entire group. + if (bestDist >= kRejectDistance) { + skipped++; + } + // Remove any compacted entries from the previous group. + if (skipped) { + i -= skipped; + aFontEntryList.RemoveElementsAt(i, skipped); + skipped = 0; + } + // Mark the start of the new group. + bestEntry = entry; + bestDist = dist; + } else { + // If this entry more closely matches the requested size than the + // current best in the group, then take this entry instead. + if (dist < bestDist) { + aFontEntryList[i - 1 - skipped] = entry; + bestEntry = entry; + bestDist = dist; + } + skipped++; + } + } + // If the best entry in this group is still outside the tolerance, + // then skip the entire group. + if (bestDist >= kRejectDistance) { + skipped++; + } + // Remove any compacted entries from the current group. + if (skipped) { + aFontEntryList.TruncateLength(aFontEntryList.Length() - skipped); + } +} + +static bool PatternHasLang(const FcPattern* aPattern, const FcChar8* aLang) { + FcLangSet* langset; + + if (FcPatternGetLangSet(aPattern, FC_LANG, 0, &langset) != FcResultMatch) { + return false; + } + + if (FcLangSetHasLang(langset, aLang) != FcLangDifferentLang) { + return true; + } + return false; +} + +bool gfxFontconfigFontFamily::SupportsLangGroup(nsAtom* aLangGroup) const { + if (!aLangGroup || aLangGroup == nsGkAtoms::Unicode) { + return true; + } + + nsAutoCString fcLang; + gfxFcPlatformFontList* pfl = gfxFcPlatformFontList::PlatformFontList(); + pfl->GetSampleLangForGroup(aLangGroup, fcLang); + if (fcLang.IsEmpty()) { + return true; + } + + // Before FindStyleVariations has been called, mFontPatterns will contain + // the font patterns. Afterward, it'll be empty, but mAvailableFonts + // will contain the font entries, each of which holds a reference to its + // pattern. We only check the first pattern in each list, because support + // for langs is considered to be consistent across all faces in a family. + AutoReadLock lock(mLock); + FcPattern* fontPattern; + if (mFontPatterns.Length()) { + fontPattern = mFontPatterns[0]; + } else if (mAvailableFonts.Length()) { + fontPattern = static_cast<gfxFontconfigFontEntry*>(mAvailableFonts[0].get()) + ->GetPattern(); + } else { + return true; + } + + // is lang included in the underlying pattern? + return PatternHasLang(fontPattern, ToFcChar8Ptr(fcLang.get())); +} + +/* virtual */ +gfxFontconfigFontFamily::~gfxFontconfigFontFamily() { + // Should not be dropped by stylo + MOZ_ASSERT(NS_IsMainThread()); +} + +template <typename Func> +void gfxFontconfigFontFamily::AddFacesToFontList(Func aAddPatternFunc) { + AutoReadLock lock(mLock); + if (HasStyles()) { + for (auto& fe : mAvailableFonts) { + if (!fe) { + continue; + } + auto fce = static_cast<gfxFontconfigFontEntry*>(fe.get()); + aAddPatternFunc(fce->GetPattern(), mContainsAppFonts); + } + } else { + for (auto& pat : mFontPatterns) { + aAddPatternFunc(pat, mContainsAppFonts); + } + } +} + +gfxFontconfigFont::gfxFontconfigFont( + const RefPtr<UnscaledFontFontconfig>& aUnscaledFont, + RefPtr<SharedFTFace>&& aFTFace, FcPattern* aPattern, gfxFloat aAdjustedSize, + gfxFontEntry* aFontEntry, const gfxFontStyle* aFontStyle, int aLoadFlags, + bool aEmbolden) + : gfxFT2FontBase(aUnscaledFont, std::move(aFTFace), aFontEntry, aFontStyle, + aLoadFlags, aEmbolden), + mPattern(aPattern) { + mAdjustedSize = aAdjustedSize; + InitMetrics(); +} + +gfxFontconfigFont::~gfxFontconfigFont() = default; + +already_AddRefed<ScaledFont> gfxFontconfigFont::GetScaledFont( + const TextRunDrawParams& aRunParams) { + if (ScaledFont* scaledFont = mAzureScaledFont) { + return do_AddRef(scaledFont); + } + + RefPtr<ScaledFont> newScaledFont = Factory::CreateScaledFontForFontconfigFont( + GetUnscaledFont(), GetAdjustedSize(), mFTFace, GetPattern()); + if (!newScaledFont) { + return nullptr; + } + + InitializeScaledFont(newScaledFont); + + if (mAzureScaledFont.compareExchange(nullptr, newScaledFont.get())) { + Unused << newScaledFont.forget(); + } + ScaledFont* scaledFont = mAzureScaledFont; + return do_AddRef(scaledFont); +} + +bool gfxFontconfigFont::ShouldHintMetrics() const { + return !GetStyle()->printerFont; +} + +gfxFcPlatformFontList::gfxFcPlatformFontList() + : mLocalNames(64), + mGenericMappings(32), + mFcSubstituteCache(64), + mLastConfig(nullptr), + mAlwaysUseFontconfigGenerics(true) { + CheckFamilyList(kBaseFonts_Ubuntu_22_04); + CheckFamilyList(kLangFonts_Ubuntu_22_04); + CheckFamilyList(kBaseFonts_Ubuntu_20_04); + CheckFamilyList(kLangFonts_Ubuntu_20_04); + CheckFamilyList(kBaseFonts_Fedora_39); + CheckFamilyList(kBaseFonts_Fedora_38); + mLastConfig = FcConfigGetCurrent(); + if (XRE_IsParentProcess()) { + // if the rescan interval is set, start the timer + int rescanInterval = FcConfigGetRescanInterval(nullptr); + if (rescanInterval) { + NS_NewTimerWithFuncCallback( + getter_AddRefs(mCheckFontUpdatesTimer), CheckFontUpdates, this, + (rescanInterval + 1) * 1000, nsITimer::TYPE_REPEATING_SLACK, + "gfxFcPlatformFontList::gfxFcPlatformFontList"); + if (!mCheckFontUpdatesTimer) { + NS_WARNING("Failure to create font updates timer"); + } + } + } + +#ifdef MOZ_BUNDLED_FONTS + mBundledFontsInitialized = false; +#endif +} + +gfxFcPlatformFontList::~gfxFcPlatformFontList() { + AutoLock lock(mLock); + + if (mCheckFontUpdatesTimer) { + mCheckFontUpdatesTimer->Cancel(); + mCheckFontUpdatesTimer = nullptr; + } +#ifdef MOZ_WIDGET_GTK + ClearSystemFontOptions(); +#endif +} + +void gfxFcPlatformFontList::AddFontSetFamilies(FcFontSet* aFontSet, + const SandboxPolicy* aPolicy, + bool aAppFonts) { + // This iterates over the fonts in a font set and adds in gfxFontFamily + // objects for each family. Individual gfxFontEntry objects for each face + // are not created here; the patterns are just stored in the family. When + // a family is actually used, it will be populated with gfxFontEntry + // records and the patterns moved to those. + + if (NS_WARN_IF(!aFontSet)) { + return; + } + + FcChar8* lastFamilyName = (FcChar8*)""; + RefPtr<gfxFontconfigFontFamily> fontFamily; + nsAutoCString familyName; + for (int f = 0; f < aFontSet->nfont; f++) { + FcPattern* pattern = aFontSet->fonts[f]; + + // Skip any fonts that aren't readable for us (e.g. due to restrictive + // file ownership/permissions). + FcChar8* path; + if (FcPatternGetString(pattern, FC_FILE, 0, &path) != FcResultMatch) { + continue; + } + if (access(reinterpret_cast<const char*>(path), F_OK | R_OK) != 0) { + continue; + } + +#if defined(MOZ_SANDBOX) && defined(XP_LINUX) + // Skip any fonts that will be blocked by the content-process sandbox + // policy. + if (aPolicy && !(aPolicy->Lookup(reinterpret_cast<const char*>(path)) & + SandboxBroker::Perms::MAY_READ)) { + continue; + } +#endif + + AddPatternToFontList(pattern, lastFamilyName, familyName, fontFamily, + aAppFonts); + } +} + +void gfxFcPlatformFontList::AddPatternToFontList( + FcPattern* aFont, FcChar8*& aLastFamilyName, nsACString& aFamilyName, + RefPtr<gfxFontconfigFontFamily>& aFontFamily, bool aAppFonts) { + // get canonical name + uint32_t cIndex = FindCanonicalNameIndex(aFont, FC_FAMILYLANG); + FcChar8* canonical = nullptr; + FcPatternGetString(aFont, FC_FAMILY, cIndex, &canonical); + if (!canonical) { + return; + } + + // same as the last one? no need to add a new family, skip + if (FcStrCmp(canonical, aLastFamilyName) != 0) { + aLastFamilyName = canonical; + + // add new family if one doesn't already exist + aFamilyName.Truncate(); + aFamilyName = ToCharPtr(canonical); + nsAutoCString keyName(aFamilyName); + ToLowerCase(keyName); + + aFontFamily = static_cast<gfxFontconfigFontFamily*>( + mFontFamilies + .LookupOrInsertWith(keyName, + [&] { + FontVisibility visibility = + aAppFonts + ? FontVisibility::Base + : GetVisibilityForFamily(keyName); + return MakeRefPtr<gfxFontconfigFontFamily>( + aFamilyName, visibility); + }) + .get()); + // Record if the family contains fonts from the app font set + // (in which case we won't rely on fontconfig's charmap, due to + // bug 1276594). + if (aAppFonts) { + aFontFamily->SetFamilyContainsAppFonts(true); + } + } + + // Add pointers to other localized family names. Most fonts + // only have a single name, so the first call to GetString + // will usually not match + FcChar8* otherName; + int n = (cIndex == 0 ? 1 : 0); + AutoTArray<nsCString, 4> otherFamilyNames; + while (FcPatternGetString(aFont, FC_FAMILY, n, &otherName) == FcResultMatch) { + otherFamilyNames.AppendElement(nsCString(ToCharPtr(otherName))); + n++; + if (n == int(cIndex)) { + n++; // skip over canonical name + } + } + if (!otherFamilyNames.IsEmpty()) { + AddOtherFamilyNames(aFontFamily, otherFamilyNames); + } + + const bool singleName = n == 1; + + MOZ_ASSERT(aFontFamily, "font must belong to a font family"); + aFontFamily->AddFontPattern(aFont, singleName); + + // map the psname, fullname ==> font family for local font lookups + nsAutoCString psname, fullname; + GetFaceNames(aFont, aFamilyName, psname, fullname); + if (!psname.IsEmpty()) { + ToLowerCase(psname); + mLocalNames.InsertOrUpdate(psname, RefPtr{aFont}); + } + if (!fullname.IsEmpty()) { + ToLowerCase(fullname); + mLocalNames.WithEntryHandle(fullname, [&](auto&& entry) { + if (entry && !singleName) { + return; + } + entry.InsertOrUpdate(RefPtr{aFont}); + }); + } +} + +nsresult gfxFcPlatformFontList::InitFontListForPlatform() { +#ifdef MOZ_BUNDLED_FONTS + if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) { + ActivateBundledFonts(); + } +#endif + + mLocalNames.Clear(); + mFcSubstituteCache.Clear(); + + ClearSystemFontOptions(); + + mAlwaysUseFontconfigGenerics = PrefFontListsUseOnlyGenerics(); + mOtherFamilyNamesInitialized = true; + + mLastConfig = FcConfigGetCurrent(); + + if (XRE_IsContentProcess()) { + // Content process: use the font list passed from the chrome process, + // because we can't rely on fontconfig in the presence of sandboxing; + // it may report fonts that we can't actually access. + + FcChar8* lastFamilyName = (FcChar8*)""; + RefPtr<gfxFontconfigFontFamily> fontFamily; + nsAutoCString familyName; + + // Get font list that was passed during XPCOM startup + // or in an UpdateFontList message. + auto& fontList = dom::ContentChild::GetSingleton()->SystemFontList(); + +#ifdef MOZ_WIDGET_GTK + UpdateSystemFontOptionsFromIpc(fontList.options()); +#endif + + // For fontconfig versions between 2.10.94 and 2.11.1 inclusive, + // we need to escape any leading space in the charset element, + // otherwise FcNameParse will fail. :( + // + // The bug was introduced on 2013-05-24 by + // https://cgit.freedesktop.org/fontconfig/commit/?id=cd9b1033a68816a7acfbba1718ba0aa5888f6ec7 + // "Bug 64906 - FcNameParse() should ignore leading whitespace in + // parameters" + // because ignoring a leading space in the encoded value of charset + // causes erroneous decoding of the whole element. + // This first shipped in version 2.10.94, and was eventually fixed as + // a side-effect of switching to the "human-readable" representation of + // charsets on 2014-07-03 in + // https://cgit.freedesktop.org/fontconfig/commit/?id=e708e97c351d3bc9f7030ef22ac2f007d5114730 + // "Change charset parse/unparse format to be human readable" + // (with a followup fix next day) which means a leading space is no + // longer significant. This fix landed after 2.11.1 had been shipped, + // so the first version tag without the bug is 2.11.91. + int fcVersion = FcGetVersion(); + bool fcCharsetParseBug = fcVersion >= 21094 && fcVersion <= 21101; + + for (FontPatternListEntry& fpe : fontList.entries()) { + nsCString& patternStr = fpe.pattern(); + if (fcCharsetParseBug) { + int32_t index = patternStr.Find(":charset= "); + if (index != kNotFound) { + // insert backslash after the =, before the space + patternStr.Insert('\\', index + 9); + } + } + FcPattern* pattern = FcNameParse((const FcChar8*)patternStr.get()); + AddPatternToFontList(pattern, lastFamilyName, familyName, fontFamily, + fpe.appFontFamily()); + FcPatternDestroy(pattern); + } + + LOG_FONTLIST( + ("got font list from chrome process: " + "%u faces in %u families", + (unsigned)fontList.entries().Length(), mFontFamilies.Count())); + + fontList.entries().Clear(); + return NS_OK; + } + + UpdateSystemFontOptions(); + + UniquePtr<SandboxPolicy> policy; + +#if defined(MOZ_SANDBOX) && defined(XP_LINUX) + // If read sandboxing is enabled, create a temporary SandboxPolicy to + // check font paths; use a fake PID to avoid picking up any PID-specific + // rules by accident. + SandboxBrokerPolicyFactory policyFactory; + if (GetEffectiveContentSandboxLevel() > 2 && + !PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX")) { + policy = policyFactory.GetContentPolicy(-1, false); + } +#endif + +#ifdef MOZ_BUNDLED_FONTS + // https://bugzilla.mozilla.org/show_bug.cgi?id=1745715: + // It's important to do this *before* processing the standard system fonts, + // so that if a family is present in both font sets, we'll treat it as app- + // bundled and therefore always visible. + if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) { + FcFontSet* appFonts = FcConfigGetFonts(nullptr, FcSetApplication); + AddFontSetFamilies(appFonts, policy.get(), /* aAppFonts = */ true); + } +#endif + + // iterate over available fonts + FcFontSet* systemFonts = FcConfigGetFonts(nullptr, FcSetSystem); + AddFontSetFamilies(systemFonts, policy.get(), /* aAppFonts = */ false); + + return NS_OK; +} + +void gfxFcPlatformFontList::ReadSystemFontList(dom::SystemFontList* retValue) { + AutoLock lock(mLock); + + // Fontconfig versions below 2.9 drop the FC_FILE element in FcNameUnparse + // (see https://bugs.freedesktop.org/show_bug.cgi?id=26718), so when using + // an older version, we manually append it to the unparsed pattern. +#ifdef MOZ_WIDGET_GTK + SystemFontOptionsToIpc(retValue->options()); +#endif + + if (FcGetVersion() < 20900) { + for (const auto& entry : mFontFamilies) { + auto* family = static_cast<gfxFontconfigFontFamily*>(entry.GetWeak()); + family->AddFacesToFontList([&](FcPattern* aPat, bool aAppFonts) { + char* s = (char*)FcNameUnparse(aPat); + nsDependentCString patternStr(s); + char* file = nullptr; + if (FcResultMatch == + FcPatternGetString(aPat, FC_FILE, 0, (FcChar8**)&file)) { + patternStr.Append(":file="); + patternStr.Append(file); + } + retValue->entries().AppendElement( + FontPatternListEntry(patternStr, aAppFonts)); + free(s); + }); + } + } else { + for (const auto& entry : mFontFamilies) { + auto* family = static_cast<gfxFontconfigFontFamily*>(entry.GetWeak()); + family->AddFacesToFontList([&](FcPattern* aPat, bool aAppFonts) { + char* s = (char*)FcNameUnparse(aPat); + nsDependentCString patternStr(s); + retValue->entries().AppendElement( + FontPatternListEntry(patternStr, aAppFonts)); + free(s); + }); + } + } +} + +using Device = nsIGfxInfo::FontVisibilityDeviceDetermination; +static Device sFontVisibilityDevice = Device::Unassigned; + +void AssignFontVisibilityDevice() { + if (sFontVisibilityDevice == Device::Unassigned) { + nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); + NS_ENSURE_SUCCESS_VOID( + gfxInfo->GetFontVisibilityDetermination(&sFontVisibilityDevice)); + } +} + +// Per family array of faces. +class FacesData { + using FaceInitArray = AutoTArray<fontlist::Face::InitData, 8>; + + FaceInitArray mFaces; + + // Number of faces that have a single name. Faces that have multiple names are + // sorted last. + uint32_t mUniqueNameFaceCount = 0; + + public: + void Add(fontlist::Face::InitData&& aData, bool aSingleName) { + if (aSingleName) { + mFaces.InsertElementAt(mUniqueNameFaceCount++, std::move(aData)); + } else { + mFaces.AppendElement(std::move(aData)); + } + } + + const FaceInitArray& Get() const { return mFaces; } +}; + +void gfxFcPlatformFontList::InitSharedFontListForPlatform() { + mLocalNames.Clear(); + mFcSubstituteCache.Clear(); + + mAlwaysUseFontconfigGenerics = PrefFontListsUseOnlyGenerics(); + mOtherFamilyNamesInitialized = true; + + mLastConfig = FcConfigGetCurrent(); + + if (!XRE_IsParentProcess()) { +#ifdef MOZ_WIDGET_GTK + auto& fontList = dom::ContentChild::GetSingleton()->SystemFontList(); + UpdateSystemFontOptionsFromIpc(fontList.options()); +#endif + // Content processes will access the shared-memory data created by the + // parent, so they do not need to query fontconfig for the available + // fonts themselves. + return; + } + +#ifdef MOZ_WIDGET_GTK + UpdateSystemFontOptions(); +#endif + +#ifdef MOZ_BUNDLED_FONTS + if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) { + TimeStamp start = TimeStamp::Now(); + ActivateBundledFonts(); + TimeStamp end = TimeStamp::Now(); + Telemetry::Accumulate(Telemetry::FONTLIST_BUNDLEDFONTS_ACTIVATE, + (end - start).ToMilliseconds()); + } +#endif + + UniquePtr<SandboxPolicy> policy; + +#if defined(MOZ_CONTENT_SANDBOX) && defined(XP_LINUX) + // If read sandboxing is enabled, create a temporary SandboxPolicy to + // check font paths; use a fake PID to avoid picking up any PID-specific + // rules by accident. + SandboxBrokerPolicyFactory policyFactory; + if (GetEffectiveContentSandboxLevel() > 2 && + !PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX")) { + policy = policyFactory.GetContentPolicy(-1, false); + } +#endif + + nsTArray<fontlist::Family::InitData> families; + + nsClassHashtable<nsCStringHashKey, FacesData> faces; + + // Do we need to work around the fontconfig FcNameParse/FcNameUnparse bug + // (present in versions between 2.10.94 and 2.11.1 inclusive)? See comment + // in InitFontListForPlatform for details. + int fcVersion = FcGetVersion(); + bool fcCharsetParseBug = fcVersion >= 21094 && fcVersion <= 21101; + + // Returns true if the font was added with FontVisibility::Base. + // This enables us to count how many known Base fonts are present. + auto addPattern = [this, fcCharsetParseBug, &families, &faces]( + FcPattern* aPattern, FcChar8*& aLastFamilyName, + nsCString& aFamilyName, bool aAppFont) -> bool { + // get canonical name + uint32_t cIndex = FindCanonicalNameIndex(aPattern, FC_FAMILYLANG); + FcChar8* canonical = nullptr; + FcPatternGetString(aPattern, FC_FAMILY, cIndex, &canonical); + if (!canonical) { + return false; + } + + nsAutoCString keyName; + keyName = ToCharPtr(canonical); + ToLowerCase(keyName); + + aLastFamilyName = canonical; + aFamilyName = ToCharPtr(canonical); + + const FontVisibility visibility = + aAppFont ? FontVisibility::Base : GetVisibilityForFamily(keyName); + + // Same canonical family name as the last one? Definitely no need to add a + // new family record. + auto* faceList = + faces + .LookupOrInsertWith( + keyName, + [&] { + families.AppendElement(fontlist::Family::InitData( + keyName, aFamilyName, fontlist::Family::kNoIndex, + visibility, + /*bundled*/ aAppFont, /*badUnderline*/ false)); + return MakeUnique<FacesData>(); + }) + .get(); + + char* s = (char*)FcNameUnparse(aPattern); + nsAutoCString descriptor(s); + free(s); + + if (fcCharsetParseBug) { + // Escape any leading space in charset to work around FcNameParse bug. + int32_t index = descriptor.Find(":charset= "); + if (index != kNotFound) { + // insert backslash after the =, before the space + descriptor.Insert('\\', index + 9); + } + } + + WeightRange weight(FontWeight::NORMAL); + StretchRange stretch(FontStretch::NORMAL); + SlantStyleRange style(FontSlantStyle::NORMAL); + uint16_t size; + GetFontProperties(aPattern, &weight, &stretch, &style, &size); + + auto initData = fontlist::Face::InitData{descriptor, 0, size, false, + weight, stretch, style}; + + // Add entries for any other localized family names. (Most fonts only have + // a single family name, so the first call to GetString will usually fail). + // These get the same visibility level as we looked up for the first name. + FcChar8* otherName; + int n = (cIndex == 0 ? 1 : 0); + while (FcPatternGetString(aPattern, FC_FAMILY, n, &otherName) == + FcResultMatch) { + nsAutoCString otherFamilyName(ToCharPtr(otherName)); + keyName = otherFamilyName; + ToLowerCase(keyName); + + faces + .LookupOrInsertWith( + keyName, + [&] { + families.AppendElement(fontlist::Family::InitData( + keyName, otherFamilyName, fontlist::Family::kNoIndex, + visibility, + /*bundled*/ aAppFont, /*badUnderline*/ false)); + + return MakeUnique<FacesData>(); + }) + .get() + ->Add(fontlist::Face::InitData(initData), /* singleName = */ false); + + n++; + if (n == int(cIndex)) { + n++; // skip over canonical name + } + } + + const bool singleName = n == 1; + faceList->Add(std::move(initData), singleName); + + // map the psname, fullname ==> font family for local font lookups + nsAutoCString psname, fullname; + GetFaceNames(aPattern, aFamilyName, psname, fullname); + if (!psname.IsEmpty()) { + ToLowerCase(psname); + mLocalNameTable.InsertOrUpdate( + psname, fontlist::LocalFaceRec::InitData(keyName, descriptor)); + } + if (!fullname.IsEmpty()) { + ToLowerCase(fullname); + if (fullname != psname) { + mLocalNameTable.WithEntryHandle(fullname, [&](auto&& entry) { + if (entry && !singleName) { + // We only override an existing entry if this is the only way to + // name this family. This prevents dubious aliases from clobbering + // the local name table. + return; + } + entry.InsertOrUpdate( + fontlist::LocalFaceRec::InitData(keyName, descriptor)); + }); + } + } + + return visibility == FontVisibility::Base; + }; + + // Returns the number of families with FontVisibility::Base that were found. + auto addFontSetFamilies = [&addPattern](FcFontSet* aFontSet, + SandboxPolicy* aPolicy, + bool aAppFonts) -> size_t { + size_t count = 0; + if (NS_WARN_IF(!aFontSet)) { + return count; + } + FcChar8* lastFamilyName = (FcChar8*)""; + RefPtr<gfxFontconfigFontFamily> fontFamily; + nsAutoCString familyName; + for (int f = 0; f < aFontSet->nfont; f++) { + FcPattern* pattern = aFontSet->fonts[f]; + + // Skip any fonts that aren't readable for us (e.g. due to restrictive + // file ownership/permissions). + FcChar8* path; + if (FcPatternGetString(pattern, FC_FILE, 0, &path) != FcResultMatch) { + continue; + } + if (access(reinterpret_cast<const char*>(path), F_OK | R_OK) != 0) { + continue; + } + +#if defined(MOZ_CONTENT_SANDBOX) && defined(XP_LINUX) + // Skip any fonts that will be blocked by the content-process sandbox + // policy. + if (aPolicy && !(aPolicy->Lookup(reinterpret_cast<const char*>(path)) & + SandboxBroker::Perms::MAY_READ)) { + continue; + } +#endif + + // Clone the pattern, because we can't operate on the one belonging to + // the FcFontSet directly. + FcPattern* clone = FcPatternDuplicate(pattern); + + // Pick up any configuration options applicable to the font (e.g. custom + // fontfeatures settings). + if (!FcConfigSubstitute(nullptr, clone, FcMatchFont)) { + // Out of memory?! We're probably doomed, but just skip this font. + FcPatternDestroy(clone); + continue; + } + // But ignore hinting settings from FcConfigSubstitute, as we don't want + // to bake them into the pattern in the font list. + FcPatternDel(clone, FC_HINT_STYLE); + FcPatternDel(clone, FC_HINTING); + + // If this is a TrueType or OpenType font, discard the FC_CHARSET object + // (which may be very large), because we'll read the 'cmap' directly. + // This substantially reduces the pressure on shared memory (bug 1664151) + // due to the large font descriptors (serialized patterns). + FcChar8* fontFormat; + if (FcPatternGetString(clone, FC_FONTFORMAT, 0, &fontFormat) == + FcResultMatch && + (!FcStrCmp(fontFormat, (const FcChar8*)"TrueType") || + !FcStrCmp(fontFormat, (const FcChar8*)"CFF"))) { + FcPatternDel(clone, FC_CHARSET); + if (addPattern(clone, lastFamilyName, familyName, aAppFonts)) { + ++count; + } + } else { + if (addPattern(clone, lastFamilyName, familyName, aAppFonts)) { + ++count; + } + } + + FcPatternDestroy(clone); + } + return count; + }; + +#ifdef MOZ_BUNDLED_FONTS + // Add bundled fonts before system fonts, to set correct visibility status + // for any families that appear in both. + if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) { + FcFontSet* appFonts = FcConfigGetFonts(nullptr, FcSetApplication); + addFontSetFamilies(appFonts, policy.get(), /* aAppFonts = */ true); + } +#endif + + // iterate over available fonts + FcFontSet* systemFonts = FcConfigGetFonts(nullptr, FcSetSystem); + auto numBaseFamilies = addFontSetFamilies(systemFonts, policy.get(), + /* aAppFonts = */ false); + AssignFontVisibilityDevice(); + if (numBaseFamilies < 3 && sFontVisibilityDevice != Device::Linux_Unknown) { + // If we found fewer than 3 known FontVisibility::Base families in the + // system (ignoring app-bundled fonts), we must be dealing with a very + // non-standard configuration; disable the distro-specific font + // fingerprinting protection by marking all fonts as Unknown. + for (auto& f : families) { + f.mVisibility = FontVisibility::Unknown; + } + // Issue a warning that we're disabling this protection. + nsCOMPtr<nsIConsoleService> console( + do_GetService("@mozilla.org/consoleservice;1")); + if (console) { + console->LogStringMessage( + u"Font-fingerprinting protection disabled; not enough standard " + u"distro fonts installed."); + } + } + + mozilla::fontlist::FontList* list = SharedFontList(); + list->SetFamilyNames(families); + + for (uint32_t i = 0; i < families.Length(); i++) { + list->Families()[i].AddFaces(list, faces.Get(families[i].mKey)->Get()); + } +} + +FontVisibility gfxFcPlatformFontList::GetVisibilityForFamily( + const nsACString& aName) const { + AssignFontVisibilityDevice(); + + switch (sFontVisibilityDevice) { + case Device::Linux_Ubuntu_any: + case Device::Linux_Ubuntu_22: + if (FamilyInList(aName, kBaseFonts_Ubuntu_22_04)) { + return FontVisibility::Base; + } + if (FamilyInList(aName, kLangFonts_Ubuntu_22_04)) { + return FontVisibility::LangPack; + } + if (sFontVisibilityDevice == Device::Linux_Ubuntu_22) { + return FontVisibility::User; + } + // For Ubuntu_any, we fall through to also check the 20_04 lists. + [[fallthrough]]; + + case Device::Linux_Ubuntu_20: + if (FamilyInList(aName, kBaseFonts_Ubuntu_20_04)) { + return FontVisibility::Base; + } + if (FamilyInList(aName, kLangFonts_Ubuntu_20_04)) { + return FontVisibility::LangPack; + } + return FontVisibility::User; + + case Device::Linux_Fedora_any: + case Device::Linux_Fedora_39: + if (FamilyInList(aName, kBaseFonts_Fedora_39)) { + return FontVisibility::Base; + } + if (sFontVisibilityDevice == Device::Linux_Fedora_39) { + return FontVisibility::User; + } + // For Fedora_any, fall through to also check Fedora 38 list. + [[fallthrough]]; + + case Device::Linux_Fedora_38: + if (FamilyInList(aName, kBaseFonts_Fedora_38)) { + return FontVisibility::Base; + } + return FontVisibility::User; + + default: + // We don't know how to categorize fonts on this system + return FontVisibility::Unknown; + } +} + +gfxFontEntry* gfxFcPlatformFontList::CreateFontEntry( + fontlist::Face* aFace, const fontlist::Family* aFamily) { + nsAutoCString desc(aFace->mDescriptor.AsString(SharedFontList())); + FcPattern* pattern = FcNameParse((const FcChar8*)desc.get()); + auto* fe = new gfxFontconfigFontEntry(desc, pattern, true); + FcPatternDestroy(pattern); + fe->InitializeFrom(aFace, aFamily); + return fe; +} + +// For displaying the fontlist in UI, use explicit call to FcFontList. Using +// FcFontList results in the list containing the localized names as dictated +// by system defaults. +static void GetSystemFontList(nsTArray<nsString>& aListOfFonts, + nsAtom* aLangGroup) { + aListOfFonts.Clear(); + + RefPtr<FcPattern> pat = dont_AddRef(FcPatternCreate()); + if (!pat) { + return; + } + + UniquePtr<FcObjectSet> os(FcObjectSetBuild(FC_FAMILY, nullptr)); + if (!os) { + return; + } + + // add the lang to the pattern + nsAutoCString fcLang; + gfxFcPlatformFontList* pfl = gfxFcPlatformFontList::PlatformFontList(); + pfl->GetSampleLangForGroup(aLangGroup, fcLang, + /*aForFontEnumerationThread*/ true); + if (!fcLang.IsEmpty()) { + FcPatternAddString(pat, FC_LANG, ToFcChar8Ptr(fcLang.get())); + } + + UniquePtr<FcFontSet> fs(FcFontList(nullptr, pat, os.get())); + if (!fs) { + return; + } + + for (int i = 0; i < fs->nfont; i++) { + char* family; + + if (FcPatternGetString(fs->fonts[i], FC_FAMILY, 0, (FcChar8**)&family) != + FcResultMatch) { + continue; + } + + // Remove duplicates... + nsAutoString strFamily; + AppendUTF8toUTF16(MakeStringSpan(family), strFamily); + if (aListOfFonts.Contains(strFamily)) { + continue; + } + + aListOfFonts.AppendElement(strFamily); + } + + aListOfFonts.Sort(); +} + +void gfxFcPlatformFontList::GetFontList(nsAtom* aLangGroup, + const nsACString& aGenericFamily, + nsTArray<nsString>& aListOfFonts) { + // Get the list of font family names using fontconfig + GetSystemFontList(aListOfFonts, aLangGroup); + + // Under Linux, the generics "serif", "sans-serif" and "monospace" + // are included in the pref fontlist. These map to whatever fontconfig + // decides they should be for a given language, rather than one of the + // fonts listed in the prefs font lists (e.g. font.name.*, font.name-list.*) + bool serif = false, sansSerif = false, monospace = false; + if (aGenericFamily.IsEmpty()) + serif = sansSerif = monospace = true; + else if (aGenericFamily.LowerCaseEqualsLiteral("serif")) + serif = true; + else if (aGenericFamily.LowerCaseEqualsLiteral("sans-serif")) + sansSerif = true; + else if (aGenericFamily.LowerCaseEqualsLiteral("monospace")) + monospace = true; + else if (aGenericFamily.LowerCaseEqualsLiteral("cursive") || + aGenericFamily.LowerCaseEqualsLiteral("fantasy")) + serif = sansSerif = true; + else + MOZ_ASSERT_UNREACHABLE("unexpected CSS generic font family"); + + // The first in the list becomes the default in + // FontBuilder.readFontSelection() if the preference-selected font is not + // available, so put system configured defaults first. + if (monospace) aListOfFonts.InsertElementAt(0, u"monospace"_ns); + if (sansSerif) aListOfFonts.InsertElementAt(0, u"sans-serif"_ns); + if (serif) aListOfFonts.InsertElementAt(0, u"serif"_ns); +} + +FontFamily gfxFcPlatformFontList::GetDefaultFontForPlatform( + nsPresContext* aPresContext, const gfxFontStyle* aStyle, + nsAtom* aLanguage) { + // Get the default font by using a fake name to retrieve the first + // scalable font that fontconfig suggests for the given language. + PrefFontList* prefFonts = + FindGenericFamilies(aPresContext, "-moz-default"_ns, + aLanguage ? aLanguage : nsGkAtoms::x_western); + NS_ASSERTION(prefFonts, "null list of generic fonts"); + if (prefFonts && !prefFonts->IsEmpty()) { + return (*prefFonts)[0]; + } + return FontFamily(); +} + +gfxFontEntry* gfxFcPlatformFontList::LookupLocalFont( + nsPresContext* aPresContext, const nsACString& aFontName, + WeightRange aWeightForEntry, StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) { + AutoLock lock(mLock); + + nsAutoCString keyName(aFontName); + ToLowerCase(keyName); + + if (SharedFontList()) { + return LookupInSharedFaceNameList(aPresContext, aFontName, aWeightForEntry, + aStretchForEntry, aStyleForEntry); + } + + // if name is not in the global list, done + const auto fontPattern = mLocalNames.Lookup(keyName); + if (!fontPattern) { + return nullptr; + } + + return new gfxFontconfigFontEntry(aFontName, *fontPattern, aWeightForEntry, + aStretchForEntry, aStyleForEntry); +} + +gfxFontEntry* gfxFcPlatformFontList::MakePlatformFont( + const nsACString& aFontName, WeightRange aWeightForEntry, + StretchRange aStretchForEntry, SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, uint32_t aLength) { + RefPtr<FTUserFontData> ufd = new FTUserFontData(aFontData, aLength); + RefPtr<SharedFTFace> face = ufd->CloneFace(); + if (!face) { + return nullptr; + } + return new gfxFontconfigFontEntry(aFontName, aWeightForEntry, + aStretchForEntry, aStyleForEntry, + std::move(face)); +} + +static bool UseCustomFontconfigLookupsForLocale(const Locale& aLocale) { + return aLocale.Script().EqualTo("Hans") || aLocale.Script().EqualTo("Hant") || + aLocale.Script().EqualTo("Jpan") || aLocale.Script().EqualTo("Kore") || + aLocale.Script().EqualTo("Arab"); +} + +bool gfxFcPlatformFontList::FindAndAddFamiliesLocked( + nsPresContext* aPresContext, StyleGenericFontFamily aGeneric, + const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput, + FindFamiliesFlags aFlags, gfxFontStyle* aStyle, nsAtom* aLanguage, + gfxFloat aDevToCssSize) { + nsAutoCString familyName(aFamily); + ToLowerCase(familyName); + + if (!(aFlags & FindFamiliesFlags::eQuotedFamilyName)) { + // deprecated generic names are explicitly converted to standard generics + bool isDeprecatedGeneric = false; + if (familyName.EqualsLiteral("sans") || + familyName.EqualsLiteral("sans serif")) { + familyName.AssignLiteral("sans-serif"); + isDeprecatedGeneric = true; + } else if (familyName.EqualsLiteral("mono")) { + familyName.AssignLiteral("monospace"); + isDeprecatedGeneric = true; + } + + // fontconfig generics? use fontconfig to determine the family for lang + if (isDeprecatedGeneric || + mozilla::StyleSingleFontFamily::Parse(familyName).IsGeneric()) { + PrefFontList* prefFonts = + FindGenericFamilies(aPresContext, familyName, aLanguage); + if (prefFonts && !prefFonts->IsEmpty()) { + aOutput->AppendElements(*prefFonts); + return true; + } + return false; + } + } + + // fontconfig allows conditional substitutions in such a way that it's + // difficult to distinguish an explicit substitution from other suggested + // choices. To sniff out explicit substitutions, compare the substitutions + // for "font, -moz-sentinel" to "-moz-sentinel" to sniff out the + // substitutions + // + // Example: + // + // serif ==> DejaVu Serif, ... + // Helvetica, serif ==> Helvetica, TeX Gyre Heros, Nimbus Sans L, DejaVu + // Serif + // + // In this case fontconfig is including Tex Gyre Heros and + // Nimbus Sans L as alternatives for Helvetica. + + // Because the FcConfigSubstitute call is quite expensive, we cache the + // actual font families found via this process. + nsAutoCString cacheKey; + + // For languages that use CJK or Arabic script, we include the language as + // part of the cache key because fontconfig may have lang-specific rules that + // specify different substitutions. (In theory, this could apply to *any* + // language, but it's highly unlikely to matter for non-CJK/Arabic scripts, + // and it gets really expensive to do separate lookups for 300+ distinct lang + // tags e.g. on wikipedia.org, when they all end up mapping to the same font + // list.) + // We remember the most recently checked aLanguage atom so that when the same + // language is used in many successive calls, we can avoid repeating the + // locale code processing every time. + if (aLanguage != mPrevLanguage) { + GetSampleLangForGroup(aLanguage, mSampleLang); + ToLowerCase(mSampleLang); + Locale locale; + mUseCustomLookups = LocaleParser::TryParse(mSampleLang, locale).isOk() && + locale.AddLikelySubtags().isOk() && + UseCustomFontconfigLookupsForLocale(locale); + mPrevLanguage = aLanguage; + } + if (mUseCustomLookups) { + cacheKey = mSampleLang; + cacheKey.Append(':'); + } + + cacheKey.Append(familyName); + auto vis = + aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User; + cacheKey.Append(':'); + cacheKey.AppendInt(int(vis)); + if (const auto& cached = mFcSubstituteCache.Lookup(cacheKey)) { + if (cached->IsEmpty()) { + return false; + } + aOutput->AppendElements(*cached); + return true; + } + + // It wasn't in the cache, so we need to ask fontconfig... + const FcChar8* kSentinelName = ToFcChar8Ptr("-moz-sentinel"); + const FcChar8* terminator = nullptr; + RefPtr<FcPattern> sentinelSubst = dont_AddRef(FcPatternCreate()); + FcPatternAddString(sentinelSubst, FC_FAMILY, kSentinelName); + if (!mSampleLang.IsEmpty()) { + FcPatternAddString(sentinelSubst, FC_LANG, ToFcChar8Ptr(mSampleLang.get())); + } + FcConfigSubstitute(nullptr, sentinelSubst, FcMatchPattern); + + // If the sentinel name is still present, we'll use that as the terminator + // for the family names we collect; this means that if fontconfig prepends + // additional family names (e.g. an emoji font, or lang-specific preferred + // font) to all patterns, it won't simply mask all actual requested names. + // If the sentinel has been deleted/replaced altogether, then we'll take + // the first substitute name as the new terminator. + FcChar8* substName; + for (int i = 0; FcPatternGetString(sentinelSubst, FC_FAMILY, i, &substName) == + FcResultMatch; + i++) { + if (FcStrCmp(substName, kSentinelName) == 0) { + terminator = kSentinelName; + break; + } + if (!terminator) { + terminator = substName; + } + } + + // substitutions for font, -moz-sentinel pattern + RefPtr<FcPattern> fontWithSentinel = dont_AddRef(FcPatternCreate()); + FcPatternAddString(fontWithSentinel, FC_FAMILY, + ToFcChar8Ptr(familyName.get())); + FcPatternAddString(fontWithSentinel, FC_FAMILY, kSentinelName); + if (!mSampleLang.IsEmpty()) { + FcPatternAddString(sentinelSubst, FC_LANG, ToFcChar8Ptr(mSampleLang.get())); + } + FcConfigSubstitute(nullptr, fontWithSentinel, FcMatchPattern); + + // Add all font family matches until reaching the terminator. + AutoTArray<FamilyAndGeneric, 10> cachedFamilies; + for (int i = 0; FcPatternGetString(fontWithSentinel, FC_FAMILY, i, + &substName) == FcResultMatch; + i++) { + if (terminator && FcStrCmp(substName, terminator) == 0) { + break; + } + gfxPlatformFontList::FindAndAddFamiliesLocked( + aPresContext, aGeneric, nsDependentCString(ToCharPtr(substName)), + &cachedFamilies, aFlags, aStyle, aLanguage); + } + + const auto& insertedCachedFamilies = + mFcSubstituteCache.InsertOrUpdate(cacheKey, std::move(cachedFamilies)); + + if (insertedCachedFamilies.IsEmpty()) { + return false; + } + aOutput->AppendElements(insertedCachedFamilies); + return true; +} + +bool gfxFcPlatformFontList::GetStandardFamilyName(const nsCString& aFontName, + nsACString& aFamilyName) { + aFamilyName.Truncate(); + + // The fontconfig list of fonts includes generic family names in the + // font list. For these, just use the generic name. + if (aFontName.EqualsLiteral("serif") || + aFontName.EqualsLiteral("sans-serif") || + aFontName.EqualsLiteral("monospace")) { + aFamilyName.Assign(aFontName); + return true; + } + + RefPtr<FcPattern> pat = dont_AddRef(FcPatternCreate()); + if (!pat) { + return true; + } + + UniquePtr<FcObjectSet> os(FcObjectSetBuild(FC_FAMILY, nullptr)); + if (!os) { + return true; + } + + // add the family name to the pattern + FcPatternAddString(pat, FC_FAMILY, ToFcChar8Ptr(aFontName.get())); + + UniquePtr<FcFontSet> givenFS(FcFontList(nullptr, pat, os.get())); + if (!givenFS) { + return true; + } + + // See if there is a font face with first family equal to the given family + // (needs to be in sync with names coming from GetFontList()) + nsTArray<nsCString> candidates; + for (int i = 0; i < givenFS->nfont; i++) { + char* firstFamily; + + if (FcPatternGetString(givenFS->fonts[i], FC_FAMILY, 0, + (FcChar8**)&firstFamily) != FcResultMatch) { + continue; + } + + nsDependentCString first(firstFamily); + if (!candidates.Contains(first)) { + candidates.AppendElement(first); + + if (aFontName.Equals(first)) { + aFamilyName.Assign(aFontName); + return true; + } + } + } + + // Because fontconfig conflates different family name types, need to + // double check that the candidate name is not simply a different + // name type. For example, if a font with nameID=16 "Minion Pro" and + // nameID=21 "Minion Pro Caption" exists, calling FcFontList with + // family="Minion Pro" will return a set of patterns some of which + // will have a first family of "Minion Pro Caption". Ignore these + // patterns and use the first candidate that maps to a font set with + // the same number of faces and an identical set of patterns. + for (uint32_t j = 0; j < candidates.Length(); ++j) { + FcPatternDel(pat, FC_FAMILY); + FcPatternAddString(pat, FC_FAMILY, (FcChar8*)candidates[j].get()); + + UniquePtr<FcFontSet> candidateFS(FcFontList(nullptr, pat, os.get())); + if (!candidateFS) { + return true; + } + + if (candidateFS->nfont != givenFS->nfont) { + continue; + } + + bool equal = true; + for (int i = 0; i < givenFS->nfont; ++i) { + if (!FcPatternEqual(candidateFS->fonts[i], givenFS->fonts[i])) { + equal = false; + break; + } + } + if (equal) { + aFamilyName = candidates[j]; + return true; + } + } + + // didn't find localized name, leave family name blank + return true; +} + +void gfxFcPlatformFontList::AddGenericFonts( + nsPresContext* aPresContext, StyleGenericFontFamily aGenericType, + nsAtom* aLanguage, nsTArray<FamilyAndGeneric>& aFamilyList) { + const char* generic = GetGenericName(aGenericType); + NS_ASSERTION(generic, "weird generic font type"); + if (!generic) { + return; + } + + // By default, most font prefs on Linux map to "use fontconfig" + // keywords. So only need to explicitly lookup font pref if + // non-default settings exist, or if we are the system-ui font, which we deal + // with in the base class. + const bool isSystemUi = aGenericType == StyleGenericFontFamily::SystemUi; + bool usePrefFontList = isSystemUi; + + nsAutoCString genericToLookup(generic); + if ((!mAlwaysUseFontconfigGenerics && aLanguage) || + aLanguage == nsGkAtoms::x_math) { + nsAtom* langGroup = GetLangGroup(aLanguage); + nsAutoCString fontlistValue; + mFontPrefs->LookupName(PrefName(generic, langGroup), fontlistValue); + if (fontlistValue.IsEmpty()) { + // The font name list may have two or more family names as comma + // separated list. In such case, not matching with generic font + // name is fine because if the list prefers specific font, we + // should try to use the pref with complicated path. + mFontPrefs->LookupNameList(PrefName(generic, langGroup), fontlistValue); + } + if (!fontlistValue.IsEmpty()) { + if (!fontlistValue.EqualsLiteral("serif") && + !fontlistValue.EqualsLiteral("sans-serif") && + !fontlistValue.EqualsLiteral("monospace")) { + usePrefFontList = true; + } else { + // serif, sans-serif or monospace was specified + genericToLookup = fontlistValue; + } + } + } + + // when pref fonts exist, use standard pref font lookup + if (usePrefFontList) { + gfxPlatformFontList::AddGenericFonts(aPresContext, aGenericType, aLanguage, + aFamilyList); + if (!isSystemUi) { + return; + } + } + + AutoLock lock(mLock); + PrefFontList* prefFonts = + FindGenericFamilies(aPresContext, genericToLookup, aLanguage); + NS_ASSERTION(prefFonts, "null generic font list"); + aFamilyList.SetCapacity(aFamilyList.Length() + prefFonts->Length()); + for (auto& f : *prefFonts) { + aFamilyList.AppendElement(FamilyAndGeneric(f, aGenericType)); + } +} + +void gfxFcPlatformFontList::ClearLangGroupPrefFontsLocked() { + ClearGenericMappingsLocked(); + gfxPlatformFontList::ClearLangGroupPrefFontsLocked(); + mAlwaysUseFontconfigGenerics = PrefFontListsUseOnlyGenerics(); +} + +gfxPlatformFontList::PrefFontList* gfxFcPlatformFontList::FindGenericFamilies( + nsPresContext* aPresContext, const nsCString& aGeneric, nsAtom* aLanguage) { + // set up name + nsAutoCString fcLang; + GetSampleLangForGroup(aLanguage, fcLang); + ToLowerCase(fcLang); + + nsAutoCString cacheKey(aGeneric); + if (fcLang.Length() > 0) { + cacheKey.Append('-'); + // If the script is CJK or Arabic, we cache by lang so that different fonts + // various locales can be supported; but otherwise, we cache by script + // subtag, to avoid a proliferation of entries for Western & similar + // languages. + // In theory, this means we could fail to respect custom fontconfig rules + // for individual (non-CJK/Arab) languages that share the same script, but + // such setups are probably vanishingly rare. + Locale locale; + if (LocaleParser::TryParse(fcLang, locale).isOk() && + locale.AddLikelySubtags().isOk()) { + if (UseCustomFontconfigLookupsForLocale(locale)) { + cacheKey.Append(fcLang); + } else { + cacheKey.Append(locale.Script().Span()); + } + } else { + cacheKey.Append(fcLang); + } + } + + // try to get the family from the cache + return mGenericMappings.WithEntryHandle( + cacheKey, [&](auto&& entry) -> PrefFontList* { + if (!entry) { + // if not found, ask fontconfig to pick the appropriate font + RefPtr<FcPattern> genericPattern = dont_AddRef(FcPatternCreate()); + FcPatternAddString(genericPattern, FC_FAMILY, + ToFcChar8Ptr(aGeneric.get())); + + // -- prefer scalable fonts + FcPatternAddBool(genericPattern, FC_SCALABLE, FcTrue); + + // -- add the lang to the pattern + if (!fcLang.IsEmpty()) { + FcPatternAddString(genericPattern, FC_LANG, + ToFcChar8Ptr(fcLang.get())); + } + + // -- perform substitutions + FcConfigSubstitute(nullptr, genericPattern, FcMatchPattern); + FcDefaultSubstitute(genericPattern); + + // -- sort to get the closest matches + FcResult result; + UniquePtr<FcFontSet> faces( + FcFontSort(nullptr, genericPattern, FcFalse, nullptr, &result)); + + if (!faces) { + return nullptr; + } + + // -- select the fonts to be used for the generic + auto prefFonts = MakeUnique<PrefFontList>(); // can be empty but in + // practice won't happen + uint32_t limit = StaticPrefs:: + gfx_font_rendering_fontconfig_max_generic_substitutions(); + bool foundFontWithLang = false; + for (int i = 0; i < faces->nfont; i++) { + FcPattern* font = faces->fonts[i]; + FcChar8* mappedGeneric = nullptr; + + FcPatternGetString(font, FC_FAMILY, 0, &mappedGeneric); + if (mappedGeneric) { + mLock.AssertCurrentThreadIn(); + nsAutoCString mappedGenericName(ToCharPtr(mappedGeneric)); + AutoTArray<FamilyAndGeneric, 1> genericFamilies; + if (gfxPlatformFontList::FindAndAddFamiliesLocked( + aPresContext, StyleGenericFontFamily::None, + mappedGenericName, &genericFamilies, + FindFamiliesFlags(0))) { + MOZ_ASSERT(genericFamilies.Length() == 1, + "expected a single family"); + if (!prefFonts->Contains(genericFamilies[0].mFamily)) { + prefFonts->AppendElement(genericFamilies[0].mFamily); + bool foundLang = + !fcLang.IsEmpty() && + PatternHasLang(font, ToFcChar8Ptr(fcLang.get())); + foundFontWithLang = foundFontWithLang || foundLang; + // check to see if the list is full + if (prefFonts->Length() >= limit) { + break; + } + } + } + } + } + + // if no font in the list matches the lang, trim all but the first one + if (!prefFonts->IsEmpty() && !foundFontWithLang) { + prefFonts->TruncateLength(1); + } + + entry.Insert(std::move(prefFonts)); + } + return entry->get(); + }); +} + +bool gfxFcPlatformFontList::PrefFontListsUseOnlyGenerics() { + for (auto iter = mFontPrefs->NameIter(); !iter.Done(); iter.Next()) { + // Check whether all font.name prefs map to generic keywords + // and that the pref name and keyword match. + // Ex: font.name.serif.ar ==> "serif" (ok) + // Ex: font.name.serif.ar ==> "monospace" (return false) + // Ex: font.name.serif.ar ==> "DejaVu Serif" (return false) + // Ex: font.name.serif.ar ==> "" and + // font.name-list.serif.ar ==> "serif" (ok) + // Ex: font.name.serif.ar ==> "" and + // font.name-list.serif.ar ==> "Something, serif" + // (return false) + const nsACString* prefValue = &iter.Data(); + nsAutoCString listValue; + if (iter.Data().IsEmpty()) { + // The font name list may have two or more family names as comma + // separated list. In such case, not matching with generic font + // name is fine because if the list prefers specific font, this + // should return false. + mFontPrefs->LookupNameList(iter.Key(), listValue); + prefValue = &listValue; + } + + nsCCharSeparatedTokenizer tokenizer(iter.Key(), '.'); + const nsDependentCSubstring& generic = tokenizer.nextToken(); + const nsDependentCSubstring& langGroup = tokenizer.nextToken(); + + if (!langGroup.EqualsLiteral("x-math") && !generic.Equals(*prefValue)) { + return false; + } + } + return true; +} + +/* static */ +void gfxFcPlatformFontList::CheckFontUpdates(nsITimer* aTimer, void* aThis) { + // A content process is not supposed to check this directly; + // it will be notified by the parent when the font list changes. + MOZ_ASSERT(XRE_IsParentProcess()); + + // check for font updates + FcInitBringUptoDate(); + + // update fontlist if current config changed + gfxFcPlatformFontList* pfl = static_cast<gfxFcPlatformFontList*>(aThis); + FcConfig* current = FcConfigGetCurrent(); + if (current != pfl->GetLastConfig()) { + pfl->UpdateFontList(); + + gfxPlatform::ForceGlobalReflow(gfxPlatform::NeedsReframe::Yes); + mozilla::dom::ContentParent::NotifyUpdatedFonts(true); + } +} + +gfxFontFamily* gfxFcPlatformFontList::CreateFontFamily( + const nsACString& aName, FontVisibility aVisibility) const { + return new gfxFontconfigFontFamily(aName, aVisibility); +} + +// mapping of moz lang groups ==> default lang +struct MozLangGroupData { + nsAtom* const& mozLangGroup; + const char* defaultLang; +}; + +const MozLangGroupData MozLangGroups[] = { + {nsGkAtoms::x_western, "en"}, {nsGkAtoms::x_cyrillic, "ru"}, + {nsGkAtoms::x_devanagari, "hi"}, {nsGkAtoms::x_tamil, "ta"}, + {nsGkAtoms::x_armn, "hy"}, {nsGkAtoms::x_beng, "bn"}, + {nsGkAtoms::x_cans, "iu"}, {nsGkAtoms::x_ethi, "am"}, + {nsGkAtoms::x_geor, "ka"}, {nsGkAtoms::x_gujr, "gu"}, + {nsGkAtoms::x_guru, "pa"}, {nsGkAtoms::x_khmr, "km"}, + {nsGkAtoms::x_knda, "kn"}, {nsGkAtoms::x_mlym, "ml"}, + {nsGkAtoms::x_orya, "or"}, {nsGkAtoms::x_sinh, "si"}, + {nsGkAtoms::x_tamil, "ta"}, {nsGkAtoms::x_telu, "te"}, + {nsGkAtoms::x_tibt, "bo"}, {nsGkAtoms::Unicode, 0}}; + +bool gfxFcPlatformFontList::TryLangForGroup(const nsACString& aOSLang, + nsAtom* aLangGroup, + nsACString& aFcLang, + bool aForFontEnumerationThread) { + // Truncate at '.' or '@' from aOSLang, and convert '_' to '-'. + // aOSLang is in the form "language[_territory][.codeset][@modifier]". + // fontconfig takes languages in the form "language-territory". + // nsLanguageAtomService takes languages in the form language-subtag, + // where subtag may be a territory. fontconfig and nsLanguageAtomService + // handle case-conversion for us. + const char *pos, *end; + aOSLang.BeginReading(pos); + aOSLang.EndReading(end); + aFcLang.Truncate(); + while (pos < end) { + switch (*pos) { + case '.': + case '@': + end = pos; + break; + case '_': + aFcLang.Append('-'); + break; + default: + aFcLang.Append(*pos); + } + ++pos; + } + + if (!aForFontEnumerationThread) { + nsAtom* atom = mLangService->LookupLanguage(aFcLang); + return atom == aLangGroup; + } + + // If we were called by the font enumeration thread, we can't use + // mLangService->LookupLanguage because it is not thread-safe. + // Use GetUncachedLanguageGroup to avoid unsafe access to the lang-group + // mapping cache hashtable. + nsAutoCString lowered(aFcLang); + ToLowerCase(lowered); + RefPtr<nsAtom> lang = NS_Atomize(lowered); + RefPtr<nsAtom> group = mLangService->GetUncachedLanguageGroup(lang); + return group.get() == aLangGroup; +} + +void gfxFcPlatformFontList::GetSampleLangForGroup( + nsAtom* aLanguage, nsACString& aLangStr, bool aForFontEnumerationThread) { + aLangStr.Truncate(); + if (!aLanguage) { + return; + } + + // set up lang string + const MozLangGroupData* mozLangGroup = nullptr; + + // -- look it up in the list of moz lang groups + for (unsigned int i = 0; i < ArrayLength(MozLangGroups); ++i) { + if (aLanguage == MozLangGroups[i].mozLangGroup) { + mozLangGroup = &MozLangGroups[i]; + break; + } + } + + // -- not a mozilla lang group? Just return the BCP47 string + // representation of the lang group + if (!mozLangGroup) { + // Not a special mozilla language group. + // Use aLanguage as a language code. + aLanguage->ToUTF8String(aLangStr); + return; + } + + // -- check the environment for the user's preferred language that + // corresponds to this mozilla lang group. + const char* languages = getenv("LANGUAGE"); + if (languages) { + const char separator = ':'; + + for (const char* pos = languages; true; ++pos) { + if (*pos == '\0' || *pos == separator) { + if (languages < pos && + TryLangForGroup(Substring(languages, pos), aLanguage, aLangStr, + aForFontEnumerationThread)) { + return; + } + + if (*pos == '\0') { + break; + } + + languages = pos + 1; + } + } + } + const char* ctype = setlocale(LC_CTYPE, nullptr); + if (ctype && TryLangForGroup(nsDependentCString(ctype), aLanguage, aLangStr, + aForFontEnumerationThread)) { + return; + } + + if (mozLangGroup->defaultLang) { + aLangStr.Assign(mozLangGroup->defaultLang); + } else { + aLangStr.Truncate(); + } +} + +#ifdef MOZ_BUNDLED_FONTS +void gfxFcPlatformFontList::ActivateBundledFonts() { + if (!mBundledFontsInitialized) { + mBundledFontsInitialized = true; + nsCOMPtr<nsIFile> localDir; + nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(localDir)); + if (NS_FAILED(rv)) { + return; + } + if (NS_FAILED(localDir->Append(u"fonts"_ns))) { + return; + } + bool isDir; + if (NS_FAILED(localDir->IsDirectory(&isDir)) || !isDir) { + return; + } + if (NS_FAILED(localDir->GetNativePath(mBundledFontsPath))) { + return; + } + } + if (!mBundledFontsPath.IsEmpty()) { + FcConfigAppFontAddDir(nullptr, ToFcChar8Ptr(mBundledFontsPath.get())); + } +} +#endif + +#ifdef MOZ_WIDGET_GTK +/*************************************************************************** + * + * These functions must be last in the file because it uses the system cairo + * library. Above this point the cairo library used is the tree cairo. + */ + +// Tree cairo symbols have different names. Disable their activation through +// preprocessor macros. +# undef cairo_ft_font_options_substitute + +# undef cairo_font_options_create +# undef cairo_font_options_destroy +# undef cairo_font_options_copy +# undef cairo_font_options_equal + +# undef cairo_font_options_get_antialias +# undef cairo_font_options_set_antialias +# undef cairo_font_options_get_hint_style +# undef cairo_font_options_set_hint_style +# undef cairo_font_options_get_lcd_filter +# undef cairo_font_options_set_lcd_filter +# undef cairo_font_options_get_subpixel_order +# undef cairo_font_options_set_subpixel_order + +// The system cairo functions are not declared because the include paths cause +// the gdk headers to pick up the tree cairo.h. +extern "C" { +NS_VISIBILITY_DEFAULT void cairo_ft_font_options_substitute( + const cairo_font_options_t* options, FcPattern* pattern); + +NS_VISIBILITY_DEFAULT cairo_font_options_t* cairo_font_options_copy( + const cairo_font_options_t*); +NS_VISIBILITY_DEFAULT cairo_font_options_t* cairo_font_options_create(); +NS_VISIBILITY_DEFAULT void cairo_font_options_destroy(cairo_font_options_t*); +NS_VISIBILITY_DEFAULT cairo_bool_t cairo_font_options_equal( + const cairo_font_options_t*, const cairo_font_options_t*); + +NS_VISIBILITY_DEFAULT cairo_antialias_t +cairo_font_options_get_antialias(const cairo_font_options_t*); +NS_VISIBILITY_DEFAULT void cairo_font_options_set_antialias( + cairo_font_options_t*, cairo_antialias_t); +NS_VISIBILITY_DEFAULT cairo_hint_style_t +cairo_font_options_get_hint_style(const cairo_font_options_t*); +NS_VISIBILITY_DEFAULT void cairo_font_options_set_hint_style( + cairo_font_options_t*, cairo_hint_style_t); +NS_VISIBILITY_DEFAULT cairo_subpixel_order_t +cairo_font_options_get_subpixel_order(const cairo_font_options_t*); +NS_VISIBILITY_DEFAULT void cairo_font_options_set_subpixel_order( + cairo_font_options_t*, cairo_subpixel_order_t); +} + +void gfxFcPlatformFontList::ClearSystemFontOptions() { + if (mSystemFontOptions) { + cairo_font_options_destroy(mSystemFontOptions); + mSystemFontOptions = nullptr; + } +} + +bool gfxFcPlatformFontList::UpdateSystemFontOptions() { + MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); + + if (gfxPlatform::IsHeadless()) { + return false; + } + +# ifdef MOZ_X11 + { + // This one shouldn't change during the X session. + int lcdfilter; + GdkDisplay* dpy = gdk_display_get_default(); + if (mozilla::widget::GdkIsX11Display(dpy) && + GetXftInt(GDK_DISPLAY_XDISPLAY(dpy), "lcdfilter", &lcdfilter)) { + mFreetypeLcdSetting = lcdfilter; + } + } +# endif // MOZ_X11 + + const cairo_font_options_t* options = + gdk_screen_get_font_options(gdk_screen_get_default()); + if (!options) { + bool changed = !!mSystemFontOptions; + ClearSystemFontOptions(); + return changed; + } + + cairo_font_options_t* newOptions = cairo_font_options_copy(options); + + if (mSystemFontOptions && + cairo_font_options_equal(mSystemFontOptions, options)) { + cairo_font_options_destroy(newOptions); + return false; + } + + ClearSystemFontOptions(); + mSystemFontOptions = newOptions; + return true; +} + +void gfxFcPlatformFontList::SystemFontOptionsToIpc( + dom::SystemFontOptions& aOptions) { + aOptions.antialias() = + mSystemFontOptions ? cairo_font_options_get_antialias(mSystemFontOptions) + : CAIRO_ANTIALIAS_DEFAULT; + aOptions.subpixelOrder() = + mSystemFontOptions + ? cairo_font_options_get_subpixel_order(mSystemFontOptions) + : CAIRO_SUBPIXEL_ORDER_DEFAULT; + aOptions.hintStyle() = + mSystemFontOptions ? cairo_font_options_get_hint_style(mSystemFontOptions) + : CAIRO_HINT_STYLE_DEFAULT; + aOptions.lcdFilter() = mFreetypeLcdSetting; +} + +void gfxFcPlatformFontList::UpdateSystemFontOptionsFromIpc( + const dom::SystemFontOptions& aOptions) { + ClearSystemFontOptions(); + mSystemFontOptions = cairo_font_options_create(); + cairo_font_options_set_antialias(mSystemFontOptions, + cairo_antialias_t(aOptions.antialias())); + cairo_font_options_set_hint_style(mSystemFontOptions, + cairo_hint_style_t(aOptions.hintStyle())); + cairo_font_options_set_subpixel_order( + mSystemFontOptions, cairo_subpixel_order_t(aOptions.subpixelOrder())); + mFreetypeLcdSetting = aOptions.lcdFilter(); +} + +void gfxFcPlatformFontList::SubstituteSystemFontOptions(FcPattern* aPattern) { + if (mSystemFontOptions) { + cairo_ft_font_options_substitute(mSystemFontOptions, aPattern); + } + + if (mFreetypeLcdSetting != -1) { + FcValue value; + if (FcPatternGet(aPattern, FC_LCD_FILTER, 0, &value) == FcResultNoMatch) { + FcPatternAddInteger(aPattern, FC_LCD_FILTER, mFreetypeLcdSetting); + } + } +} + +#endif // MOZ_WIDGET_GTK diff --git a/gfx/thebes/gfxFcPlatformFontList.h b/gfx/thebes/gfxFcPlatformFontList.h new file mode 100644 index 0000000000..54070d1410 --- /dev/null +++ b/gfx/thebes/gfxFcPlatformFontList.h @@ -0,0 +1,427 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFXFCPLATFORMFONTLIST_H_ +#define GFXFCPLATFORMFONTLIST_H_ + +#include "gfxFT2FontBase.h" +#include "gfxPlatformFontList.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/mozalloc.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "nsClassHashtable.h" +#include "nsTHashMap.h" + +#include <fontconfig/fontconfig.h> +#include "ft2build.h" +#include FT_FREETYPE_H +#include FT_TRUETYPE_TABLES_H +#include FT_MULTIPLE_MASTERS_H + +#if defined(MOZ_SANDBOX) && defined(XP_LINUX) +# include "mozilla/SandboxBroker.h" +#endif + +namespace mozilla { +namespace dom { +class SystemFontListEntry; +class SystemFontList; +class SystemFontOptions; +}; // namespace dom + +template <> +class RefPtrTraits<FcPattern> { + public: + static void Release(FcPattern* ptr) { FcPatternDestroy(ptr); } + static void AddRef(FcPattern* ptr) { FcPatternReference(ptr); } +}; + +template <> +class RefPtrTraits<FcConfig> { + public: + static void Release(FcConfig* ptr) { FcConfigDestroy(ptr); } + static void AddRef(FcConfig* ptr) { FcConfigReference(ptr); } +}; + +template <> +class DefaultDelete<FcFontSet> { + public: + void operator()(FcFontSet* aPtr) { FcFontSetDestroy(aPtr); } +}; + +template <> +class DefaultDelete<FcObjectSet> { + public: + void operator()(FcObjectSet* aPtr) { FcObjectSetDestroy(aPtr); } +}; + +}; // namespace mozilla + +// The names for the font entry and font classes should really +// the common 'Fc' abbreviation but the gfxPangoFontGroup code already +// defines versions of these, so use the verbose name for now. + +class gfxFontconfigFontEntry final : public gfxFT2FontEntryBase { + friend class gfxFcPlatformFontList; + using FTUserFontData = mozilla::gfx::FTUserFontData; + + public: + // used for system fonts with explicit patterns + explicit gfxFontconfigFontEntry(const nsACString& aFaceName, + FcPattern* aFontPattern, + bool aIgnoreFcCharmap); + + // used for data fonts where the fontentry takes ownership + // of the font data and the FT_Face + explicit gfxFontconfigFontEntry(const nsACString& aFaceName, + WeightRange aWeight, StretchRange aStretch, + SlantStyleRange aStyle, + RefPtr<mozilla::gfx::SharedFTFace>&& aFace); + + // used for @font-face local system fonts with explicit patterns + explicit gfxFontconfigFontEntry(const nsACString& aFaceName, + FcPattern* aFontPattern, WeightRange aWeight, + StretchRange aStretch, + SlantStyleRange aStyle); + + gfxFontEntry* Clone() const override; + + FcPattern* GetPattern() { return mFontPattern; } + + nsresult ReadCMAP(FontInfoData* aFontInfoData = nullptr) override; + bool TestCharacterMap(uint32_t aCh) override; + + mozilla::gfx::SharedFTFace* GetFTFace(); + FTUserFontData* GetUserFontData(); + + FT_MM_Var* GetMMVar() override; + + bool HasVariations() override; + void GetVariationAxes(nsTArray<gfxFontVariationAxis>& aAxes) override; + void GetVariationInstances( + nsTArray<gfxFontVariationInstance>& aInstances) override; + + bool HasFontTable(uint32_t aTableTag) override; + nsresult CopyFontTable(uint32_t aTableTag, nsTArray<uint8_t>&) override; + hb_blob_t* GetFontTable(uint32_t aTableTag) override; + + double GetAspect(uint8_t aSizeAdjustBasis); + + protected: + virtual ~gfxFontconfigFontEntry(); + + gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle) override; + + void GetUserFontFeatures(FcPattern* aPattern); + + // pattern for a single face of a family + RefPtr<FcPattern> mFontPattern; + + // FTFace - initialized when needed. Once mFTFaceInitialized is true, + // the face can be accessed without locking. + // Note that mFTFace owns a reference to the SharedFTFace, but is not + // a RefPtr because we need it to be an atomic. + mozilla::Atomic<mozilla::gfx::SharedFTFace*> mFTFace; + mozilla::Atomic<bool> mFTFaceInitialized; + + // Whether TestCharacterMap should check the actual cmap rather than asking + // fontconfig about character coverage. + // We do this for app-bundled (rather than system) fonts, as they may + // include color glyphs that fontconfig would overlook, and for fonts + // loaded via @font-face. + bool mIgnoreFcCharmap; + + // Whether the face supports variations. For system-installed fonts, we + // query fontconfig for this (so they will only work if fontconfig is + // recent enough to include support); for downloaded user-fonts we query + // the FreeType face. + enum class HasVariationsState : int8_t { + Uninitialized = -1, + No = 0, + Yes = 1, + }; + std::atomic<HasVariationsState> mHasVariations = + HasVariationsState::Uninitialized; + + class UnscaledFontCache { + public: + already_AddRefed<mozilla::gfx::UnscaledFontFontconfig> Lookup( + const std::string& aFile, uint32_t aIndex); + + void Add( + const RefPtr<mozilla::gfx::UnscaledFontFontconfig>& aUnscaledFont) { + mUnscaledFonts[kNumEntries - 1] = aUnscaledFont; + MoveToFront(kNumEntries - 1); + } + + private: + void MoveToFront(size_t aIndex); + + static const size_t kNumEntries = 3; + mozilla::ThreadSafeWeakPtr<mozilla::gfx::UnscaledFontFontconfig> + mUnscaledFonts[kNumEntries]; + }; + + UnscaledFontCache mUnscaledFontCache; + + // Because of FreeType bug 52955, we keep the FT_MM_Var struct when it is + // first loaded, rather than releasing it and re-fetching it as needed. + FT_MM_Var* mMMVar = nullptr; + bool mMMVarInitialized = false; +}; + +class gfxFontconfigFontFamily final : public gfxFontFamily { + public: + gfxFontconfigFontFamily(const nsACString& aName, FontVisibility aVisibility) + : gfxFontFamily(aName, aVisibility), + mContainsAppFonts(false), + mHasNonScalableFaces(false), + mForceScalable(false) {} + + template <typename Func> + void AddFacesToFontList(Func aAddPatternFunc); + + void FindStyleVariationsLocked(FontInfoData* aFontInfoData = nullptr) + MOZ_REQUIRES(mLock) override; + + // Families are constructed initially with just references to patterns. + // When necessary, these are enumerated within FindStyleVariations. + void AddFontPattern(FcPattern* aFontPattern, bool aSingleName); + + void SetFamilyContainsAppFonts(bool aContainsAppFonts) { + mContainsAppFonts = aContainsAppFonts; + } + + void FindAllFontsForStyle(const gfxFontStyle& aFontStyle, + nsTArray<gfxFontEntry*>& aFontEntryList, + bool aIgnoreSizeTolerance) override; + + bool FilterForFontList(nsAtom* aLangGroup, + const nsACString& aGeneric) const final { + return SupportsLangGroup(aLangGroup); + } + + protected: + virtual ~gfxFontconfigFontFamily(); + + // helper for FilterForFontList + bool SupportsLangGroup(nsAtom* aLangGroup) const; + + nsTArray<RefPtr<FcPattern>> mFontPatterns; + + // Number of faces that have a single name. Faces that have multiple names are + // sorted last. + uint32_t mUniqueNameFaceCount = 0; + bool mContainsAppFonts : 1; + bool mHasNonScalableFaces : 1; + bool mForceScalable : 1; +}; + +class gfxFontconfigFont final : public gfxFT2FontBase { + public: + gfxFontconfigFont( + const RefPtr<mozilla::gfx::UnscaledFontFontconfig>& aUnscaledFont, + RefPtr<mozilla::gfx::SharedFTFace>&& aFTFace, FcPattern* aPattern, + gfxFloat aAdjustedSize, gfxFontEntry* aFontEntry, + const gfxFontStyle* aFontStyle, int aLoadFlags, bool aEmbolden); + + FontType GetType() const override { return FONT_TYPE_FONTCONFIG; } + FcPattern* GetPattern() const { return mPattern; } + + already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont( + const TextRunDrawParams& aRunParams) override; + + bool ShouldHintMetrics() const override; + + private: + ~gfxFontconfigFont() override; + + RefPtr<FcPattern> mPattern; +}; + +class gfxFcPlatformFontList final : public gfxPlatformFontList { + using FontPatternListEntry = mozilla::dom::SystemFontListEntry; + + public: + gfxFcPlatformFontList(); + + static gfxFcPlatformFontList* PlatformFontList() { + return static_cast<gfxFcPlatformFontList*>( + gfxPlatformFontList::PlatformFontList()); + } + + // initialize font lists + nsresult InitFontListForPlatform() MOZ_REQUIRES(mLock) override; + void InitSharedFontListForPlatform() MOZ_REQUIRES(mLock) override; + + void GetFontList(nsAtom* aLangGroup, const nsACString& aGenericFamily, + nsTArray<nsString>& aListOfFonts) override; + + void ReadSystemFontList(mozilla::dom::SystemFontList*); + + gfxFontEntry* CreateFontEntry( + mozilla::fontlist::Face* aFace, + const mozilla::fontlist::Family* aFamily) override; + + gfxFontEntry* LookupLocalFont(nsPresContext* aPresContext, + const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) override; + + gfxFontEntry* MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, + uint32_t aLength) override; + + bool FindAndAddFamiliesLocked( + nsPresContext* aPresContext, mozilla::StyleGenericFontFamily aGeneric, + const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput, + FindFamiliesFlags aFlags, gfxFontStyle* aStyle = nullptr, + nsAtom* aLanguage = nullptr, gfxFloat aDevToCssSize = 1.0) + MOZ_REQUIRES(mLock) override; + + bool GetStandardFamilyName(const nsCString& aFontName, + nsACString& aFamilyName) override; + + FcConfig* GetLastConfig() const { return mLastConfig; } + + // override to use fontconfig lookup for generics + void AddGenericFonts(nsPresContext* aPresContext, + mozilla::StyleGenericFontFamily, nsAtom* aLanguage, + nsTArray<FamilyAndGeneric>& aFamilyList) override; + + void ClearLangGroupPrefFontsLocked() MOZ_REQUIRES(mLock) override; + + // clear out cached generic-lang ==> family-list mappings + void ClearGenericMappings() { + AutoLock lock(mLock); + ClearGenericMappingsLocked(); + } + void ClearGenericMappingsLocked() MOZ_REQUIRES(mLock) { + mGenericMappings.Clear(); + } + + // map lang group ==> lang string + // When aForFontEnumerationThread is true, this method will avoid using + // LanguageService::LookupLanguage, because it is not safe for off-main- + // thread use (except by stylo traversal, which does the necessary locking) + void GetSampleLangForGroup(nsAtom* aLanguage, nsACString& aLangStr, + bool aForFontEnumerationThread = false); + + protected: + virtual ~gfxFcPlatformFontList(); + +#if defined(MOZ_SANDBOX) && defined(XP_LINUX) + typedef mozilla::SandboxBroker::Policy SandboxPolicy; +#else + // Dummy type just so we can still have a SandboxPolicy* parameter. + struct SandboxPolicy {}; +#endif + + // Add all the font families found in a font set. + // aAppFonts indicates whether this is the system or application fontset. + void AddFontSetFamilies(FcFontSet* aFontSet, const SandboxPolicy* aPolicy, + bool aAppFonts) MOZ_REQUIRES(mLock); + + // Helper for above, to add a single font pattern. + void AddPatternToFontList(FcPattern* aFont, FcChar8*& aLastFamilyName, + nsACString& aFamilyName, + RefPtr<gfxFontconfigFontFamily>& aFontFamily, + bool aAppFonts) MOZ_REQUIRES(mLock); + + // figure out which families fontconfig maps a generic to + // (aGeneric assumed already lowercase) + PrefFontList* FindGenericFamilies(nsPresContext* aPresContext, + const nsCString& aGeneric, + nsAtom* aLanguage) MOZ_REQUIRES(mLock); + + // are all pref font settings set to use fontconfig generics? + bool PrefFontListsUseOnlyGenerics() MOZ_REQUIRES(mLock); + + static void CheckFontUpdates(nsITimer* aTimer, void* aThis); + + FontFamily GetDefaultFontForPlatform(nsPresContext* aPresContext, + const gfxFontStyle* aStyle, + nsAtom* aLanguage = nullptr) + MOZ_REQUIRES(mLock) override; + + FontVisibility GetVisibilityForFamily(const nsACString& aName) const; + + gfxFontFamily* CreateFontFamily(const nsACString& aName, + FontVisibility aVisibility) const override; + + // helper method for finding an appropriate lang string + bool TryLangForGroup(const nsACString& aOSLang, nsAtom* aLangGroup, + nsACString& aLang, bool aForFontEnumerationThread); + +#ifdef MOZ_BUNDLED_FONTS + void ActivateBundledFonts(); + nsCString mBundledFontsPath; + bool mBundledFontsInitialized; +#endif + + // to avoid enumerating all fonts, maintain a mapping of local font + // names to family + nsTHashMap<nsCString, RefPtr<FcPattern>> mLocalNames; + + // caching generic/lang ==> font family list + nsClassHashtable<nsCStringHashKey, PrefFontList> mGenericMappings; + + // Caching family lookups as found by FindAndAddFamilies after resolving + // substitutions. The gfxFontFamily objects cached here are owned by the + // gfxFcPlatformFontList via its mFamilies table; note that if the main + // font list is rebuilt (e.g. due to a fontconfig configuration change), + // these pointers will be invalidated. InitFontList() flushes the cache + // in this case. + nsTHashMap<nsCStringHashKey, nsTArray<FamilyAndGeneric>> mFcSubstituteCache; + + nsCOMPtr<nsITimer> mCheckFontUpdatesTimer; + RefPtr<FcConfig> mLastConfig; + + // The current system font options in effect. +#ifdef MOZ_WIDGET_GTK + // NOTE(emilio): This is a *system cairo* cairo_font_options_t object. As + // such, it can't be used outside of the few functions defined here. + cairo_font_options_t* mSystemFontOptions = nullptr; + int32_t mFreetypeLcdSetting = -1; // -1 for not set + + void ClearSystemFontOptions(); + + // Returns whether options actually changed. + // TODO(emilio): We could call this when gsettings change or such, but + // historically we haven't reacted to these settings changes, so keeping it + // simple for now. + bool UpdateSystemFontOptions(); + + void UpdateSystemFontOptionsFromIpc(const mozilla::dom::SystemFontOptions&); + void SystemFontOptionsToIpc(mozilla::dom::SystemFontOptions&); + + public: + void SubstituteSystemFontOptions(FcPattern*); + + private: +#endif + + // Cache for most recently used language code in FindAndAddFamiliesLocked, + // and the result of checking whether to use lang-specific lookups. + RefPtr<nsAtom> mPrevLanguage; + nsCString mSampleLang; + bool mUseCustomLookups = false; + + // By default, font prefs under Linux are set to simply lookup + // via fontconfig the appropriate font for serif/sans-serif/monospace. + // Rather than check each time a font pref is used, check them all at startup + // and set a boolean to flag the case that non-default user font prefs exist + // Note: langGroup == x-math is handled separately + bool mAlwaysUseFontconfigGenerics; + + static FT_Library sFTLibrary; +}; + +#endif /* GFXPLATFORMFONTLIST_H_ */ diff --git a/gfx/thebes/gfxFont.cpp b/gfx/thebes/gfxFont.cpp new file mode 100644 index 0000000000..618eb49455 --- /dev/null +++ b/gfx/thebes/gfxFont.cpp @@ -0,0 +1,4848 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxFont.h" + +#include "mozilla/BinarySearch.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/IntegerRange.h" +#include "mozilla/intl/Segmenter.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/SVGContextPaint.h" + +#include "mozilla/Logging.h" + +#include "nsITimer.h" + +#include "gfxGlyphExtents.h" +#include "gfxPlatform.h" +#include "gfxTextRun.h" +#include "nsGkAtoms.h" + +#include "gfxTypes.h" +#include "gfxContext.h" +#include "gfxFontMissingGlyphs.h" +#include "gfxGraphiteShaper.h" +#include "gfxHarfBuzzShaper.h" +#include "gfxUserFontSet.h" +#include "nsCRT.h" +#include "nsContentUtils.h" +#include "nsSpecialCasingData.h" +#include "nsTextRunTransformations.h" +#include "nsUGenCategory.h" +#include "nsUnicodeProperties.h" +#include "nsStyleConsts.h" +#include "mozilla/AppUnits.h" +#include "mozilla/HashTable.h" +#include "mozilla/Likely.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/Telemetry.h" +#include "gfxMathTable.h" +#include "gfxSVGGlyphs.h" +#include "gfx2DGlue.h" +#include "TextDrawTarget.h" + +#include "ThebesRLBox.h" + +#include "GreekCasing.h" + +#include "cairo.h" +#ifdef XP_WIN +# include "cairo-win32.h" +# include "gfxWindowsPlatform.h" +#endif + +#include "harfbuzz/hb.h" +#include "harfbuzz/hb-ot.h" + +#include <algorithm> +#include <limits> +#include <cmath> + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::unicode; +using mozilla::services::GetObserverService; + +gfxFontCache* gfxFontCache::gGlobalCache = nullptr; + +#ifdef DEBUG_roc +# define DEBUG_TEXT_RUN_STORAGE_METRICS +#endif + +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS +uint32_t gTextRunStorageHighWaterMark = 0; +uint32_t gTextRunStorage = 0; +uint32_t gFontCount = 0; +uint32_t gGlyphExtentsCount = 0; +uint32_t gGlyphExtentsWidthsTotalSize = 0; +uint32_t gGlyphExtentsSetupEagerSimple = 0; +uint32_t gGlyphExtentsSetupEagerTight = 0; +uint32_t gGlyphExtentsSetupLazyTight = 0; +uint32_t gGlyphExtentsSetupFallBackToTight = 0; +#endif + +#define LOG_FONTINIT(args) \ + MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug, args) +#define LOG_FONTINIT_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug) + +/* + * gfxFontCache - global cache of gfxFont instances. + * Expires unused fonts after a short interval; + * notifies fonts to age their cached shaped-word records; + * observes memory-pressure notification and tells fonts to clear their + * shaped-word caches to free up memory. + */ + +MOZ_DEFINE_MALLOC_SIZE_OF(FontCacheMallocSizeOf) + +NS_IMPL_ISUPPORTS(gfxFontCache::MemoryReporter, nsIMemoryReporter) + +/*virtual*/ +gfxTextRunFactory::~gfxTextRunFactory() { + // Should not be dropped by stylo + MOZ_ASSERT(!Servo_IsWorkerThread()); +} + +NS_IMETHODIMP +gfxFontCache::MemoryReporter::CollectReports( + nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) { + FontCacheSizes sizes; + + gfxFontCache::GetCache()->AddSizeOfIncludingThis(&FontCacheMallocSizeOf, + &sizes); + + MOZ_COLLECT_REPORT("explicit/gfx/font-cache", KIND_HEAP, UNITS_BYTES, + sizes.mFontInstances, + "Memory used for active font instances."); + + MOZ_COLLECT_REPORT("explicit/gfx/font-shaped-words", KIND_HEAP, UNITS_BYTES, + sizes.mShapedWords, + "Memory used to cache shaped glyph data."); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(gfxFontCache::Observer, nsIObserver) + +NS_IMETHODIMP +gfxFontCache::Observer::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* someData) { + if (!nsCRT::strcmp(aTopic, "memory-pressure")) { + gfxFontCache* fontCache = gfxFontCache::GetCache(); + if (fontCache) { + fontCache->FlushShapedWordCaches(); + } + } else { + MOZ_ASSERT_UNREACHABLE("unexpected notification topic"); + } + return NS_OK; +} + +nsresult gfxFontCache::Init() { + NS_ASSERTION(!gGlobalCache, "Where did this come from?"); + gGlobalCache = new gfxFontCache(GetMainThreadSerialEventTarget()); + if (!gGlobalCache) { + return NS_ERROR_OUT_OF_MEMORY; + } + RegisterStrongMemoryReporter(new MemoryReporter()); + return NS_OK; +} + +void gfxFontCache::Shutdown() { + delete gGlobalCache; + gGlobalCache = nullptr; + +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + printf("Textrun storage high water mark=%d\n", gTextRunStorageHighWaterMark); + printf("Total number of fonts=%d\n", gFontCount); + printf("Total glyph extents allocated=%d (size %d)\n", gGlyphExtentsCount, + int(gGlyphExtentsCount * sizeof(gfxGlyphExtents))); + printf("Total glyph extents width-storage size allocated=%d\n", + gGlyphExtentsWidthsTotalSize); + printf("Number of simple glyph extents eagerly requested=%d\n", + gGlyphExtentsSetupEagerSimple); + printf("Number of tight glyph extents eagerly requested=%d\n", + gGlyphExtentsSetupEagerTight); + printf("Number of tight glyph extents lazily requested=%d\n", + gGlyphExtentsSetupLazyTight); + printf("Number of simple glyph extent setups that fell back to tight=%d\n", + gGlyphExtentsSetupFallBackToTight); +#endif +} + +gfxFontCache::gfxFontCache(nsIEventTarget* aEventTarget) + : ExpirationTrackerImpl<gfxFont, 3, Lock, AutoLock>( + FONT_TIMEOUT_SECONDS * 1000, "gfxFontCache", aEventTarget) { + nsCOMPtr<nsIObserverService> obs = GetObserverService(); + if (obs) { + obs->AddObserver(new Observer, "memory-pressure", false); + } + + nsIEventTarget* target = nullptr; + if (XRE_IsContentProcess() && NS_IsMainThread()) { + target = aEventTarget; + } + + // Create the timer used to expire shaped-word records from each font's + // cache after a short period of non-use. We have a single timer in + // gfxFontCache that loops over all fonts known to the cache, to avoid + // the overhead of individual timers in each font instance. + // The timer will be started any time shaped word records are cached + // (and pauses itself when all caches become empty). + mWordCacheExpirationTimer = NS_NewTimer(target); +} + +gfxFontCache::~gfxFontCache() { + // Ensure the user font cache releases its references to font entries, + // so they aren't kept alive after the font instances and font-list + // have been shut down. + gfxUserFontSet::UserFontCache::Shutdown(); + + if (mWordCacheExpirationTimer) { + mWordCacheExpirationTimer->Cancel(); + mWordCacheExpirationTimer = nullptr; + } + + // Expire everything manually so we don't leak them. + Flush(); +} + +bool gfxFontCache::HashEntry::KeyEquals(const KeyTypePointer aKey) const { + const gfxCharacterMap* fontUnicodeRangeMap = mFont->GetUnicodeRangeMap(); + return aKey->mFontEntry == mFont->GetFontEntry() && + aKey->mStyle->Equals(*mFont->GetStyle()) && + ((!aKey->mUnicodeRangeMap && !fontUnicodeRangeMap) || + (aKey->mUnicodeRangeMap && fontUnicodeRangeMap && + aKey->mUnicodeRangeMap->Equals(fontUnicodeRangeMap))); +} + +already_AddRefed<gfxFont> gfxFontCache::Lookup( + const gfxFontEntry* aFontEntry, const gfxFontStyle* aStyle, + const gfxCharacterMap* aUnicodeRangeMap) { + MutexAutoLock lock(mMutex); + + Key key(aFontEntry, aStyle, aUnicodeRangeMap); + HashEntry* entry = mFonts.GetEntry(key); + + Telemetry::Accumulate(Telemetry::FONT_CACHE_HIT, entry != nullptr); + + if (!entry) { + return nullptr; + } + + RefPtr<gfxFont> font = entry->mFont; + if (font->GetExpirationState()->IsTracked()) { + RemoveObjectLocked(font, lock); + } + return font.forget(); +} + +already_AddRefed<gfxFont> gfxFontCache::MaybeInsert(gfxFont* aFont) { + MOZ_ASSERT(aFont); + MutexAutoLock lock(mMutex); + + Key key(aFont->GetFontEntry(), aFont->GetStyle(), + aFont->GetUnicodeRangeMap()); + HashEntry* entry = mFonts.PutEntry(key); + if (!entry) { + return do_AddRef(aFont); + } + + // If it is null, then we are inserting a new entry. Otherwise we are + // attempting to replace an existing font, probably due to a thread race, in + // which case stick with the original font. + if (!entry->mFont) { + entry->mFont = aFont; + // Assert that we can find the entry we just put in (this fails if the key + // has a NaN float value in it, e.g. 'sizeAdjust'). + MOZ_ASSERT(entry == mFonts.GetEntry(key)); + } else { + MOZ_ASSERT(entry->mFont != aFont); + aFont->Destroy(); + if (entry->mFont->GetExpirationState()->IsTracked()) { + RemoveObjectLocked(entry->mFont, lock); + } + } + + return do_AddRef(entry->mFont); +} + +bool gfxFontCache::MaybeDestroy(gfxFont* aFont) { + MOZ_ASSERT(aFont); + MutexAutoLock lock(mMutex); + + // If the font has a non-zero refcount, then we must have lost the race with + // gfxFontCache::Lookup and the same font was reacquired. + if (aFont->GetRefCount() > 0) { + return false; + } + + Key key(aFont->GetFontEntry(), aFont->GetStyle(), + aFont->GetUnicodeRangeMap()); + HashEntry* entry = mFonts.GetEntry(key); + if (!entry || entry->mFont != aFont) { + MOZ_ASSERT(!aFont->GetExpirationState()->IsTracked()); + return true; + } + + // If the font is being tracked, we must have then also lost another race with + // gfxFontCache::MaybeDestroy which re-added it to the tracker. + if (aFont->GetExpirationState()->IsTracked()) { + return false; + } + + // Typically this won't fail, but it may during startup/shutdown if the timer + // service is not available. + nsresult rv = AddObjectLocked(aFont, lock); + if (NS_SUCCEEDED(rv)) { + return false; + } + + mFonts.RemoveEntry(entry); + return true; +} + +void gfxFontCache::NotifyExpiredLocked(gfxFont* aFont, const AutoLock& aLock) { + MOZ_ASSERT(aFont->GetRefCount() == 0); + + RemoveObjectLocked(aFont, aLock); + mTrackerDiscard.AppendElement(aFont); + + Key key(aFont->GetFontEntry(), aFont->GetStyle(), + aFont->GetUnicodeRangeMap()); + HashEntry* entry = mFonts.GetEntry(key); + if (!entry || entry->mFont != aFont) { + MOZ_ASSERT_UNREACHABLE("Invalid font?"); + return; + } + + mFonts.RemoveEntry(entry); +} + +void gfxFontCache::NotifyHandlerEnd() { + nsTArray<gfxFont*> discard; + { + MutexAutoLock lock(mMutex); + discard = std::move(mTrackerDiscard); + } + DestroyDiscard(discard); +} + +void gfxFontCache::DestroyDiscard(nsTArray<gfxFont*>& aDiscard) { + for (auto& font : aDiscard) { + NS_ASSERTION(font->GetRefCount() == 0, + "Destroying with refs outside cache!"); + font->ClearCachedWords(); + font->Destroy(); + } + aDiscard.Clear(); +} + +void gfxFontCache::Flush() { + nsTArray<gfxFont*> discard; + { + MutexAutoLock lock(mMutex); + discard.SetCapacity(mFonts.Count()); + for (auto iter = mFonts.Iter(); !iter.Done(); iter.Next()) { + HashEntry* entry = static_cast<HashEntry*>(iter.Get()); + if (!entry || !entry->mFont) { + MOZ_ASSERT_UNREACHABLE("Invalid font?"); + continue; + } + + if (entry->mFont->GetRefCount() == 0) { + // If we are not tracked, then we must have won the race with + // gfxFont::MaybeDestroy and it is waiting on the mutex. To avoid a + // double free, we let gfxFont::MaybeDestroy handle the freeing when it + // acquires the mutex and discovers there is no matching entry in the + // hashtable. + if (entry->mFont->GetExpirationState()->IsTracked()) { + RemoveObjectLocked(entry->mFont, lock); + discard.AppendElement(entry->mFont); + } + } else { + MOZ_ASSERT(!entry->mFont->GetExpirationState()->IsTracked()); + } + } + MOZ_ASSERT(IsEmptyLocked(lock), + "Cache tracker still has fonts after flush!"); + mFonts.Clear(); + } + DestroyDiscard(discard); +} + +/*static*/ +void gfxFontCache::WordCacheExpirationTimerCallback(nsITimer* aTimer, + void* aCache) { + gfxFontCache* cache = static_cast<gfxFontCache*>(aCache); + cache->AgeCachedWords(); +} + +void gfxFontCache::AgeCachedWords() { + bool allEmpty = true; + { + MutexAutoLock lock(mMutex); + for (const auto& entry : mFonts) { + allEmpty = entry.mFont->AgeCachedWords() && allEmpty; + } + } + if (allEmpty) { + PauseWordCacheExpirationTimer(); + } +} + +void gfxFontCache::FlushShapedWordCaches() { + { + MutexAutoLock lock(mMutex); + for (const auto& entry : mFonts) { + entry.mFont->ClearCachedWords(); + } + } + PauseWordCacheExpirationTimer(); +} + +void gfxFontCache::NotifyGlyphsChanged() { + MutexAutoLock lock(mMutex); + for (const auto& entry : mFonts) { + entry.mFont->NotifyGlyphsChanged(); + } +} + +void gfxFontCache::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const { + // TODO: add the overhead of the expiration tracker (generation arrays) + + MutexAutoLock lock(*const_cast<Mutex*>(&mMutex)); + aSizes->mFontInstances += mFonts.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& entry : mFonts) { + entry.mFont->AddSizeOfExcludingThis(aMallocSizeOf, aSizes); + } +} + +void gfxFontCache::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const { + aSizes->mFontInstances += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +#define MAX_SSXX_VALUE 99 +#define MAX_CVXX_VALUE 99 + +static void LookupAlternateValues(const gfxFontFeatureValueSet& aFeatureLookup, + const nsACString& aFamily, + const StyleVariantAlternates& aAlternates, + nsTArray<gfxFontFeature>& aFontFeatures) { + using Tag = StyleVariantAlternates::Tag; + + // historical-forms gets handled in nsFont::AddFontFeaturesToStyle. + if (aAlternates.IsHistoricalForms()) { + return; + } + + gfxFontFeature feature; + if (aAlternates.IsCharacterVariant()) { + for (auto& ident : aAlternates.AsCharacterVariant().AsSpan()) { + Span<const uint32_t> values = aFeatureLookup.GetFontFeatureValuesFor( + aFamily, NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT, + ident.AsAtom()); + // nothing defined, skip + if (values.IsEmpty()) { + continue; + } + NS_ASSERTION(values.Length() <= 2, + "too many values allowed for character-variant"); + // character-variant(12 3) ==> 'cv12' = 3 + uint32_t nn = values[0]; + // ignore values greater than 99 + if (nn == 0 || nn > MAX_CVXX_VALUE) { + continue; + } + feature.mValue = values.Length() > 1 ? values[1] : 1; + feature.mTag = HB_TAG('c', 'v', ('0' + nn / 10), ('0' + nn % 10)); + aFontFeatures.AppendElement(feature); + } + return; + } + + if (aAlternates.IsStyleset()) { + for (auto& ident : aAlternates.AsStyleset().AsSpan()) { + Span<const uint32_t> values = aFeatureLookup.GetFontFeatureValuesFor( + aFamily, NS_FONT_VARIANT_ALTERNATES_STYLESET, ident.AsAtom()); + + // styleset(1 2 7) ==> 'ss01' = 1, 'ss02' = 1, 'ss07' = 1 + feature.mValue = 1; + for (uint32_t nn : values) { + if (nn == 0 || nn > MAX_SSXX_VALUE) { + continue; + } + feature.mTag = HB_TAG('s', 's', ('0' + nn / 10), ('0' + nn % 10)); + aFontFeatures.AppendElement(feature); + } + } + return; + } + + uint32_t constant = 0; + nsAtom* name = nullptr; + switch (aAlternates.tag) { + case Tag::Swash: + constant = NS_FONT_VARIANT_ALTERNATES_SWASH; + name = aAlternates.AsSwash().AsAtom(); + break; + case Tag::Stylistic: + constant = NS_FONT_VARIANT_ALTERNATES_STYLISTIC; + name = aAlternates.AsStylistic().AsAtom(); + break; + case Tag::Ornaments: + constant = NS_FONT_VARIANT_ALTERNATES_ORNAMENTS; + name = aAlternates.AsOrnaments().AsAtom(); + break; + case Tag::Annotation: + constant = NS_FONT_VARIANT_ALTERNATES_ANNOTATION; + name = aAlternates.AsAnnotation().AsAtom(); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown font-variant-alternates value!"); + return; + } + + Span<const uint32_t> values = + aFeatureLookup.GetFontFeatureValuesFor(aFamily, constant, name); + if (values.IsEmpty()) { + return; + } + MOZ_ASSERT(values.Length() == 1, + "too many values for font-specific font-variant-alternates"); + + feature.mValue = values[0]; + switch (aAlternates.tag) { + case Tag::Swash: // swsh, cswh + feature.mTag = HB_TAG('s', 'w', 's', 'h'); + aFontFeatures.AppendElement(feature); + feature.mTag = HB_TAG('c', 's', 'w', 'h'); + break; + case Tag::Stylistic: // salt + feature.mTag = HB_TAG('s', 'a', 'l', 't'); + break; + case Tag::Ornaments: // ornm + feature.mTag = HB_TAG('o', 'r', 'n', 'm'); + break; + case Tag::Annotation: // nalt + feature.mTag = HB_TAG('n', 'a', 'l', 't'); + break; + default: + MOZ_ASSERT_UNREACHABLE("how?"); + return; + } + aFontFeatures.AppendElement(feature); +} + +/* static */ +void gfxFontShaper::MergeFontFeatures( + const gfxFontStyle* aStyle, const nsTArray<gfxFontFeature>& aFontFeatures, + bool aDisableLigatures, const nsACString& aFamilyName, bool aAddSmallCaps, + void (*aHandleFeature)(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; + } + + AutoTArray<gfxFontFeature, 32> mergedFeatures; + + struct FeatureTagCmp { + bool Equals(const gfxFontFeature& a, const gfxFontFeature& b) const { + return a.mTag == b.mTag; + } + bool LessThan(const gfxFontFeature& a, const gfxFontFeature& b) const { + return a.mTag < b.mTag; + } + } cmp; + + auto addOrReplace = [&](const gfxFontFeature& aFeature) { + auto index = mergedFeatures.BinaryIndexOf(aFeature, cmp); + if (index == nsTArray<gfxFontFeature>::NoIndex) { + mergedFeatures.InsertElementSorted(aFeature, cmp); + } else { + mergedFeatures[index].mValue = aFeature.mValue; + } + }; + + // add feature values from font + for (const gfxFontFeature& feature : aFontFeatures) { + addOrReplace(feature); + } + + // 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: + addOrReplace(gfxFontFeature{HB_TAG('c', '2', 's', 'c'), 1}); + // fall through to the small-caps case + [[fallthrough]]; + + case NS_FONT_VARIANT_CAPS_SMALLCAPS: + addOrReplace(gfxFontFeature{HB_TAG('s', 'm', 'c', 'p'), 1}); + break; + + case NS_FONT_VARIANT_CAPS_ALLPETITE: + addOrReplace(gfxFontFeature{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: + addOrReplace(gfxFontFeature{aAddSmallCaps ? HB_TAG('s', 'm', 'c', 'p') + : HB_TAG('p', 'c', 'a', 'p'), + 1}); + break; + + case NS_FONT_VARIANT_CAPS_TITLING: + addOrReplace(gfxFontFeature{HB_TAG('t', 'i', 't', 'l'), 1}); + break; + + case NS_FONT_VARIANT_CAPS_UNICASE: + addOrReplace(gfxFontFeature{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: + addOrReplace(gfxFontFeature{HB_TAG('s', 'u', 'p', 's'), 1}); + break; + case NS_FONT_VARIANT_POSITION_SUB: + addOrReplace(gfxFontFeature{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) { + addOrReplace(gfxFontFeature{feature.mTag, feature.mValue}); + } + } + + auto disableOptionalLigatures = [&]() -> void { + addOrReplace(gfxFontFeature{HB_TAG('l', 'i', 'g', 'a'), 0}); + addOrReplace(gfxFontFeature{HB_TAG('c', 'l', 'i', 'g'), 0}); + addOrReplace(gfxFontFeature{HB_TAG('d', 'l', 'i', 'g'), 0}); + addOrReplace(gfxFontFeature{HB_TAG('h', 'l', 'i', 'g'), 0}); + }; + + // Add features that are already resolved to tags & values in the style. + if (styleRuleFeatures.IsEmpty()) { + // Disable optional ligatures if non-zero letter-spacing is in effect. + if (aDisableLigatures) { + disableOptionalLigatures(); + } + } else { + for (const gfxFontFeature& feature : styleRuleFeatures) { + // A dummy feature (0,0) is used as a sentinel to separate features + // originating from font-variant-* or other high-level properties from + // those directly specified as font-feature-settings. The high-level + // features may be overridden by aDisableLigatures, while low-level + // features specified directly as tags will come last and therefore + // take precedence over everything else. + if (feature.mTag) { + addOrReplace(gfxFontFeature{feature.mTag, feature.mValue}); + } else if (aDisableLigatures) { + // Handle ligature-disabling setting at the boundary between high- + // and low-level features. + disableOptionalLigatures(); + } + } + } + + for (const auto& f : mergedFeatures) { + aHandleFeature(f.mTag, f.mValue, aHandleFeatureData); + } +} + +void gfxShapedText::SetupClusterBoundaries(uint32_t aOffset, + const char16_t* aString, + uint32_t aLength) { + if (aLength == 0) { + return; + } + + CompressedGlyph* const glyphs = GetCharacterGlyphs() + aOffset; + CompressedGlyph extendCluster = CompressedGlyph::MakeComplex(false, true); + + // GraphemeClusterBreakIteratorUtf16 won't be able to tell us if the string + // _begins_ with a cluster-extender, so we handle that here + uint32_t ch = aString[0]; + if (aLength > 1 && NS_IS_SURROGATE_PAIR(ch, aString[1])) { + ch = SURROGATE_TO_UCS4(ch, aString[1]); + } + if (IsClusterExtender(ch)) { + glyphs[0] = extendCluster; + } + + intl::GraphemeClusterBreakIteratorUtf16 iter( + Span<const char16_t>(aString, aLength)); + uint32_t pos = 0; + + const char16_t kIdeographicSpace = 0x3000; + // Special case for Bengali: although Virama normally clusters with the + // preceding letter, we *also* want to cluster it with a following Ya + // so that when the Virama+Ya form ya-phala, this is not separated from the + // preceding letter by any letter-spacing or justification. + const char16_t kBengaliVirama = 0x09CD; + const char16_t kBengaliYa = 0x09AF; + // Characters treated as hyphens for the purpose of "emergency" breaking + // when the content would otherwise overflow. + auto isHyphen = [](char16_t c) { + return c == char16_t('-') || // HYPHEN-MINUS + c == 0x2010 || // HYPHEN + c == 0x2012 || // FIGURE DASH + c == 0x2013 || // EN DASH + c == 0x058A; // ARMENIAN HYPHEN + }; + bool prevWasHyphen = false; + while (pos < aLength) { + const char16_t ch = aString[pos]; + if (prevWasHyphen) { + if (nsContentUtils::IsAlphanumeric(ch)) { + glyphs[pos].SetCanBreakBefore( + CompressedGlyph::FLAG_BREAK_TYPE_EMERGENCY_WRAP); + } + prevWasHyphen = false; + } + if (ch == char16_t(' ') || ch == kIdeographicSpace) { + glyphs[pos].SetIsSpace(); + } else if (isHyphen(ch) && pos && + nsContentUtils::IsAlphanumeric(aString[pos - 1])) { + prevWasHyphen = true; + } else if (ch == kBengaliYa) { + // Unless we're at the start, check for a preceding virama. + if (pos > 0 && aString[pos - 1] == kBengaliVirama) { + glyphs[pos] = extendCluster; + } + } + // advance iter to the next cluster-start (or end of text) + const uint32_t nextPos = *iter.Next(); + // step past the first char of the cluster + ++pos; + // mark all the rest as cluster-continuations + for (; pos < nextPos; ++pos) { + glyphs[pos] = extendCluster; + } + } +} + +void gfxShapedText::SetupClusterBoundaries(uint32_t aOffset, + const uint8_t* aString, + uint32_t aLength) { + CompressedGlyph* glyphs = GetCharacterGlyphs() + aOffset; + uint32_t pos = 0; + bool prevWasHyphen = false; + while (pos < aLength) { + uint8_t ch = aString[pos]; + if (prevWasHyphen) { + if (nsContentUtils::IsAlphanumeric(ch)) { + glyphs->SetCanBreakBefore( + CompressedGlyph::FLAG_BREAK_TYPE_EMERGENCY_WRAP); + } + prevWasHyphen = false; + } + if (ch == uint8_t(' ')) { + glyphs->SetIsSpace(); + } else if (ch == uint8_t('-') && pos && + nsContentUtils::IsAlphanumeric(aString[pos - 1])) { + prevWasHyphen = true; + } + ++pos; + ++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::ApplyTrackingToClusters(gfxFloat aTrackingAdjustment, + uint32_t aOffset, + uint32_t aLength) { + int32_t appUnitAdjustment = + NS_round(aTrackingAdjustment * gfxFloat(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 = std::max(0, advance + appUnitAdjustment); + 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; + } + auto& advance = IsRightToLeft() ? details[0].mAdvance + : details[detailedLength - 1].mAdvance; + if (advance > 0) { + advance = std::max(0, advance + appUnitAdjustment); + } + } + } + } +} + +size_t gfxShapedWord::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + size_t total = aMallocSizeOf(this); + if (mDetailedGlyphs) { + total += mDetailedGlyphs->SizeOfIncludingThis(aMallocSizeOf); + } + return total; +} + +float gfxFont::AngleForSyntheticOblique() const { + // First check conditions that mean no synthetic slant should be used: + if (mStyle.style == FontSlantStyle::NORMAL) { + return 0.0f; // Requested style is 'normal'. + } + if (!mStyle.allowSyntheticStyle) { + return 0.0f; // Synthetic obliquing is disabled. + } + if (!mFontEntry->MayUseSyntheticSlant()) { + return 0.0f; // The resource supports "real" slant, so don't synthesize. + } + + // If style calls for italic, and face doesn't support it, use default + // oblique angle as a simulation. + if (mStyle.style.IsItalic()) { + return mFontEntry->SupportsItalic() + ? 0.0f + : FontSlantStyle::DEFAULT_OBLIQUE_DEGREES; + } + + // OK, we're going to use synthetic oblique: return the requested angle. + return mStyle.style.ObliqueAngle(); +} + +float gfxFont::SkewForSyntheticOblique() const { + // Precomputed value of tan(kDefaultAngle), the default italic/oblique slant; + // avoids calling tan() at runtime except for custom oblique values. + static const float kTanDefaultAngle = + tan(FontSlantStyle::DEFAULT_OBLIQUE_DEGREES * (M_PI / 180.0)); + + float angle = AngleForSyntheticOblique(); + if (angle == 0.0f) { + return 0.0f; + } else if (angle == FontSlantStyle::DEFAULT_OBLIQUE_DEGREES) { + return kTanDefaultAngle; + } else { + return tan(angle * (M_PI / 180.0)); + } +} + +void gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther, + bool aOtherIsOnLeft) { + mAscent = std::max(mAscent, aOther.mAscent); + mDescent = std::max(mDescent, aOther.mDescent); + if (aOtherIsOnLeft) { + mBoundingBox = (mBoundingBox + gfxPoint(aOther.mAdvanceWidth, 0)) + .Union(aOther.mBoundingBox); + } else { + mBoundingBox = + mBoundingBox.Union(aOther.mBoundingBox + gfxPoint(mAdvanceWidth, 0)); + } + mAdvanceWidth += aOther.mAdvanceWidth; +} + +gfxFont::gfxFont(const RefPtr<UnscaledFont>& aUnscaledFont, + gfxFontEntry* aFontEntry, const gfxFontStyle* aFontStyle, + AntialiasOption anAAOption) + : mFontEntry(aFontEntry), + mLock("gfxFont lock"), + mUnscaledFont(aUnscaledFont), + mStyle(*aFontStyle), + mAdjustedSize(-1.0), // negative to indicate "not yet initialized" + mFUnitsConvFactor(-1.0f), // negative to indicate "not yet initialized" + mAntialiasOption(anAAOption), + mIsValid(true), + mApplySyntheticBold(false), + mKerningEnabled(false), + mMathInitialized(false) { +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + ++gFontCount; +#endif + + if (MOZ_UNLIKELY(StaticPrefs::gfx_text_disable_aa_AtStartup())) { + mAntialiasOption = kAntialiasNone; + } + + // Turn off AA for Ahem for testing purposes when requested. + if (MOZ_UNLIKELY(StaticPrefs::gfx_font_rendering_ahem_antialias_none() && + mFontEntry->FamilyName().EqualsLiteral("Ahem"))) { + mAntialiasOption = kAntialiasNone; + } + + mKerningSet = HasFeatureSet(HB_TAG('k', 'e', 'r', 'n'), mKerningEnabled); +} + +gfxFont::~gfxFont() { + mFontEntry->NotifyFontDestroyed(this); + + // Delete objects owned through atomic pointers. (Some of these may be null, + // but that's OK.) + delete mVerticalMetrics.exchange(nullptr); + delete mHarfBuzzShaper.exchange(nullptr); + delete mGraphiteShaper.exchange(nullptr); + delete mMathTable.exchange(nullptr); + delete mNonAAFont.exchange(nullptr); + + if (auto* scaledFont = mAzureScaledFont.exchange(nullptr)) { + scaledFont->Release(); + } + + if (mGlyphChangeObservers) { + for (const auto& key : *mGlyphChangeObservers) { + key->ForgetFont(); + } + } +} + +// Work out whether cairo will snap inter-glyph spacing to pixels. +// +// Layout does not align text to pixel boundaries, so, with font drawing +// backends that snap glyph positions to pixels, it is important that +// inter-glyph spacing within words is always an integer number of pixels. +// This ensures that the drawing backend snaps all of the word's glyphs in the +// same direction and so inter-glyph spacing remains the same. +// +gfxFont::RoundingFlags gfxFont::GetRoundOffsetsToPixels( + DrawTarget* aDrawTarget) { + // Could do something fancy here for ScaleFactors of + // AxisAlignedTransforms, but we leave things simple. + // Not much point rounding if a matrix will mess things up anyway. + // Also check if the font already knows hint metrics is off... + if (aDrawTarget->GetTransform().HasNonTranslation() || !ShouldHintMetrics()) { + return RoundingFlags(0); + } + + cairo_t* cr = static_cast<cairo_t*>( + aDrawTarget->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT)); + if (cr) { + cairo_surface_t* target = cairo_get_target(cr); + + // Check whether the cairo surface's font options hint metrics. + cairo_font_options_t* fontOptions = cairo_font_options_create(); + cairo_surface_get_font_options(target, fontOptions); + cairo_hint_metrics_t hintMetrics = + cairo_font_options_get_hint_metrics(fontOptions); + cairo_font_options_destroy(fontOptions); + + switch (hintMetrics) { + case CAIRO_HINT_METRICS_OFF: + return RoundingFlags(0); + case CAIRO_HINT_METRICS_ON: + return RoundingFlags::kRoundX | RoundingFlags::kRoundY; + default: + break; + } + } + + if (ShouldRoundXOffset(cr)) { + return RoundingFlags::kRoundX | RoundingFlags::kRoundY; + } else { + return RoundingFlags::kRoundY; + } +} + +gfxHarfBuzzShaper* gfxFont::GetHarfBuzzShaper() { + if (!mHarfBuzzShaper) { + auto* shaper = new gfxHarfBuzzShaper(this); + shaper->Initialize(); + if (!mHarfBuzzShaper.compareExchange(nullptr, shaper)) { + delete shaper; + } + } + gfxHarfBuzzShaper* shaper = mHarfBuzzShaper; + return shaper->IsInitialized() ? shaper : nullptr; +} + +gfxFloat gfxFont::GetGlyphAdvance(uint16_t aGID, bool aVertical) { + if (!aVertical && ProvidesGlyphWidths()) { + return GetGlyphWidth(aGID) / 65536.0; + } + if (mFUnitsConvFactor < 0.0f) { + // Metrics haven't been initialized; lock while we do that. + AutoWriteLock lock(mLock); + if (mFUnitsConvFactor < 0.0f) { + GetMetrics(nsFontMetrics::eHorizontal); + } + } + NS_ASSERTION(mFUnitsConvFactor >= 0.0f, + "missing font unit conversion factor"); + if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) { + if (aVertical) { + // Note that GetGlyphVAdvance may return -1 to indicate it was unable + // to retrieve vertical metrics; in that case we fall back to the + // aveCharWidth value as a default advance. + int32_t advance = shaper->GetGlyphVAdvance(aGID); + if (advance < 0) { + return GetMetrics(nsFontMetrics::eVertical).aveCharWidth; + } + return advance / 65536.0; + } + return shaper->GetGlyphHAdvance(aGID) / 65536.0; + } + return 0.0; +} + +gfxFloat gfxFont::GetCharAdvance(uint32_t aUnicode, bool aVertical) { + uint32_t gid = 0; + if (ProvidesGetGlyph()) { + gid = GetGlyph(aUnicode, 0); + } else { + if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) { + gid = shaper->GetNominalGlyph(aUnicode); + } + } + if (!gid) { + return -1.0; + } + return GetGlyphAdvance(gid, aVertical); +} + +static void CollectLookupsByFeature(hb_face_t* aFace, hb_tag_t aTableTag, + uint32_t aFeatureIndex, + hb_set_t* aLookups) { + uint32_t lookups[32]; + uint32_t i, len, offset; + + offset = 0; + do { + len = ArrayLength(lookups); + hb_ot_layout_feature_get_lookups(aFace, aTableTag, aFeatureIndex, offset, + &len, lookups); + for (i = 0; i < len; i++) { + hb_set_add(aLookups, lookups[i]); + } + offset += len; + } while (len == ArrayLength(lookups)); +} + +static void CollectLookupsByLanguage( + hb_face_t* aFace, hb_tag_t aTableTag, + const nsTHashSet<uint32_t>& aSpecificFeatures, hb_set_t* aOtherLookups, + hb_set_t* aSpecificFeatureLookups, uint32_t aScriptIndex, + uint32_t aLangIndex) { + uint32_t reqFeatureIndex; + if (hb_ot_layout_language_get_required_feature_index( + aFace, aTableTag, aScriptIndex, aLangIndex, &reqFeatureIndex)) { + CollectLookupsByFeature(aFace, aTableTag, reqFeatureIndex, aOtherLookups); + } + + uint32_t featureIndexes[32]; + uint32_t i, len, offset; + + offset = 0; + do { + len = ArrayLength(featureIndexes); + hb_ot_layout_language_get_feature_indexes(aFace, aTableTag, aScriptIndex, + aLangIndex, offset, &len, + featureIndexes); + + for (i = 0; i < len; i++) { + uint32_t featureIndex = featureIndexes[i]; + + // get the feature tag + hb_tag_t featureTag; + uint32_t tagLen = 1; + hb_ot_layout_language_get_feature_tags(aFace, aTableTag, aScriptIndex, + aLangIndex, offset + i, &tagLen, + &featureTag); + + // collect lookups + hb_set_t* lookups = aSpecificFeatures.Contains(featureTag) + ? aSpecificFeatureLookups + : aOtherLookups; + CollectLookupsByFeature(aFace, aTableTag, featureIndex, lookups); + } + offset += len; + } while (len == ArrayLength(featureIndexes)); +} + +static bool HasLookupRuleWithGlyphByScript( + hb_face_t* aFace, hb_tag_t aTableTag, hb_tag_t aScriptTag, + uint32_t aScriptIndex, uint16_t aGlyph, + const nsTHashSet<uint32_t>& aDefaultFeatures, + bool& aHasDefaultFeatureWithGlyph) { + uint32_t numLangs, lang; + hb_set_t* defaultFeatureLookups = hb_set_create(); + hb_set_t* nonDefaultFeatureLookups = hb_set_create(); + + // default lang + CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures, + nonDefaultFeatureLookups, defaultFeatureLookups, + aScriptIndex, HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX); + + // iterate over langs + numLangs = hb_ot_layout_script_get_language_tags( + aFace, aTableTag, aScriptIndex, 0, nullptr, nullptr); + for (lang = 0; lang < numLangs; lang++) { + CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures, + nonDefaultFeatureLookups, defaultFeatureLookups, + aScriptIndex, lang); + } + + // look for the glyph among default feature lookups + aHasDefaultFeatureWithGlyph = false; + hb_set_t* glyphs = hb_set_create(); + hb_codepoint_t index = -1; + while (hb_set_next(defaultFeatureLookups, &index)) { + hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs, + glyphs, nullptr); + if (hb_set_has(glyphs, aGlyph)) { + aHasDefaultFeatureWithGlyph = true; + break; + } + } + + // look for the glyph among non-default feature lookups + // if no default feature lookups contained spaces + bool hasNonDefaultFeatureWithGlyph = false; + if (!aHasDefaultFeatureWithGlyph) { + hb_set_clear(glyphs); + index = -1; + while (hb_set_next(nonDefaultFeatureLookups, &index)) { + hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, + glyphs, glyphs, nullptr); + if (hb_set_has(glyphs, aGlyph)) { + hasNonDefaultFeatureWithGlyph = true; + break; + } + } + } + + hb_set_destroy(glyphs); + hb_set_destroy(defaultFeatureLookups); + hb_set_destroy(nonDefaultFeatureLookups); + + return aHasDefaultFeatureWithGlyph || hasNonDefaultFeatureWithGlyph; +} + +static void HasLookupRuleWithGlyph(hb_face_t* aFace, hb_tag_t aTableTag, + bool& aHasGlyph, hb_tag_t aSpecificFeature, + bool& aHasGlyphSpecific, uint16_t aGlyph) { + // iterate over the scripts in the font + uint32_t numScripts, numLangs, script, lang; + hb_set_t* otherLookups = hb_set_create(); + hb_set_t* specificFeatureLookups = hb_set_create(); + nsTHashSet<uint32_t> specificFeature(1); + + specificFeature.Insert(aSpecificFeature); + + numScripts = + hb_ot_layout_table_get_script_tags(aFace, aTableTag, 0, nullptr, nullptr); + + for (script = 0; script < numScripts; script++) { + // default lang + CollectLookupsByLanguage(aFace, aTableTag, specificFeature, otherLookups, + specificFeatureLookups, script, + HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX); + + // iterate over langs + numLangs = hb_ot_layout_script_get_language_tags( + aFace, HB_OT_TAG_GPOS, script, 0, nullptr, nullptr); + for (lang = 0; lang < numLangs; lang++) { + CollectLookupsByLanguage(aFace, aTableTag, specificFeature, otherLookups, + specificFeatureLookups, script, lang); + } + } + + // look for the glyph among non-specific feature lookups + hb_set_t* glyphs = hb_set_create(); + hb_codepoint_t index = -1; + while (hb_set_next(otherLookups, &index)) { + hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs, + glyphs, nullptr); + if (hb_set_has(glyphs, aGlyph)) { + aHasGlyph = true; + break; + } + } + + // look for the glyph among specific feature lookups + hb_set_clear(glyphs); + index = -1; + while (hb_set_next(specificFeatureLookups, &index)) { + hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs, + glyphs, nullptr); + if (hb_set_has(glyphs, aGlyph)) { + aHasGlyphSpecific = true; + break; + } + } + + hb_set_destroy(glyphs); + hb_set_destroy(specificFeatureLookups); + hb_set_destroy(otherLookups); +} + +Atomic<nsTHashMap<nsUint32HashKey, intl::Script>*> gfxFont::sScriptTagToCode; +Atomic<nsTHashSet<uint32_t>*> gfxFont::sDefaultFeatures; + +static inline bool HasSubstitution(uint32_t* aBitVector, intl::Script aScript) { + return (aBitVector[static_cast<uint32_t>(aScript) >> 5] & + (1 << (static_cast<uint32_t>(aScript) & 0x1f))) != 0; +} + +// union of all default substitution features across scripts +static const hb_tag_t defaultFeatures[] = { + HB_TAG('a', 'b', 'v', 'f'), HB_TAG('a', 'b', 'v', 's'), + HB_TAG('a', 'k', 'h', 'n'), HB_TAG('b', 'l', 'w', 'f'), + HB_TAG('b', 'l', 'w', 's'), HB_TAG('c', 'a', 'l', 't'), + HB_TAG('c', 'c', 'm', 'p'), HB_TAG('c', 'f', 'a', 'r'), + HB_TAG('c', 'j', 'c', 't'), HB_TAG('c', 'l', 'i', 'g'), + HB_TAG('f', 'i', 'n', '2'), HB_TAG('f', 'i', 'n', '3'), + HB_TAG('f', 'i', 'n', 'a'), HB_TAG('h', 'a', 'l', 'f'), + HB_TAG('h', 'a', 'l', 'n'), HB_TAG('i', 'n', 'i', 't'), + HB_TAG('i', 's', 'o', 'l'), HB_TAG('l', 'i', 'g', 'a'), + HB_TAG('l', 'j', 'm', 'o'), HB_TAG('l', 'o', 'c', 'l'), + HB_TAG('l', 't', 'r', 'a'), HB_TAG('l', 't', 'r', 'm'), + HB_TAG('m', 'e', 'd', '2'), HB_TAG('m', 'e', 'd', 'i'), + HB_TAG('m', 's', 'e', 't'), HB_TAG('n', 'u', 'k', 't'), + HB_TAG('p', 'r', 'e', 'f'), HB_TAG('p', 'r', 'e', 's'), + HB_TAG('p', 's', 't', 'f'), HB_TAG('p', 's', 't', 's'), + HB_TAG('r', 'c', 'l', 't'), HB_TAG('r', 'l', 'i', 'g'), + HB_TAG('r', 'k', 'r', 'f'), HB_TAG('r', 'p', 'h', 'f'), + HB_TAG('r', 't', 'l', 'a'), HB_TAG('r', 't', 'l', 'm'), + HB_TAG('t', 'j', 'm', 'o'), HB_TAG('v', 'a', 't', 'u'), + HB_TAG('v', 'e', 'r', 't'), HB_TAG('v', 'j', 'm', 'o')}; + +void gfxFont::CheckForFeaturesInvolvingSpace() const { + gfxFontEntry::SpaceFeatures flags = gfxFontEntry::SpaceFeatures::None; + + auto setFlags = + MakeScopeExit([&]() { mFontEntry->mHasSpaceFeatures = flags; }); + + bool log = LOG_FONTINIT_ENABLED(); + TimeStamp start; + if (MOZ_UNLIKELY(log)) { + start = TimeStamp::Now(); + } + + uint32_t spaceGlyph = GetSpaceGlyph(); + if (!spaceGlyph) { + return; + } + + auto face(GetFontEntry()->GetHBFace()); + + // GSUB lookups - examine per script + if (hb_ot_layout_has_substitution(face)) { + // Get the script ==> code hashtable, creating it on first use. + nsTHashMap<nsUint32HashKey, Script>* tagToCode = sScriptTagToCode; + if (!tagToCode) { + tagToCode = new nsTHashMap<nsUint32HashKey, Script>( + size_t(Script::NUM_SCRIPT_CODES)); + tagToCode->InsertOrUpdate(HB_TAG('D', 'F', 'L', 'T'), Script::COMMON); + // Ensure that we don't try to look at script codes beyond what the + // current version of ICU (at runtime -- in case of system ICU) + // knows about. + Script scriptCount = Script( + std::min<int>(intl::UnicodeProperties::GetMaxNumberOfScripts() + 1, + int(Script::NUM_SCRIPT_CODES))); + for (Script s = Script::ARABIC; s < scriptCount; + s = Script(static_cast<int>(s) + 1)) { + hb_script_t script = hb_script_t(GetScriptTagForCode(s)); + unsigned int scriptCount = 4; + hb_tag_t scriptTags[4]; + hb_ot_tags_from_script_and_language(script, HB_LANGUAGE_INVALID, + &scriptCount, scriptTags, nullptr, + nullptr); + for (unsigned int i = 0; i < scriptCount; i++) { + tagToCode->InsertOrUpdate(scriptTags[i], s); + } + } + if (!sScriptTagToCode.compareExchange(nullptr, tagToCode)) { + // We lost a race! Discard our new table and use the winner. + delete tagToCode; + tagToCode = sScriptTagToCode; + } + } + + // Set up the default-features hashset on first use. + if (!sDefaultFeatures) { + uint32_t numDefaultFeatures = ArrayLength(defaultFeatures); + auto* set = new nsTHashSet<uint32_t>(numDefaultFeatures); + for (uint32_t i = 0; i < numDefaultFeatures; i++) { + set->Insert(defaultFeatures[i]); + } + if (!sDefaultFeatures.compareExchange(nullptr, set)) { + delete set; + } + } + + // iterate over the scripts in the font + hb_tag_t scriptTags[8]; + + uint32_t len, offset = 0; + do { + len = ArrayLength(scriptTags); + hb_ot_layout_table_get_script_tags(face, HB_OT_TAG_GSUB, offset, &len, + scriptTags); + for (uint32_t i = 0; i < len; i++) { + bool isDefaultFeature = false; + Script s; + if (!HasLookupRuleWithGlyphByScript( + face, HB_OT_TAG_GSUB, scriptTags[i], offset + i, spaceGlyph, + *sDefaultFeatures, isDefaultFeature) || + !tagToCode->Get(scriptTags[i], &s)) { + continue; + } + flags = flags | gfxFontEntry::SpaceFeatures::HasFeatures; + uint32_t index = static_cast<uint32_t>(s) >> 5; + uint32_t bit = static_cast<uint32_t>(s) & 0x1f; + if (isDefaultFeature) { + mFontEntry->mDefaultSubSpaceFeatures[index] |= (1 << bit); + } else { + mFontEntry->mNonDefaultSubSpaceFeatures[index] |= (1 << bit); + } + } + offset += len; + } while (len == ArrayLength(scriptTags)); + } + + // spaces in default features of default script? + // ==> can't use word cache, skip GPOS analysis + bool canUseWordCache = true; + if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, Script::COMMON)) { + canUseWordCache = false; + } + + // GPOS lookups - distinguish kerning from non-kerning features + if (canUseWordCache && hb_ot_layout_has_positioning(face)) { + bool hasKerning = false, hasNonKerning = false; + HasLookupRuleWithGlyph(face, HB_OT_TAG_GPOS, hasNonKerning, + HB_TAG('k', 'e', 'r', 'n'), hasKerning, spaceGlyph); + if (hasKerning) { + flags |= gfxFontEntry::SpaceFeatures::HasFeatures | + gfxFontEntry::SpaceFeatures::Kerning; + } + if (hasNonKerning) { + flags |= gfxFontEntry::SpaceFeatures::HasFeatures | + gfxFontEntry::SpaceFeatures::NonKerning; + } + } + + if (MOZ_UNLIKELY(log)) { + TimeDuration elapsed = TimeStamp::Now() - start; + LOG_FONTINIT(( + "(fontinit-spacelookups) font: %s - " + "subst default: %8.8x %8.8x %8.8x %8.8x " + "subst non-default: %8.8x %8.8x %8.8x %8.8x " + "kerning: %s non-kerning: %s time: %6.3f\n", + mFontEntry->Name().get(), mFontEntry->mDefaultSubSpaceFeatures[3], + mFontEntry->mDefaultSubSpaceFeatures[2], + mFontEntry->mDefaultSubSpaceFeatures[1], + mFontEntry->mDefaultSubSpaceFeatures[0], + mFontEntry->mNonDefaultSubSpaceFeatures[3], + mFontEntry->mNonDefaultSubSpaceFeatures[2], + mFontEntry->mNonDefaultSubSpaceFeatures[1], + mFontEntry->mNonDefaultSubSpaceFeatures[0], + (mFontEntry->mHasSpaceFeatures & gfxFontEntry::SpaceFeatures::Kerning + ? "true" + : "false"), + (mFontEntry->mHasSpaceFeatures & gfxFontEntry::SpaceFeatures::NonKerning + ? "true" + : "false"), + elapsed.ToMilliseconds())); + } +} + +bool gfxFont::HasSubstitutionRulesWithSpaceLookups(Script aRunScript) const { + NS_ASSERTION(GetFontEntry()->mHasSpaceFeatures != + gfxFontEntry::SpaceFeatures::Uninitialized, + "need to initialize space lookup flags"); + NS_ASSERTION(aRunScript < Script::NUM_SCRIPT_CODES, "weird script code"); + if (aRunScript == Script::INVALID || aRunScript >= Script::NUM_SCRIPT_CODES) { + return false; + } + + // default features have space lookups ==> true + if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, Script::COMMON) || + HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, aRunScript)) { + return true; + } + + // non-default features have space lookups and some type of + // font feature, in font or style is specified ==> true + if ((HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures, + Script::COMMON) || + HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures, aRunScript)) && + (!mStyle.featureSettings.IsEmpty() || + !mFontEntry->mFeatureSettings.IsEmpty())) { + return true; + } + + return false; +} + +tainted_boolean_hint gfxFont::SpaceMayParticipateInShaping( + Script aRunScript) const { + // avoid checking fonts known not to include default space-dependent features + if (MOZ_UNLIKELY(mFontEntry->mSkipDefaultFeatureSpaceCheck)) { + if (!mKerningSet && mStyle.featureSettings.IsEmpty() && + mFontEntry->mFeatureSettings.IsEmpty()) { + return false; + } + } + + if (FontCanSupportGraphite()) { + if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) { + return mFontEntry->HasGraphiteSpaceContextuals(); + } + } + + // We record the presence of space-dependent features in the font entry + // so that subsequent instantiations for the same font face won't + // require us to re-check the tables; however, the actual check is done + // by gfxFont because not all font entry subclasses know how to create + // a harfbuzz face for introspection. + gfxFontEntry::SpaceFeatures flags = mFontEntry->mHasSpaceFeatures; + if (flags == gfxFontEntry::SpaceFeatures::Uninitialized) { + CheckForFeaturesInvolvingSpace(); + flags = mFontEntry->mHasSpaceFeatures; + } + + if (!(flags & gfxFontEntry::SpaceFeatures::HasFeatures)) { + return false; + } + + // if font has substitution rules or non-kerning positioning rules + // that involve spaces, bypass + if (HasSubstitutionRulesWithSpaceLookups(aRunScript) || + (flags & gfxFontEntry::SpaceFeatures::NonKerning)) { + return true; + } + + // if kerning explicitly enabled/disabled via font-feature-settings or + // font-kerning and kerning rules use spaces, only bypass when enabled + if (mKerningSet && (flags & gfxFontEntry::SpaceFeatures::Kerning)) { + return mKerningEnabled; + } + + return false; +} + +bool gfxFont::SupportsFeature(Script aScript, uint32_t aFeatureTag) { + if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) { + return GetFontEntry()->SupportsGraphiteFeature(aFeatureTag); + } + return GetFontEntry()->SupportsOpenTypeFeature(aScript, aFeatureTag); +} + +bool gfxFont::SupportsVariantCaps(Script aScript, uint32_t aVariantCaps, + bool& aFallbackToSmallCaps, + bool& aSyntheticLowerToSmallCaps, + bool& aSyntheticUpperToSmallCaps) { + bool ok = true; // cases without fallback are fine + aFallbackToSmallCaps = false; + aSyntheticLowerToSmallCaps = false; + aSyntheticUpperToSmallCaps = false; + switch (aVariantCaps) { + case NS_FONT_VARIANT_CAPS_SMALLCAPS: + ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p')); + if (!ok) { + aSyntheticLowerToSmallCaps = true; + } + break; + case NS_FONT_VARIANT_CAPS_ALLSMALL: + ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p')) && + SupportsFeature(aScript, HB_TAG('c', '2', 's', 'c')); + if (!ok) { + aSyntheticLowerToSmallCaps = true; + aSyntheticUpperToSmallCaps = true; + } + break; + case NS_FONT_VARIANT_CAPS_PETITECAPS: + ok = SupportsFeature(aScript, HB_TAG('p', 'c', 'a', 'p')); + if (!ok) { + ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p')); + aFallbackToSmallCaps = ok; + } + if (!ok) { + aSyntheticLowerToSmallCaps = true; + } + break; + case NS_FONT_VARIANT_CAPS_ALLPETITE: + ok = SupportsFeature(aScript, HB_TAG('p', 'c', 'a', 'p')) && + SupportsFeature(aScript, HB_TAG('c', '2', 'p', 'c')); + if (!ok) { + ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p')) && + SupportsFeature(aScript, HB_TAG('c', '2', 's', 'c')); + aFallbackToSmallCaps = ok; + } + if (!ok) { + aSyntheticLowerToSmallCaps = true; + aSyntheticUpperToSmallCaps = true; + } + break; + default: + break; + } + + NS_ASSERTION( + !(ok && (aSyntheticLowerToSmallCaps || aSyntheticUpperToSmallCaps)), + "shouldn't use synthetic features if we found real ones"); + + NS_ASSERTION(!(!ok && aFallbackToSmallCaps), + "if we found a usable fallback, that counts as ok"); + + return ok; +} + +bool gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript, + const uint8_t* aString, uint32_t aLength, + Script aRunScript) { + NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aString), + aLength); + return SupportsSubSuperscript(aSubSuperscript, unicodeString.get(), aLength, + aRunScript); +} + +bool gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript, + const char16_t* aString, uint32_t aLength, + Script aRunScript) { + NS_ASSERTION(aSubSuperscript == NS_FONT_VARIANT_POSITION_SUPER || + aSubSuperscript == NS_FONT_VARIANT_POSITION_SUB, + "unknown value of font-variant-position"); + + uint32_t feature = aSubSuperscript == NS_FONT_VARIANT_POSITION_SUPER + ? HB_TAG('s', 'u', 'p', 's') + : HB_TAG('s', 'u', 'b', 's'); + + if (!SupportsFeature(aRunScript, feature)) { + return false; + } + + // xxx - for graphite, don't really know how to sniff lookups so bail + if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) { + return true; + } + + gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper(); + if (!shaper) { + return false; + } + + // get the hbset containing input glyphs for the feature + const hb_set_t* inputGlyphs = + mFontEntry->InputsForOpenTypeFeature(aRunScript, feature); + + // create an hbset containing default glyphs for the script run + hb_set_t* defaultGlyphsInRun = hb_set_create(); + + // for each character, get the glyph id + for (uint32_t i = 0; i < aLength; i++) { + uint32_t ch = aString[i]; + + if (i + 1 < aLength && NS_IS_SURROGATE_PAIR(ch, aString[i + 1])) { + i++; + ch = SURROGATE_TO_UCS4(ch, aString[i]); + } + + hb_codepoint_t gid = shaper->GetNominalGlyph(ch); + hb_set_add(defaultGlyphsInRun, gid); + } + + // intersect with input glyphs, if size is not the same ==> fallback + uint32_t origSize = hb_set_get_population(defaultGlyphsInRun); + hb_set_intersect(defaultGlyphsInRun, inputGlyphs); + uint32_t intersectionSize = hb_set_get_population(defaultGlyphsInRun); + hb_set_destroy(defaultGlyphsInRun); + + return origSize == intersectionSize; +} + +bool gfxFont::FeatureWillHandleChar(Script aRunScript, uint32_t aFeature, + uint32_t aUnicode) { + if (!SupportsFeature(aRunScript, aFeature)) { + return false; + } + + // xxx - for graphite, don't really know how to sniff lookups so bail + if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) { + return true; + } + + if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) { + // get the hbset containing input glyphs for the feature + const hb_set_t* inputGlyphs = + mFontEntry->InputsForOpenTypeFeature(aRunScript, aFeature); + + hb_codepoint_t gid = shaper->GetNominalGlyph(aUnicode); + return hb_set_has(inputGlyphs, gid); + } + + return false; +} + +bool gfxFont::HasFeatureSet(uint32_t aFeature, bool& aFeatureOn) { + aFeatureOn = false; + + if (mStyle.featureSettings.IsEmpty() && + GetFontEntry()->mFeatureSettings.IsEmpty()) { + return false; + } + + // add feature values from font + bool featureSet = false; + uint32_t i, count; + + nsTArray<gfxFontFeature>& fontFeatures = GetFontEntry()->mFeatureSettings; + count = fontFeatures.Length(); + for (i = 0; i < count; i++) { + const gfxFontFeature& feature = fontFeatures.ElementAt(i); + if (feature.mTag == aFeature) { + featureSet = true; + aFeatureOn = (feature.mValue != 0); + } + } + + // add feature values from style rules + nsTArray<gfxFontFeature>& styleFeatures = mStyle.featureSettings; + count = styleFeatures.Length(); + for (i = 0; i < count; i++) { + const gfxFontFeature& feature = styleFeatures.ElementAt(i); + if (feature.mTag == aFeature) { + featureSet = true; + aFeatureOn = (feature.mValue != 0); + } + } + + return featureSet; +} + +already_AddRefed<mozilla::gfx::ScaledFont> gfxFont::GetScaledFont( + mozilla::gfx::DrawTarget* aDrawTarget) { + mozilla::gfx::PaletteCache dummy; + TextRunDrawParams params(dummy); + return GetScaledFont(params); +} + +void gfxFont::InitializeScaledFont( + const RefPtr<mozilla::gfx::ScaledFont>& aScaledFont) { + if (!aScaledFont) { + return; + } + + float angle = AngleForSyntheticOblique(); + if (angle != 0.0f) { + aScaledFont->SetSyntheticObliqueAngle(angle); + } +} + +/** + * A helper function in case we need to do any rounding or other + * processing here. + */ +#define ToDeviceUnits(aAppUnits, aDevUnitsPerAppUnit) \ + (double(aAppUnits) * double(aDevUnitsPerAppUnit)) + +static AntialiasMode Get2DAAMode(gfxFont::AntialiasOption aAAOption) { + switch (aAAOption) { + case gfxFont::kAntialiasSubpixel: + return AntialiasMode::SUBPIXEL; + case gfxFont::kAntialiasGrayscale: + return AntialiasMode::GRAY; + case gfxFont::kAntialiasNone: + return AntialiasMode::NONE; + default: + return AntialiasMode::DEFAULT; + } +} + +class GlyphBufferAzure { +#define AUTO_BUFFER_SIZE (2048 / sizeof(Glyph)) + + typedef mozilla::image::imgDrawingParams imgDrawingParams; + + public: + GlyphBufferAzure(const TextRunDrawParams& aRunParams, + const FontDrawParams& aFontParams) + : mRunParams(aRunParams), + mFontParams(aFontParams), + mBuffer(*mAutoBuffer.addr()), + mBufSize(AUTO_BUFFER_SIZE), + mCapacity(0), + mNumGlyphs(0) {} + + ~GlyphBufferAzure() { + if (mNumGlyphs > 0) { + FlushGlyphs(); + } + + if (mBuffer != *mAutoBuffer.addr()) { + free(mBuffer); + } + } + + // Ensure the buffer has enough space for aGlyphCount glyphs to be added, + // considering the supplied strike multipler aStrikeCount. + // This MUST be called before OutputGlyph is used to actually store glyph + // records in the buffer. It may be called repeated to add further capacity + // in case we don't know up-front exactly what will be needed. + void AddCapacity(uint32_t aGlyphCount, uint32_t aStrikeCount) { + // Calculate the new capacity and ensure it will fit within the maximum + // allowed capacity. + static const uint64_t kMaxCapacity = 64 * 1024; + mCapacity = uint32_t(std::min( + kMaxCapacity, + uint64_t(mCapacity) + uint64_t(aGlyphCount) * uint64_t(aStrikeCount))); + // See if the required capacity fits within the already-allocated space + if (mCapacity <= mBufSize) { + return; + } + // We need to grow the buffer: determine a new size, allocate, and + // copy the existing data over if we didn't use realloc (which would + // do it automatically). + mBufSize = std::max(mCapacity, mBufSize * 2); + if (mBuffer == *mAutoBuffer.addr()) { + // switching from autobuffer to malloc, so we need to copy + mBuffer = reinterpret_cast<Glyph*>(moz_xmalloc(mBufSize * sizeof(Glyph))); + std::memcpy(mBuffer, *mAutoBuffer.addr(), mNumGlyphs * sizeof(Glyph)); + } else { + mBuffer = reinterpret_cast<Glyph*>( + moz_xrealloc(mBuffer, mBufSize * sizeof(Glyph))); + } + } + + void OutputGlyph(uint32_t aGlyphID, const gfx::Point& aPt) { + // If the buffer is full, flush to make room for the new glyph. + if (mNumGlyphs >= mCapacity) { + // Check that AddCapacity has been used appropriately! + MOZ_ASSERT(mCapacity > 0 && mNumGlyphs == mCapacity); + Flush(); + } + Glyph* glyph = mBuffer + mNumGlyphs++; + glyph->mIndex = aGlyphID; + glyph->mPosition = aPt; + } + + void Flush() { + if (mNumGlyphs > 0) { + FlushGlyphs(); + mNumGlyphs = 0; + } + } + + const TextRunDrawParams& mRunParams; + const FontDrawParams& mFontParams; + + private: + static DrawMode GetStrokeMode(DrawMode aMode) { + return aMode & (DrawMode::GLYPH_STROKE | DrawMode::GLYPH_STROKE_UNDERNEATH); + } + + // Render the buffered glyphs to the draw target. + void FlushGlyphs() { + gfx::GlyphBuffer buf; + buf.mGlyphs = mBuffer; + buf.mNumGlyphs = mNumGlyphs; + + const gfxContext::AzureState& state = mRunParams.context->CurrentState(); + + // Draw stroke first if the UNDERNEATH flag is set in drawMode. + if (mRunParams.strokeOpts && + GetStrokeMode(mRunParams.drawMode) == + (DrawMode::GLYPH_STROKE | DrawMode::GLYPH_STROKE_UNDERNEATH)) { + DrawStroke(state, buf); + } + + if (mRunParams.drawMode & DrawMode::GLYPH_FILL) { + if (state.pattern || mFontParams.contextPaint) { + Pattern* pat; + + RefPtr<gfxPattern> fillPattern; + if (mFontParams.contextPaint) { + imgDrawingParams imgParams; + fillPattern = mFontParams.contextPaint->GetFillPattern( + mRunParams.context->GetDrawTarget(), + mRunParams.context->CurrentMatrixDouble(), imgParams); + } + if (!fillPattern) { + if (state.pattern) { + RefPtr<gfxPattern> statePattern = + mRunParams.context->CurrentState().pattern; + pat = statePattern->GetPattern(mRunParams.dt, + state.patternTransformChanged + ? &state.patternTransform + : nullptr); + } else { + pat = nullptr; + } + } else { + pat = fillPattern->GetPattern(mRunParams.dt); + } + + if (pat) { + mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf, *pat, + mFontParams.drawOptions); + } + } else { + mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf, + ColorPattern(state.color), + mFontParams.drawOptions); + } + } + + // Draw stroke if the UNDERNEATH flag is not set. + if (mRunParams.strokeOpts && + GetStrokeMode(mRunParams.drawMode) == DrawMode::GLYPH_STROKE) { + DrawStroke(state, buf); + } + + if (mRunParams.drawMode & DrawMode::GLYPH_PATH) { + mRunParams.context->EnsurePathBuilder(); + Matrix mat = mRunParams.dt->GetTransform(); + mFontParams.scaledFont->CopyGlyphsToBuilder( + buf, mRunParams.context->mPathBuilder, &mat); + } + } + + void DrawStroke(const gfxContext::AzureState& aState, + gfx::GlyphBuffer& aBuffer) { + if (mRunParams.textStrokePattern) { + Pattern* pat = mRunParams.textStrokePattern->GetPattern( + mRunParams.dt, + aState.patternTransformChanged ? &aState.patternTransform : nullptr); + + if (pat) { + FlushStroke(aBuffer, *pat); + } + } else { + FlushStroke(aBuffer, + ColorPattern(ToDeviceColor(mRunParams.textStrokeColor))); + } + } + + void FlushStroke(gfx::GlyphBuffer& aBuf, const Pattern& aPattern) { + mRunParams.dt->StrokeGlyphs(mFontParams.scaledFont, aBuf, aPattern, + *mRunParams.strokeOpts, + mFontParams.drawOptions); + } + + // We use an "inline" buffer automatically allocated (on the stack) as part + // of the GlyphBufferAzure object to hold the glyphs in most cases, falling + // back to a separately-allocated heap buffer if the count of buffered + // glyphs gets too big. + // + // This is basically a rudimentary AutoTArray; so why not use AutoTArray + // itself? + // + // If we used an AutoTArray, we'd want to avoid using SetLength or + // AppendElements to allocate the space we actually need, because those + // methods would default-construct the new elements. + // + // Could we use SetCapacity to reserve the necessary buffer space without + // default-constructing all the Glyph records? No, because of a failure + // that could occur when we need to grow the buffer, which happens when we + // encounter a DetailedGlyph in the textrun that refers to a sequence of + // several real glyphs. At that point, we need to add some extra capacity + // to the buffer we initially allocated based on the length of the textrun + // range we're rendering. + // + // This buffer growth would work fine as long as it still fits within the + // array's inline buffer (we just use a bit more of it), or if the buffer + // was already heap-allocated (in which case AutoTArray will use realloc(), + // preserving its contents). But a problem will arise when the initial + // capacity we allocated (based on the length of the run) fits within the + // array's inline buffer, but subsequently we need to extend the buffer + // beyond the inline buffer size, so we reallocate to the heap. Because we + // haven't "officially" filled the array with SetLength or AppendElements, + // its mLength is still zero; as far as it's concerned the buffer is just + // uninitialized space, and when it switches to use a malloc'd buffer it + // won't copy the existing contents. + + // Allocate space for a buffer of Glyph records, without initializing them. + AlignedStorage2<Glyph[AUTO_BUFFER_SIZE]> mAutoBuffer; + + // Pointer to the buffer we're currently using -- initially mAutoBuffer, + // but may be changed to a malloc'd buffer, in which case that buffer must + // be free'd on destruction. + Glyph* mBuffer; + + uint32_t mBufSize; // size of allocated buffer; capacity can grow to + // this before reallocation is needed + uint32_t mCapacity; // amount of buffer size reserved + uint32_t mNumGlyphs; // number of glyphs actually present in the buffer + +#undef AUTO_BUFFER_SIZE +}; + +// Bug 674909. When synthetic bolding text by drawing twice, need to +// render using a pixel offset in device pixels, otherwise text +// doesn't appear bolded, it appears as if a bad text shadow exists +// when a non-identity transform exists. Use an offset factor so that +// the second draw occurs at a constant offset in device pixels. + +gfx::Float gfxFont::CalcXScale(DrawTarget* aDrawTarget) { + // determine magnitude of a 1px x offset in device space + Size t = aDrawTarget->GetTransform().TransformSize(Size(1.0, 0.0)); + if (t.width == 1.0 && t.height == 0.0) { + // short-circuit the most common case to avoid sqrt() and division + return 1.0; + } + + gfx::Float m = sqrtf(t.width * t.width + t.height * t.height); + + NS_ASSERTION(m != 0.0, "degenerate transform while synthetic bolding"); + if (m == 0.0) { + return 0.0; // effectively disables offset + } + + // scale factor so that offsets are 1px in device pixels + return 1.0 / m; +} + +// Draw a run of CharacterGlyph records from the given offset in aShapedText. +// Returns true if glyph paths were actually emitted. +template <gfxFont::FontComplexityT FC, gfxFont::SpacingT S> +bool gfxFont::DrawGlyphs(const gfxShapedText* aShapedText, + uint32_t aOffset, // offset in the textrun + uint32_t aCount, // length of run to draw + gfx::Point* aPt, + const gfx::Matrix* aOffsetMatrix, // may be null + GlyphBufferAzure& aBuffer) { + float& inlineCoord = + aBuffer.mFontParams.isVerticalFont ? aPt->y.value : aPt->x.value; + + const gfxShapedText::CompressedGlyph* glyphData = + &aShapedText->GetCharacterGlyphs()[aOffset]; + + if (S == SpacingT::HasSpacing) { + float space = aBuffer.mRunParams.spacing[0].mBefore * + aBuffer.mFontParams.advanceDirection; + inlineCoord += space; + } + + // Allocate buffer space for the run, assuming all simple glyphs. + uint32_t capacityMult = 1 + aBuffer.mFontParams.extraStrikes; + aBuffer.AddCapacity(aCount, capacityMult); + + bool emittedGlyphs = false; + + for (uint32_t i = 0; i < aCount; ++i, ++glyphData) { + if (glyphData->IsSimpleGlyph()) { + float advance = + glyphData->GetSimpleAdvance() * aBuffer.mFontParams.advanceDirection; + if (aBuffer.mRunParams.isRTL) { + inlineCoord += advance; + } + DrawOneGlyph<FC>(glyphData->GetSimpleGlyph(), *aPt, aBuffer, + &emittedGlyphs); + if (!aBuffer.mRunParams.isRTL) { + inlineCoord += advance; + } + } else { + uint32_t glyphCount = glyphData->GetGlyphCount(); + if (glyphCount > 0) { + // Add extra buffer capacity to allow for multiple-glyph entry. + aBuffer.AddCapacity(glyphCount - 1, capacityMult); + const gfxShapedText::DetailedGlyph* details = + aShapedText->GetDetailedGlyphs(aOffset + i); + MOZ_ASSERT(details, "missing DetailedGlyph!"); + for (uint32_t j = 0; j < glyphCount; ++j, ++details) { + float advance = + details->mAdvance * aBuffer.mFontParams.advanceDirection; + if (aBuffer.mRunParams.isRTL) { + inlineCoord += advance; + } + if (glyphData->IsMissing()) { + if (!DrawMissingGlyph(aBuffer.mRunParams, aBuffer.mFontParams, + details, *aPt)) { + return false; + } + } else { + gfx::Point glyphPt( + *aPt + (aOffsetMatrix + ? aOffsetMatrix->TransformPoint(details->mOffset) + : details->mOffset)); + DrawOneGlyph<FC>(details->mGlyphID, glyphPt, aBuffer, + &emittedGlyphs); + } + if (!aBuffer.mRunParams.isRTL) { + inlineCoord += advance; + } + } + } + } + + if (S == SpacingT::HasSpacing) { + float space = aBuffer.mRunParams.spacing[i].mAfter; + if (i + 1 < aCount) { + space += aBuffer.mRunParams.spacing[i + 1].mBefore; + } + space *= aBuffer.mFontParams.advanceDirection; + inlineCoord += space; + } + } + + return emittedGlyphs; +} + +// Draw an individual glyph at a specific location. +// *aPt is the glyph position in appUnits; it is converted to device +// coordinates (devPt) here. +template <gfxFont::FontComplexityT FC> +void gfxFont::DrawOneGlyph(uint32_t aGlyphID, const gfx::Point& aPt, + GlyphBufferAzure& aBuffer, bool* aEmittedGlyphs) { + const TextRunDrawParams& runParams(aBuffer.mRunParams); + + gfx::Point devPt(ToDeviceUnits(aPt.x, runParams.devPerApp), + ToDeviceUnits(aPt.y, runParams.devPerApp)); + + auto* textDrawer = runParams.textDrawer; + if (textDrawer) { + // If the glyph is entirely outside the clip rect, we don't need to draw it + // at all. (We check the font extents here rather than the individual glyph + // bounds because that's cheaper to look up, and provides a conservative + // "worst case" for where this glyph might want to draw.) + LayoutDeviceRect extents = + LayoutDeviceRect::FromUnknownRect(aBuffer.mFontParams.fontExtents); + extents.MoveBy(LayoutDevicePoint::FromUnknownPoint(devPt)); + if (!extents.Intersects(runParams.clipRect)) { + return; + } + } + + if (FC == FontComplexityT::ComplexFont) { + const FontDrawParams& fontParams(aBuffer.mFontParams); + + gfxContextMatrixAutoSaveRestore matrixRestore; + + if (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont && + !textDrawer) { + // We have to flush each glyph individually when doing + // synthetic-oblique for vertical-upright text, because + // the skew transform needs to be applied to a separate + // origin for each glyph, not once for the whole run. + aBuffer.Flush(); + matrixRestore.SetContext(runParams.context); + gfx::Point skewPt( + devPt.x + GetMetrics(nsFontMetrics::eVertical).emHeight / 2, devPt.y); + gfx::Matrix mat = + runParams.context->CurrentMatrix() + .PreTranslate(skewPt) + .PreMultiply(gfx::Matrix(1, fontParams.obliqueSkew, 0, 1, 0, 0)) + .PreTranslate(-skewPt); + runParams.context->SetMatrix(mat); + } + + if (fontParams.haveSVGGlyphs) { + if (!runParams.paintSVGGlyphs) { + return; + } + NS_WARNING_ASSERTION( + runParams.drawMode != DrawMode::GLYPH_PATH, + "Rendering SVG glyph despite request for glyph path"); + if (RenderSVGGlyph(runParams.context, textDrawer, devPt, aGlyphID, + fontParams.contextPaint, runParams.callbacks, + *aEmittedGlyphs)) { + return; + } + } + + if (fontParams.haveColorGlyphs && !UseNativeColrFontSupport() && + RenderColorGlyph(runParams.dt, runParams.context, textDrawer, + fontParams, devPt, aGlyphID)) { + return; + } + + aBuffer.OutputGlyph(aGlyphID, devPt); + + // Synthetic bolding (if required) by multi-striking. + for (int32_t i = 0; i < fontParams.extraStrikes; ++i) { + if (fontParams.isVerticalFont) { + devPt.y += fontParams.synBoldOnePixelOffset; + } else { + devPt.x += fontParams.synBoldOnePixelOffset; + } + aBuffer.OutputGlyph(aGlyphID, devPt); + } + + if (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont && + !textDrawer) { + aBuffer.Flush(); + } + } else { + aBuffer.OutputGlyph(aGlyphID, devPt); + } + + *aEmittedGlyphs = true; +} + +bool gfxFont::DrawMissingGlyph(const TextRunDrawParams& aRunParams, + const FontDrawParams& aFontParams, + const gfxShapedText::DetailedGlyph* aDetails, + const gfx::Point& aPt) { + // Default-ignorable chars will have zero advance width; + // we don't have to draw the hexbox for them. + float advance = aDetails->mAdvance; + if (aRunParams.drawMode != DrawMode::GLYPH_PATH && advance > 0) { + auto* textDrawer = aRunParams.textDrawer; + const Matrix* matPtr = nullptr; + Matrix mat; + if (textDrawer) { + // Generate an orientation matrix for the current writing mode + wr::FontInstanceFlags flags = textDrawer->GetWRGlyphFlags(); + if (flags & wr::FontInstanceFlags::TRANSPOSE) { + std::swap(mat._11, mat._12); + std::swap(mat._21, mat._22); + } + mat.PostScale(flags & wr::FontInstanceFlags::FLIP_X ? -1.0f : 1.0f, + flags & wr::FontInstanceFlags::FLIP_Y ? -1.0f : 1.0f); + matPtr = &mat; + } + + Point pt(Float(ToDeviceUnits(aPt.x, aRunParams.devPerApp)), + Float(ToDeviceUnits(aPt.y, aRunParams.devPerApp))); + Float advanceDevUnits = Float(ToDeviceUnits(advance, aRunParams.devPerApp)); + Float height = GetMetrics(nsFontMetrics::eHorizontal).maxAscent; + // Horizontally center if drawing vertically upright with no sideways + // transform. + Rect glyphRect = + aFontParams.isVerticalFont && !mat.HasNonAxisAlignedTransform() + ? Rect(pt.x - height / 2, pt.y, height, advanceDevUnits) + : Rect(pt.x, pt.y - height, advanceDevUnits, height); + + // If there's a fake-italic skew in effect as part + // of the drawTarget's transform, we need to undo + // this before drawing the hexbox. (Bug 983985) + gfxContextMatrixAutoSaveRestore matrixRestore; + if (aFontParams.obliqueSkew != 0.0f && !aFontParams.isVerticalFont && + !textDrawer) { + matrixRestore.SetContext(aRunParams.context); + gfx::Matrix mat = + aRunParams.context->CurrentMatrix() + .PreTranslate(pt) + .PreMultiply(gfx::Matrix(1, 0, aFontParams.obliqueSkew, 1, 0, 0)) + .PreTranslate(-pt); + aRunParams.context->SetMatrix(mat); + } + + gfxFontMissingGlyphs::DrawMissingGlyph( + aDetails->mGlyphID, glyphRect, *aRunParams.dt, + PatternFromState(aRunParams.context), matPtr); + } + return true; +} + +// This method is mostly parallel to DrawGlyphs. +void gfxFont::DrawEmphasisMarks(const gfxTextRun* aShapedText, gfx::Point* aPt, + uint32_t aOffset, uint32_t aCount, + const EmphasisMarkDrawParams& aParams) { + float& inlineCoord = aParams.isVertical ? aPt->y.value : aPt->x.value; + gfxTextRun::Range markRange(aParams.mark); + gfxTextRun::DrawParams params(aParams.context, aParams.paletteCache); + + 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, TextRunDrawParams& aRunParams, + gfx::ShapedTextFlags aOrientation) { + NS_ASSERTION(aRunParams.drawMode == DrawMode::GLYPH_PATH || + !(int(aRunParams.drawMode) & int(DrawMode::GLYPH_PATH)), + "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or " + "GLYPH_STROKE_UNDERNEATH"); + + if (aStart >= aEnd) { + return; + } + + FontDrawParams fontParams; + + if (aRunParams.drawOpts) { + fontParams.drawOptions = *aRunParams.drawOpts; + } + + fontParams.scaledFont = GetScaledFont(aRunParams); + if (!fontParams.scaledFont) { + return; + } + auto* textDrawer = aRunParams.textDrawer; + + fontParams.obliqueSkew = SkewForSyntheticOblique(); + fontParams.haveSVGGlyphs = GetFontEntry()->TryGetSVGData(this); + fontParams.haveColorGlyphs = GetFontEntry()->TryGetColorGlyphs(); + fontParams.hasTextShadow = aRunParams.hasTextShadow; + fontParams.contextPaint = aRunParams.runContextPaint; + + if (fontParams.haveColorGlyphs && !UseNativeColrFontSupport()) { + DeviceColor ctxColor; + fontParams.currentColor = aRunParams.context->GetDeviceColor(ctxColor) + ? sRGBColor::FromABGR(ctxColor.ToABGR()) + : sRGBColor::OpaqueBlack(); + fontParams.palette = aRunParams.paletteCache.GetPaletteFor( + GetFontEntry(), aRunParams.fontPalette); + } + + if (textDrawer) { + fontParams.isVerticalFont = aRunParams.isVerticalRun; + } else { + fontParams.isVerticalFont = + aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT; + } + + gfxContextMatrixAutoSaveRestore matrixRestore; + layout::TextDrawTarget::AutoRestoreWRGlyphFlags glyphFlagsRestore; + + // Save the current baseline offset for restoring later, in case it is + // modified. + float& baseline = fontParams.isVerticalFont ? aPt->x.value : aPt->y.value; + float origBaseline = baseline; + + // The point may be advanced in local-space, while the resulting point on + // return must be advanced in transformed space. So save the original point so + // we can properly transform the advance later. + gfx::Point origPt = *aPt; + const gfx::Matrix* offsetMatrix = nullptr; + + // Default to advancing along the +X direction (-X if RTL). + fontParams.advanceDirection = aRunParams.isRTL ? -1.0f : 1.0f; + // Default to offsetting baseline downward along the +Y direction. + float baselineDir = 1.0f; + // The direction of sideways rotation, if applicable. + // -1 for rotating left/counter-clockwise + // 1 for rotating right/clockwise + // 0 for no rotation + float sidewaysDir = + (aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT + ? -1.0f + : (aOrientation == + gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT + ? 1.0f + : 0.0f)); + // If we're rendering a sideways run, we need to push a rotation transform to + // the context. + if (sidewaysDir != 0.0f) { + if (textDrawer) { + // For WebRender, we can't use a DrawTarget transform and must instead use + // flags that locally transform the glyph, without affecting the glyph + // origin. The glyph origins must thus be offset in the transformed + // directions (instead of local-space directions). Modify the advance and + // baseline directions to account for the indicated transform. + + // The default text orientation is down being +Y and right being +X. + // Rotating 90 degrees left/CCW makes down be +X and right be -Y. + // Rotating 90 degrees right/CW makes down be -X and right be +Y. + // Thus the advance direction (moving right) is just sidewaysDir, + // i.e. negative along Y axis if rotated left and positive if + // rotated right. + fontParams.advanceDirection *= sidewaysDir; + // The baseline direction (moving down) is negated relative to the + // advance direction for sideways transforms. + baselineDir *= -sidewaysDir; + + glyphFlagsRestore.Save(textDrawer); + // Set the transform flags accordingly. Both sideways rotations transpose + // X and Y, while left rotation flips the resulting Y axis, and right + // rotation flips the resulting X axis. + textDrawer->SetWRGlyphFlags( + textDrawer->GetWRGlyphFlags() | wr::FontInstanceFlags::TRANSPOSE | + (aOrientation == + gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT + ? wr::FontInstanceFlags::FLIP_Y + : wr::FontInstanceFlags::FLIP_X)); + // We also need to set up a transform for the glyph offset vector that + // may be present in DetailedGlyph records. + static const gfx::Matrix kSidewaysLeft = {0, -1, 1, 0, 0, 0}; + static const gfx::Matrix kSidewaysRight = {0, 1, -1, 0, 0, 0}; + offsetMatrix = + (aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT) + ? &kSidewaysLeft + : &kSidewaysRight; + } else { + // For non-WebRender targets, just push a rotation transform. + matrixRestore.SetContext(aRunParams.context); + gfxPoint p(aPt->x * aRunParams.devPerApp, aPt->y * aRunParams.devPerApp); + // Get a matrix we can use to draw the (horizontally-shaped) textrun + // with 90-degree CW rotation. + const gfxFloat rotation = sidewaysDir * M_PI / 2.0f; + gfxMatrix mat = aRunParams.context->CurrentMatrixDouble() + .PreTranslate(p) + . // translate origin for rotation + PreRotate(rotation) + . // turn 90deg CCW (sideways-left) or CW (*-right) + PreTranslate(-p); // undo the translation + + aRunParams.context->SetMatrixDouble(mat); + } + + // If we're drawing rotated horizontal text for an element styled + // text-orientation:mixed, the dominant baseline will be vertical- + // centered. So in this case, we need to adjust the position so that + // the rotated horizontal text (which uses an alphabetic baseline) will + // look OK when juxtaposed with upright glyphs (rendered on a centered + // vertical baseline). The adjustment here is somewhat ad hoc; we + // should eventually look for baseline tables[1] in the fonts and use + // those if available. + // [1] See http://www.microsoft.com/typography/otspec/base.htm + if (aTextRun->UseCenterBaseline()) { + const Metrics& metrics = GetMetrics(nsFontMetrics::eHorizontal); + float baseAdj = (metrics.emAscent - metrics.emDescent) / 2; + baseline += baseAdj * aTextRun->GetAppUnitsPerDevUnit() * baselineDir; + } + } else if (textDrawer && + aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT) { + glyphFlagsRestore.Save(textDrawer); + textDrawer->SetWRGlyphFlags(textDrawer->GetWRGlyphFlags() | + wr::FontInstanceFlags::VERTICAL); + } + + if (fontParams.obliqueSkew != 0.0f && !fontParams.isVerticalFont && + !textDrawer) { + // Adjust matrix for synthetic-oblique, except if we're doing vertical- + // upright text, in which case this will be handled for each glyph + // individually in DrawOneGlyph. + if (!matrixRestore.HasMatrix()) { + matrixRestore.SetContext(aRunParams.context); + } + gfx::Point p(aPt->x * aRunParams.devPerApp, aPt->y * aRunParams.devPerApp); + gfx::Matrix mat = + aRunParams.context->CurrentMatrix() + .PreTranslate(p) + .PreMultiply(gfx::Matrix(1, 0, -fontParams.obliqueSkew, 1, 0, 0)) + .PreTranslate(-p); + aRunParams.context->SetMatrix(mat); + } + + RefPtr<SVGContextPaint> contextPaint; + if (fontParams.haveSVGGlyphs && !fontParams.contextPaint) { + // If no pattern is specified for fill, use the current pattern + NS_ASSERTION((int(aRunParams.drawMode) & int(DrawMode::GLYPH_STROKE)) == 0, + "no pattern supplied for stroking text"); + RefPtr<gfxPattern> fillPattern = aRunParams.context->GetPattern(); + contextPaint = new SimpleTextContextPaint( + fillPattern, nullptr, aRunParams.context->CurrentMatrixDouble()); + fontParams.contextPaint = contextPaint.get(); + } + + // Synthetic-bold strikes are each offset one device pixel in run direction + // (these values are only needed if ApplySyntheticBold() is true). + // If drawing via webrender, it will do multistrike internally so we don't + // need to handle it here. + bool doMultistrikeBold = ApplySyntheticBold() && !textDrawer; + if (doMultistrikeBold) { + // For screen display, we want to try and repeat strikes with an offset of + // one device pixel, accounting for zoom or other transforms that may be + // in effect, so compute x-axis scale factor from the drawtarget. + // However, when generating PDF output the drawtarget's transform does not + // really bear any relation to "device pixels", and may result in an + // excessively large offset relative to the font size (bug 1823888), so + // we limit it based on the used font size to avoid this. + // The constant 48.0 reflects the threshold where the calculation in + // gfxFont::GetSyntheticBoldOffset() switches to a simple origin-based + // slope, though the exact value is somewhat arbitrary; it's selected to + // allow a visible amount of boldness while preventing the offset from + // becoming "large" in relation to the glyphs. + Float xscale = + std::min<Float>(GetAdjustedSize() / 48.0, + CalcXScale(aRunParams.context->GetDrawTarget())); + fontParams.synBoldOnePixelOffset = aRunParams.direction * xscale; + if (xscale != 0.0) { + static const int32_t kMaxExtraStrikes = 128; + gfxFloat extraStrikes = GetSyntheticBoldOffset() / xscale; + if (extraStrikes > kMaxExtraStrikes) { + // if too many strikes are required, limit them and increase the step + // size to compensate + fontParams.extraStrikes = kMaxExtraStrikes; + fontParams.synBoldOnePixelOffset = aRunParams.direction * + GetSyntheticBoldOffset() / + fontParams.extraStrikes; + } else { + // use as many strikes as needed for the increased advance + fontParams.extraStrikes = NS_lroundf(std::max(1.0, extraStrikes)); + } + } else { + // Degenerate transform?! + fontParams.extraStrikes = 0; + } + } else { + fontParams.synBoldOnePixelOffset = 0; + fontParams.extraStrikes = 0; + } + + // Figure out the maximum extents for the font, accounting for synthetic + // oblique and bold. + if (mFUnitsConvFactor > 0.0) { + fontParams.fontExtents = GetFontEntry()->GetFontExtents(mFUnitsConvFactor); + } else { + // Was it not an sfnt? Maybe on Linux... use arbitrary huge extents, so we + // don't inadvertently clip stuff. A bit less efficient than true extents, + // but this should be extremely rare. + auto size = GetAdjustedSize(); + fontParams.fontExtents = Rect(-2 * size, -2 * size, 5 * size, 5 * size); + } + if (fontParams.obliqueSkew != 0.0f) { + gfx::Point p(fontParams.fontExtents.x, fontParams.fontExtents.y); + gfx::Matrix skew(1, 0, fontParams.obliqueSkew, 1, 0, 0); + fontParams.fontExtents = skew.TransformBounds(fontParams.fontExtents); + } + if (fontParams.extraStrikes) { + if (fontParams.isVerticalFont) { + fontParams.fontExtents.height += + float(fontParams.extraStrikes) * fontParams.synBoldOnePixelOffset; + } else { + fontParams.fontExtents.width += + float(fontParams.extraStrikes) * fontParams.synBoldOnePixelOffset; + } + } + + bool oldSubpixelAA = aRunParams.dt->GetPermitSubpixelAA(); + if (!AllowSubpixelAA()) { + aRunParams.dt->SetPermitSubpixelAA(false); + } + + Matrix mat; + Matrix oldMat = aRunParams.dt->GetTransform(); + + fontParams.drawOptions.mAntialiasMode = Get2DAAMode(mAntialiasOption); + + if (mStyle.baselineOffset != 0.0) { + baseline += + mStyle.baselineOffset * aTextRun->GetAppUnitsPerDevUnit() * baselineDir; + } + + bool emittedGlyphs; + { + // Select appropriate version of the templated DrawGlyphs method + // to output glyphs to the buffer, depending on complexity needed + // for the type of font, and whether added inter-glyph spacing + // is specified. + GlyphBufferAzure buffer(aRunParams, fontParams); + if (fontParams.haveSVGGlyphs || fontParams.haveColorGlyphs || + fontParams.extraStrikes || + (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont && + !textDrawer)) { + if (aRunParams.spacing) { + emittedGlyphs = + DrawGlyphs<FontComplexityT::ComplexFont, SpacingT::HasSpacing>( + aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer); + } else { + emittedGlyphs = + DrawGlyphs<FontComplexityT::ComplexFont, SpacingT::NoSpacing>( + aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer); + } + } else { + if (aRunParams.spacing) { + emittedGlyphs = + DrawGlyphs<FontComplexityT::SimpleFont, SpacingT::HasSpacing>( + aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer); + } else { + emittedGlyphs = + DrawGlyphs<FontComplexityT::SimpleFont, SpacingT::NoSpacing>( + aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer); + } + } + } + + baseline = origBaseline; + + if (aRunParams.callbacks && emittedGlyphs) { + aRunParams.callbacks->NotifyGlyphPathEmitted(); + } + + aRunParams.dt->SetTransform(oldMat); + aRunParams.dt->SetPermitSubpixelAA(oldSubpixelAA); + + if (sidewaysDir != 0.0f && !textDrawer) { + // Adjust updated aPt to account for the transform we were using. + // The advance happened horizontally in local-space, but the transformed + // sideways advance is actually vertical, with sign depending on the + // direction of rotation. + float advance = aPt->x - origPt.x; + *aPt = gfx::Point(origPt.x, origPt.y + advance * sidewaysDir); + } +} + +bool gfxFont::RenderSVGGlyph(gfxContext* aContext, + layout::TextDrawTarget* aTextDrawer, + gfx::Point aPoint, uint32_t aGlyphId, + SVGContextPaint* aContextPaint) const { + if (!GetFontEntry()->HasSVGGlyph(aGlyphId)) { + return false; + } + + if (aTextDrawer) { + // WebRender doesn't support SVG Glyphs. + // (pretend to succeed, output doesn't matter, we will emit a blob) + aTextDrawer->FoundUnsupportedFeature(); + return true; + } + + const gfxFloat devUnitsPerSVGUnit = + GetAdjustedSize() / GetFontEntry()->UnitsPerEm(); + gfxContextMatrixAutoSaveRestore matrixRestore(aContext); + + aContext->SetMatrix(aContext->CurrentMatrix() + .PreTranslate(aPoint.x, aPoint.y) + .PreScale(devUnitsPerSVGUnit, devUnitsPerSVGUnit)); + + aContextPaint->InitStrokeGeometry(aContext, devUnitsPerSVGUnit); + + GetFontEntry()->RenderSVGGlyph(aContext, aGlyphId, aContextPaint); + aContext->NewPath(); + return true; +} + +bool gfxFont::RenderSVGGlyph(gfxContext* aContext, + layout::TextDrawTarget* aTextDrawer, + gfx::Point aPoint, uint32_t aGlyphId, + SVGContextPaint* aContextPaint, + gfxTextRunDrawCallbacks* aCallbacks, + bool& aEmittedGlyphs) const { + if (aCallbacks && aEmittedGlyphs) { + aCallbacks->NotifyGlyphPathEmitted(); + aEmittedGlyphs = false; + } + return RenderSVGGlyph(aContext, aTextDrawer, aPoint, aGlyphId, aContextPaint); +} + +bool gfxFont::RenderColorGlyph(DrawTarget* aDrawTarget, gfxContext* aContext, + layout::TextDrawTarget* aTextDrawer, + const FontDrawParams& aFontParams, + const Point& aPoint, uint32_t aGlyphId) { + if (aTextDrawer && aFontParams.hasTextShadow) { + aTextDrawer->FoundUnsupportedFeature(); + return true; + } + + auto* colr = GetFontEntry()->GetCOLR(); + if (const auto* paintGraph = COLRFonts::GetGlyphPaintGraph(colr, aGlyphId)) { + const auto* hbShaper = GetHarfBuzzShaper(); + if (hbShaper && hbShaper->IsInitialized()) { + if (aTextDrawer) { + aTextDrawer->FoundUnsupportedFeature(); + return true; + } + + // For reasonable font sizes, use a cache of rasterized glyphs. + if (GetAdjustedSize() <= 256.0) { + AutoWriteLock lock(mLock); + if (!mColorGlyphCache) { + mColorGlyphCache = MakeUnique<ColorGlyphCache>(); + } + + // Tell the cache what colors we're using; if they have changed, it will + // discard any currently-cached entries. + mColorGlyphCache->SetColors(aFontParams.currentColor, + aFontParams.palette); + + bool ok = false; + auto cached = mColorGlyphCache->mCache.lookupForAdd(aGlyphId); + Rect bounds = COLRFonts::GetColorGlyphBounds( + colr, hbShaper->GetHBFont(), aGlyphId, aDrawTarget, + aFontParams.scaledFont, mFUnitsConvFactor); + bounds.RoundOut(); + + if (cached) { + ok = true; + } else { + // Create a temporary DrawTarget, render the glyph, and save a + // snapshot of the rendering in the cache. + IntSize size(int(bounds.width), int(bounds.height)); + SurfaceFormat format = SurfaceFormat::B8G8R8A8; + RefPtr target = + Factory::CreateDrawTarget(BackendType::SKIA, size, format); + if (target) { + ok = COLRFonts::PaintGlyphGraph( + GetFontEntry()->GetCOLR(), hbShaper->GetHBFont(), paintGraph, + target, nullptr, aFontParams.scaledFont, + aFontParams.drawOptions, -bounds.TopLeft(), + aFontParams.currentColor, aFontParams.palette->Colors(), + aGlyphId, mFUnitsConvFactor); + if (ok) { + RefPtr snapshot = target->Snapshot(); + ok = mColorGlyphCache->mCache.add(cached, aGlyphId, snapshot); + } + } + } + if (ok) { + // Paint the snapshot from cached->value(), and return. + aDrawTarget->DrawSurface( + cached->value(), Rect(aPoint + bounds.TopLeft(), bounds.Size()), + Rect(Point(), bounds.Size())); + return true; + } + } + + // If we failed to cache the glyph, or it was too large to even try, + // just paint directly to the target. + return COLRFonts::PaintGlyphGraph( + colr, hbShaper->GetHBFont(), paintGraph, aDrawTarget, aTextDrawer, + aFontParams.scaledFont, aFontParams.drawOptions, aPoint, + aFontParams.currentColor, aFontParams.palette->Colors(), aGlyphId, + mFUnitsConvFactor); + } + } + + if (const auto* layers = + COLRFonts::GetGlyphLayers(GetFontEntry()->GetCOLR(), aGlyphId)) { + auto face(GetFontEntry()->GetHBFace()); + bool ok = COLRFonts::PaintGlyphLayers( + colr, face, layers, aDrawTarget, aTextDrawer, aFontParams.scaledFont, + aFontParams.drawOptions, aPoint, aFontParams.currentColor, + aFontParams.palette->Colors()); + return ok; + } + + return false; +} + +void gfxFont::ColorGlyphCache::SetColors(sRGBColor aCurrentColor, + FontPalette* aPalette) { + if (aCurrentColor != mCurrentColor || aPalette != mPalette) { + mCache.clear(); + mCurrentColor = aCurrentColor; + mPalette = aPalette; + } +} + +bool gfxFont::HasColorGlyphFor(uint32_t aCh, uint32_t aNextCh) { + // Bitmap fonts are assumed to provide "color" glyphs for all supported chars. + gfxFontEntry* fe = GetFontEntry(); + if (fe->HasColorBitmapTable()) { + return true; + } + // Use harfbuzz shaper to look up the default glyph ID for the character. + auto* shaper = GetHarfBuzzShaper(); + if (!shaper) { + return false; + } + uint32_t gid = 0; + if (gfxFontUtils::IsVarSelector(aNextCh)) { + gid = shaper->GetVariationGlyph(aCh, aNextCh); + } + if (!gid) { + gid = shaper->GetNominalGlyph(aCh); + } + if (!gid) { + return false; + } + + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1801521: + // Emoji special-case: flag sequences NOT based on Regional Indicator pairs + // use the BLACK FLAG character plus a series of plane-14 TAG LETTERs, e.g. + // England = <black-flag, tag-G, tag-B, tag-E, tag-N, tag-G, tag-cancel> + // Here, we don't check for support of the entire sequence (too much + // expensive lookahead), but we check that the font at least supports the + // first of the tag letter codes, because if it doesn't, we're at risk of + // just getting an undifferentiated black flag glyph. + if (gfxFontUtils::IsEmojiFlagAndTag(aCh, aNextCh)) { + if (!shaper->GetNominalGlyph(aNextCh)) { + return false; + } + } + + // Check if there is a COLR/CPAL or SVG glyph for this ID. + if (fe->TryGetColorGlyphs() && + (COLRFonts::GetGlyphPaintGraph(fe->GetCOLR(), gid) || + COLRFonts::GetGlyphLayers(fe->GetCOLR(), gid))) { + return true; + } + if (fe->TryGetSVGData(this) && fe->HasSVGGlyph(gid)) { + return true; + } + return false; +} + +static void UnionRange(gfxFloat aX, gfxFloat* aDestMin, gfxFloat* aDestMax) { + *aDestMin = std::min(*aDestMin, aX); + *aDestMax = std::max(*aDestMax, aX); +} + +// We get precise glyph extents if the textrun creator requested them, or +// if the font is a user font --- in which case the author may be relying +// on overflowing glyphs. +static bool NeedsGlyphExtents(gfxFont* aFont, const gfxTextRun* aTextRun) { + return (aTextRun->GetFlags() & + gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX) || + aFont->GetFontEntry()->IsUserFont(); +} + +bool gfxFont::IsSpaceGlyphInvisible(DrawTarget* aRefDrawTarget, + const gfxTextRun* aTextRun) { + gfxFontEntry::LazyFlag flag = mFontEntry->mSpaceGlyphIsInvisible; + if (flag == gfxFontEntry::LazyFlag::Uninitialized && + GetAdjustedSize() >= 1.0) { + gfxGlyphExtents* extents = + GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit()); + gfxRect glyphExtents; + flag = extents->GetTightGlyphExtentsAppUnits( + this, aRefDrawTarget, GetSpaceGlyph(), &glyphExtents) && + glyphExtents.IsEmpty() + ? gfxFontEntry::LazyFlag::Yes + : gfxFontEntry::LazyFlag::No; + mFontEntry->mSpaceGlyphIsInvisible = flag; + } + return flag == gfxFontEntry::LazyFlag::Yes; +} + +bool gfxFont::MeasureGlyphs(const gfxTextRun* aTextRun, uint32_t aStart, + uint32_t aEnd, BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, Spacing* aSpacing, + gfxGlyphExtents* aExtents, bool aIsRTL, + bool aNeedsGlyphExtents, RunMetrics& aMetrics, + gfxFloat* aAdvanceMin, gfxFloat* aAdvanceMax) { + const gfxTextRun::CompressedGlyph* charGlyphs = + aTextRun->GetCharacterGlyphs(); + double x = 0; + if (aSpacing) { + x += aSpacing[0].mBefore; + } + uint32_t spaceGlyph = GetSpaceGlyph(); + bool allGlyphsInvisible = true; + + AutoReadLock lock(aExtents->mLock); + + for (uint32_t i = aStart; i < aEnd; ++i) { + const gfxTextRun::CompressedGlyph* glyphData = &charGlyphs[i]; + if (glyphData->IsSimpleGlyph()) { + double advance = glyphData->GetSimpleAdvance(); + uint32_t glyphIndex = glyphData->GetSimpleGlyph(); + if (allGlyphsInvisible) { + if (glyphIndex != spaceGlyph) { + allGlyphsInvisible = false; + } else { + gfxFontEntry::LazyFlag flag = mFontEntry->mSpaceGlyphIsInvisible; + if (flag == gfxFontEntry::LazyFlag::Uninitialized && + GetAdjustedSize() >= 1.0) { + gfxRect glyphExtents; + flag = aExtents->GetTightGlyphExtentsAppUnitsLocked( + this, aRefDrawTarget, spaceGlyph, &glyphExtents) && + glyphExtents.IsEmpty() + ? gfxFontEntry::LazyFlag::Yes + : gfxFontEntry::LazyFlag::No; + mFontEntry->mSpaceGlyphIsInvisible = flag; + } + if (flag == gfxFontEntry::LazyFlag::No) { + allGlyphsInvisible = false; + } + } + } + // Only get the real glyph horizontal extent if we were asked + // for the tight bounding box or we're in quality mode + if (aBoundingBoxType != LOOSE_INK_EXTENTS || aNeedsGlyphExtents) { + uint16_t extentsWidth = + aExtents->GetContainedGlyphWidthAppUnitsLocked(glyphIndex); + if (extentsWidth != gfxGlyphExtents::INVALID_WIDTH && + aBoundingBoxType == LOOSE_INK_EXTENTS) { + UnionRange(x, aAdvanceMin, aAdvanceMax); + UnionRange(x + extentsWidth, aAdvanceMin, aAdvanceMax); + } else { + gfxRect glyphRect; + if (!aExtents->GetTightGlyphExtentsAppUnitsLocked( + this, aRefDrawTarget, glyphIndex, &glyphRect)) { + glyphRect = gfxRect(0, aMetrics.mBoundingBox.Y(), advance, + aMetrics.mBoundingBox.Height()); + } + if (aIsRTL) { + // In effect, swap left and right sidebearings of the glyph, for + // proper accumulation of potentially-overlapping glyph rects. + glyphRect.MoveToX(advance - glyphRect.XMost()); + } + glyphRect.MoveByX(x); + aMetrics.mBoundingBox = aMetrics.mBoundingBox.Union(glyphRect); + } + } + x += advance; + } else { + allGlyphsInvisible = false; + uint32_t glyphCount = glyphData->GetGlyphCount(); + if (glyphCount > 0) { + const gfxTextRun::DetailedGlyph* details = + aTextRun->GetDetailedGlyphs(i); + NS_ASSERTION(details != nullptr, + "detailedGlyph record should not be missing!"); + uint32_t j; + for (j = 0; j < glyphCount; ++j, ++details) { + uint32_t glyphIndex = details->mGlyphID; + double advance = details->mAdvance; + gfxRect glyphRect; + if (glyphData->IsMissing() || + !aExtents->GetTightGlyphExtentsAppUnitsLocked( + this, aRefDrawTarget, glyphIndex, &glyphRect)) { + // We might have failed to get glyph extents due to + // OOM or something + glyphRect = gfxRect(0, -aMetrics.mAscent, advance, + aMetrics.mAscent + aMetrics.mDescent); + } + if (aIsRTL) { + // Swap left/right sidebearings of the glyph, because we're doing + // mirrored measurement. + glyphRect.MoveToX(advance - glyphRect.XMost()); + // Move to current x position, mirroring any x-offset amount. + glyphRect.MoveByX(x - details->mOffset.x); + } else { + glyphRect.MoveByX(x + details->mOffset.x); + } + glyphRect.MoveByY(details->mOffset.y); + aMetrics.mBoundingBox = aMetrics.mBoundingBox.Union(glyphRect); + x += advance; + } + } + } + if (aSpacing) { + double space = aSpacing[i - aStart].mAfter; + if (i + 1 < aEnd) { + space += aSpacing[i + 1 - aStart].mBefore; + } + x += space; + } + } + + aMetrics.mAdvanceWidth = x; + return allGlyphsInvisible; +} + +bool gfxFont::MeasureGlyphs(const gfxTextRun* aTextRun, uint32_t aStart, + uint32_t aEnd, BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, Spacing* aSpacing, + bool aIsRTL, RunMetrics& aMetrics) { + const gfxTextRun::CompressedGlyph* charGlyphs = + aTextRun->GetCharacterGlyphs(); + double x = 0; + if (aSpacing) { + x += aSpacing[0].mBefore; + } + uint32_t spaceGlyph = GetSpaceGlyph(); + bool allGlyphsInvisible = true; + + for (uint32_t i = aStart; i < aEnd; ++i) { + const gfxTextRun::CompressedGlyph* glyphData = &charGlyphs[i]; + if (glyphData->IsSimpleGlyph()) { + double advance = glyphData->GetSimpleAdvance(); + uint32_t glyphIndex = glyphData->GetSimpleGlyph(); + if (allGlyphsInvisible && + (glyphIndex != spaceGlyph || + !IsSpaceGlyphInvisible(aRefDrawTarget, aTextRun))) { + allGlyphsInvisible = false; + } + x += advance; + } else { + allGlyphsInvisible = false; + uint32_t glyphCount = glyphData->GetGlyphCount(); + if (glyphCount > 0) { + const gfxTextRun::DetailedGlyph* details = + aTextRun->GetDetailedGlyphs(i); + NS_ASSERTION(details != nullptr, + "detailedGlyph record should not be missing!"); + uint32_t j; + for (j = 0; j < glyphCount; ++j, ++details) { + double advance = details->mAdvance; + gfxRect glyphRect(0, -aMetrics.mAscent, advance, + aMetrics.mAscent + aMetrics.mDescent); + if (aIsRTL) { + // Swap left/right sidebearings of the glyph, because we're doing + // mirrored measurement. + glyphRect.MoveToX(advance - glyphRect.XMost()); + // Move to current x position, mirroring any x-offset amount. + glyphRect.MoveByX(x - details->mOffset.x); + } else { + glyphRect.MoveByX(x + details->mOffset.x); + } + glyphRect.MoveByY(details->mOffset.y); + aMetrics.mBoundingBox = aMetrics.mBoundingBox.Union(glyphRect); + x += advance; + } + } + } + if (aSpacing) { + double space = aSpacing[i - aStart].mAfter; + if (i + 1 < aEnd) { + space += aSpacing[i + 1 - aStart].mBefore; + } + x += space; + } + } + + aMetrics.mAdvanceWidth = x; + return allGlyphsInvisible; +} + +gfxFont::RunMetrics gfxFont::Measure(const gfxTextRun* aTextRun, + uint32_t aStart, uint32_t aEnd, + BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, + Spacing* aSpacing, + gfx::ShapedTextFlags aOrientation) { + // If aBoundingBoxType is TIGHT_HINTED_OUTLINE_EXTENTS + // and the underlying cairo font may be antialiased, + // we need to create a copy in order to avoid getting cached extents. + // This is only used by MathML layout at present. + if (aBoundingBoxType == TIGHT_HINTED_OUTLINE_EXTENTS && + mAntialiasOption != kAntialiasNone) { + gfxFont* nonAA = mNonAAFont; + if (!nonAA) { + nonAA = CopyWithAntialiasOption(kAntialiasNone); + if (nonAA) { + if (!mNonAAFont.compareExchange(nullptr, nonAA)) { + delete nonAA; + nonAA = mNonAAFont; + } + } + } + // if font subclass doesn't implement CopyWithAntialiasOption(), + // it will return null and we'll proceed to use the existing font + if (nonAA) { + return nonAA->Measure(aTextRun, aStart, aEnd, + TIGHT_HINTED_OUTLINE_EXTENTS, aRefDrawTarget, + aSpacing, aOrientation); + } + } + + const int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit(); + // Current position in appunits + Orientation orientation = + aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT + ? nsFontMetrics::eVertical + : nsFontMetrics::eHorizontal; + const gfxFont::Metrics& fontMetrics = GetMetrics(orientation); + + gfxFloat baselineOffset = 0; + if (aTextRun->UseCenterBaseline() && + orientation == nsFontMetrics::eHorizontal) { + // For a horizontal font being used in vertical writing mode with + // text-orientation:mixed, the overall metrics we're accumulating + // will be aimed at a center baseline. But this font's metrics were + // based on the alphabetic baseline. So we compute a baseline offset + // that will be applied to ascent/descent values and glyph rects + // to effectively shift them relative to the baseline. + // XXX Eventually we should probably use the BASE table, if present. + // But it usually isn't, so we need an ad hoc adjustment for now. + baselineOffset = + appUnitsPerDevUnit * (fontMetrics.emAscent - fontMetrics.emDescent) / 2; + } + + RunMetrics metrics; + metrics.mAscent = fontMetrics.maxAscent * appUnitsPerDevUnit; + metrics.mDescent = fontMetrics.maxDescent * appUnitsPerDevUnit; + + if (aStart == aEnd) { + // exit now before we look at aSpacing[0], which is undefined + metrics.mAscent -= baselineOffset; + metrics.mDescent += baselineOffset; + metrics.mBoundingBox = + gfxRect(0, -metrics.mAscent, 0, metrics.mAscent + metrics.mDescent); + return metrics; + } + + gfxFloat advanceMin = 0, advanceMax = 0; + bool isRTL = aTextRun->IsRightToLeft(); + bool needsGlyphExtents = NeedsGlyphExtents(this, aTextRun); + gfxGlyphExtents* extents = + ((aBoundingBoxType == LOOSE_INK_EXTENTS && !needsGlyphExtents && + !aTextRun->HasDetailedGlyphs()) || + MOZ_UNLIKELY(GetStyle()->AdjustedSizeMustBeZero())) + ? nullptr + : GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit()); + + bool allGlyphsInvisible; + if (extents) { + allGlyphsInvisible = MeasureGlyphs( + aTextRun, aStart, aEnd, aBoundingBoxType, aRefDrawTarget, aSpacing, + extents, isRTL, needsGlyphExtents, metrics, &advanceMin, &advanceMax); + } else { + allGlyphsInvisible = + MeasureGlyphs(aTextRun, aStart, aEnd, aBoundingBoxType, aRefDrawTarget, + aSpacing, isRTL, metrics); + } + + if (allGlyphsInvisible) { + metrics.mBoundingBox.SetEmpty(); + } else if (aBoundingBoxType == LOOSE_INK_EXTENTS) { + UnionRange(metrics.mAdvanceWidth, &advanceMin, &advanceMax); + gfxRect fontBox(advanceMin, -metrics.mAscent, advanceMax - advanceMin, + metrics.mAscent + metrics.mDescent); + metrics.mBoundingBox = metrics.mBoundingBox.Union(fontBox); + } + + if (isRTL) { + // Reverse the effect of having swapped each glyph's sidebearings, to get + // the correct sidebearings of the merged bounding box. + metrics.mBoundingBox.MoveToX(metrics.mAdvanceWidth - + metrics.mBoundingBox.XMost()); + } + + // If the font may be rendered with a fake-italic effect, we need to allow + // for the top-right of the glyphs being skewed to the right, and the + // bottom-left being skewed further left. + gfxFloat skew = SkewForSyntheticOblique(); + if (skew != 0.0) { + gfxFloat extendLeftEdge, extendRightEdge; + if (orientation == nsFontMetrics::eVertical) { + // The glyph will actually be skewed vertically, but "left" and "right" + // here refer to line-left (physical top) and -right (bottom), so these + // are still the directions in which we need to extend the box. + extendLeftEdge = skew < 0.0 ? ceil(-skew * metrics.mBoundingBox.XMost()) + : ceil(skew * -metrics.mBoundingBox.X()); + extendRightEdge = skew < 0.0 ? ceil(-skew * -metrics.mBoundingBox.X()) + : ceil(skew * metrics.mBoundingBox.XMost()); + } else { + extendLeftEdge = skew < 0.0 ? ceil(-skew * -metrics.mBoundingBox.Y()) + : ceil(skew * metrics.mBoundingBox.YMost()); + extendRightEdge = skew < 0.0 ? ceil(-skew * metrics.mBoundingBox.YMost()) + : ceil(skew * -metrics.mBoundingBox.Y()); + } + metrics.mBoundingBox.SetWidth(metrics.mBoundingBox.Width() + + extendLeftEdge + extendRightEdge); + metrics.mBoundingBox.MoveByX(-extendLeftEdge); + } + + if (baselineOffset != 0) { + metrics.mAscent -= baselineOffset; + metrics.mDescent += baselineOffset; + metrics.mBoundingBox.MoveByY(baselineOffset); + } + + return metrics; +} + +bool gfxFont::AgeCachedWords() { + mozilla::AutoWriteLock lock(mLock); + if (mWordCache) { + for (auto it = mWordCache->modIter(); !it.done(); it.next()) { + auto& entry = it.get().value(); + if (!entry) { + NS_ASSERTION(entry, "cache entry has no gfxShapedWord!"); + it.remove(); + } else if (entry->IncrementAge() == kShapedWordCacheMaxAge) { + it.remove(); + } + } + return mWordCache->empty(); + } + return true; +} + +void gfxFont::NotifyGlyphsChanged() const { + AutoReadLock lock(mLock); + uint32_t i, count = mGlyphExtentsArray.Length(); + for (i = 0; i < count; ++i) { + // Flush cached extents array + mGlyphExtentsArray[i]->NotifyGlyphsChanged(); + } + + if (mGlyphChangeObservers) { + for (const auto& key : *mGlyphChangeObservers) { + key->NotifyGlyphsChanged(); + } + } +} + +// If aChar is a "word boundary" for shaped-word caching purposes, return it; +// else return 0. +static char16_t IsBoundarySpace(char16_t aChar, char16_t aNextChar) { + if ((aChar == ' ' || aChar == 0x00A0) && !IsClusterExtender(aNextChar)) { + return aChar; + } + return 0; +} + +// In 8-bit text, there cannot be any cluster-extenders. +static uint8_t IsBoundarySpace(uint8_t aChar, uint8_t aNextChar) { + if (aChar == ' ' || aChar == 0x00A0) { + return aChar; + } + return 0; +} + +#ifdef __GNUC__ +# define GFX_MAYBE_UNUSED __attribute__((unused)) +#else +# define GFX_MAYBE_UNUSED +#endif + +template <typename T, typename Func> +bool gfxFont::ProcessShapedWordInternal( + DrawTarget* aDrawTarget, const T* aText, uint32_t aLength, uint32_t aHash, + Script aRunScript, nsAtom* aLanguage, bool aVertical, + int32_t aAppUnitsPerDevUnit, gfx::ShapedTextFlags aFlags, + RoundingFlags aRounding, gfxTextPerfMetrics* aTextPerf GFX_MAYBE_UNUSED, + Func aCallback) { + WordCacheKey key(aText, aLength, aHash, aRunScript, aLanguage, + aAppUnitsPerDevUnit, aFlags, aRounding); + { + // If we have a word cache, attempt to look up the word in it. + AutoReadLock lock(mLock); + if (mWordCache) { + // if there's a cached entry for this word, just return it + if (auto entry = mWordCache->lookup(key)) { + entry->value()->ResetAge(); +#ifndef RELEASE_OR_BETA + if (aTextPerf) { + // XXX we should make sure this is atomic + aTextPerf->current.wordCacheHit++; + } +#endif + aCallback(entry->value().get()); + return true; + } + } + } + + // We didn't find a cached word (or don't even have a cache yet), so create + // a new gfxShapedWord and cache it. We don't have to lock during shaping, + // only when it comes time to cache the new entry. + + UniquePtr<gfxShapedWord> newShapedWord( + gfxShapedWord::Create(aText, aLength, aRunScript, aLanguage, + aAppUnitsPerDevUnit, aFlags, aRounding)); + if (!newShapedWord) { + NS_WARNING("failed to create gfxShapedWord - expect missing text"); + return false; + } + DebugOnly<bool> ok = + ShapeText(aDrawTarget, aText, 0, aLength, aRunScript, aLanguage, + aVertical, aRounding, newShapedWord.get()); + NS_WARNING_ASSERTION(ok, "failed to shape word - expect garbled text"); + + { + // We're going to cache the new shaped word, so lock for writing now. + AutoWriteLock lock(mLock); + if (!mWordCache) { + mWordCache = MakeUnique<HashMap<WordCacheKey, UniquePtr<gfxShapedWord>, + WordCacheKey::HashPolicy>>(); + } else { + // If the cache is getting too big, flush it and start over. + uint32_t wordCacheMaxEntries = + gfxPlatform::GetPlatform()->WordCacheMaxEntries(); + if (mWordCache->count() > wordCacheMaxEntries) { + // Flush the cache if it is getting overly big. + NS_WARNING("flushing shaped-word cache"); + ClearCachedWordsLocked(); + } + } + + // Update key so that it references the text stored in the newShapedWord, + // which is guaranteed to live as long as the hashtable entry. + if ((key.mTextIs8Bit = newShapedWord->TextIs8Bit())) { + key.mText.mSingle = newShapedWord->Text8Bit(); + } else { + key.mText.mDouble = newShapedWord->TextUnicode(); + } + auto entry = mWordCache->lookupForAdd(key); + + // It's unlikely, but maybe another thread got there before us... + if (entry) { + // Use the existing entry; the newShapedWord will be discarded. + entry->value()->ResetAge(); +#ifndef RELEASE_OR_BETA + if (aTextPerf) { + aTextPerf->current.wordCacheHit++; + } +#endif + aCallback(entry->value().get()); + return true; + } + + if (!mWordCache->add(entry, key, std::move(newShapedWord))) { + NS_WARNING("failed to cache gfxShapedWord - expect missing text"); + return false; + } + +#ifndef RELEASE_OR_BETA + if (aTextPerf) { + aTextPerf->current.wordCacheMiss++; + } +#endif + aCallback(entry->value().get()); + } + + gfxFontCache::GetCache()->RunWordCacheExpirationTimer(); + return true; +} + +bool gfxFont::WordCacheKey::HashPolicy::match(const Key& aKey, + const Lookup& aLookup) { + if (aKey.mLength != aLookup.mLength || aKey.mFlags != aLookup.mFlags || + aKey.mRounding != aLookup.mRounding || + aKey.mAppUnitsPerDevUnit != aLookup.mAppUnitsPerDevUnit || + aKey.mScript != aLookup.mScript || aKey.mLanguage != aLookup.mLanguage) { + return false; + } + + if (aKey.mTextIs8Bit) { + if (aLookup.mTextIs8Bit) { + return (0 == memcmp(aKey.mText.mSingle, aLookup.mText.mSingle, + aKey.mLength * sizeof(uint8_t))); + } + // The lookup 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 = aKey.mText.mSingle; + const char16_t* s2 = aLookup.mText.mDouble; + const char16_t* s2end = s2 + aKey.mLength; + while (s2 < s2end) { + if (*s1++ != *s2++) { + return false; + } + } + return true; + } + NS_ASSERTION(!(aLookup.mFlags & gfx::ShapedTextFlags::TEXT_IS_8BIT) && + !aLookup.mTextIs8Bit, + "didn't expect 8-bit text here"); + return (0 == memcmp(aKey.mText.mDouble, aLookup.mText.mDouble, + aKey.mLength * sizeof(char16_t))); +} + +bool gfxFont::ProcessSingleSpaceShapedWord( + DrawTarget* aDrawTarget, bool aVertical, int32_t aAppUnitsPerDevUnit, + gfx::ShapedTextFlags aFlags, RoundingFlags aRounding, + const std::function<void(gfxShapedWord*)>& aCallback) { + static const uint8_t space = ' '; + return ProcessShapedWordInternal( + aDrawTarget, &space, 1, gfxShapedWord::HashMix(0, ' '), Script::LATIN, + /* aLanguage = */ nullptr, aVertical, aAppUnitsPerDevUnit, aFlags, + aRounding, nullptr, aCallback); +} + +bool gfxFont::ShapeText(DrawTarget* aDrawTarget, const uint8_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, + RoundingFlags aRounding, gfxShapedText* aShapedText) { + nsDependentCSubstring ascii((const char*)aText, aLength); + nsAutoString utf16; + AppendASCIItoUTF16(ascii, utf16); + if (utf16.Length() != aLength) { + return false; + } + return ShapeText(aDrawTarget, utf16.BeginReading(), aOffset, aLength, aScript, + aLanguage, aVertical, aRounding, aShapedText); +} + +bool gfxFont::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, + RoundingFlags aRounding, gfxShapedText* aShapedText) { + // XXX Currently, we do all vertical shaping through harfbuzz. + // Vertical graphite support may be wanted as a future enhancement. + // XXX Graphite shaping currently only supported on the main thread! + // Worker-thread shaping (offscreen canvas) will always go via harfbuzz. + if (FontCanSupportGraphite() && !aVertical && NS_IsMainThread()) { + if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) { + gfxGraphiteShaper* shaper = mGraphiteShaper; + if (!shaper) { + shaper = new gfxGraphiteShaper(this); + if (mGraphiteShaper.compareExchange(nullptr, shaper)) { + Telemetry::ScalarAdd(Telemetry::ScalarID::BROWSER_USAGE_GRAPHITE, 1); + } else { + delete shaper; + shaper = mGraphiteShaper; + } + } + if (shaper->ShapeText(aDrawTarget, aText, aOffset, aLength, aScript, + aLanguage, aVertical, aRounding, aShapedText)) { + PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical, + aShapedText); + return true; + } + } + } + + gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper(); + if (shaper && + shaper->ShapeText(aDrawTarget, aText, aOffset, aLength, aScript, + aLanguage, aVertical, aRounding, aShapedText)) { + PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical, + aShapedText); + if (GetFontEntry()->HasTrackingTable()) { + // Convert font size from device pixels back to CSS px + // to use in selecting tracking value + gfxFloat trackSize = GetAdjustedSize() * + aShapedText->GetAppUnitsPerDevUnit() / + AppUnitsPerCSSPixel(); + // Usually, a given font will be used with the same appunit scale, so we + // can cache the tracking value rather than recompute it every time. + { + AutoReadLock lock(mLock); + if (trackSize == mCachedTrackingSize) { + // 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->ApplyTrackingToClusters(mTracking, aOffset, aLength); + return true; + } + } + // We didn't have the appropriate tracking value cached yet. + AutoWriteLock lock(mLock); + if (trackSize != mCachedTrackingSize) { + mCachedTrackingSize = trackSize; + mTracking = + GetFontEntry()->TrackingForCSSPx(trackSize) * mFUnitsConvFactor; + } + aShapedText->ApplyTrackingToClusters(mTracking, aOffset, aLength); + } + return true; + } + + NS_WARNING_ASSERTION(false, "shaper failed, expect scrambled/missing text"); + return false; +} + +void gfxFont::PostShapingFixup(DrawTarget* aDrawTarget, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, + bool aVertical, gfxShapedText* aShapedText) { + if (ApplySyntheticBold()) { + const Metrics& metrics = GetMetrics(aVertical ? nsFontMetrics::eVertical + : nsFontMetrics::eHorizontal); + if (metrics.maxAdvance > metrics.aveCharWidth) { + aShapedText->ApplyTrackingToClusters(GetSyntheticBoldOffset(), aOffset, + aLength); + } + } +} + +#define MAX_SHAPING_LENGTH \ + 32760 // slightly less than 32K, trying to avoid + // over-stressing platform shapers +#define BACKTRACK_LIMIT \ + 16 // backtrack this far looking for a good place + // to split into fragments for separate shaping + +template <typename T> +bool gfxFont::ShapeFragmentWithoutWordCache(DrawTarget* aDrawTarget, + const T* aText, uint32_t aOffset, + uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, + RoundingFlags aRounding, + gfxTextRun* aTextRun) { + aTextRun->SetupClusterBoundaries(aOffset, aText, aLength); + + bool ok = true; + + while (ok && aLength > 0) { + uint32_t fragLen = aLength; + + // limit the length of text we pass to shapers in a single call + if (fragLen > MAX_SHAPING_LENGTH) { + fragLen = MAX_SHAPING_LENGTH; + + // in the 8-bit case, there are no multi-char clusters, + // so we don't need to do this check + if constexpr (sizeof(T) == sizeof(char16_t)) { + uint32_t i; + for (i = 0; i < BACKTRACK_LIMIT; ++i) { + if (aTextRun->IsClusterStart(aOffset + fragLen - i)) { + fragLen -= i; + break; + } + } + if (i == BACKTRACK_LIMIT) { + // if we didn't find any cluster start while backtracking, + // just check that we're not in the middle of a surrogate + // pair; back up by one code unit if we are. + if (NS_IS_SURROGATE_PAIR(aText[fragLen - 1], aText[fragLen])) { + --fragLen; + } + } + } + } + + ok = ShapeText(aDrawTarget, aText, aOffset, fragLen, aScript, aLanguage, + aVertical, aRounding, aTextRun); + + aText += fragLen; + aOffset += fragLen; + aLength -= fragLen; + } + + return ok; +} + +// Check if aCh is an unhandled control character that should be displayed +// as a hexbox rather than rendered by some random font on the system. +// We exclude \r as stray s are rather common (bug 941940). +// Note that \n and \t don't come through here, as they have specific +// meanings that have already been handled. +static bool IsInvalidControlChar(uint32_t aCh) { + return aCh != '\r' && ((aCh & 0x7f) < 0x20 || aCh == 0x7f); +} + +template <typename T> +bool gfxFont::ShapeTextWithoutWordCache(DrawTarget* aDrawTarget, const T* aText, + uint32_t aOffset, uint32_t aLength, + Script aScript, nsAtom* aLanguage, + bool aVertical, RoundingFlags aRounding, + gfxTextRun* aTextRun) { + uint32_t fragStart = 0; + bool ok = true; + + for (uint32_t i = 0; i <= aLength && ok; ++i) { + T ch = (i < aLength) ? aText[i] : '\n'; + bool invalid = gfxFontGroup::IsInvalidChar(ch); + uint32_t length = i - fragStart; + + // break into separate fragments when we hit an invalid char + if (!invalid) { + continue; + } + + if (length > 0) { + ok = ShapeFragmentWithoutWordCache( + aDrawTarget, aText + fragStart, aOffset + fragStart, length, aScript, + aLanguage, aVertical, aRounding, aTextRun); + } + + if (i == aLength) { + break; + } + + // fragment was terminated by an invalid char: skip it, + // unless it's a control char that we want to show as a hexbox, + // but record where TAB or NEWLINE occur + if (ch == '\t') { + aTextRun->SetIsTab(aOffset + i); + } else if (ch == '\n') { + aTextRun->SetIsNewline(aOffset + i); + } else if (GetGeneralCategory(ch) == HB_UNICODE_GENERAL_CATEGORY_FORMAT) { + aTextRun->SetIsFormattingControl(aOffset + i); + } else if (IsInvalidControlChar(ch) && + !(aTextRun->GetFlags() & + gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS)) { + if (GetFontEntry()->IsUserFont() && HasCharacter(ch)) { + ShapeFragmentWithoutWordCache(aDrawTarget, aText + i, aOffset + i, 1, + aScript, aLanguage, aVertical, aRounding, + aTextRun); + } else { + aTextRun->SetMissingGlyph(aOffset + i, ch, this); + } + } + fragStart = i + 1; + } + + NS_WARNING_ASSERTION(ok, "failed to shape text - expect garbled text"); + return ok; +} + +#ifndef RELEASE_OR_BETA +# define TEXT_PERF_INCR(tp, m) (tp ? (tp)->current.m++ : 0) +#else +# define TEXT_PERF_INCR(tp, m) +#endif + +inline static bool IsChar8Bit(uint8_t /*aCh*/) { return true; } +inline static bool IsChar8Bit(char16_t aCh) { return aCh < 0x100; } + +inline static bool HasSpaces(const uint8_t* aString, uint32_t aLen) { + return memchr(aString, 0x20, aLen) != nullptr; +} + +inline static bool HasSpaces(const char16_t* aString, uint32_t aLen) { + for (const char16_t* ch = aString; ch < aString + aLen; ch++) { + if (*ch == 0x20) { + return true; + } + } + return false; +} + +template <typename T> +bool gfxFont::SplitAndInitTextRun( + DrawTarget* aDrawTarget, gfxTextRun* aTextRun, + const T* aString, // text for this font run + uint32_t aRunStart, // position in the textrun + uint32_t aRunLength, Script aRunScript, nsAtom* aLanguage, + ShapedTextFlags aOrientation) { + if (aRunLength == 0) { + return true; + } + + gfxTextPerfMetrics* tp = nullptr; + RoundingFlags rounding = GetRoundOffsetsToPixels(aDrawTarget); + +#ifndef RELEASE_OR_BETA + tp = aTextRun->GetFontGroup()->GetTextPerfMetrics(); + if (tp) { + if (mStyle.systemFont) { + tp->current.numChromeTextRuns++; + } else { + tp->current.numContentTextRuns++; + } + tp->current.numChars += aRunLength; + if (aRunLength > tp->current.maxTextRunLen) { + tp->current.maxTextRunLen = aRunLength; + } + } +#endif + + uint32_t wordCacheCharLimit = + gfxPlatform::GetPlatform()->WordCacheCharLimit(); + + bool vertical = aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT; + + // If spaces can participate in shaping (e.g. within lookups for automatic + // fractions), need to shape without using the word cache which segments + // textruns on space boundaries. Word cache can be used if the textrun + // is short enough to fit in the word cache and it lacks spaces. + tainted_boolean_hint t_canParticipate = + SpaceMayParticipateInShaping(aRunScript); + bool canParticipate = t_canParticipate.unverified_safe_because( + "We need to ensure that this function operates safely independent of " + "t_canParticipate. The worst that can happen here is that the decision " + "to use the cache is incorrectly made, resulting in a bad " + "rendering/slowness. However, this would not compromise the memory " + "safety of Firefox in any way, and can thus be permitted"); + + if (canParticipate) { + if (aRunLength > wordCacheCharLimit || HasSpaces(aString, aRunLength)) { + TEXT_PERF_INCR(tp, wordCacheSpaceRules); + return ShapeTextWithoutWordCache(aDrawTarget, aString, aRunStart, + aRunLength, aRunScript, aLanguage, + vertical, rounding, aTextRun); + } + } + + // the only flags we care about for ShapedWord construction/caching + gfx::ShapedTextFlags flags = aTextRun->GetFlags(); + flags &= (gfx::ShapedTextFlags::TEXT_IS_RTL | + gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES | + gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT | + gfx::ShapedTextFlags::TEXT_ORIENT_MASK); + if constexpr (sizeof(T) == sizeof(uint8_t)) { + flags |= gfx::ShapedTextFlags::TEXT_IS_8BIT; + } + + uint32_t wordStart = 0; + uint32_t hash = 0; + bool wordIs8Bit = true; + int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit(); + + T nextCh = aString[0]; + for (uint32_t i = 0; i <= aRunLength; ++i) { + T ch = nextCh; + nextCh = (i < aRunLength - 1) ? aString[i + 1] : '\n'; + T boundary = IsBoundarySpace(ch, nextCh); + bool invalid = !boundary && gfxFontGroup::IsInvalidChar(ch); + uint32_t length = i - wordStart; + + // break into separate ShapedWords when we hit an invalid char, + // or a boundary space (always handled individually), + // or the first non-space after a space + if (!boundary && !invalid) { + if (!IsChar8Bit(ch)) { + wordIs8Bit = false; + } + // include this character in the hash, and move on to next + hash = gfxShapedWord::HashMix(hash, ch); + continue; + } + + // We've decided to break here (i.e. we're at the end of a "word"); + // shape the word and add it to the textrun. + // For words longer than the limit, we don't use the + // font's word cache but just shape directly into the textrun. + if (length > wordCacheCharLimit) { + TEXT_PERF_INCR(tp, wordCacheLong); + bool ok = ShapeFragmentWithoutWordCache( + aDrawTarget, aString + wordStart, aRunStart + wordStart, length, + aRunScript, aLanguage, vertical, rounding, aTextRun); + if (!ok) { + return false; + } + } else if (length > 0) { + gfx::ShapedTextFlags wordFlags = flags; + // in the 8-bit version of this method, TEXT_IS_8BIT was + // already set as part of |flags|, so no need for a per-word + // adjustment here + if (sizeof(T) == sizeof(char16_t)) { + if (wordIs8Bit) { + wordFlags |= gfx::ShapedTextFlags::TEXT_IS_8BIT; + } + } + bool processed = ProcessShapedWordInternal( + aDrawTarget, aString + wordStart, length, hash, aRunScript, aLanguage, + vertical, appUnitsPerDevUnit, wordFlags, rounding, tp, + [&](gfxShapedWord* aShapedWord) { + aTextRun->CopyGlyphDataFrom(aShapedWord, aRunStart + wordStart); + }); + if (!processed) { + return false; // failed, presumably out of memory? + } + } + + if (boundary) { + // word was terminated by a space: add that to the textrun + MOZ_ASSERT(aOrientation != ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED, + "text-orientation:mixed should be resolved earlier"); + if (boundary != ' ' || !aTextRun->SetSpaceGlyphIfSimple( + this, aRunStart + i, ch, aOrientation)) { + // Currently, the only "boundary" characters we recognize are + // space and no-break space, which are both 8-bit, so we force + // that flag (below). If we ever change IsBoundarySpace, we + // may need to revise this. + // Avoid tautological-constant-out-of-range-compare in 8-bit: + DebugOnly<char16_t> boundary16 = boundary; + NS_ASSERTION(boundary16 < 256, "unexpected boundary!"); + bool processed = ProcessShapedWordInternal( + aDrawTarget, &boundary, 1, gfxShapedWord::HashMix(0, boundary), + aRunScript, aLanguage, vertical, appUnitsPerDevUnit, + flags | gfx::ShapedTextFlags::TEXT_IS_8BIT, rounding, tp, + [&](gfxShapedWord* aShapedWord) { + aTextRun->CopyGlyphDataFrom(aShapedWord, aRunStart + i); + if (boundary == ' ') { + aTextRun->GetCharacterGlyphs()[aRunStart + i].SetIsSpace(); + } + }); + if (!processed) { + return false; + } + } + hash = 0; + wordStart = i + 1; + wordIs8Bit = true; + continue; + } + + if (i == aRunLength) { + break; + } + + NS_ASSERTION(invalid, "how did we get here except via an invalid char?"); + + // word was terminated by an invalid char: skip it, + // unless it's a control char that we want to show as a hexbox, + // but record where TAB or NEWLINE occur + if (ch == '\t') { + aTextRun->SetIsTab(aRunStart + i); + } else if (ch == '\n') { + aTextRun->SetIsNewline(aRunStart + i); + } else if (GetGeneralCategory(ch) == HB_UNICODE_GENERAL_CATEGORY_FORMAT) { + aTextRun->SetIsFormattingControl(aRunStart + i); + } else if (IsInvalidControlChar(ch) && + !(aTextRun->GetFlags() & + gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS)) { + if (GetFontEntry()->IsUserFont() && HasCharacter(ch)) { + ShapeFragmentWithoutWordCache(aDrawTarget, aString + i, aRunStart + i, + 1, aRunScript, aLanguage, vertical, + rounding, aTextRun); + } else { + aTextRun->SetMissingGlyph(aRunStart + i, ch, this); + } + } + + hash = 0; + wordStart = i + 1; + wordIs8Bit = true; + } + + return true; +} + +// Explicit instantiations of SplitAndInitTextRun, to avoid libxul link failure +template bool gfxFont::SplitAndInitTextRun( + DrawTarget* aDrawTarget, gfxTextRun* aTextRun, const uint8_t* aString, + uint32_t aRunStart, uint32_t aRunLength, Script aRunScript, + nsAtom* aLanguage, ShapedTextFlags aOrientation); +template bool gfxFont::SplitAndInitTextRun( + DrawTarget* aDrawTarget, gfxTextRun* aTextRun, const char16_t* aString, + uint32_t aRunStart, uint32_t aRunLength, Script aRunScript, + nsAtom* aLanguage, ShapedTextFlags aOrientation); + +template <> +bool gfxFont::InitFakeSmallCapsRun( + nsPresContext* aPresContext, DrawTarget* aDrawTarget, gfxTextRun* aTextRun, + const char16_t* aText, uint32_t aOffset, uint32_t aLength, + FontMatchType aMatchType, gfx::ShapedTextFlags aOrientation, Script aScript, + nsAtom* aLanguage, bool aSyntheticLower, bool aSyntheticUpper) { + bool ok = true; + + RefPtr<gfxFont> smallCapsFont = GetSmallCapsFont(); + if (!smallCapsFont) { + NS_WARNING("failed to get reduced-size font for smallcaps!"); + smallCapsFont = this; + } + + bool isCJK = gfxTextRun::IsCJKScript(aScript); + + enum RunCaseAction { kNoChange, kUppercaseReduce, kUppercase }; + + RunCaseAction runAction = kNoChange; + uint32_t runStart = 0; + + for (uint32_t i = 0; i <= aLength; ++i) { + uint32_t extraCodeUnits = 0; // Will be set to 1 if we need to consume + // a trailing surrogate as well as the + // current code unit. + RunCaseAction chAction = kNoChange; + // Unless we're at the end, figure out what treatment the current + // character will need. + if (i < aLength) { + uint32_t ch = aText[i]; + if (i < aLength - 1 && NS_IS_SURROGATE_PAIR(ch, aText[i + 1])) { + ch = SURROGATE_TO_UCS4(ch, aText[i + 1]); + extraCodeUnits = 1; + } + // Characters that aren't the start of a cluster are ignored here. + // They get added to whatever lowercase/non-lowercase run we're in. + if (IsClusterExtender(ch)) { + chAction = runAction; + } else { + if (ch != ToUpperCase(ch) || SpecialUpper(ch)) { + // ch is lower case + chAction = (aSyntheticLower ? kUppercaseReduce : kNoChange); + } else if (ch != ToLowerCase(ch)) { + // ch is upper case + chAction = (aSyntheticUpper ? kUppercaseReduce : kNoChange); + if (aLanguage == nsGkAtoms::el) { + // In Greek, check for characters that will be modified by + // the GreekUpperCase mapping - this catches accented + // capitals where the accent is to be removed (bug 307039). + // These are handled by using the full-size font with the + // uppercasing transform. + mozilla::GreekCasing::State state; + bool markEta, updateEta; + uint32_t ch2 = + mozilla::GreekCasing::UpperCase(ch, state, markEta, updateEta); + if ((ch != ch2 || markEta) && !aSyntheticUpper) { + chAction = kUppercase; + } + } + } + } + } + + // At the end of the text or when the current character needs different + // casing treatment from the current run, finish the run-in-progress + // and prepare to accumulate a new run. + // Note that we do not look at any source data for offset [i] here, + // as that would be invalid in the case where i==length. + if ((i == aLength || runAction != chAction) && runStart < i) { + uint32_t runLength = i - runStart; + gfxFont* f = this; + switch (runAction) { + case kNoChange: + // just use the current font and the existing string + aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart, true, + aOrientation, isCJK); + if (!f->SplitAndInitTextRun(aDrawTarget, aTextRun, aText + runStart, + aOffset + runStart, runLength, aScript, + aLanguage, aOrientation)) { + ok = false; + } + break; + + case kUppercaseReduce: + // use reduced-size font, then fall through to uppercase the text + f = smallCapsFont; + [[fallthrough]]; + + case kUppercase: + // apply uppercase transform to the string + nsDependentSubstring origString(aText + runStart, runLength); + nsAutoString convertedString; + AutoTArray<bool, 50> charsToMergeArray; + AutoTArray<bool, 50> deletedCharsArray; + + StyleTextTransform globalTransform{StyleTextTransformCase::Uppercase, + {}}; + // No mask needed; we're doing case conversion, not password-hiding. + const char16_t maskChar = 0; + bool mergeNeeded = nsCaseTransformTextRunFactory::TransformString( + origString, convertedString, Some(globalTransform), maskChar, + /* aCaseTransformsOnly = */ false, aLanguage, charsToMergeArray, + deletedCharsArray); + + // Check whether the font supports the uppercased characters needed; + // if not, we're not going to be able to simulate small-caps. + bool failed = false; + char16_t highSurrogate = 0; + for (const char16_t* cp = convertedString.BeginReading(); + cp != convertedString.EndReading(); ++cp) { + if (NS_IS_HIGH_SURROGATE(*cp)) { + highSurrogate = *cp; + continue; + } + uint32_t ch = *cp; + if (NS_IS_LOW_SURROGATE(*cp) && highSurrogate) { + ch = SURROGATE_TO_UCS4(highSurrogate, *cp); + } + highSurrogate = 0; + if (!f->HasCharacter(ch)) { + if (IsDefaultIgnorable(ch)) { + continue; + } + failed = true; + break; + } + } + // Required uppercase letter(s) missing from the font. Just use the + // original text with the original font, no fake small caps! + if (failed) { + convertedString = origString; + mergeNeeded = false; + f = this; + } + + 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( + nsPresContext* aPresContext, DrawTarget* aDrawTarget, gfxTextRun* aTextRun, + const uint8_t* aText, uint32_t aOffset, uint32_t aLength, + FontMatchType aMatchType, gfx::ShapedTextFlags aOrientation, Script aScript, + nsAtom* aLanguage, bool aSyntheticLower, bool aSyntheticUpper) { + NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aText), + aLength); + return InitFakeSmallCapsRun(aPresContext, aDrawTarget, aTextRun, + static_cast<const char16_t*>(unicodeString.get()), + aOffset, aLength, aMatchType, aOrientation, + aScript, aLanguage, aSyntheticLower, + aSyntheticUpper); +} + +already_AddRefed<gfxFont> gfxFont::GetSmallCapsFont() const { + gfxFontStyle style(*GetStyle()); + style.size *= SMALL_CAPS_SCALE_FACTOR; + style.variantCaps = NS_FONT_VARIANT_CAPS_NORMAL; + gfxFontEntry* fe = GetFontEntry(); + return fe->FindOrMakeFont(&style, mUnicodeRangeMap); +} + +already_AddRefed<gfxFont> gfxFont::GetSubSuperscriptFont( + int32_t aAppUnitsPerDevPixel) const { + gfxFontStyle style(*GetStyle()); + style.AdjustForSubSuperscript(aAppUnitsPerDevPixel); + gfxFontEntry* fe = GetFontEntry(); + return fe->FindOrMakeFont(&style, mUnicodeRangeMap); +} + +gfxGlyphExtents* gfxFont::GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit) { + uint32_t readCount; + { + AutoReadLock lock(mLock); + readCount = mGlyphExtentsArray.Length(); + for (uint32_t i = 0; i < readCount; ++i) { + if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit) + return mGlyphExtentsArray[i].get(); + } + } + AutoWriteLock lock(mLock); + // Re-check in case of race. + uint32_t count = mGlyphExtentsArray.Length(); + for (uint32_t i = readCount; i < count; ++i) { + if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit) + return mGlyphExtentsArray[i].get(); + } + gfxGlyphExtents* glyphExtents = new gfxGlyphExtents(aAppUnitsPerDevUnit); + if (glyphExtents) { + mGlyphExtentsArray.AppendElement(glyphExtents); + // Initialize the extents of a space glyph, assuming that spaces don't + // render anything! + glyphExtents->SetContainedGlyphWidthAppUnits(GetSpaceGlyph(), 0); + } + return glyphExtents; +} + +void gfxFont::SetupGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphID, + bool aNeedTight, gfxGlyphExtents* aExtents) { + gfxRect svgBounds; + if (mFontEntry->TryGetSVGData(this) && mFontEntry->HasSVGGlyph(aGlyphID) && + mFontEntry->GetSVGGlyphExtents(aDrawTarget, aGlyphID, GetAdjustedSize(), + &svgBounds)) { + gfxFloat d2a = aExtents->GetAppUnitsPerDevUnit(); + aExtents->SetTightGlyphExtents( + aGlyphID, gfxRect(svgBounds.X() * d2a, svgBounds.Y() * d2a, + svgBounds.Width() * d2a, svgBounds.Height() * d2a)); + return; + } + + if (mFontEntry->TryGetColorGlyphs() && mFontEntry->mCOLR && + COLRFonts::GetColrTableVersion(mFontEntry->mCOLR) == 1) { + auto* shaper = GetHarfBuzzShaper(); + if (shaper && shaper->IsInitialized()) { + RefPtr scaledFont = GetScaledFont(aDrawTarget); + Rect r = COLRFonts::GetColorGlyphBounds( + mFontEntry->mCOLR, shaper->GetHBFont(), aGlyphID, aDrawTarget, + scaledFont, mFUnitsConvFactor); + if (!r.IsEmpty()) { + gfxFloat d2a = aExtents->GetAppUnitsPerDevUnit(); + aExtents->SetTightGlyphExtents( + aGlyphID, gfxRect(r.X() * d2a, r.Y() * d2a, r.Width() * d2a, + r.Height() * d2a)); + return; + } + } + } + + gfxRect bounds; + GetGlyphBounds(aGlyphID, &bounds, mAntialiasOption == kAntialiasNone); + + const Metrics& fontMetrics = GetMetrics(nsFontMetrics::eHorizontal); + int32_t appUnitsPerDevUnit = aExtents->GetAppUnitsPerDevUnit(); + if (!aNeedTight && bounds.x >= 0.0 && bounds.y >= -fontMetrics.maxAscent && + bounds.height + bounds.y <= fontMetrics.maxDescent) { + uint32_t appUnitsWidth = + uint32_t(ceil((bounds.x + bounds.width) * appUnitsPerDevUnit)); + if (appUnitsWidth < gfxGlyphExtents::INVALID_WIDTH) { + aExtents->SetContainedGlyphWidthAppUnits(aGlyphID, + uint16_t(appUnitsWidth)); + return; + } + } +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + if (!aNeedTight) { + ++gGlyphExtentsSetupFallBackToTight; + } +#endif + + gfxFloat d2a = appUnitsPerDevUnit; + aExtents->SetTightGlyphExtents( + aGlyphID, gfxRect(bounds.x * d2a, bounds.y * d2a, bounds.width * d2a, + bounds.height * d2a)); +} + +// Try to initialize font metrics by reading sfnt tables directly; +// set mIsValid=TRUE and return TRUE on success. +// Return FALSE if the gfxFontEntry subclass does not +// implement GetFontTable(), or for non-sfnt fonts where tables are +// not available. +// If this returns TRUE without setting the mIsValid flag, then we -did- +// apparently find an sfnt, but it was too broken to be used. +bool gfxFont::InitMetricsFromSfntTables(Metrics& aMetrics) { + mIsValid = false; // font is NOT valid in case of early return + + const uint32_t kHheaTableTag = TRUETYPE_TAG('h', 'h', 'e', 'a'); + const uint32_t kOS_2TableTag = TRUETYPE_TAG('O', 'S', '/', '2'); + + uint32_t len; + + if (mFUnitsConvFactor < 0.0) { + // If the conversion factor from FUnits is not yet set, + // get the unitsPerEm from the 'head' table via the font entry + uint16_t unitsPerEm = GetFontEntry()->UnitsPerEm(); + if (unitsPerEm == gfxFontEntry::kInvalidUPEM) { + return false; + } + mFUnitsConvFactor = GetAdjustedSize() / unitsPerEm; + } + + // 'hhea' table is required for the advanceWidthMax field + gfxFontEntry::AutoTable hheaTable(mFontEntry, kHheaTableTag); + if (!hheaTable) { + return false; // no 'hhea' table -> not an sfnt + } + const MetricsHeader* hhea = + reinterpret_cast<const MetricsHeader*>(hb_blob_get_data(hheaTable, &len)); + if (len < sizeof(MetricsHeader)) { + return false; + } + +#define SET_UNSIGNED(field, src) \ + aMetrics.field = uint16_t(src) * mFUnitsConvFactor +#define SET_SIGNED(field, src) aMetrics.field = int16_t(src) * mFUnitsConvFactor + + SET_UNSIGNED(maxAdvance, hhea->advanceWidthMax); + + // 'OS/2' table is optional, if not found we'll estimate xHeight + // and aveCharWidth by measuring glyphs + gfxFontEntry::AutoTable os2Table(mFontEntry, kOS_2TableTag); + if (os2Table) { + const OS2Table* os2 = + reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len)); + // this should always be present in any valid OS/2 of any version + if (len >= offsetof(OS2Table, xAvgCharWidth) + sizeof(int16_t)) { + SET_SIGNED(aveCharWidth, os2->xAvgCharWidth); + } + } + +#undef SET_SIGNED +#undef SET_UNSIGNED + + hb_font_t* hbFont = gfxHarfBuzzShaper::CreateHBFont(this); + hb_position_t position; + + auto FixedToFloat = [](hb_position_t f) -> gfxFloat { return f / 65536.0; }; + + if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_ASCENDER, + &position)) { + aMetrics.maxAscent = FixedToFloat(position); + } + if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_DESCENDER, + &position)) { + aMetrics.maxDescent = -FixedToFloat(position); + } + if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_LINE_GAP, + &position)) { + aMetrics.externalLeading = FixedToFloat(position); + } + + if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_UNDERLINE_OFFSET, + &position)) { + aMetrics.underlineOffset = FixedToFloat(position); + } + if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_UNDERLINE_SIZE, + &position)) { + aMetrics.underlineSize = FixedToFloat(position); + } + if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_STRIKEOUT_OFFSET, + &position)) { + aMetrics.strikeoutOffset = FixedToFloat(position); + } + if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_STRIKEOUT_SIZE, + &position)) { + aMetrics.strikeoutSize = FixedToFloat(position); + } + + // Although sxHeight and sCapHeight are signed fields, we consider + // zero/negative values to be erroneous and just ignore them. + if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_X_HEIGHT, + &position) && + position > 0) { + aMetrics.xHeight = FixedToFloat(position); + } + if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_CAP_HEIGHT, + &position) && + position > 0) { + aMetrics.capHeight = FixedToFloat(position); + } + hb_font_destroy(hbFont); + + mIsValid = true; + + return true; +} + +static double RoundToNearestMultiple(double aValue, double aFraction) { + return floor(aValue / aFraction + 0.5) * aFraction; +} + +void gfxFont::CalculateDerivedMetrics(Metrics& aMetrics) { + aMetrics.maxAscent = + ceil(RoundToNearestMultiple(aMetrics.maxAscent, 1 / 1024.0)); + aMetrics.maxDescent = + ceil(RoundToNearestMultiple(aMetrics.maxDescent, 1 / 1024.0)); + + if (aMetrics.xHeight <= 0) { + // only happens if we couldn't find either font metrics + // or a char to measure; + // pick an arbitrary value that's better than zero + aMetrics.xHeight = aMetrics.maxAscent * DEFAULT_XHEIGHT_FACTOR; + } + + // If we have a font that doesn't provide a capHeight value, use maxAscent + // as a reasonable fallback. + if (aMetrics.capHeight <= 0) { + aMetrics.capHeight = aMetrics.maxAscent; + } + + aMetrics.maxHeight = aMetrics.maxAscent + aMetrics.maxDescent; + + if (aMetrics.maxHeight - aMetrics.emHeight > 0.0) { + aMetrics.internalLeading = aMetrics.maxHeight - aMetrics.emHeight; + } else { + aMetrics.internalLeading = 0.0; + } + + aMetrics.emAscent = + aMetrics.maxAscent * aMetrics.emHeight / aMetrics.maxHeight; + aMetrics.emDescent = aMetrics.emHeight - aMetrics.emAscent; + + if (GetFontEntry()->IsFixedPitch()) { + // Some Quartz fonts are fixed pitch, but there's some glyph with a bigger + // advance than the average character width... this forces + // those fonts to be recognized like fixed pitch fonts by layout. + aMetrics.maxAdvance = aMetrics.aveCharWidth; + } + + if (!aMetrics.strikeoutOffset) { + aMetrics.strikeoutOffset = aMetrics.xHeight * 0.5; + } + if (!aMetrics.strikeoutSize) { + aMetrics.strikeoutSize = aMetrics.underlineSize; + } +} + +void gfxFont::SanitizeMetrics(gfxFont::Metrics* aMetrics, + bool aIsBadUnderlineFont) { + // Even if this font size is zero, this font is created with non-zero size. + // However, for layout and others, we should return the metrics of zero size + // font. + if (mStyle.AdjustedSizeMustBeZero()) { + memset(aMetrics, 0, sizeof(gfxFont::Metrics)); + return; + } + + // If the font entry has ascent/descent/lineGap-override values, + // replace the metrics from the font with the overrides. + gfxFloat adjustedSize = GetAdjustedSize(); + if (mFontEntry->mAscentOverride >= 0.0) { + aMetrics->maxAscent = mFontEntry->mAscentOverride * adjustedSize; + aMetrics->maxHeight = aMetrics->maxAscent + aMetrics->maxDescent; + aMetrics->internalLeading = + std::max(0.0, aMetrics->maxHeight - aMetrics->emHeight); + } + if (mFontEntry->mDescentOverride >= 0.0) { + aMetrics->maxDescent = mFontEntry->mDescentOverride * adjustedSize; + aMetrics->maxHeight = aMetrics->maxAscent + aMetrics->maxDescent; + aMetrics->internalLeading = + std::max(0.0, aMetrics->maxHeight - aMetrics->emHeight); + } + if (mFontEntry->mLineGapOverride >= 0.0) { + aMetrics->externalLeading = mFontEntry->mLineGapOverride * adjustedSize; + } + + aMetrics->underlineSize = std::max(1.0, aMetrics->underlineSize); + aMetrics->strikeoutSize = std::max(1.0, aMetrics->strikeoutSize); + + aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -1.0); + + if (aMetrics->maxAscent < 1.0) { + // We cannot draw strikeout line and overline in the ascent... + aMetrics->underlineSize = 0; + aMetrics->underlineOffset = 0; + aMetrics->strikeoutSize = 0; + aMetrics->strikeoutOffset = 0; + return; + } + + /** + * Some CJK fonts have bad underline offset. Therefore, if this is such font, + * we need to lower the underline offset to bottom of *em* descent. + * However, if this is system font, we should not do this for the rendering + * compatibility with another application's UI on the platform. + * XXX Should not use this hack if the font size is too small? + * Such text cannot be read, this might be used for tight CSS + * rendering? (E.g., Acid2) + */ + if (!mStyle.systemFont && aIsBadUnderlineFont) { + // First, we need 2 pixels between baseline and underline at least. Because + // many CJK characters put their glyphs on the baseline, so, 1 pixel is too + // close for CJK characters. + aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -2.0); + + // Next, we put the underline to bottom of below of the descent space. + if (aMetrics->internalLeading + aMetrics->externalLeading > + aMetrics->underlineSize) { + aMetrics->underlineOffset = + std::min(aMetrics->underlineOffset, -aMetrics->emDescent); + } else { + aMetrics->underlineOffset = + std::min(aMetrics->underlineOffset, + aMetrics->underlineSize - aMetrics->emDescent); + } + } + // If underline positioned is too far from the text, descent position is + // preferred so that underline will stay within the boundary. + else if (aMetrics->underlineSize - aMetrics->underlineOffset > + aMetrics->maxDescent) { + if (aMetrics->underlineSize > aMetrics->maxDescent) + aMetrics->underlineSize = std::max(aMetrics->maxDescent, 1.0); + // The max underlineOffset is 1px (the min underlineSize is 1px, and min + // maxDescent is 0px.) + aMetrics->underlineOffset = aMetrics->underlineSize - aMetrics->maxDescent; + } + + // If strikeout line is overflowed from the ascent, the line should be resized + // and moved for that being in the ascent space. Note that the strikeoutOffset + // is *middle* of the strikeout line position. + gfxFloat halfOfStrikeoutSize = floor(aMetrics->strikeoutSize / 2.0 + 0.5); + if (halfOfStrikeoutSize + aMetrics->strikeoutOffset > aMetrics->maxAscent) { + if (aMetrics->strikeoutSize > aMetrics->maxAscent) { + aMetrics->strikeoutSize = std::max(aMetrics->maxAscent, 1.0); + halfOfStrikeoutSize = floor(aMetrics->strikeoutSize / 2.0 + 0.5); + } + gfxFloat ascent = floor(aMetrics->maxAscent + 0.5); + aMetrics->strikeoutOffset = std::max(halfOfStrikeoutSize, ascent / 2.0); + } + + // If overline is larger than the ascent, the line should be resized. + if (aMetrics->underlineSize > aMetrics->maxAscent) { + aMetrics->underlineSize = aMetrics->maxAscent; + } +} + +gfxFont::Baselines gfxFont::GetBaselines(Orientation aOrientation) { + // Approximated baselines for fonts lacking actual baseline data. These are + // fractions of the em ascent/descent from the alphabetic baseline. + const double kHangingBaselineDefault = 0.8; // fraction of ascent + const double kIdeographicBaselineDefault = -0.5; // fraction of descent + + // If no BASE table is present, just return synthetic values immediately. + if (!mFontEntry->HasFontTable(TRUETYPE_TAG('B', 'A', 'S', 'E'))) { + // No baseline table; just synthesize them immediately. + const Metrics& metrics = GetMetrics(aOrientation); + return Baselines{ + 0.0, // alphabetic + kHangingBaselineDefault * metrics.emAscent, // hanging + kIdeographicBaselineDefault * metrics.emDescent // ideographic + }; + } + + // Use harfbuzz to try to read the font's baseline metrics. + Baselines result{NAN, NAN, NAN}; + hb_font_t* hbFont = gfxHarfBuzzShaper::CreateHBFont(this); + hb_direction_t hbDir = aOrientation == nsFontMetrics::eHorizontal + ? HB_DIRECTION_LTR + : HB_DIRECTION_TTB; + hb_position_t position; + unsigned count = 0; + auto Fix2Float = [](hb_position_t f) -> gfxFloat { return f / 65536.0; }; + if (hb_ot_layout_get_baseline(hbFont, HB_OT_LAYOUT_BASELINE_TAG_ROMAN, hbDir, + HB_OT_TAG_DEFAULT_SCRIPT, + HB_OT_TAG_DEFAULT_LANGUAGE, &position)) { + result.mAlphabetic = Fix2Float(position); + count++; + } + if (hb_ot_layout_get_baseline(hbFont, HB_OT_LAYOUT_BASELINE_TAG_HANGING, + hbDir, HB_OT_TAG_DEFAULT_SCRIPT, + HB_OT_TAG_DEFAULT_LANGUAGE, &position)) { + result.mHanging = Fix2Float(position); + count++; + } + if (hb_ot_layout_get_baseline( + hbFont, HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT, hbDir, + HB_OT_TAG_DEFAULT_SCRIPT, HB_OT_TAG_DEFAULT_LANGUAGE, &position)) { + result.mIdeographic = Fix2Float(position); + count++; + } + hb_font_destroy(hbFont); + // If we successfully read all three, we can return now. + if (count == 3) { + return result; + } + + // Synthesize the baselines that we didn't find in the font. + const Metrics& metrics = GetMetrics(aOrientation); + if (std::isnan(result.mAlphabetic)) { + result.mAlphabetic = 0.0; + } + if (std::isnan(result.mHanging)) { + result.mHanging = kHangingBaselineDefault * metrics.emAscent; + } + if (std::isnan(result.mIdeographic)) { + result.mIdeographic = kIdeographicBaselineDefault * metrics.emDescent; + } + + return result; +} + +// Create a Metrics record to be used for vertical layout. This should never +// fail, as we've already decided this is a valid font. We do not have the +// option of marking it invalid (as can happen if we're unable to read +// horizontal metrics), because that could break a font that we're already +// using for horizontal text. +// So we will synthesize *something* usable here even if there aren't any of the +// usual font tables (which can happen in the case of a legacy bitmap or Type1 +// font for which the platform-specific backend used platform APIs instead of +// sfnt tables to create the horizontal metrics). +void gfxFont::CreateVerticalMetrics() { + const uint32_t kHheaTableTag = TRUETYPE_TAG('h', 'h', 'e', 'a'); + const uint32_t kVheaTableTag = TRUETYPE_TAG('v', 'h', 'e', 'a'); + const uint32_t kPostTableTag = TRUETYPE_TAG('p', 'o', 's', 't'); + const uint32_t kOS_2TableTag = TRUETYPE_TAG('O', 'S', '/', '2'); + uint32_t len; + + auto* metrics = new Metrics(); + ::memset(metrics, 0, sizeof(Metrics)); + + // Some basic defaults, in case the font lacks any real metrics tables. + // TODO: consider what rounding (if any) we should apply to these. + metrics->emHeight = GetAdjustedSize(); + metrics->emAscent = metrics->emHeight / 2; + metrics->emDescent = metrics->emHeight - metrics->emAscent; + + metrics->maxAscent = metrics->emAscent; + metrics->maxDescent = metrics->emDescent; + + const float UNINITIALIZED_LEADING = -10000.0f; + metrics->externalLeading = UNINITIALIZED_LEADING; + + if (mFUnitsConvFactor < 0.0) { + uint16_t upem = GetFontEntry()->UnitsPerEm(); + if (upem != gfxFontEntry::kInvalidUPEM) { + AutoWriteLock lock(mLock); + mFUnitsConvFactor = GetAdjustedSize() / upem; + } + } + +#define SET_UNSIGNED(field, src) \ + metrics->field = uint16_t(src) * mFUnitsConvFactor +#define SET_SIGNED(field, src) metrics->field = int16_t(src) * mFUnitsConvFactor + + gfxFontEntry::AutoTable os2Table(mFontEntry, kOS_2TableTag); + if (os2Table && mFUnitsConvFactor >= 0.0) { + const OS2Table* os2 = + reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len)); + // These fields should always be present in any valid OS/2 table + if (len >= offsetof(OS2Table, sTypoLineGap) + sizeof(int16_t)) { + SET_SIGNED(strikeoutSize, os2->yStrikeoutSize); + // Use ascent+descent from the horizontal metrics as the default + // advance (aveCharWidth) in vertical mode + gfxFloat ascentDescent = + gfxFloat(mFUnitsConvFactor) * + (int16_t(os2->sTypoAscender) - int16_t(os2->sTypoDescender)); + metrics->aveCharWidth = std::max(metrics->emHeight, ascentDescent); + // Use xAvgCharWidth from horizontal metrics as minimum font extent + // for vertical layout, applying half of it to ascent and half to + // descent (to work with a default centered baseline). + gfxFloat halfCharWidth = + int16_t(os2->xAvgCharWidth) * gfxFloat(mFUnitsConvFactor) / 2; + metrics->maxAscent = std::max(metrics->maxAscent, halfCharWidth); + metrics->maxDescent = std::max(metrics->maxDescent, halfCharWidth); + } + } + + // If we didn't set aveCharWidth from OS/2, try to read 'hhea' metrics + // and use the line height from its ascent/descent. + if (!metrics->aveCharWidth) { + gfxFontEntry::AutoTable hheaTable(mFontEntry, kHheaTableTag); + if (hheaTable && mFUnitsConvFactor >= 0.0) { + const MetricsHeader* hhea = reinterpret_cast<const MetricsHeader*>( + hb_blob_get_data(hheaTable, &len)); + if (len >= sizeof(MetricsHeader)) { + SET_SIGNED(aveCharWidth, + int16_t(hhea->ascender) - int16_t(hhea->descender)); + metrics->maxAscent = metrics->aveCharWidth / 2; + metrics->maxDescent = metrics->aveCharWidth - metrics->maxAscent; + } + } + } + + // Read real vertical metrics if available. + metrics->ideographicWidth = -1.0; + metrics->zeroWidth = -1.0; + gfxFontEntry::AutoTable vheaTable(mFontEntry, kVheaTableTag); + if (vheaTable && mFUnitsConvFactor >= 0.0) { + const MetricsHeader* vhea = reinterpret_cast<const MetricsHeader*>( + hb_blob_get_data(vheaTable, &len)); + if (len >= sizeof(MetricsHeader)) { + SET_UNSIGNED(maxAdvance, vhea->advanceWidthMax); + // Redistribute space between ascent/descent because we want a + // centered vertical baseline by default. + gfxFloat halfExtent = + 0.5 * gfxFloat(mFUnitsConvFactor) * + (int16_t(vhea->ascender) + std::abs(int16_t(vhea->descender))); + // Some bogus fonts have ascent and descent set to zero in 'vhea'. + // In that case we just ignore them and keep our synthetic values + // from above. + if (halfExtent > 0) { + metrics->maxAscent = halfExtent; + metrics->maxDescent = halfExtent; + SET_SIGNED(externalLeading, vhea->lineGap); + } + // Call gfxHarfBuzzShaper::GetGlyphVAdvance directly, as GetCharAdvance + // would potentially recurse if no v-advance is available and it attempts + // to fall back to a value from mVerticalMetrics. + if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) { + uint32_t gid = ProvidesGetGlyph() + ? GetGlyph(kWaterIdeograph, 0) + : shaper->GetNominalGlyph(kWaterIdeograph); + if (gid) { + int32_t advance = shaper->GetGlyphVAdvance(gid); + // Convert 16.16 fixed-point advance from the shaper to a float. + metrics->ideographicWidth = + advance < 0 ? metrics->aveCharWidth : advance / 65536.0; + } + gid = ProvidesGetGlyph() ? GetGlyph('0', 0) + : shaper->GetNominalGlyph('0'); + if (gid) { + int32_t advance = shaper->GetGlyphVAdvance(gid); + metrics->zeroWidth = + advance < 0 ? metrics->aveCharWidth : advance / 65536.0; + } + } + } + } + + // If we didn't set aveCharWidth above, we must be dealing with a non-sfnt + // font of some kind (Type1, bitmap, vector, ...), so fall back to using + // whatever the platform backend figured out for horizontal layout. + // And if we haven't set externalLeading yet, then copy that from the + // horizontal metrics as well, to help consistency of CSS line-height. + if (!metrics->aveCharWidth || + metrics->externalLeading == UNINITIALIZED_LEADING) { + const Metrics& horizMetrics = GetHorizontalMetrics(); + if (!metrics->aveCharWidth) { + metrics->aveCharWidth = horizMetrics.maxAscent + horizMetrics.maxDescent; + } + if (metrics->externalLeading == UNINITIALIZED_LEADING) { + metrics->externalLeading = horizMetrics.externalLeading; + } + } + + // Get underline thickness from the 'post' table if available. + // We also read the underline position, although in vertical-upright mode + // this will not be appropriate to use directly (see nsTextFrame.cpp). + gfxFontEntry::AutoTable postTable(mFontEntry, kPostTableTag); + if (postTable) { + const PostTable* post = + reinterpret_cast<const PostTable*>(hb_blob_get_data(postTable, &len)); + if (len >= offsetof(PostTable, underlineThickness) + sizeof(uint16_t)) { + static_assert(offsetof(PostTable, underlinePosition) < + offsetof(PostTable, underlineThickness), + "broken PostTable struct?"); + SET_SIGNED(underlineOffset, post->underlinePosition); + SET_UNSIGNED(underlineSize, post->underlineThickness); + // Also use for strikeout if we didn't find that in OS/2 above. + if (!metrics->strikeoutSize) { + metrics->strikeoutSize = metrics->underlineSize; + } + } + } + +#undef SET_UNSIGNED +#undef SET_SIGNED + + // If we didn't read this from a vhea table, it will still be zero. + // In any case, let's make sure it is not less than the value we've + // come up with for aveCharWidth. + metrics->maxAdvance = std::max(metrics->maxAdvance, metrics->aveCharWidth); + + // Thickness of underline and strikeout may have been read from tables, + // but in case they were not present, ensure a minimum of 1 pixel. + metrics->underlineSize = std::max(1.0, metrics->underlineSize); + + metrics->strikeoutSize = std::max(1.0, metrics->strikeoutSize); + metrics->strikeoutOffset = -0.5 * metrics->strikeoutSize; + + // Somewhat arbitrary values for now, subject to future refinement... + metrics->spaceWidth = metrics->aveCharWidth; + metrics->maxHeight = metrics->maxAscent + metrics->maxDescent; + metrics->xHeight = metrics->emHeight / 2; + metrics->capHeight = metrics->maxAscent; + + if (metrics->zeroWidth < 0.0) { + metrics->zeroWidth = metrics->aveCharWidth; + } + + if (!mVerticalMetrics.compareExchange(nullptr, metrics)) { + delete metrics; + } +} + +gfxFloat gfxFont::SynthesizeSpaceWidth(uint32_t aCh) { + // return an appropriate width for various Unicode space characters + // that we "fake" if they're not actually present in the font; + // returns negative value if the char is not a known space. + switch (aCh) { + case 0x2000: // en quad + case 0x2002: + return GetAdjustedSize() / 2; // en space + case 0x2001: // em quad + case 0x2003: + return GetAdjustedSize(); // em space + case 0x2004: + return GetAdjustedSize() / 3; // three-per-em space + case 0x2005: + return GetAdjustedSize() / 4; // four-per-em space + case 0x2006: + return GetAdjustedSize() / 6; // six-per-em space + case 0x2007: + return GetMetrics(nsFontMetrics::eHorizontal) + .ZeroOrAveCharWidth(); // figure space + case 0x2008: + return GetMetrics(nsFontMetrics::eHorizontal) + .spaceWidth; // punctuation space + case 0x2009: + return GetAdjustedSize() / 5; // thin space + case 0x200a: + return GetAdjustedSize() / 10; // hair space + case 0x202f: + return GetAdjustedSize() / 5; // narrow no-break space + case 0x3000: + return GetAdjustedSize(); // ideographic space + default: + return -1.0; + } +} + +void gfxFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const { + AutoReadLock lock(mLock); + for (uint32_t i = 0; i < mGlyphExtentsArray.Length(); ++i) { + aSizes->mFontInstances += + mGlyphExtentsArray[i]->SizeOfIncludingThis(aMallocSizeOf); + } + if (mWordCache) { + aSizes->mShapedWords += + mWordCache->shallowSizeOfIncludingThis(aMallocSizeOf); + for (auto it = mWordCache->iter(); !it.done(); it.next()) { + aSizes->mShapedWords += + it.get().value()->SizeOfIncludingThis(aMallocSizeOf); + } + } +} + +void gfxFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const { + aSizes->mFontInstances += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +void gfxFont::AddGlyphChangeObserver(GlyphChangeObserver* aObserver) { + AutoWriteLock lock(mLock); + if (!mGlyphChangeObservers) { + mGlyphChangeObservers = MakeUnique<nsTHashSet<GlyphChangeObserver*>>(); + } + mGlyphChangeObservers->Insert(aObserver); +} + +void gfxFont::RemoveGlyphChangeObserver(GlyphChangeObserver* aObserver) { + AutoWriteLock lock(mLock); + NS_ASSERTION(mGlyphChangeObservers, "No observers registered"); + NS_ASSERTION(mGlyphChangeObservers->Contains(aObserver), + "Observer not registered"); + mGlyphChangeObservers->Remove(aObserver); +} + +#define DEFAULT_PIXEL_FONT_SIZE 16.0f + +gfxFontStyle::gfxFontStyle() + : size(DEFAULT_PIXEL_FONT_SIZE), + sizeAdjust(0.0f), + baselineOffset(0.0f), + languageOverride(NO_FONT_LANGUAGE_OVERRIDE), + weight(FontWeight::NORMAL), + stretch(FontStretch::NORMAL), + style(FontSlantStyle::NORMAL), + variantCaps(NS_FONT_VARIANT_CAPS_NORMAL), + variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL), + sizeAdjustBasis(uint8_t(FontSizeAdjust::Tag::None)), + systemFont(true), + printerFont(false), + useGrayscaleAntialiasing(false), + allowSyntheticWeight(true), + allowSyntheticStyle(true), + allowSyntheticSmallCaps(true), + useSyntheticPosition(true), + noFallbackVariantFeatures(true) {} + +gfxFontStyle::gfxFontStyle(FontSlantStyle aStyle, FontWeight aWeight, + FontStretch aStretch, gfxFloat aSize, + const FontSizeAdjust& aSizeAdjust, bool aSystemFont, + bool aPrinterFont, bool aAllowWeightSynthesis, + bool aAllowStyleSynthesis, + bool aAllowSmallCapsSynthesis, + bool aUsePositionSynthesis, + uint32_t aLanguageOverride) + : size(aSize), + baselineOffset(0.0f), + languageOverride(aLanguageOverride), + weight(aWeight), + stretch(aStretch), + style(aStyle), + variantCaps(NS_FONT_VARIANT_CAPS_NORMAL), + variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL), + systemFont(aSystemFont), + printerFont(aPrinterFont), + useGrayscaleAntialiasing(false), + allowSyntheticWeight(aAllowWeightSynthesis), + allowSyntheticStyle(aAllowStyleSynthesis), + allowSyntheticSmallCaps(aAllowSmallCapsSynthesis), + useSyntheticPosition(aUsePositionSynthesis), + noFallbackVariantFeatures(true) { + MOZ_ASSERT(!std::isnan(size)); + + sizeAdjustBasis = uint8_t(aSizeAdjust.tag); + // sizeAdjustBasis is currently a small bitfield, so let's assert that the + // tag value was not truncated. + MOZ_ASSERT(FontSizeAdjust::Tag(sizeAdjustBasis) == aSizeAdjust.tag, + "gfxFontStyle.sizeAdjustBasis too small?"); + +#define HANDLE_TAG(TAG) \ + case FontSizeAdjust::Tag::TAG: \ + sizeAdjust = aSizeAdjust.As##TAG(); \ + break; + + switch (aSizeAdjust.tag) { + case FontSizeAdjust::Tag::None: + sizeAdjust = 0.0f; + break; + HANDLE_TAG(ExHeight) + HANDLE_TAG(CapHeight) + HANDLE_TAG(ChWidth) + HANDLE_TAG(IcWidth) + HANDLE_TAG(IcHeight) + } + +#undef HANDLE_TAG + + MOZ_ASSERT(!std::isnan(sizeAdjust)); + + if (weight > FontWeight::FromInt(1000)) { + weight = FontWeight::FromInt(1000); + } + if (weight < FontWeight::FromInt(1)) { + weight = FontWeight::FromInt(1); + } + + if (size >= FONT_MAX_SIZE) { + size = FONT_MAX_SIZE; + sizeAdjust = 0.0f; + sizeAdjustBasis = uint8_t(FontSizeAdjust::Tag::None); + } else if (size < 0.0) { + NS_WARNING("negative font size"); + size = 0.0; + } +} + +PLDHashNumber gfxFontStyle::Hash() const { + uint32_t hash = variationSettings.IsEmpty() + ? 0 + : mozilla::HashBytes(variationSettings.Elements(), + variationSettings.Length() * + sizeof(gfxFontVariation)); + return mozilla::AddToHash(hash, systemFont, style.Raw(), stretch.Raw(), + weight.Raw(), size, int32_t(sizeAdjust * 1000.0f)); +} + +void gfxFontStyle::AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel) { + MOZ_ASSERT( + variantSubSuper != NS_FONT_VARIANT_POSITION_NORMAL && baselineOffset == 0, + "can't adjust this style for sub/superscript"); + + // calculate the baseline offset (before changing the size) + if (variantSubSuper == NS_FONT_VARIANT_POSITION_SUPER) { + baselineOffset = size * -NS_FONT_SUPERSCRIPT_OFFSET_RATIO; + } else { + baselineOffset = size * NS_FONT_SUBSCRIPT_OFFSET_RATIO; + } + + // calculate reduced size, roughly mimicing behavior of font-size: smaller + float cssSize = size * aAppUnitsPerDevPixel / AppUnitsPerCSSPixel(); + if (cssSize < NS_FONT_SUB_SUPER_SMALL_SIZE) { + size *= NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL; + } else if (cssSize >= NS_FONT_SUB_SUPER_LARGE_SIZE) { + size *= NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE; + } else { + gfxFloat t = (cssSize - NS_FONT_SUB_SUPER_SMALL_SIZE) / + (NS_FONT_SUB_SUPER_LARGE_SIZE - NS_FONT_SUB_SUPER_SMALL_SIZE); + size *= (1.0 - t) * NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL + + t * NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE; + } + + // clear the variant field + variantSubSuper = NS_FONT_VARIANT_POSITION_NORMAL; +} + +bool gfxFont::TryGetMathTable() { + if (mMathInitialized) { + return !!mMathTable; + } + + auto face(GetFontEntry()->GetHBFace()); + if (hb_ot_math_has_data(face)) { + auto* mathTable = new gfxMathTable(face, GetAdjustedSize()); + if (!mMathTable.compareExchange(nullptr, mathTable)) { + delete mathTable; + } + } + mMathInitialized = true; + + return !!mMathTable; +} diff --git a/gfx/thebes/gfxFont.h b/gfx/thebes/gfxFont.h new file mode 100644 index 0000000000..f51c08ea82 --- /dev/null +++ b/gfx/thebes/gfxFont.h @@ -0,0 +1,2378 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=4 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FONT_H +#define GFX_FONT_H + +#include <new> +#include <utility> +#include <functional> +#include "PLDHashTable.h" +#include "ThebesRLBoxTypes.h" +#include "gfxFontVariations.h" +#include "gfxRect.h" +#include "gfxTypes.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Attributes.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/HashTable.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/MruCache.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/RWLock.h" +#include "mozilla/TypedEnumBits.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/gfx/FontPaletteCache.h" +#include "mozilla/gfx/MatrixFwd.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/intl/UnicodeScriptCodes.h" +#include "nsCOMPtr.h" +#include "nsColor.h" +#include "nsTHashMap.h" +#include "nsTHashSet.h" +#include "nsExpirationTracker.h" +#include "nsFontMetrics.h" +#include "nsHashKeys.h" +#include "nsIMemoryReporter.h" +#include "nsIObserver.h" +#include "nsISupports.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsTHashtable.h" +#include "nscore.h" +#include "DrawMode.h" + +// Only required for function bodies +#include "gfxFontEntry.h" +#include "gfxFontFeatures.h" + +class gfxContext; +class gfxGraphiteShaper; +class gfxHarfBuzzShaper; +class gfxGlyphExtents; +class gfxMathTable; +class gfxPattern; +class gfxShapedText; +class gfxShapedWord; +class gfxSkipChars; +class gfxTextRun; +class nsIEventTarget; +class nsITimer; +struct gfxTextRunDrawCallbacks; + +namespace mozilla { +class SVGContextPaint; +namespace layout { +class TextDrawTarget; +} +} // namespace mozilla + +typedef struct _cairo cairo_t; +typedef struct _cairo_scaled_font cairo_scaled_font_t; + +#define FONT_MAX_SIZE 2000.0 + +#define SMALL_CAPS_SCALE_FACTOR 0.8 + +// The skew factor used for synthetic-italic [oblique] fonts; +// we use a platform-dependent value to harmonize with the platform's own APIs. +#ifdef XP_WIN +# define OBLIQUE_SKEW_FACTOR 0.3f +#elif defined(MOZ_WIDGET_GTK) +# define OBLIQUE_SKEW_FACTOR 0.2f +#else +# define OBLIQUE_SKEW_FACTOR 0.25f +#endif + +struct gfxFontStyle { + using FontStretch = mozilla::FontStretch; + using FontSlantStyle = mozilla::FontSlantStyle; + using FontWeight = mozilla::FontWeight; + using FontSizeAdjust = mozilla::StyleFontSizeAdjust; + + gfxFontStyle(); + gfxFontStyle(FontSlantStyle aStyle, FontWeight aWeight, FontStretch aStretch, + gfxFloat aSize, const FontSizeAdjust& aSizeAdjust, + bool aSystemFont, bool aPrinterFont, bool aWeightSynthesis, + bool aStyleSynthesis, bool aSmallCapsSynthesis, + bool aPositionSynthesis, uint32_t aLanguageOverride); + // Features are composed of (1) features from style rules (2) features + // from feature settings rules and (3) family-specific features. (1) and + // (3) are guaranteed to be mutually exclusive + + // custom opentype feature settings + CopyableTArray<gfxFontFeature> featureSettings; + + // Some font-variant property values require font-specific settings + // defined via @font-feature-values rules. These are resolved after + // font matching occurs. + + // -- list of value tags for specific alternate features + mozilla::StyleFontVariantAlternates variantAlternates; + + // -- object used to look these up once the font is matched + RefPtr<gfxFontFeatureValueSet> featureValueLookup; + + // opentype variation settings + CopyableTArray<gfxFontVariation> variationSettings; + + // The logical size of the font, in pixels + gfxFloat size; + + // The optical size value to apply (if supported); negative means none. + float autoOpticalSize = -1.0f; + + // The aspect-value (ie., the ratio actualsize:actualxheight) that any + // actual physical font created from this font structure must have when + // rendering or measuring a string. A value of -1.0 means no adjustment + // needs to be done; otherwise the value must be nonnegative. + float sizeAdjust; + + // baseline offset, used when simulating sub/superscript glyphs + float baselineOffset; + + // Language system tag, to override document language; + // an OpenType "language system" tag represented as a 32-bit integer + // (see http://www.microsoft.com/typography/otspec/languagetags.htm). + // Normally 0, so font rendering will use the document or element language + // (see above) to control any language-specific rendering, but the author + // can override this for cases where the options implemented in the font + // do not directly match the actual language. (E.g. lang may be Macedonian, + // but the font in use does not explicitly support this; the author can + // use font-language-override to request the Serbian option in the font + // in order to get correct glyph shapes.) + uint32_t languageOverride; + + // The Font{Weight,Stretch,SlantStyle} fields are each a 16-bit type. + + // The weight of the font: 100, 200, ... 900. + FontWeight weight; + + // The stretch of the font + FontStretch stretch; + + // The style of font + FontSlantStyle style; + + // Whether face-selection properties weight/style/stretch are all 'normal' + bool IsNormalStyle() const { + return weight.IsNormal() && style.IsNormal() && stretch.IsNormal(); + } + + // We pack these three small-integer fields into a single byte to avoid + // overflowing an 8-byte boundary [in a 64-bit build] and ending up with + // 7 bytes of padding at the end of the struct. + + // caps variant (small-caps, petite-caps, etc.) + uint8_t variantCaps : 3; // uses range 0..6 + + // sub/superscript variant + uint8_t variantSubSuper : 2; // uses range 0..2 + + // font metric used as basis of font-size-adjust + uint8_t sizeAdjustBasis : 3; // uses range 0..4 + + // Say that this font is a system font and therefore does not + // require certain fixup that we do for fonts from untrusted + // sources. + bool systemFont : 1; + + // Say that this font is used for print or print preview. + bool printerFont : 1; + + // Used to imitate -webkit-font-smoothing: antialiased + bool useGrayscaleAntialiasing : 1; + + // Whether synthetic styles are allowed (required, in the case of position) + bool allowSyntheticWeight : 1; + bool allowSyntheticStyle : 1; + bool allowSyntheticSmallCaps : 1; + bool useSyntheticPosition : 1; + + // some variant features require fallback which complicates the shaping + // code, so set up a bool to indicate when shaping with fallback is needed + bool noFallbackVariantFeatures : 1; + + // Return the final adjusted font size for the given aspect ratio. + // Not meant to be called when sizeAdjustBasis is NONE. + gfxFloat GetAdjustedSize(gfxFloat aspect) const { + MOZ_ASSERT( + FontSizeAdjust::Tag(sizeAdjustBasis) != FontSizeAdjust::Tag::None, + "Not meant to be called when sizeAdjustBasis is none"); + gfxFloat adjustedSize = + std::max(NS_round(size * (sizeAdjust / aspect)), 1.0); + return std::min(adjustedSize, FONT_MAX_SIZE); + } + + // Some callers want to take a short-circuit path if they can be sure the + // adjusted size will be zero. + bool AdjustedSizeMustBeZero() const { + return size == 0.0 || + (FontSizeAdjust::Tag(sizeAdjustBasis) != FontSizeAdjust::Tag::None && + sizeAdjust == 0.0); + } + + PLDHashNumber Hash() const; + + // Adjust this style to simulate sub/superscript (as requested in the + // variantSubSuper field) using size and baselineOffset instead. + void AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel); + + // Should this style cause the given font entry to use synthetic bold? + bool NeedsSyntheticBold(gfxFontEntry* aFontEntry) const { + return weight.IsBold() && allowSyntheticWeight && + !aFontEntry->SupportsBold(); + } + + bool Equals(const gfxFontStyle& other) const { + return mozilla::NumbersAreBitwiseIdentical(size, other.size) && + (style == other.style) && (weight == other.weight) && + (stretch == other.stretch) && (variantCaps == other.variantCaps) && + (variantSubSuper == other.variantSubSuper) && + (allowSyntheticWeight == other.allowSyntheticWeight) && + (allowSyntheticStyle == other.allowSyntheticStyle) && + (allowSyntheticSmallCaps == other.allowSyntheticSmallCaps) && + (useSyntheticPosition == other.useSyntheticPosition) && + (systemFont == other.systemFont) && + (printerFont == other.printerFont) && + (useGrayscaleAntialiasing == other.useGrayscaleAntialiasing) && + (baselineOffset == other.baselineOffset) && + mozilla::NumbersAreBitwiseIdentical(sizeAdjust, other.sizeAdjust) && + (sizeAdjustBasis == other.sizeAdjustBasis) && + (featureSettings == other.featureSettings) && + (variantAlternates == other.variantAlternates) && + (featureValueLookup == other.featureValueLookup) && + (variationSettings == other.variationSettings) && + (languageOverride == other.languageOverride) && + mozilla::NumbersAreBitwiseIdentical(autoOpticalSize, + other.autoOpticalSize); + } +}; + +/** + * Font cache design: + * + * The mFonts hashtable contains most fonts, indexed by (gfxFontEntry*, style). + * It maintains a strong reference to the fonts it contains. + * Whenever a font is accessed, it is marked as used to move it to a new + * generation in the tracker to avoid expiration. + * The expiration tracker will only expire fonts with a single reference, the + * cache itself. Fonts with more than one reference are marked as used. + * + * We're using 3 generations with a ten-second generation interval, so + * zero-refcount fonts will be deleted 20-30 seconds after their refcount + * goes to zero, if timer events fire in a timely manner. + * + * The font cache also handles timed expiration of cached ShapedWords + * for "persistent" fonts: it has a repeating timer, and notifies + * each cached font to "age" its shaped words. The words will be released + * by the fonts if they get aged three times without being re-used in the + * meantime. + * + * Note that the ShapedWord timeout is much larger than the font timeout, + * so that in the case of a short-lived font, we'll discard the gfxFont + * completely, with all its words, and avoid the cost of aging the words + * individually. That only happens with longer-lived fonts. + */ +struct FontCacheSizes { + FontCacheSizes() : mFontInstances(0), mShapedWords(0) {} + + size_t mFontInstances; // memory used by instances of gfxFont subclasses + size_t mShapedWords; // memory used by the per-font shapedWord caches +}; + +class gfxFontCache final + : public ExpirationTrackerImpl<gfxFont, 3, mozilla::Mutex, + mozilla::MutexAutoLock> { + protected: + // Expiration tracker implementation. + enum { FONT_TIMEOUT_SECONDS = 10 }; + + typedef mozilla::Mutex Lock; + typedef mozilla::MutexAutoLock AutoLock; + + // This protects the ExpirationTracker tables. + Lock mMutex = Lock("fontCacheExpirationMutex"); + + Lock& GetMutex() override { return mMutex; } + + public: + explicit gfxFontCache(nsIEventTarget* aEventTarget); + ~gfxFontCache(); + + enum { SHAPED_WORD_TIMEOUT_SECONDS = 60 }; + + /* + * Get the global gfxFontCache. You must call Init() before + * calling this method --- the result will not be null. + */ + static gfxFontCache* GetCache() { return gGlobalCache; } + + static nsresult Init(); + // It's OK to call this even if Init() has not been called. + static void Shutdown(); + + // Look up a font in the cache. Returns null if there's nothing matching + // in the cache + already_AddRefed<gfxFont> Lookup(const gfxFontEntry* aFontEntry, + const gfxFontStyle* aStyle, + const gfxCharacterMap* aUnicodeRangeMap); + + // We created a new font (presumably because Lookup returned null); + // put it in the cache. The font's refcount should be nonzero. It is + // allowable to add a new font even if there is one already in the + // cache with the same key, as we may race with other threads to do + // the insertion -- in that case we will return the original font, + // and destroy the new one. + already_AddRefed<gfxFont> MaybeInsert(gfxFont* aFont); + + bool MaybeDestroy(gfxFont* aFont); + + // Cleans out the hashtable and removes expired fonts waiting for cleanup. + // Other gfxFont objects may be still in use but they will be pushed + // into the expiration queues and removed. + void Flush(); + + void FlushShapedWordCaches(); + void NotifyGlyphsChanged(); + + void AgeCachedWords(); + + void RunWordCacheExpirationTimer() { + if (!mTimerRunning) { + mozilla::MutexAutoLock lock(mMutex); + if (!mTimerRunning && mWordCacheExpirationTimer) { + mWordCacheExpirationTimer->InitWithNamedFuncCallback( + WordCacheExpirationTimerCallback, this, + SHAPED_WORD_TIMEOUT_SECONDS * 1000, nsITimer::TYPE_REPEATING_SLACK, + "gfxFontCache::WordCacheExpiration"); + mTimerRunning = true; + } + } + } + void PauseWordCacheExpirationTimer() { + if (mTimerRunning) { + mozilla::MutexAutoLock lock(mMutex); + if (mTimerRunning && mWordCacheExpirationTimer) { + mWordCacheExpirationTimer->Cancel(); + mTimerRunning = false; + } + } + } + + void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const; + void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const; + + protected: + class MemoryReporter final : public nsIMemoryReporter { + ~MemoryReporter() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMEMORYREPORTER + }; + + // Observer for notifications that the font cache cares about + class Observer final : public nsIObserver { + ~Observer() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + }; + + nsresult AddObject(gfxFont* aFont) { + AutoLock lock(mMutex); + return AddObjectLocked(aFont, lock); + } + + // This gets called when the timeout has expired on a single-refcount + // font; we just delete it. + void NotifyExpiredLocked(gfxFont* aFont, const AutoLock&) + MOZ_REQUIRES(mMutex) override; + void NotifyHandlerEnd() override; + + void DestroyDiscard(nsTArray<gfxFont*>& aDiscard); + + static gfxFontCache* gGlobalCache; + + struct MOZ_STACK_CLASS Key { + const gfxFontEntry* mFontEntry; + const gfxFontStyle* mStyle; + const gfxCharacterMap* mUnicodeRangeMap; + Key(const gfxFontEntry* aFontEntry, const gfxFontStyle* aStyle, + const gfxCharacterMap* aUnicodeRangeMap) + : mFontEntry(aFontEntry), + mStyle(aStyle), + mUnicodeRangeMap(aUnicodeRangeMap) {} + }; + + class HashEntry : public PLDHashEntryHdr { + public: + typedef const Key& KeyType; + typedef const Key* KeyTypePointer; + + // When constructing a new entry in the hashtable, we'll leave this + // blank. The caller of Put() will fill this in. + explicit HashEntry(KeyTypePointer aStr) {} + + bool KeyEquals(const KeyTypePointer aKey) const; + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(const KeyTypePointer aKey) { + return mozilla::HashGeneric(aKey->mStyle->Hash(), aKey->mFontEntry, + aKey->mUnicodeRangeMap); + } + enum { ALLOW_MEMMOVE = true }; + + gfxFont* MOZ_UNSAFE_REF("tracking for deferred deletion") mFont = nullptr; + }; + + nsTHashtable<HashEntry> mFonts MOZ_GUARDED_BY(mMutex); + + nsTArray<gfxFont*> mTrackerDiscard MOZ_GUARDED_BY(mMutex); + + static void WordCacheExpirationTimerCallback(nsITimer* aTimer, void* aCache); + + nsCOMPtr<nsITimer> mWordCacheExpirationTimer MOZ_GUARDED_BY(mMutex); + std::atomic<bool> mTimerRunning = false; +}; + +class gfxTextPerfMetrics { + public: + struct TextCounts { + uint32_t numContentTextRuns; + uint32_t numChromeTextRuns; + uint32_t numChars; + uint32_t maxTextRunLen; + uint32_t wordCacheSpaceRules; + uint32_t wordCacheLong; + uint32_t wordCacheHit; + uint32_t wordCacheMiss; + uint32_t fallbackPrefs; + uint32_t fallbackSystem; + uint32_t textrunConst; + uint32_t textrunDestr; + uint32_t genericLookups; + }; + + uint32_t reflowCount; + + // counts per reflow operation + TextCounts current; + + // totals for the lifetime of a document + TextCounts cumulative; + + gfxTextPerfMetrics() { memset(this, 0, sizeof(gfxTextPerfMetrics)); } + + // add current totals to cumulative ones + void Accumulate() { + if (current.numChars == 0) { + return; + } + cumulative.numContentTextRuns += current.numContentTextRuns; + cumulative.numChromeTextRuns += current.numChromeTextRuns; + cumulative.numChars += current.numChars; + if (current.maxTextRunLen > cumulative.maxTextRunLen) { + cumulative.maxTextRunLen = current.maxTextRunLen; + } + cumulative.wordCacheSpaceRules += current.wordCacheSpaceRules; + cumulative.wordCacheLong += current.wordCacheLong; + cumulative.wordCacheHit += current.wordCacheHit; + cumulative.wordCacheMiss += current.wordCacheMiss; + cumulative.fallbackPrefs += current.fallbackPrefs; + cumulative.fallbackSystem += current.fallbackSystem; + cumulative.textrunConst += current.textrunConst; + cumulative.textrunDestr += current.textrunDestr; + cumulative.genericLookups += current.genericLookups; + memset(¤t, 0, sizeof(current)); + } +}; + +namespace mozilla { +namespace gfx { + +class UnscaledFont; + +// Flags that live in the gfxShapedText::mFlags field. +// (Note that gfxTextRun has an additional mFlags2 field for use +// by textrun clients like nsTextFrame.) +// +// If you add a flag, please add support for it in gfxTextRun::Dump. +enum class ShapedTextFlags : uint16_t { + /** + * When set, the text is RTL. + */ + TEXT_IS_RTL = 0x0001, + /** + * When set, spacing is enabled and the textrun needs to call GetSpacing + * on the spacing provider. + */ + TEXT_ENABLE_SPACING = 0x0002, + /** + * When set, the text has no characters above 255 and it is stored + * in the textrun in 8-bit format. + */ + TEXT_IS_8BIT = 0x0004, + /** + * When set, GetHyphenationBreaks may return true for some character + * positions, otherwise it will always return false for all characters. + */ + TEXT_ENABLE_HYPHEN_BREAKS = 0x0008, + /** + * When set, the RunMetrics::mBoundingBox field will be initialized + * properly based on glyph extents, in particular, glyph extents that + * overflow the standard font-box (the box defined by the ascent, descent + * and advance width of the glyph). When not set, it may just be the + * standard font-box even if glyphs overflow. + */ + TEXT_NEED_BOUNDING_BOX = 0x0010, + /** + * When set, optional ligatures are disabled. Ligatures that are + * required for legible text should still be enabled. + */ + TEXT_DISABLE_OPTIONAL_LIGATURES = 0x0020, + /** + * When set, the textrun should favour speed of construction over + * quality. This may involve disabling ligatures and/or kerning or + * other effects. + */ + TEXT_OPTIMIZE_SPEED = 0x0040, + /** + * When set, the textrun should discard control characters instead of + * turning them into hexboxes. + */ + TEXT_HIDE_CONTROL_CHARACTERS = 0x0080, + + /** + * nsTextFrameThebes sets these, but they're defined here rather than + * in nsTextFrameUtils.h because ShapedWord creation/caching also needs + * to check the _INCOMING flag + */ + TEXT_TRAILING_ARABICCHAR = 0x0100, + /** + * When set, the previous character for this textrun was an Arabic + * character. This is used for the context detection necessary for + * bidi.numeral implementation. + */ + TEXT_INCOMING_ARABICCHAR = 0x0200, + + /** + * Set if the textrun should use the OpenType 'math' script. + */ + TEXT_USE_MATH_SCRIPT = 0x0400, + + /* + * Bit 0x0800 is currently unused. + */ + + /** + * Field for orientation of the textrun and glyphs within it. + * Possible values of the TEXT_ORIENT_MASK field: + * TEXT_ORIENT_HORIZONTAL + * TEXT_ORIENT_VERTICAL_UPRIGHT + * TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT + * TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT + * TEXT_ORIENT_VERTICAL_MIXED + * For all VERTICAL settings, the x and y coordinates of glyph + * positions are exchanged, so that simple advances are vertical. + * + * The MIXED value indicates vertical textRuns for which the CSS + * text-orientation property is 'mixed', but is never used for + * individual glyphRuns; it will be resolved to either UPRIGHT + * or SIDEWAYS_RIGHT according to the UTR50 properties of the + * characters, and separate glyphRuns created for the resulting + * glyph orientations. + */ + TEXT_ORIENT_MASK = 0x7000, + TEXT_ORIENT_HORIZONTAL = 0x0000, + TEXT_ORIENT_VERTICAL_UPRIGHT = 0x1000, + TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT = 0x2000, + TEXT_ORIENT_VERTICAL_MIXED = 0x3000, + TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT = 0x4000, +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ShapedTextFlags) +} // namespace gfx +} // namespace mozilla + +class gfxTextRunFactory { + // Used by stylo + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxTextRunFactory) + + public: + typedef mozilla::gfx::DrawTarget DrawTarget; + + /** + * This record contains all the parameters needed to initialize a textrun. + */ + struct MOZ_STACK_CLASS Parameters { + // Shape text params suggesting where the textrun will be rendered + DrawTarget* mDrawTarget; + // Pointer to arbitrary user data (which should outlive the textrun) + void* mUserData; + // A description of which characters have been stripped from the original + // DOM string to produce the characters in the textrun. May be null + // if that information is not relevant. + gfxSkipChars* mSkipChars; + // A list of where linebreaks are currently placed in the textrun. May + // be null if mInitialBreakCount is zero. + uint32_t* mInitialBreaks; + uint32_t mInitialBreakCount; + // The ratio to use to convert device pixels to application layout units + int32_t mAppUnitsPerDevUnit; + }; + + protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~gfxTextRunFactory(); +}; + +/** + * gfxFontShaper + * + * This class implements text shaping (character to glyph mapping and + * glyph layout). There is a gfxFontShaper subclass for each text layout + * technology (uniscribe, core text, harfbuzz,....) we support. + * + * The shaper is responsible for setting up glyph data in gfxTextRuns. + * + * A generic, platform-independent shaper relies only on the standard + * gfxFont interface and can work with any concrete subclass of gfxFont. + * + * Platform-specific implementations designed to interface to platform + * shaping APIs such as Uniscribe or CoreText may rely on features of a + * specific font subclass to access native font references + * (such as CTFont, HFONT, DWriteFont, etc). + */ + +class gfxFontShaper { + public: + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::intl::Script Script; + + enum class RoundingFlags : uint8_t { kRoundX = 0x01, kRoundY = 0x02 }; + + explicit gfxFontShaper(gfxFont* aFont) : mFont(aFont) { + NS_ASSERTION(aFont, "shaper requires a valid font!"); + } + + virtual ~gfxFontShaper() = default; + + // Shape a piece of text and store the resulting glyph data into + // aShapedText. Parameters aOffset/aLength indicate the range of + // aShapedText to be updated; aLength is also the length of aText. + virtual bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, // may be null, indicating no + // lang-specific shaping to be + // applied + bool aVertical, RoundingFlags aRounding, + gfxShapedText* aShapedText) = 0; + + gfxFont* GetFont() const { return mFont; } + + static void MergeFontFeatures( + const gfxFontStyle* aStyle, const nsTArray<gfxFontFeature>& aFontFeatures, + bool aDisableLigatures, const nsACString& aFamilyName, bool aAddSmallCaps, + void (*aHandleFeature)(uint32_t, uint32_t, void*), + void* aHandleFeatureData); + + protected: + // the font this shaper is working with. The font owns a UniquePtr reference + // to this object, and will destroy it before it dies. Thus, mFont will always + // be valid. + gfxFont* MOZ_NON_OWNING_REF mFont; +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(gfxFontShaper::RoundingFlags) + +/* + * gfxShapedText is an abstract superclass for gfxShapedWord and gfxTextRun. + * These are objects that store a list of zero or more glyphs for each + * character. For each glyph we store the glyph ID, the advance, and possibly + * x/y-offsets. The idea is that a string is rendered by a loop that draws each + * glyph at its designated offset from the current point, then advances the + * current point by the glyph's advance in the direction of the textrun (LTR or + * RTL). Each glyph advance is always rounded to the nearest appunit; this + * ensures consistent results when dividing the text in a textrun into multiple + * text frames (frame boundaries are always aligned to appunits). We optimize + * for the case where a character has a single glyph and zero xoffset and + * yoffset, and the glyph ID and advance are in a reasonable range so we can + * pack all necessary data into 32 bits. + * + * gfxFontShaper can shape text into either a gfxShapedWord (cached by a + * gfxFont) or directly into a gfxTextRun (for cases where we want to shape + * textruns in their entirety rather than using cached words, because there may + * be layout features that depend on the inter-word spaces). + */ +class gfxShapedText { + public: + typedef mozilla::intl::Script Script; + + gfxShapedText(uint32_t aLength, mozilla::gfx::ShapedTextFlags aFlags, + uint16_t aAppUnitsPerDevUnit) + : mLength(aLength), + mFlags(aFlags), + mAppUnitsPerDevUnit(aAppUnitsPerDevUnit) {} + + virtual ~gfxShapedText() = default; + + /** + * This class records the information associated with a character in the + * input string. It's optimized for the case where there is one glyph + * representing that character alone. + * + * A character can have zero or more associated glyphs. Each glyph + * has an advance width and an x and y offset. + * A character may be the start of a cluster. + * A character may be the start of a ligature group. + * A character can be "missing", indicating that the system is unable + * to render the character. + * + * All characters in a ligature group conceptually share all the glyphs + * associated with the characters in a group. + */ + class CompressedGlyph { + public: + enum { + // Indicates that a cluster and ligature group starts at this + // character; this character has a single glyph with a reasonable + // advance and zero offsets. A "reasonable" advance + // is one that fits in the available bits (currently 12) (specified + // in appunits). + FLAG_IS_SIMPLE_GLYPH = 0x80000000U, + + // These flags are applicable to both "simple" and "complex" records. + COMMON_FLAGS_MASK = 0x70000000U, + + // Indicates whether a linebreak is allowed before this character; + // this is a two-bit field that holds a FLAG_BREAK_TYPE_xxx value + // indicating the kind of linebreak (if any) allowed here. + FLAGS_CAN_BREAK_BEFORE = 0x60000000U, + + FLAGS_CAN_BREAK_SHIFT = 29, + FLAG_BREAK_TYPE_NONE = 0, + FLAG_BREAK_TYPE_NORMAL = 1, + FLAG_BREAK_TYPE_HYPHEN = 2, + // Allow break before this position if needed to avoid overflow: + FLAG_BREAK_TYPE_EMERGENCY_WRAP = 3, + + 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 <= 3, "Bogus break-flags 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 ApplyTrackingToClusters(gfxFloat aTrackingAdjustment, uint32_t aOffset, + uint32_t aLength); + + // Mark clusters in the CompressedGlyph records, starting at aOffset, + // based on the Unicode properties of the text in aString. + // This is also responsible to set the IsSpace flag for space characters. + void SetupClusterBoundaries(uint32_t aOffset, const char16_t* aString, + uint32_t aLength); + // In 8-bit text, there won't actually be any clusters, but we still need + // the space-marking functionality. + void SetupClusterBoundaries(uint32_t aOffset, const uint8_t* aString, + uint32_t aLength); + + mozilla::gfx::ShapedTextFlags GetFlags() const { return mFlags; } + + bool IsVertical() const { + return (GetFlags() & mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_MASK) != + mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_HORIZONTAL; + } + + bool UseCenterBaseline() const { + mozilla::gfx::ShapedTextFlags orient = + GetFlags() & mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_MASK; + return orient == + mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED || + orient == + mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT; + } + + bool IsRightToLeft() const { + return (GetFlags() & mozilla::gfx::ShapedTextFlags::TEXT_IS_RTL) == + mozilla::gfx::ShapedTextFlags::TEXT_IS_RTL; + } + + bool IsSidewaysLeft() const { + return (GetFlags() & mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_MASK) == + mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT; + } + + // Return true if the logical inline direction is reversed compared to + // normal physical coordinates (i.e. if it is leftwards or upwards) + bool IsInlineReversed() const { return IsSidewaysLeft() != IsRightToLeft(); } + + gfxFloat GetDirection() const { return IsInlineReversed() ? -1.0f : 1.0f; } + + bool DisableLigatures() const { + return (GetFlags() & + mozilla::gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES) == + mozilla::gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES; + } + + bool TextIs8Bit() const { + return (GetFlags() & mozilla::gfx::ShapedTextFlags::TEXT_IS_8BIT) == + mozilla::gfx::ShapedTextFlags::TEXT_IS_8BIT; + } + + int32_t GetAppUnitsPerDevUnit() const { return mAppUnitsPerDevUnit; } + + uint32_t GetLength() const { return mLength; } + + bool FilterIfIgnorable(uint32_t aIndex, uint32_t aCh); + + protected: + // Allocate aCount DetailedGlyphs for the given index + DetailedGlyph* AllocateDetailedGlyphs(uint32_t aCharIndex, uint32_t aCount); + + // Ensure the glyph on the given index is complex glyph so that we can use + // it to record specific characters that layout may need to detect. + void EnsureComplexGlyph(uint32_t aIndex, CompressedGlyph& aGlyph) { + MOZ_ASSERT(GetCharacterGlyphs() + aIndex == &aGlyph); + if (aGlyph.IsSimpleGlyph()) { + DetailedGlyph details = {aGlyph.GetSimpleGlyph(), + (int32_t)aGlyph.GetSimpleAdvance(), + mozilla::gfx::Point()}; + aGlyph.SetComplex(true, true); + SetDetailedGlyphs(aIndex, 1, &details); + } + } + + // For characters whose glyph data does not fit the "simple" glyph criteria + // in CompressedGlyph, we use a sorted array to store the association + // between the source character offset and an index into an array + // DetailedGlyphs. The CompressedGlyph record includes a count of + // the number of DetailedGlyph records that belong to the character, + // starting at the given index. + class DetailedGlyphStore { + public: + DetailedGlyphStore() = default; + + // This is optimized for the most common calling patterns: + // we rarely need random access to the records, access is most commonly + // sequential through the textRun, so we record the last-used index + // and check whether the caller wants the same record again, or the + // next; if not, it's most likely we're starting over from the start + // of the run, so we check the first entry before resorting to binary + // search as a last resort. + // NOTE that this must not be called for a character offset that does + // not have any DetailedGlyph records; callers must have verified that + // mCharacterGlyphs[aOffset].GetGlyphCount() is greater than zero + // before calling this, otherwise the assertions here will fire (in a + // debug build), and we'll probably crash. + DetailedGlyph* Get(uint32_t aOffset) { + NS_ASSERTION(mOffsetToIndex.Length() > 0, "no detailed glyph records!"); + DetailedGlyph* details = mDetails.Elements(); + // check common cases (fwd iteration, initial entry, etc) first + if (mLastUsed < mOffsetToIndex.Length() - 1 && + aOffset == mOffsetToIndex[mLastUsed + 1].mOffset) { + ++mLastUsed; + } else if (aOffset == mOffsetToIndex[0].mOffset) { + mLastUsed = 0; + } else if (aOffset == mOffsetToIndex[mLastUsed].mOffset) { + // do nothing + } else if (mLastUsed > 0 && + aOffset == mOffsetToIndex[mLastUsed - 1].mOffset) { + --mLastUsed; + } else { + mLastUsed = mOffsetToIndex.BinaryIndexOf(aOffset, CompareToOffset()); + } + NS_ASSERTION(mLastUsed != nsTArray<DGRec>::NoIndex, + "detailed glyph record missing!"); + return details + mOffsetToIndex[mLastUsed].mIndex; + } + + DetailedGlyph* Allocate(uint32_t aOffset, uint32_t aCount) { + uint32_t detailIndex = mDetails.Length(); + DetailedGlyph* details = mDetails.AppendElements(aCount); + // We normally set up glyph records sequentially, so the common case + // here is to append new records to the mOffsetToIndex array; + // test for that before falling back to the InsertElementSorted + // method. + if (mOffsetToIndex.Length() == 0 || + aOffset > mOffsetToIndex[mOffsetToIndex.Length() - 1].mOffset) { + mOffsetToIndex.AppendElement(DGRec(aOffset, detailIndex)); + } else { + mOffsetToIndex.InsertElementSorted(DGRec(aOffset, detailIndex), + CompareRecordOffsets()); + } + return details; + } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) { + return aMallocSizeOf(this) + + mDetails.ShallowSizeOfExcludingThis(aMallocSizeOf) + + mOffsetToIndex.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + private: + struct DGRec { + DGRec(const uint32_t& aOffset, const uint32_t& aIndex) + : mOffset(aOffset), mIndex(aIndex) {} + uint32_t mOffset; // source character offset in the textrun + uint32_t mIndex; // index where this char's DetailedGlyphs begin + }; + + struct CompareToOffset { + bool Equals(const DGRec& a, const uint32_t& b) const { + return a.mOffset == b; + } + bool LessThan(const DGRec& a, const uint32_t& b) const { + return a.mOffset < b; + } + }; + + struct CompareRecordOffsets { + bool Equals(const DGRec& a, const DGRec& b) const { + return a.mOffset == b.mOffset; + } + bool LessThan(const DGRec& a, const DGRec& b) const { + return a.mOffset < b.mOffset; + } + }; + + // Concatenated array of all the DetailedGlyph records needed for the + // textRun; individual character offsets are associated with indexes + // into this array via the mOffsetToIndex table. + nsTArray<DetailedGlyph> mDetails; + + // For each character offset that needs DetailedGlyphs, we record the + // index in mDetails where the list of glyphs begins. This array is + // sorted by mOffset. + nsTArray<DGRec> mOffsetToIndex; + + // Records the most recently used index into mOffsetToIndex, so that + // we can support sequential access more quickly than just doing + // a binary search each time. + nsTArray<DGRec>::index_type mLastUsed = 0; + }; + + mozilla::UniquePtr<DetailedGlyphStore> mDetailedGlyphs; + + // Number of char16_t characters and CompressedGlyph glyph records + uint32_t mLength; + + // Shaping flags (direction, ligature-suppression) + mozilla::gfx::ShapedTextFlags mFlags; + + uint16_t mAppUnitsPerDevUnit; +}; + +/* + * gfxShapedWord: an individual (space-delimited) run of text shaped with a + * particular font, without regard to external context. + * + * The glyph data is copied into gfxTextRuns as needed from the cache of + * ShapedWords associated with each gfxFont instance. + */ +class gfxShapedWord final : public gfxShapedText { + public: + typedef mozilla::intl::Script Script; + + // Create a ShapedWord that can hold glyphs for aLength characters, + // with mCharacterGlyphs sized appropriately. + // + // Returns null on allocation failure (does NOT use infallible alloc) + // so caller must check for success. + // + // This does NOT perform shaping, so the returned word contains no + // glyph data; the caller must call gfxFont::ShapeText() with appropriate + // parameters to set up the glyphs. + static gfxShapedWord* Create(const uint8_t* aText, uint32_t aLength, + Script aRunScript, nsAtom* aLanguage, + uint16_t aAppUnitsPerDevUnit, + mozilla::gfx::ShapedTextFlags aFlags, + gfxFontShaper::RoundingFlags aRounding) { + NS_ASSERTION(aLength <= gfxPlatform::GetPlatform()->WordCacheCharLimit(), + "excessive length for gfxShapedWord!"); + + // Compute size needed including the mCharacterGlyphs array + // and a copy of the original text + uint32_t size = offsetof(gfxShapedWord, mCharGlyphsStorage) + + aLength * (sizeof(CompressedGlyph) + sizeof(uint8_t)); + void* storage = malloc(size); + if (!storage) { + return nullptr; + } + + // Construct in the pre-allocated storage, using placement new + return new (storage) gfxShapedWord(aText, aLength, aRunScript, aLanguage, + aAppUnitsPerDevUnit, aFlags, aRounding); + } + + static gfxShapedWord* Create(const char16_t* aText, uint32_t aLength, + Script aRunScript, nsAtom* aLanguage, + uint16_t aAppUnitsPerDevUnit, + mozilla::gfx::ShapedTextFlags aFlags, + gfxFontShaper::RoundingFlags aRounding) { + NS_ASSERTION(aLength <= gfxPlatform::GetPlatform()->WordCacheCharLimit(), + "excessive length for gfxShapedWord!"); + + // In the 16-bit version of Create, if the TEXT_IS_8BIT flag is set, + // then we convert the text to an 8-bit version and call the 8-bit + // Create function instead. + if (aFlags & mozilla::gfx::ShapedTextFlags::TEXT_IS_8BIT) { + nsAutoCString narrowText; + LossyAppendUTF16toASCII(nsDependentSubstring(aText, aLength), narrowText); + return Create((const uint8_t*)(narrowText.BeginReading()), aLength, + aRunScript, aLanguage, aAppUnitsPerDevUnit, aFlags, + aRounding); + } + + uint32_t size = offsetof(gfxShapedWord, mCharGlyphsStorage) + + aLength * (sizeof(CompressedGlyph) + sizeof(char16_t)); + void* storage = malloc(size); + if (!storage) { + return nullptr; + } + + return new (storage) gfxShapedWord(aText, aLength, aRunScript, aLanguage, + aAppUnitsPerDevUnit, aFlags, aRounding); + } + + // Override operator delete to properly free the object that was + // allocated via malloc. + void operator delete(void* p) { free(p); } + + const CompressedGlyph* GetCharacterGlyphs() const override { + return &mCharGlyphsStorage[0]; + } + CompressedGlyph* GetCharacterGlyphs() override { + return &mCharGlyphsStorage[0]; + } + + const uint8_t* Text8Bit() const { + NS_ASSERTION(TextIs8Bit(), "invalid use of Text8Bit()"); + return reinterpret_cast<const uint8_t*>(mCharGlyphsStorage + GetLength()); + } + + const char16_t* TextUnicode() const { + NS_ASSERTION(!TextIs8Bit(), "invalid use of TextUnicode()"); + return reinterpret_cast<const char16_t*>(mCharGlyphsStorage + GetLength()); + } + + char16_t GetCharAt(uint32_t aOffset) const { + NS_ASSERTION(aOffset < GetLength(), "aOffset out of range"); + return TextIs8Bit() ? char16_t(Text8Bit()[aOffset]) + : TextUnicode()[aOffset]; + } + + Script GetScript() const { return mScript; } + nsAtom* GetLanguage() const { return mLanguage.get(); } + + gfxFontShaper::RoundingFlags GetRounding() const { return mRounding; } + + void ResetAge() { mAgeCounter = 0; } + uint32_t IncrementAge() { return ++mAgeCounter; } + + // Helper used when hashing a word for the shaped-word caches + static uint32_t HashMix(uint32_t aHash, char16_t aCh) { + return (aHash >> 28) ^ (aHash << 4) ^ aCh; + } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + 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)); + SetupClusterBoundaries(0, aText, aLength); + } + + gfxShapedWord(const char16_t* aText, uint32_t aLength, Script aRunScript, + nsAtom* aLanguage, uint16_t aAppUnitsPerDevUnit, + mozilla::gfx::ShapedTextFlags aFlags, + gfxFontShaper::RoundingFlags aRounding) + : gfxShapedText(aLength, aFlags, aAppUnitsPerDevUnit), + mLanguage(aLanguage), + mScript(aRunScript), + mRounding(aRounding), + mAgeCounter(0) { + memset(mCharGlyphsStorage, 0, aLength * sizeof(CompressedGlyph)); + char16_t* text = reinterpret_cast<char16_t*>(&mCharGlyphsStorage[aLength]); + memcpy(text, aText, aLength * sizeof(char16_t)); + SetupClusterBoundaries(0, aText, aLength); + } + + RefPtr<nsAtom> mLanguage; + Script mScript; + + gfxFontShaper::RoundingFlags mRounding; + + // With multithreaded shaping, this may be updated by any thread. + std::atomic<uint32_t> mAgeCounter; + + // The mCharGlyphsStorage array is actually a variable-size member; + // when the ShapedWord is created, its size will be increased as necessary + // to allow the proper number of glyphs to be stored. + // The original text, in either 8-bit or 16-bit form, will be stored + // immediately following the CompressedGlyphs. + CompressedGlyph mCharGlyphsStorage[1]; +}; + +class GlyphBufferAzure; +struct TextRunDrawParams; +struct FontDrawParams; +struct EmphasisMarkDrawParams; + +class gfxFont { + friend class gfxHarfBuzzShaper; + friend class gfxGraphiteShaper; + + protected: + using DrawTarget = mozilla::gfx::DrawTarget; + using Script = mozilla::intl::Script; + using SVGContextPaint = mozilla::SVGContextPaint; + + using RoundingFlags = gfxFontShaper::RoundingFlags; + using ShapedTextFlags = mozilla::gfx::ShapedTextFlags; + + public: + using FontSlantStyle = mozilla::FontSlantStyle; + using FontSizeAdjust = mozilla::StyleFontSizeAdjust; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(gfxFont, MaybeDestroy()) + int32_t GetRefCount() { return int32_t(mRefCnt); } + + // options to specify the kind of AA to be used when creating a font + typedef enum : uint8_t { + kAntialiasDefault, + kAntialiasNone, + kAntialiasGrayscale, + kAntialiasSubpixel + } AntialiasOption; + + protected: + gfxFont(const RefPtr<mozilla::gfx::UnscaledFont>& aUnscaledFont, + gfxFontEntry* aFontEntry, const gfxFontStyle* aFontStyle, + AntialiasOption anAAOption = kAntialiasDefault); + + virtual ~gfxFont(); + + void MaybeDestroy() { + bool destroy = true; + if (gfxFontCache* fc = gfxFontCache::GetCache()) { + destroy = fc->MaybeDestroy(this); + } + if (destroy) { + Destroy(); + } + } + + public: + void Destroy() { + MOZ_ASSERT(GetRefCount() == 0); + delete this; + } + + bool Valid() const { return mIsValid; } + + // options for the kind of bounding box to return from measurement + typedef enum { + LOOSE_INK_EXTENTS, + // A box that encloses all the painted pixels, and may + // include sidebearings and/or additional ascent/descent + // within the glyph cell even if the ink is smaller. + TIGHT_INK_EXTENTS, + // A box that tightly encloses all the painted pixels + // (although actually on Windows, at least, it may be + // slightly larger than strictly necessary because + // we can't get precise extents with ClearType). + TIGHT_HINTED_OUTLINE_EXTENTS + // A box that tightly encloses the glyph outline, + // ignoring possible antialiasing pixels that extend + // beyond this. + // NOTE: The default implementation of gfxFont::Measure(), + // which works with the glyph extents cache, does not + // differentiate between this and TIGHT_INK_EXTENTS. + // Whether the distinction is important depends on the + // antialiasing behavior of the platform; currently the + // distinction is only implemented in the gfxWindowsFont + // subclass, because of ClearType's tendency to paint + // outside the hinted outline. + // Also NOTE: it is relatively expensive to request this, + // as it does not use cached glyph extents in the font. + } BoundingBoxType; + + const nsCString& GetName() const { return mFontEntry->Name(); } + const gfxFontStyle* GetStyle() const { return &mStyle; } + + virtual gfxFont* CopyWithAntialiasOption(AntialiasOption anAAOption) const { + // platforms where this actually matters should override + return nullptr; + } + + gfxFloat GetAdjustedSize() const { + // mAdjustedSize is cached here if not already set to a non-zero value; + // but it may be overridden by a value computed in metrics initialization + // from font-size-adjust. + if (mAdjustedSize < 0.0) { + mAdjustedSize = mStyle.AdjustedSizeMustBeZero() + ? 0.0 + : mStyle.size * mFontEntry->mSizeAdjust; + } + return mAdjustedSize; + } + + float FUnitsToDevUnitsFactor() const { + // check this was set up during font initialization + NS_ASSERTION(mFUnitsConvFactor >= 0.0f, "mFUnitsConvFactor not valid"); + return mFUnitsConvFactor; + } + + // check whether this is an sfnt we can potentially use with harfbuzz + bool FontCanSupportHarfBuzz() const { return mFontEntry->HasCmapTable(); } + + // check whether this is an sfnt we can potentially use with Graphite + bool FontCanSupportGraphite() const { + return mFontEntry->HasGraphiteTables(); + } + + // Whether this is a font that may be doing full-color rendering, + // and therefore needs us to use a mask for text-shadow even when + // we're not actually blurring. + bool AlwaysNeedsMaskForShadow() const { + return mFontEntry->TryGetColorGlyphs() || mFontEntry->TryGetSVGData(this) || + mFontEntry->HasFontTable(TRUETYPE_TAG('C', 'B', 'D', 'T')) || + mFontEntry->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x')); + } + + // whether a feature is supported by the font (limited to a small set + // of features for which some form of fallback needs to be implemented) + bool SupportsFeature(Script aScript, uint32_t aFeatureTag); + + // whether the font supports "real" small caps, petite caps etc. + // aFallbackToSmallCaps true when petite caps should fallback to small caps + bool SupportsVariantCaps(Script aScript, uint32_t aVariantCaps, + bool& aFallbackToSmallCaps, + bool& aSyntheticLowerToSmallCaps, + bool& aSyntheticUpperToSmallCaps); + + // whether the font supports subscript/superscript feature + // for fallback, need to verify that all characters in the run + // have variant substitutions + bool SupportsSubSuperscript(uint32_t aSubSuperscript, const uint8_t* aString, + uint32_t aLength, Script aRunScript); + + bool SupportsSubSuperscript(uint32_t aSubSuperscript, const char16_t* aString, + uint32_t aLength, Script aRunScript); + + // whether the specified feature will apply to the given character + bool FeatureWillHandleChar(Script aRunScript, uint32_t aFeature, + uint32_t aUnicode); + + // Subclasses may choose to look up glyph ids for characters. + // If they do not override this, gfxHarfBuzzShaper will fetch the cmap + // table and use that. + virtual bool ProvidesGetGlyph() const { return false; } + // Map unicode character to glyph ID. + // Only used if ProvidesGetGlyph() returns true. + virtual uint32_t GetGlyph(uint32_t unicode, uint32_t variation_selector) { + return 0; + } + + // Return the advance of a glyph. + gfxFloat GetGlyphAdvance(uint16_t aGID, bool aVertical = false); + + // Return the advance of a given Unicode char in isolation. + // Returns -1.0 if the char is not supported. + gfxFloat GetCharAdvance(uint32_t aUnicode, bool aVertical = false); + + gfxFloat SynthesizeSpaceWidth(uint32_t aCh); + + // Work out whether cairo will snap inter-glyph spacing to pixels + // when rendering to the given drawTarget. + RoundingFlags GetRoundOffsetsToPixels(DrawTarget* aDrawTarget); + + virtual bool ShouldHintMetrics() const { return true; } + virtual bool ShouldRoundXOffset(cairo_t* aCairo) const { return true; } + + // Return the font's owned harfbuzz shaper, creating and initializing it if + // necessary; returns null if shaper initialization has failed. + gfxHarfBuzzShaper* GetHarfBuzzShaper(); + + // Font metrics + struct Metrics { + gfxFloat capHeight; + gfxFloat xHeight; + gfxFloat strikeoutSize; + gfxFloat strikeoutOffset; + gfxFloat underlineSize; + gfxFloat underlineOffset; + + gfxFloat internalLeading; + gfxFloat externalLeading; + + gfxFloat emHeight; + gfxFloat emAscent; + gfxFloat emDescent; + gfxFloat maxHeight; + gfxFloat maxAscent; + gfxFloat maxDescent; + gfxFloat maxAdvance; + + gfxFloat aveCharWidth; + gfxFloat spaceWidth; + gfxFloat zeroWidth; // -1 if there was no zero glyph + gfxFloat ideographicWidth; // -1 if kWaterIdeograph is not supported + + gfxFloat ZeroOrAveCharWidth() const { + return zeroWidth >= 0 ? zeroWidth : aveCharWidth; + } + }; + // Unicode character used as basis for 'ic' unit: + static constexpr uint32_t kWaterIdeograph = 0x6C34; + + typedef nsFontMetrics::FontOrientation Orientation; + + const Metrics& GetMetrics(Orientation aOrientation) { + if (aOrientation == nsFontMetrics::eHorizontal) { + return GetHorizontalMetrics(); + } + if (!mVerticalMetrics) { + CreateVerticalMetrics(); + } + return *mVerticalMetrics; + } + + struct Baselines { + gfxFloat mAlphabetic; + gfxFloat mHanging; + gfxFloat mIdeographic; + }; + Baselines GetBaselines(Orientation aOrientation); + + /** + * We let layout specify spacing on either side of any + * character. We need to specify both before and after + * spacing so that substring measurement can do the right things. + * These values are in appunits. They're always an integral number of + * appunits, but we specify them in floats in case very large spacing + * values are required. + */ + struct Spacing { + gfxFloat mBefore; + gfxFloat mAfter; + }; + /** + * Metrics for a particular string + */ + struct RunMetrics { + RunMetrics() { mAdvanceWidth = mAscent = mDescent = 0.0; } + + void CombineWith(const RunMetrics& aOther, bool aOtherIsOnLeft); + + // can be negative (partly due to negative spacing). + // Advance widths should be additive: the advance width of the + // (offset1, length1) plus the advance width of (offset1 + length1, + // length2) should be the advance width of (offset1, length1 + length2) + gfxFloat mAdvanceWidth; + + // For zero-width substrings, these must be zero! + gfxFloat mAscent; // always non-negative + gfxFloat mDescent; // always non-negative + + // Bounding box that is guaranteed to include everything drawn. + // If a tight boundingBox was requested when these metrics were + // generated, this will tightly wrap the glyphs, otherwise it is + // "loose" and may be larger than the true bounding box. + // Coordinates are relative to the baseline left origin, so typically + // mBoundingBox.y == -mAscent + gfxRect mBoundingBox; + }; + + /** + * Draw a series of glyphs to aContext. The direction of aTextRun must + * be honoured. + * @param aStart the first character to draw + * @param aEnd draw characters up to here + * @param aPt the baseline origin; the left end of the baseline + * for LTR textruns, the right end for RTL textruns. + * On return, this will be updated to the other end of the baseline. + * In application units, really! + * @param aRunParams record with drawing parameters, see TextRunDrawParams. + * Particular fields of interest include + * .spacing spacing to insert before and after characters (for RTL + * glyphs, before-spacing is inserted to the right of characters). There + * are aEnd - aStart elements in this array, unless it's null to indicate + * that there is no spacing. + * .drawMode specifies whether the fill or stroke of the glyph should be + * drawn, or if it should be drawn into the current path + * .contextPaint information about how to construct the fill and + * stroke pattern. Can be nullptr if we are not stroking the text, which + * indicates that the current source from context should be used for fill + * .context the Thebes graphics context to which we're drawing + * .dt Moz2D DrawTarget to which we're drawing + * + * Callers guarantee: + * -- aStart and aEnd are aligned to cluster and ligature boundaries + * -- all glyphs use this font + */ + void Draw(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd, + mozilla::gfx::Point* aPt, TextRunDrawParams& aRunParams, + mozilla::gfx::ShapedTextFlags aOrientation); + + /** + * Draw the emphasis marks for the given text run. Its prerequisite + * and output are similiar to the method Draw(). + * @param aPt the baseline origin of the emphasis marks. + * @param aParams some drawing parameters, see EmphasisMarkDrawParams. + */ + void DrawEmphasisMarks(const gfxTextRun* aShapedText, + mozilla::gfx::Point* aPt, uint32_t aOffset, + uint32_t aCount, + const EmphasisMarkDrawParams& aParams); + + /** + * Measure a run of characters. See gfxTextRun::Metrics. + * @param aTight if false, then return the union of the glyph extents + * with the font-box for the characters (the rectangle with x=0,width= + * the advance width for the character run,y=-(font ascent), and height= + * font ascent + font descent). Otherwise, we must return as tight as possible + * an approximation to the area actually painted by glyphs. + * @param aDrawTargetForTightBoundingBox when aTight is true, this must + * be non-null. + * @param aSpacing spacing to insert before and after glyphs. The bounding box + * need not include the spacing itself, but the spacing affects the glyph + * positions. null if there is no spacing. + * + * Callers guarantee: + * -- aStart and aEnd are aligned to cluster and ligature boundaries + * -- all glyphs use this font + * + * The default implementation just uses font metrics and aTextRun's + * advances, and assumes no characters fall outside the font box. In + * general this is insufficient, because that assumption is not always true. + */ + virtual RunMetrics Measure(const gfxTextRun* aTextRun, uint32_t aStart, + uint32_t aEnd, BoundingBoxType aBoundingBoxType, + DrawTarget* aDrawTargetForTightBoundingBox, + Spacing* aSpacing, + mozilla::gfx::ShapedTextFlags aOrientation); + /** + * Line breaks have been changed at the beginning and/or end of a substring + * of the text. Reshaping may be required; glyph updating is permitted. + * @return true if anything was changed, false otherwise + */ + bool NotifyLineBreaksChanged(gfxTextRun* aTextRun, uint32_t aStart, + uint32_t aLength) { + return false; + } + + // Expiration tracking + nsExpirationState* GetExpirationState() { return &mExpirationState; } + + // Get the glyphID of a space + uint16_t GetSpaceGlyph() const { return mSpaceGlyph; } + + gfxGlyphExtents* GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit); + + void SetupGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphID, + bool aNeedTight, gfxGlyphExtents* aExtents); + + virtual bool AllowSubpixelAA() const { return true; } + + bool ApplySyntheticBold() const { return mApplySyntheticBold; } + + float AngleForSyntheticOblique() const; + float SkewForSyntheticOblique() const; + + // Amount by which synthetic bold "fattens" the glyphs: + // For size S up to a threshold size T, we use (0.25 + 3S / 4T), + // so that the result ranges from 0.25 to 1.0; thereafter, + // simply use (S / T). + gfxFloat GetSyntheticBoldOffset() const { + gfxFloat size = GetAdjustedSize(); + const gfxFloat threshold = 48.0; + return size < threshold ? (0.25 + 0.75 * size / threshold) + : (size / threshold); + } + + gfxFontEntry* GetFontEntry() const { return mFontEntry.get(); } + bool HasCharacter(uint32_t ch) const { + if (!mIsValid || (mUnicodeRangeMap && !mUnicodeRangeMap->test(ch))) { + return false; + } + return mFontEntry->HasCharacter(ch); + } + + const gfxCharacterMap* GetUnicodeRangeMap() const { + return mUnicodeRangeMap.get(); + } + + void SetUnicodeRangeMap(gfxCharacterMap* aUnicodeRangeMap) { + mUnicodeRangeMap = aUnicodeRangeMap; + } + + uint16_t GetUVSGlyph(uint32_t aCh, uint32_t aVS) const { + if (!mIsValid) { + return 0; + } + return mFontEntry->GetUVSGlyph(aCh, aVS); + } + + template <typename T> + bool InitFakeSmallCapsRun(nsPresContext* aPresContext, + DrawTarget* aDrawTarget, gfxTextRun* aTextRun, + const T* aText, uint32_t aOffset, uint32_t aLength, + FontMatchType aMatchType, + mozilla::gfx::ShapedTextFlags aOrientation, + Script aScript, nsAtom* aLanguage, + bool aSyntheticLower, bool aSyntheticUpper); + + // call the (virtual) InitTextRun method to do glyph generation/shaping, + // limiting the length of text passed by processing the run in multiple + // segments if necessary + template <typename T> + bool SplitAndInitTextRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun, + const T* aString, uint32_t aRunStart, + uint32_t aRunLength, Script aRunScript, + nsAtom* aLanguage, + mozilla::gfx::ShapedTextFlags aOrientation); + + // Get a ShapedWord representing a single space for use in setting up a + // gfxTextRun. + bool ProcessSingleSpaceShapedWord( + DrawTarget* aDrawTarget, bool aVertical, int32_t aAppUnitsPerDevUnit, + mozilla::gfx::ShapedTextFlags aFlags, RoundingFlags aRounding, + const std::function<void(gfxShapedWord*)>& aCallback); + + // Called by the gfxFontCache timer to increment the age of all the words, + // so that they'll expire after a sufficient period of non-use. + // Returns true if the cache is now empty, otherwise false. + bool AgeCachedWords(); + + // Discard all cached word records; called on memory-pressure notification. + void ClearCachedWords() { + mozilla::AutoWriteLock lock(mLock); + if (mWordCache) { + ClearCachedWordsLocked(); + } + } + void ClearCachedWordsLocked() MOZ_REQUIRES(mLock) { + MOZ_ASSERT(mWordCache); + mWordCache->clear(); + } + + // Glyph rendering/geometry has changed, so invalidate data as necessary. + void NotifyGlyphsChanged() const; + + virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const; + virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const; + + typedef enum { + FONT_TYPE_DWRITE, + FONT_TYPE_GDI, + FONT_TYPE_FT2, + FONT_TYPE_MAC, + FONT_TYPE_OS2, + FONT_TYPE_CAIRO, + FONT_TYPE_FONTCONFIG + } FontType; + + virtual FontType GetType() const = 0; + + const RefPtr<mozilla::gfx::UnscaledFont>& GetUnscaledFont() const { + return mUnscaledFont; + } + + virtual already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont( + const TextRunDrawParams& aRunParams) = 0; + already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont( + mozilla::gfx::DrawTarget* aDrawTarget); + + // gfxFont implementations may cache ScaledFont versions other than the + // default, so InitializeScaledFont must support explicitly specifying + // which ScaledFonts to initialize. + void InitializeScaledFont( + const RefPtr<mozilla::gfx::ScaledFont>& aScaledFont); + + bool KerningDisabled() const { return mKerningSet && !mKerningEnabled; } + + /** + * Subclass this object to be notified of glyph changes. Delete the object + * when no longer needed. + */ + class GlyphChangeObserver { + public: + virtual ~GlyphChangeObserver() { + if (mFont) { + mFont->RemoveGlyphChangeObserver(this); + } + } + // This gets called when the gfxFont dies. + void ForgetFont() { mFont = nullptr; } + virtual void NotifyGlyphsChanged() = 0; + + protected: + explicit GlyphChangeObserver(gfxFont* aFont) : mFont(aFont) { + mFont->AddGlyphChangeObserver(this); + } + // This pointer is nulled by ForgetFont in the gfxFont's + // destructor. Before the gfxFont dies. + gfxFont* MOZ_NON_OWNING_REF mFont; + }; + friend class GlyphChangeObserver; + + bool GlyphsMayChange() const { + // Currently only fonts with SVG glyphs can have animated glyphs + return mFontEntry->TryGetSVGData(this); + } + + static void DestroySingletons() { + delete sScriptTagToCode; + delete sDefaultFeatures; + } + + // Call TryGetMathTable() to try and load the Open Type MATH table. + // If (and ONLY if) TryGetMathTable() has returned true, the MathTable() + // method may be called to access the gfxMathTable data. + bool TryGetMathTable(); + gfxMathTable* MathTable() const { + MOZ_RELEASE_ASSERT(mMathTable, + "A successful call to TryGetMathTable() must be " + "performed before calling this function"); + return mMathTable; + } + + // Return a cloned font resized and offset to simulate sub/superscript + // glyphs. This does not add a reference to the returned font. + already_AddRefed<gfxFont> GetSubSuperscriptFont( + int32_t aAppUnitsPerDevPixel) const; + + bool HasColorGlyphFor(uint32_t aCh, uint32_t aNextCh); + + protected: + virtual const Metrics& GetHorizontalMetrics() const = 0; + + void CreateVerticalMetrics(); + + bool MeasureGlyphs(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd, + BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, Spacing* aSpacing, + gfxGlyphExtents* aExtents, bool aIsRTL, + bool aNeedsGlyphExtents, RunMetrics& aMetrics, + gfxFloat* aAdvanceMin, gfxFloat* aAdvanceMax); + + bool MeasureGlyphs(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd, + BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, Spacing* aSpacing, bool aIsRTL, + RunMetrics& aMetrics); + + // Template parameters for DrawGlyphs/DrawOneGlyph, used to select + // simplified versions of the methods in the most common cases. + enum class FontComplexityT { SimpleFont, ComplexFont }; + enum class SpacingT { NoSpacing, HasSpacing }; + + // Output a run of glyphs at *aPt, which is updated to follow the last glyph + // in the run. This method also takes account of any letter-spacing provided + // in aRunParams. + template <FontComplexityT FC, SpacingT S> + bool DrawGlyphs(const gfxShapedText* aShapedText, + uint32_t aOffset, // offset in the textrun + uint32_t aCount, // length of run to draw + mozilla::gfx::Point* aPt, + // transform for mOffset field in DetailedGlyph records, + // to account for rotations (may be null) + const mozilla::gfx::Matrix* aOffsetMatrix, + GlyphBufferAzure& aBuffer); + + // Output a single glyph at *aPt. + // Normal glyphs are simply accumulated in aBuffer until it is full and + // gets flushed, but SVG or color-font glyphs will instead be rendered + // directly to the destination (found from the buffer's parameters). + template <FontComplexityT FC> + void DrawOneGlyph(uint32_t aGlyphID, const mozilla::gfx::Point& aPt, + GlyphBufferAzure& aBuffer, bool* aEmittedGlyphs); + + // Helper for DrawOneGlyph to handle missing glyphs, rendering either + // nothing (for default-ignorables) or a missing-glyph hexbox. + bool DrawMissingGlyph(const TextRunDrawParams& aRunParams, + const FontDrawParams& aFontParams, + const gfxShapedText::DetailedGlyph* aDetails, + const mozilla::gfx::Point& aPt); + + // set the font size and offset used for + // synthetic subscript/superscript glyphs + void CalculateSubSuperSizeAndOffset(int32_t aAppUnitsPerDevPixel, + gfxFloat& aSubSuperSizeRatio, + float& aBaselineOffset); + + // Return a font that is a "clone" of this one, but reduced to 80% size + // (and with variantCaps set to normal). This does not add a reference to + // the returned font. + already_AddRefed<gfxFont> GetSmallCapsFont() const; + + // subclasses may provide (possibly hinted) glyph widths (in font units); + // if they do not override this, harfbuzz will use unhinted widths + // derived from the font tables + virtual bool ProvidesGlyphWidths() const { return false; } + + // The return value is interpreted as a horizontal advance in 16.16 fixed + // point format. + virtual int32_t GetGlyphWidth(uint16_t aGID) { return -1; } + + virtual bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, + bool aTight = false) { + return false; + } + + bool IsSpaceGlyphInvisible(DrawTarget* aRefDrawTarget, + const gfxTextRun* aTextRun); + + void AddGlyphChangeObserver(GlyphChangeObserver* aObserver); + void RemoveGlyphChangeObserver(GlyphChangeObserver* aObserver); + + // whether font contains substitution lookups containing spaces + bool HasSubstitutionRulesWithSpaceLookups(Script aRunScript) const; + + // do spaces participate in shaping rules? if so, can't used word cache + // Note that this function uses HasGraphiteSpaceContextuals, so it can only + // return a "hint" to the correct answer. The calling code must ensure it + // performs safe actions independent of the value returned. + tainted_boolean_hint SpaceMayParticipateInShaping(Script aRunScript) const; + + // For 8-bit text, expand to 16-bit and then call the following method. + bool ShapeText(DrawTarget* aContext, const uint8_t* aText, + uint32_t aOffset, // dest offset in gfxShapedText + uint32_t aLength, Script aScript, nsAtom* aLanguage, + bool aVertical, RoundingFlags aRounding, + gfxShapedText* aShapedText); // where to store the result + + // Call the appropriate shaper to generate glyphs for aText and store + // them into aShapedText. + virtual bool ShapeText(DrawTarget* aContext, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, + RoundingFlags aRounding, gfxShapedText* aShapedText); + + // Helper to adjust for synthetic bold and set character-type flags + // in the shaped text; implementations of ShapeText should call this + // after glyph shaping has been completed. + void PostShapingFixup(DrawTarget* aContext, const char16_t* aText, + uint32_t aOffset, // position within aShapedText + uint32_t aLength, bool aVertical, + gfxShapedText* aShapedText); + + // Shape text directly into a range within a textrun, without using the + // font's word cache. Intended for use when the font has layout features + // that involve space, and therefore require shaping complete runs rather + // than isolated words, or for long strings that are inefficient to cache. + // This will split the text on "invalid" characters (tab/newline) that are + // not handled via normal shaping, but does not otherwise divide up the + // text. + template <typename T> + bool ShapeTextWithoutWordCache(DrawTarget* aDrawTarget, const T* aText, + uint32_t aOffset, uint32_t aLength, + Script aScript, nsAtom* aLanguage, + bool aVertical, RoundingFlags aRounding, + gfxTextRun* aTextRun); + + // Shape a fragment of text (a run that is known to contain only + // "valid" characters, no newlines/tabs/other control chars). + // All non-wordcache shaping goes through here; this is the function + // that will ensure we don't pass excessively long runs to the various + // platform shapers. + template <typename T> + bool ShapeFragmentWithoutWordCache(DrawTarget* aDrawTarget, const T* aText, + uint32_t aOffset, uint32_t aLength, + Script aScript, nsAtom* aLanguage, + bool aVertical, RoundingFlags aRounding, + gfxTextRun* aTextRun); + + void CheckForFeaturesInvolvingSpace() const; + + // Get a ShapedWord representing the given text (either 8- or 16-bit) + // for use in setting up a gfxTextRun. + template <typename T, typename Func> + bool ProcessShapedWordInternal(DrawTarget* aDrawTarget, const T* aText, + uint32_t aLength, uint32_t aHash, + Script aRunScript, nsAtom* aLanguage, + bool aVertical, int32_t aAppUnitsPerDevUnit, + mozilla::gfx::ShapedTextFlags aFlags, + RoundingFlags aRounding, + gfxTextPerfMetrics* aTextPerf, Func aCallback); + + // whether a given feature is included in feature settings from both the + // font and the style. aFeatureOn set if resolved feature value is non-zero + bool HasFeatureSet(uint32_t aFeature, bool& aFeatureOn); + + // used when analyzing whether a font has space contextual lookups + static mozilla::Atomic<nsTHashMap<nsUint32HashKey, Script>*> sScriptTagToCode; + static mozilla::Atomic<nsTHashSet<uint32_t>*> sDefaultFeatures; + + RefPtr<gfxFontEntry> mFontEntry; + mutable mozilla::RWLock mLock; + + // Note that WordCacheKey contains a pointer to the text of the word, which + // must be valid for as long as the key is in use. When using for a Lookup, + // the string may be local/temporary, but when storing in the HashMap, we + // set the Key text pointer to reference the text in the associated + // gfxShapedWord that is being stored. + struct WordCacheKey { + union { + const uint8_t* mSingle; + const char16_t* mDouble; + } mText; + uint32_t mLength; + ShapedTextFlags mFlags; + Script mScript; + RefPtr<nsAtom> mLanguage; + int32_t mAppUnitsPerDevUnit; + PLDHashNumber mHashKey; + bool mTextIs8Bit; + RoundingFlags mRounding; + + WordCacheKey(const uint8_t* aText, uint32_t aLength, uint32_t aStringHash, + Script aScriptCode, nsAtom* aLanguage, + int32_t aAppUnitsPerDevUnit, 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 & ShapedTextFlags::TEXT_IS_8BIT, + "8-bit flag should have been set"); + mText.mSingle = aText; + } + + WordCacheKey(const char16_t* aText, uint32_t aLength, uint32_t aStringHash, + Script aScriptCode, nsAtom* aLanguage, + int32_t aAppUnitsPerDevUnit, 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; + } + + bool Matches(const WordCacheKey& aLookup) const; + + class HashPolicy { + public: + typedef WordCacheKey Key; + typedef WordCacheKey Lookup; + static mozilla::HashNumber hash(const Lookup& aLookup) { + return aLookup.mHashKey; + } + static bool match(const Key& aKey, const Lookup& aLookup); + }; + }; + + mozilla::UniquePtr< + mozilla::HashMap<WordCacheKey, mozilla::UniquePtr<gfxShapedWord>, + WordCacheKey::HashPolicy>> + mWordCache MOZ_GUARDED_BY(mLock); + + static const uint32_t kShapedWordCacheMaxAge = 3; + + nsTArray<mozilla::UniquePtr<gfxGlyphExtents>> mGlyphExtentsArray + MOZ_GUARDED_BY(mLock); + mozilla::UniquePtr<nsTHashSet<GlyphChangeObserver*>> mGlyphChangeObservers + MOZ_GUARDED_BY(mLock); + + // a copy of the font without antialiasing, if needed for separate + // measurement by mathml code + mozilla::Atomic<gfxFont*> mNonAAFont; + + // we create either or both of these shapers when needed, depending + // whether the font has graphite tables, and whether graphite shaping + // is actually enabled + mozilla::Atomic<gfxHarfBuzzShaper*> mHarfBuzzShaper; + mozilla::Atomic<gfxGraphiteShaper*> mGraphiteShaper; + + // If a userfont with unicode-range specified, contains map of *possible* + // ranges supported by font. This is set during user-font initialization, + // before the font is available to other threads, and thereafter is inert + // so no guard is needed. + RefPtr<gfxCharacterMap> mUnicodeRangeMap; + + // This is immutable once initialized by the constructor, so does not need + // locking. + RefPtr<mozilla::gfx::UnscaledFont> mUnscaledFont; + + mozilla::Atomic<mozilla::gfx::ScaledFont*> mAzureScaledFont; + + // For vertical metrics, created on demand. + mozilla::Atomic<Metrics*> mVerticalMetrics; + + // Table used for MathML layout. + mozilla::Atomic<gfxMathTable*> mMathTable; + + gfxFontStyle mStyle; + mutable gfxFloat mAdjustedSize; + + // Tracking adjustment to be applied for CSS px size mCachedTrackingSize. + gfxFloat mTracking = 0.0; + gfxFloat mCachedTrackingSize = -1.0; + + // Conversion factor from font units to dev units; note that this may be + // zero (in the degenerate case where mAdjustedSize has become zero). + // This is OK because we only multiply by this factor, never divide. + float mFUnitsConvFactor; + + // This is guarded by gfxFontCache::GetCache()->GetMutex() but it is difficult + // to annotate that fact. + nsExpirationState mExpirationState; + + // Glyph ID of the font's <space> glyph, zero if missing + uint16_t mSpaceGlyph = 0; + + // the AA setting requested for this font - may affect glyph bounds + AntialiasOption mAntialiasOption; + + bool mIsValid; + + // use synthetic bolding for environments where this is not supported + // by the platform + bool mApplySyntheticBold; + + bool mKerningSet; // kerning explicitly set? + bool mKerningEnabled; // if set, on or off? + + mozilla::Atomic<bool> mMathInitialized; // TryGetMathTable() called? + + // Helper for subclasses that want to initialize standard metrics from the + // tables of sfnt (TrueType/OpenType) fonts. + // This will use mFUnitsConvFactor if it is already set, else compute it + // from mAdjustedSize and the unitsPerEm in the font's 'head' table. + // Returns TRUE and sets mIsValid=TRUE if successful; + // Returns TRUE but leaves mIsValid=FALSE if the font seems to be broken. + // Returns FALSE if the font does not appear to be an sfnt at all, + // and should be handled (if possible) using other APIs. + bool InitMetricsFromSfntTables(Metrics& aMetrics); + + // Helper to calculate various derived metrics from the results of + // InitMetricsFromSfntTables or equivalent platform code + void CalculateDerivedMetrics(Metrics& aMetrics); + + // some fonts have bad metrics, this method sanitize them. + // if this font has bad underline offset, aIsBadUnderlineFont should be true. + void SanitizeMetrics(Metrics* aMetrics, bool aIsBadUnderlineFont); + + bool RenderSVGGlyph(gfxContext* aContext, + mozilla::layout::TextDrawTarget* aTextDrawer, + mozilla::gfx::Point aPoint, uint32_t aGlyphId, + SVGContextPaint* aContextPaint) const; + bool RenderSVGGlyph(gfxContext* aContext, + mozilla::layout::TextDrawTarget* aTextDrawer, + mozilla::gfx::Point aPoint, uint32_t aGlyphId, + SVGContextPaint* aContextPaint, + gfxTextRunDrawCallbacks* aCallbacks, + bool& aEmittedGlyphs) const; + + bool RenderColorGlyph(DrawTarget* aDrawTarget, gfxContext* aContext, + mozilla::layout::TextDrawTarget* aTextDrawer, + const FontDrawParams& aFontParams, + const mozilla::gfx::Point& aPoint, uint32_t aGlyphId); + + class ColorGlyphCache { + public: + ColorGlyphCache() = default; + ~ColorGlyphCache() = default; + + void SetColors(mozilla::gfx::sRGBColor aCurrentColor, + mozilla::gfx::FontPalette* aPalette); + + mozilla::HashMap<uint32_t, RefPtr<mozilla::gfx::SourceSurface>> mCache; + + private: + mozilla::gfx::sRGBColor mCurrentColor; + RefPtr<mozilla::gfx::FontPalette> mPalette; + }; + mozilla::UniquePtr<ColorGlyphCache> mColorGlyphCache; + + // Subclasses can override to return true if the platform is able to render + // COLR-font glyphs directly, instead of us painting the layers explicitly. + // (Currently used only for COLR.v0 fonts on macOS.) + virtual bool UseNativeColrFontSupport() const { return false; } + + // Bug 674909. When synthetic bolding text by drawing twice, need to + // render using a pixel offset in device pixels, otherwise text + // doesn't appear bolded, it appears as if a bad text shadow exists + // when a non-identity transform exists. Use an offset factor so that + // the second draw occurs at a constant offset in device pixels. + // This helper calculates the scale factor we need to apply to the + // synthetic-bold offset. + static mozilla::gfx::Float CalcXScale(DrawTarget* aDrawTarget); +}; + +// proportion of ascent used for x-height, if unable to read value from font +#define DEFAULT_XHEIGHT_FACTOR 0.56f + +// Parameters passed to gfxFont methods for drawing glyphs from a textrun. +// The TextRunDrawParams are set up once per textrun; the FontDrawParams +// are dependent on the specific font, so they are set per GlyphRun. + +struct MOZ_STACK_CLASS TextRunDrawParams { + explicit TextRunDrawParams(mozilla::gfx::PaletteCache& aPaletteCache) + : paletteCache(aPaletteCache) {} + + mozilla::gfx::PaletteCache& paletteCache; + RefPtr<mozilla::gfx::DrawTarget> dt; + gfxContext* context = nullptr; + gfxFont::Spacing* spacing = nullptr; + gfxTextRunDrawCallbacks* callbacks = nullptr; + mozilla::SVGContextPaint* runContextPaint = nullptr; + mozilla::layout::TextDrawTarget* textDrawer = nullptr; + mozilla::LayoutDeviceRect clipRect; + mozilla::gfx::Float direction = 1.0f; + double devPerApp = 1.0; + nscolor textStrokeColor = 0; + gfxPattern* textStrokePattern = nullptr; + const mozilla::gfx::StrokeOptions* strokeOpts = nullptr; + const mozilla::gfx::DrawOptions* drawOpts = nullptr; + nsAtom* fontPalette = nullptr; + DrawMode drawMode = DrawMode::GLYPH_FILL; + bool isVerticalRun = false; + bool isRTL = false; + bool paintSVGGlyphs = true; + bool allowGDI = true; + bool hasTextShadow = false; +}; + +struct MOZ_STACK_CLASS FontDrawParams { + RefPtr<mozilla::gfx::ScaledFont> scaledFont; + mozilla::SVGContextPaint* contextPaint; + mozilla::gfx::Float synBoldOnePixelOffset; + mozilla::gfx::Float obliqueSkew; + int32_t extraStrikes; + mozilla::gfx::DrawOptions drawOptions; + gfxFloat advanceDirection; + mozilla::gfx::sRGBColor currentColor; + RefPtr<mozilla::gfx::FontPalette> palette; + mozilla::gfx::Rect fontExtents; + bool isVerticalFont; + bool haveSVGGlyphs; + bool haveColorGlyphs; + bool hasTextShadow; // whether we're rendering with a text-shadow +}; + +struct MOZ_STACK_CLASS EmphasisMarkDrawParams { + EmphasisMarkDrawParams(gfxContext* aContext, + mozilla::gfx::PaletteCache& aPaletteCache) + : context(aContext), paletteCache(aPaletteCache) {} + gfxContext* context; + mozilla::gfx::PaletteCache& paletteCache; + gfxFont::Spacing* spacing; + gfxTextRun* mark; + gfxFloat advance; + gfxFloat direction; + bool isVertical; +}; + +#endif diff --git a/gfx/thebes/gfxFontConstants.h b/gfx/thebes/gfxFontConstants.h new file mode 100644 index 0000000000..f41af73aad --- /dev/null +++ b/gfx/thebes/gfxFontConstants.h @@ -0,0 +1,185 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* font constants shared by both thebes and layout */ + +#ifndef GFX_FONT_CONSTANTS_H +#define GFX_FONT_CONSTANTS_H + +/* + * This file is separate from gfxFont.h so that layout can include it + * without bringing in gfxFont.h and everything it includes. + */ + +#define NS_FONT_STYLE_NORMAL 0 +#define NS_FONT_STYLE_ITALIC 1 +#define NS_FONT_STYLE_OBLIQUE 2 + +#define NS_FONT_WEIGHT_NORMAL 400 +#define NS_FONT_WEIGHT_BOLD 700 +#define NS_FONT_WEIGHT_THIN 100 + +#define NS_FONT_STRETCH_ULTRA_CONDENSED 50 +#define NS_FONT_STRETCH_EXTRA_CONDENSED 62 +#define NS_FONT_STRETCH_CONDENSED 75 +#define NS_FONT_STRETCH_SEMI_CONDENSED 87 +#define NS_FONT_STRETCH_NORMAL 100 +#define NS_FONT_STRETCH_SEMI_EXPANDED 112 +#define NS_FONT_STRETCH_EXPANDED 125 +#define NS_FONT_STRETCH_EXTRA_EXPANDED 150 +#define NS_FONT_STRETCH_ULTRA_EXPANDED 200 + +#define NS_FONT_SMOOTHING_AUTO 0 +#define NS_FONT_SMOOTHING_GRAYSCALE 1 + +#define NS_FONT_KERNING_AUTO 0 +#define NS_FONT_KERNING_NONE 1 +#define NS_FONT_KERNING_NORMAL 2 + +#define NS_FONT_OPTICAL_SIZING_AUTO 0 +#define NS_FONT_OPTICAL_SIZING_NONE 1 + +#define NS_FONT_VARIANT_ALTERNATES_NORMAL 0 +// alternates - simple enumerated values +#define NS_FONT_VARIANT_ALTERNATES_HISTORICAL (1 << 0) + +// alternates - values that use functional syntax +#define NS_FONT_VARIANT_ALTERNATES_STYLISTIC (1 << 1) +#define NS_FONT_VARIANT_ALTERNATES_STYLESET (1 << 2) +#define NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT (1 << 3) +#define NS_FONT_VARIANT_ALTERNATES_SWASH (1 << 4) +#define NS_FONT_VARIANT_ALTERNATES_ORNAMENTS (1 << 5) +#define NS_FONT_VARIANT_ALTERNATES_ANNOTATION (1 << 6) +#define NS_FONT_VARIANT_ALTERNATES_COUNT 7 + +#define NS_FONT_VARIANT_ALTERNATES_ENUMERATED_MASK \ + NS_FONT_VARIANT_ALTERNATES_HISTORICAL + +#define NS_FONT_VARIANT_ALTERNATES_FUNCTIONAL_MASK \ + (NS_FONT_VARIANT_ALTERNATES_STYLISTIC | \ + NS_FONT_VARIANT_ALTERNATES_STYLESET | \ + NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT | \ + NS_FONT_VARIANT_ALTERNATES_SWASH | NS_FONT_VARIANT_ALTERNATES_ORNAMENTS | \ + NS_FONT_VARIANT_ALTERNATES_ANNOTATION) + +#define NS_FONT_VARIANT_CAPS_NORMAL 0 +#define NS_FONT_VARIANT_CAPS_SMALLCAPS 1 +#define NS_FONT_VARIANT_CAPS_ALLSMALL 2 +#define NS_FONT_VARIANT_CAPS_PETITECAPS 3 +#define NS_FONT_VARIANT_CAPS_ALLPETITE 4 +#define NS_FONT_VARIANT_CAPS_TITLING 5 +#define NS_FONT_VARIANT_CAPS_UNICASE 6 + +#define NS_FONT_VARIANT_EAST_ASIAN_NORMAL 0 +#define NS_FONT_VARIANT_EAST_ASIAN_JIS78 (1 << 0) +#define NS_FONT_VARIANT_EAST_ASIAN_JIS83 (1 << 1) +#define NS_FONT_VARIANT_EAST_ASIAN_JIS90 (1 << 2) +#define NS_FONT_VARIANT_EAST_ASIAN_JIS04 (1 << 3) +#define NS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED (1 << 4) +#define NS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL (1 << 5) +#define NS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH (1 << 6) +#define NS_FONT_VARIANT_EAST_ASIAN_PROP_WIDTH (1 << 7) +#define NS_FONT_VARIANT_EAST_ASIAN_RUBY (1 << 8) +#define NS_FONT_VARIANT_EAST_ASIAN_COUNT 9 + +#define NS_FONT_VARIANT_EAST_ASIAN_VARIANT_MASK \ + (NS_FONT_VARIANT_EAST_ASIAN_JIS78 | NS_FONT_VARIANT_EAST_ASIAN_JIS83 | \ + NS_FONT_VARIANT_EAST_ASIAN_JIS90 | NS_FONT_VARIANT_EAST_ASIAN_JIS04 | \ + NS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED | \ + NS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL) + +#define NS_FONT_VARIANT_EAST_ASIAN_WIDTH_MASK \ + (NS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH | \ + NS_FONT_VARIANT_EAST_ASIAN_PROP_WIDTH) + +#define NS_FONT_VARIANT_LIGATURES_NORMAL 0 +#define NS_FONT_VARIANT_LIGATURES_NONE (1 << 0) +#define NS_FONT_VARIANT_LIGATURES_COMMON (1 << 1) +#define NS_FONT_VARIANT_LIGATURES_NO_COMMON (1 << 2) +#define NS_FONT_VARIANT_LIGATURES_DISCRETIONARY (1 << 3) +#define NS_FONT_VARIANT_LIGATURES_NO_DISCRETIONARY (1 << 4) +#define NS_FONT_VARIANT_LIGATURES_HISTORICAL (1 << 5) +#define NS_FONT_VARIANT_LIGATURES_NO_HISTORICAL (1 << 6) +#define NS_FONT_VARIANT_LIGATURES_CONTEXTUAL (1 << 7) +#define NS_FONT_VARIANT_LIGATURES_NO_CONTEXTUAL (1 << 8) +#define NS_FONT_VARIANT_LIGATURES_COUNT 9 + +#define NS_FONT_VARIANT_LIGATURES_COMMON_MASK \ + (NS_FONT_VARIANT_LIGATURES_COMMON | NS_FONT_VARIANT_LIGATURES_NO_COMMON) + +#define NS_FONT_VARIANT_LIGATURES_DISCRETIONARY_MASK \ + (NS_FONT_VARIANT_LIGATURES_DISCRETIONARY | \ + NS_FONT_VARIANT_LIGATURES_NO_DISCRETIONARY) + +#define NS_FONT_VARIANT_LIGATURES_HISTORICAL_MASK \ + (NS_FONT_VARIANT_LIGATURES_HISTORICAL | \ + NS_FONT_VARIANT_LIGATURES_NO_HISTORICAL) + +#define NS_FONT_VARIANT_LIGATURES_CONTEXTUAL_MASK \ + NS_FONT_VARIANT_LIGATURES_CONTEXTUAL | NS_FONT_VARIANT_LIGATURES_NO_CONTEXTUAL + +#define NS_FONT_VARIANT_NUMERIC_NORMAL 0 +#define NS_FONT_VARIANT_NUMERIC_LINING (1 << 0) +#define NS_FONT_VARIANT_NUMERIC_OLDSTYLE (1 << 1) +#define NS_FONT_VARIANT_NUMERIC_PROPORTIONAL (1 << 2) +#define NS_FONT_VARIANT_NUMERIC_TABULAR (1 << 3) +#define NS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS (1 << 4) +#define NS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS (1 << 5) +#define NS_FONT_VARIANT_NUMERIC_SLASHZERO (1 << 6) +#define NS_FONT_VARIANT_NUMERIC_ORDINAL (1 << 7) +#define NS_FONT_VARIANT_NUMERIC_COUNT 8 + +#define NS_FONT_VARIANT_NUMERIC_FIGURE_MASK \ + NS_FONT_VARIANT_NUMERIC_LINING | NS_FONT_VARIANT_NUMERIC_OLDSTYLE + +#define NS_FONT_VARIANT_NUMERIC_SPACING_MASK \ + NS_FONT_VARIANT_NUMERIC_PROPORTIONAL | NS_FONT_VARIANT_NUMERIC_TABULAR + +#define NS_FONT_VARIANT_NUMERIC_FRACTION_MASK \ + NS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS | \ + NS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS + +#define NS_FONT_VARIANT_POSITION_NORMAL 0 +#define NS_FONT_VARIANT_POSITION_SUPER 1 +#define NS_FONT_VARIANT_POSITION_SUB 2 + +#define NS_FONT_VARIANT_WIDTH_NORMAL 0 +#define NS_FONT_VARIANT_WIDTH_FULL 1 +#define NS_FONT_VARIANT_WIDTH_HALF 2 +#define NS_FONT_VARIANT_WIDTH_THIRD 3 +#define NS_FONT_VARIANT_WIDTH_QUARTER 4 + +enum class StyleFontVariantEmoji : uint8_t { Normal, Text, Emoji, Unicode }; + +// based on fixed offset values used within WebKit +#define NS_FONT_SUBSCRIPT_OFFSET_RATIO (0.20) +#define NS_FONT_SUPERSCRIPT_OFFSET_RATIO (0.34) + +// this roughly corresponds to font-size: smaller behavior +// at smaller sizes <20px the ratio is closer to 0.8 while at +// larger sizes >45px the ratio is closer to 0.667 and in between +// a blend of values is used +#define NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL (0.82) +#define NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE (0.667) +#define NS_FONT_SUB_SUPER_SMALL_SIZE (20.0) +#define NS_FONT_SUB_SUPER_LARGE_SIZE (45.0) + +// pref lang id's for font prefs +enum eFontPrefLang { +#define FONT_PREF_LANG(enum_id_, str_, atom_id_) eFontPrefLang_##enum_id_ +#include "gfxFontPrefLangList.h" +#undef FONT_PREF_LANG + + , + eFontPrefLang_CJKSet // special code for CJK set + , + eFontPrefLang_Emoji // special code for emoji presentation + , + eFontPrefLang_First = eFontPrefLang_Western, + eFontPrefLang_Last = eFontPrefLang_Others, + eFontPrefLang_Count = (eFontPrefLang_Last - eFontPrefLang_First + 1) +}; + +#endif diff --git a/gfx/thebes/gfxFontEntry.cpp b/gfx/thebes/gfxFontEntry.cpp new file mode 100644 index 0000000000..a9fe04125c --- /dev/null +++ b/gfx/thebes/gfxFontEntry.cpp @@ -0,0 +1,2201 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxFontEntry.h" + +#include "mozilla/DebugOnly.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/MathAlgorithms.h" + +#include "mozilla/Logging.h" + +#include "gfxTextRun.h" +#include "gfxPlatform.h" +#include "nsGkAtoms.h" + +#include "gfxTypes.h" +#include "gfxContext.h" +#include "gfxFontConstants.h" +#include "gfxGraphiteShaper.h" +#include "gfxHarfBuzzShaper.h" +#include "gfxUserFontSet.h" +#include "gfxPlatformFontList.h" +#include "nsUnicodeProperties.h" +#include "nsMathUtils.h" +#include "nsBidiUtils.h" +#include "nsStyleConsts.h" +#include "mozilla/AppUnits.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/Likely.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/Telemetry.h" +#include "gfxSVGGlyphs.h" +#include "gfx2DGlue.h" + +#include "harfbuzz/hb.h" +#include "harfbuzz/hb-ot.h" +#include "graphite2/Font.h" + +#include "ThebesRLBox.h" + +#include <algorithm> + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::unicode; + +void gfxCharacterMap::NotifyMaybeReleased(gfxCharacterMap* aCmap) { + // Tell gfxPlatformFontList that a charmap's refcount was decremented, + // so it should check whether the object is to be deleted. + gfxPlatformFontList::PlatformFontList()->MaybeRemoveCmap(aCmap); +} + +gfxFontEntry::gfxFontEntry(const nsACString& aName, bool aIsStandardFace) + : mName(aName), + mLock("gfxFontEntry lock"), + mFeatureInfoLock("gfxFontEntry featureInfo mutex"), + mFixedPitch(false), + mIsBadUnderlineFont(false), + mIsUserFontContainer(false), + mIsDataUserFont(false), + mIsLocalUserFont(false), + mStandardFace(aIsStandardFace), + mIgnoreGDEF(false), + mIgnoreGSUB(false), + mSkipDefaultFeatureSpaceCheck(false), + mSVGInitialized(false), + mHasCmapTable(false), + mGrFaceInitialized(false), + mCheckedForColorGlyph(false), + mCheckedForVariationAxes(false), + mSpaceGlyphIsInvisible(LazyFlag::Uninitialized), + mHasGraphiteTables(LazyFlag::Uninitialized), + mHasGraphiteSpaceContextuals(LazyFlag::Uninitialized), + mHasColorBitmapTable(LazyFlag::Uninitialized), + mHasSpaceFeatures(SpaceFeatures::Uninitialized) { + mTrakTable.exchange(kTrakTableUninitialized); + memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures)); + memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures)); +} + +gfxFontEntry::~gfxFontEntry() { + // Should not be dropped by stylo + MOZ_ASSERT(!gfxFontUtils::IsInServoTraversal()); + + hb_blob_destroy(mCOLR.exchange(nullptr)); + hb_blob_destroy(mCPAL.exchange(nullptr)); + + if (TrakTableInitialized()) { + // Only if it was initialized, so that we don't try to call hb_blob_destroy + // on the kTrakTableUninitialized flag value! + hb_blob_destroy(mTrakTable.exchange(nullptr)); + } + + // For downloaded fonts, we need to tell the user font cache that this + // entry is being deleted. + if (mIsDataUserFont) { + gfxUserFontSet::UserFontCache::ForgetFont(this); + } + + if (mFeatureInputs) { + for (auto iter = mFeatureInputs->Iter(); !iter.Done(); iter.Next()) { + hb_set_t*& set = iter.Data(); + hb_set_destroy(set); + } + } + + delete mFontTableCache.exchange(nullptr); + delete mSVGGlyphs.exchange(nullptr); + delete[] mUVSData.exchange(nullptr); + + gfxCharacterMap* cmap = mCharacterMap.exchange(nullptr); + NS_IF_RELEASE(cmap); + + // By the time the entry is destroyed, all font instances that were + // using it should already have been deleted, and so the HB and/or Gr + // face objects should have been released. + MOZ_ASSERT(!mHBFace); + MOZ_ASSERT(!mGrFaceInitialized); +} + +// Only used during initialization, before any other thread has a chance to see +// the entry, so locking not required. +void gfxFontEntry::InitializeFrom(fontlist::Face* aFace, + const fontlist::Family* aFamily) { + mShmemFace = aFace; + mShmemFamily = aFamily; + mStyleRange = aFace->mStyle; + mWeightRange = aFace->mWeight; + mStretchRange = aFace->mStretch; + mFixedPitch = aFace->mFixedPitch; + mIsBadUnderlineFont = aFamily->IsBadUnderlineFamily(); + auto* list = gfxPlatformFontList::PlatformFontList()->SharedFontList(); + mFamilyName = aFamily->DisplayName().AsString(list); + mHasCmapTable = TrySetShmemCharacterMap(); +} + +bool gfxFontEntry::TrySetShmemCharacterMap() { + MOZ_ASSERT(mShmemFace); + auto list = gfxPlatformFontList::PlatformFontList()->SharedFontList(); + auto* shmemCmap = mShmemFace->mCharacterMap.ToPtr<const SharedBitSet>(list); + mShmemCharacterMap.exchange(shmemCmap); + return shmemCmap != nullptr; +} + +bool gfxFontEntry::TestCharacterMap(uint32_t aCh) { + if (!mCharacterMap && !mShmemCharacterMap) { + ReadCMAP(); + MOZ_ASSERT(mCharacterMap || mShmemCharacterMap, + "failed to initialize character map"); + } + return mShmemCharacterMap ? GetShmemCharacterMap()->test(aCh) + : GetCharacterMap()->test(aCh); +} + +void gfxFontEntry::EnsureUVSMapInitialized() { + // mUVSOffset will not be initialized + // until cmap is initialized. + if (!mCharacterMap && !mShmemCharacterMap) { + ReadCMAP(); + NS_ASSERTION(mCharacterMap || mShmemCharacterMap, + "failed to initialize character map"); + } + + if (!mUVSOffset) { + return; + } + + if (!mUVSData) { + nsresult rv = NS_ERROR_NOT_AVAILABLE; + const uint32_t kCmapTag = TRUETYPE_TAG('c', 'm', 'a', 'p'); + AutoTable cmapTable(this, kCmapTag); + if (cmapTable) { + const uint8_t* uvsData = nullptr; + unsigned int cmapLen; + const char* cmapData = hb_blob_get_data(cmapTable, &cmapLen); + rv = gfxFontUtils::ReadCMAPTableFormat14( + (const uint8_t*)cmapData + mUVSOffset, cmapLen - mUVSOffset, uvsData); + if (NS_SUCCEEDED(rv)) { + if (!mUVSData.compareExchange(nullptr, uvsData)) { + delete uvsData; + } + } + } + if (NS_FAILED(rv)) { + mUVSOffset = 0; // don't try to read the table again + } + } +} + +uint16_t gfxFontEntry::GetUVSGlyph(uint32_t aCh, uint32_t aVS) { + EnsureUVSMapInitialized(); + + if (const auto* uvsData = GetUVSData()) { + return gfxFontUtils::MapUVSToGlyphFormat14(uvsData, aCh, aVS); + } + + return 0; +} + +bool gfxFontEntry::SupportsScriptInGSUB(const hb_tag_t* aScriptTags, + uint32_t aNumTags) { + auto face(GetHBFace()); + + unsigned int index; + hb_tag_t chosenScript; + bool found = hb_ot_layout_table_select_script( + face, TRUETYPE_TAG('G', 'S', 'U', 'B'), aNumTags, aScriptTags, &index, + &chosenScript); + + return found && chosenScript != TRUETYPE_TAG('D', 'F', 'L', 'T'); +} + +nsresult gfxFontEntry::ReadCMAP(FontInfoData* aFontInfoData) { + MOZ_ASSERT(false, "using default no-op implementation of ReadCMAP"); + RefPtr<gfxCharacterMap> cmap = new gfxCharacterMap(); + if (mCharacterMap.compareExchange(nullptr, cmap.get())) { + Unused << cmap.forget(); // mCharacterMap now owns the reference + } + return NS_OK; +} + +nsCString gfxFontEntry::RealFaceName() { + AutoTable nameTable(this, TRUETYPE_TAG('n', 'a', 'm', 'e')); + if (nameTable) { + nsAutoCString name; + nsresult rv = gfxFontUtils::GetFullNameFromTable(nameTable, name); + if (NS_SUCCEEDED(rv)) { + return std::move(name); + } + } + return Name(); +} + +already_AddRefed<gfxFont> gfxFontEntry::FindOrMakeFont( + const gfxFontStyle* aStyle, gfxCharacterMap* aUnicodeRangeMap) { + RefPtr<gfxFont> font = + gfxFontCache::GetCache()->Lookup(this, aStyle, aUnicodeRangeMap); + if (font) { + return font.forget(); + } + + gfxFont* newFont = CreateFontInstance(aStyle); + if (!newFont) { + return nullptr; + } + if (!newFont->Valid()) { + newFont->Destroy(); + return nullptr; + } + newFont->SetUnicodeRangeMap(aUnicodeRangeMap); + return gfxFontCache::GetCache()->MaybeInsert(newFont); +} + +uint16_t gfxFontEntry::UnitsPerEm() { + if (!mUnitsPerEm) { + AutoTable headTable(this, TRUETYPE_TAG('h', 'e', 'a', 'd')); + if (headTable) { + uint32_t len; + const HeadTable* head = + reinterpret_cast<const HeadTable*>(hb_blob_get_data(headTable, &len)); + if (len >= sizeof(HeadTable)) { + mUnitsPerEm = head->unitsPerEm; + if (int16_t(head->xMax) > int16_t(head->xMin) && + int16_t(head->yMax) > int16_t(head->yMin)) { + mXMin = head->xMin; + mYMin = head->yMin; + mXMax = head->xMax; + mYMax = head->yMax; + } + } + } + + // if we didn't find a usable 'head' table, or if the value was + // outside the valid range, record it as invalid + if (mUnitsPerEm < kMinUPEM || mUnitsPerEm > kMaxUPEM) { + mUnitsPerEm = kInvalidUPEM; + } + } + return mUnitsPerEm; +} + +bool gfxFontEntry::HasSVGGlyph(uint32_t aGlyphId) { + NS_ASSERTION(mSVGInitialized, + "SVG data has not yet been loaded. TryGetSVGData() first."); + return GetSVGGlyphs()->HasSVGGlyph(aGlyphId); +} + +bool gfxFontEntry::GetSVGGlyphExtents(DrawTarget* aDrawTarget, + uint32_t aGlyphId, gfxFloat aSize, + gfxRect* aResult) { + MOZ_ASSERT(mSVGInitialized, + "SVG data has not yet been loaded. TryGetSVGData() first."); + MOZ_ASSERT(mUnitsPerEm >= kMinUPEM && mUnitsPerEm <= kMaxUPEM, + "font has invalid unitsPerEm"); + + gfxMatrix svgToApp(aSize / mUnitsPerEm, 0, 0, aSize / mUnitsPerEm, 0, 0); + return GetSVGGlyphs()->GetGlyphExtents(aGlyphId, svgToApp, aResult); +} + +void gfxFontEntry::RenderSVGGlyph(gfxContext* aContext, uint32_t aGlyphId, + SVGContextPaint* aContextPaint) { + NS_ASSERTION(mSVGInitialized, + "SVG data has not yet been loaded. TryGetSVGData() first."); + GetSVGGlyphs()->RenderGlyph(aContext, aGlyphId, aContextPaint); +} + +bool gfxFontEntry::TryGetSVGData(const gfxFont* aFont) { + if (!gfxPlatform::GetPlatform()->OpenTypeSVGEnabled()) { + return false; + } + + // We don't support SVG-in-OT glyphs in offscreen-canvas worker threads. + if (!NS_IsMainThread()) { + return false; + } + + if (!mSVGInitialized) { + // If UnitsPerEm is not known/valid, we can't use SVG glyphs + if (UnitsPerEm() == kInvalidUPEM) { + mSVGInitialized = true; + return false; + } + + // We don't use AutoTable here because we'll pass ownership of this + // blob to the gfxSVGGlyphs, once we've confirmed the table exists + hb_blob_t* svgTable = GetFontTable(TRUETYPE_TAG('S', 'V', 'G', ' ')); + if (!svgTable) { + mSVGInitialized = true; + return false; + } + + // gfxSVGGlyphs will hb_blob_destroy() the table when it is finished + // with it. + auto* svgGlyphs = new gfxSVGGlyphs(svgTable, this); + if (!mSVGGlyphs.compareExchange(nullptr, svgGlyphs)) { + delete svgGlyphs; + } + mSVGInitialized = true; + } + + if (GetSVGGlyphs()) { + AutoWriteLock lock(mLock); + if (!mFontsUsingSVGGlyphs.Contains(aFont)) { + mFontsUsingSVGGlyphs.AppendElement(aFont); + } + } + + return !!GetSVGGlyphs(); +} + +void gfxFontEntry::NotifyFontDestroyed(gfxFont* aFont) { + AutoWriteLock lock(mLock); + mFontsUsingSVGGlyphs.RemoveElement(aFont); +} + +void gfxFontEntry::NotifyGlyphsChanged() { + AutoReadLock lock(mLock); + for (uint32_t i = 0, count = mFontsUsingSVGGlyphs.Length(); i < count; ++i) { + const gfxFont* font = mFontsUsingSVGGlyphs[i]; + font->NotifyGlyphsChanged(); + } +} + +bool gfxFontEntry::TryGetColorGlyphs() { + if (mCheckedForColorGlyph) { + return mCOLR && mCPAL; + } + + auto* colr = GetFontTable(TRUETYPE_TAG('C', 'O', 'L', 'R')); + auto* cpal = colr ? GetFontTable(TRUETYPE_TAG('C', 'P', 'A', 'L')) : nullptr; + + if (colr && cpal && gfx::COLRFonts::ValidateColorGlyphs(colr, cpal)) { + if (!mCOLR.compareExchange(nullptr, colr)) { + hb_blob_destroy(colr); + } + if (!mCPAL.compareExchange(nullptr, cpal)) { + hb_blob_destroy(cpal); + } + } else { + hb_blob_destroy(colr); + hb_blob_destroy(cpal); + } + + mCheckedForColorGlyph = true; + return mCOLR && mCPAL; +} + +/** + * FontTableBlobData + * + * See FontTableHashEntry for the general strategy. + */ + +class gfxFontEntry::FontTableBlobData { + public: + explicit FontTableBlobData(nsTArray<uint8_t>&& aBuffer) + : mTableData(std::move(aBuffer)), mHashtable(nullptr), mHashKey(0) { + MOZ_COUNT_CTOR(FontTableBlobData); + } + + ~FontTableBlobData() { + MOZ_COUNT_DTOR(FontTableBlobData); + if (mHashtable && mHashKey) { + mHashtable->RemoveEntry(mHashKey); + } + } + + // Useful for creating blobs + const char* GetTable() const { + return reinterpret_cast<const char*>(mTableData.Elements()); + } + uint32_t GetTableLength() const { return mTableData.Length(); } + + // Tell this FontTableBlobData to remove the HashEntry when this is + // destroyed. + void ManageHashEntry(nsTHashtable<FontTableHashEntry>* aHashtable, + uint32_t aHashKey) { + mHashtable = aHashtable; + mHashKey = aHashKey; + } + + // Disconnect from the HashEntry (because the blob has already been + // removed from the hashtable). + void ForgetHashEntry() { + mHashtable = nullptr; + mHashKey = 0; + } + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + return mTableData.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + + private: + // The font table data block + nsTArray<uint8_t> mTableData; + + // The blob destroy function needs to know the owning hashtable + // and the hashtable key, so that it can remove the entry. + nsTHashtable<FontTableHashEntry>* mHashtable; + uint32_t mHashKey; + + // not implemented + FontTableBlobData(const FontTableBlobData&); +}; + +hb_blob_t* gfxFontEntry::FontTableHashEntry::ShareTableAndGetBlob( + nsTArray<uint8_t>&& aTable, nsTHashtable<FontTableHashEntry>* aHashtable) { + Clear(); + // adopts elements of aTable + mSharedBlobData = new FontTableBlobData(std::move(aTable)); + + mBlob = hb_blob_create( + mSharedBlobData->GetTable(), mSharedBlobData->GetTableLength(), + HB_MEMORY_MODE_READONLY, mSharedBlobData, DeleteFontTableBlobData); + if (mBlob == hb_blob_get_empty()) { + // The FontTableBlobData was destroyed during hb_blob_create(). + // The (empty) blob is still be held in the hashtable with a strong + // reference. + return hb_blob_reference(mBlob); + } + + // Tell the FontTableBlobData to remove this hash entry when destroyed. + // The hashtable does not keep a strong reference. + mSharedBlobData->ManageHashEntry(aHashtable, GetKey()); + return mBlob; +} + +void gfxFontEntry::FontTableHashEntry::Clear() { + // If the FontTableBlobData is managing the hash entry, then the blob is + // not owned by this HashEntry; otherwise there is strong reference to the + // blob that must be removed. + if (mSharedBlobData) { + mSharedBlobData->ForgetHashEntry(); + mSharedBlobData = nullptr; + } else { + hb_blob_destroy(mBlob); + } + mBlob = nullptr; +} + +// a hb_destroy_func for hb_blob_create + +/* static */ +void gfxFontEntry::FontTableHashEntry::DeleteFontTableBlobData( + void* aBlobData) { + delete static_cast<FontTableBlobData*>(aBlobData); +} + +hb_blob_t* gfxFontEntry::FontTableHashEntry::GetBlob() const { + return hb_blob_reference(mBlob); +} + +bool gfxFontEntry::GetExistingFontTable(uint32_t aTag, hb_blob_t** aBlob) { + // Accessing the mFontTableCache pointer is atomic, so we don't need to take + // a write lock even if we're initializing it here... + MOZ_PUSH_IGNORE_THREAD_SAFETY + if (MOZ_UNLIKELY(!mFontTableCache)) { + // We do this here rather than on fontEntry construction + // because not all shapers will access the table cache at all. + // + // We're not holding a write lock, so make sure to atomically update + // the cache pointer. + auto* newCache = new FontTableCache(8); + if (MOZ_UNLIKELY(!mFontTableCache.compareExchange(nullptr, newCache))) { + delete newCache; + } + } + FontTableCache* cache = GetFontTableCache(); + MOZ_POP_THREAD_SAFETY + + // ...but we do need a lock to read the actual hashtable contents. + AutoReadLock lock(mLock); + FontTableHashEntry* entry = cache->GetEntry(aTag); + if (!entry) { + return false; + } + + *aBlob = entry->GetBlob(); + return true; +} + +hb_blob_t* gfxFontEntry::ShareFontTableAndGetBlob(uint32_t aTag, + nsTArray<uint8_t>* aBuffer) { + MOZ_PUSH_IGNORE_THREAD_SAFETY + if (MOZ_UNLIKELY(!mFontTableCache)) { + auto* newCache = new FontTableCache(8); + if (MOZ_UNLIKELY(!mFontTableCache.compareExchange(nullptr, newCache))) { + delete newCache; + } + } + FontTableCache* cache = GetFontTableCache(); + MOZ_POP_THREAD_SAFETY + + AutoWriteLock lock(mLock); + FontTableHashEntry* entry = cache->PutEntry(aTag); + if (MOZ_UNLIKELY(!entry)) { // OOM + return nullptr; + } + + if (!aBuffer) { + // ensure the entry is null + entry->Clear(); + return nullptr; + } + + return entry->ShareTableAndGetBlob(std::move(*aBuffer), cache); +} + +already_AddRefed<gfxCharacterMap> gfxFontEntry::GetCMAPFromFontInfo( + FontInfoData* aFontInfoData, uint32_t& aUVSOffset) { + if (!aFontInfoData || !aFontInfoData->mLoadCmaps) { + return nullptr; + } + + return aFontInfoData->GetCMAP(mName, aUVSOffset); +} + +hb_blob_t* gfxFontEntry::GetFontTable(uint32_t aTag) { + hb_blob_t* blob; + if (GetExistingFontTable(aTag, &blob)) { + return blob; + } + + nsTArray<uint8_t> buffer; + bool haveTable = NS_SUCCEEDED(CopyFontTable(aTag, buffer)); + + return ShareFontTableAndGetBlob(aTag, haveTable ? &buffer : nullptr); +} + +// callback for HarfBuzz to get a font table (in hb_blob_t form) +// from the font entry (passed as aUserData) +/*static*/ +hb_blob_t* gfxFontEntry::HBGetTable(hb_face_t* face, uint32_t aTag, + void* aUserData) { + gfxFontEntry* fontEntry = static_cast<gfxFontEntry*>(aUserData); + + // bug 589682 - ignore the GDEF table in buggy fonts (applies to + // Italic and BoldItalic faces of Times New Roman) + if (aTag == TRUETYPE_TAG('G', 'D', 'E', 'F') && fontEntry->IgnoreGDEF()) { + return nullptr; + } + + // bug 721719 - ignore the GSUB table in buggy fonts (applies to Roboto, + // at least on some Android ICS devices; set in gfxFT2FontList.cpp) + if (aTag == TRUETYPE_TAG('G', 'S', 'U', 'B') && fontEntry->IgnoreGSUB()) { + return nullptr; + } + + return fontEntry->GetFontTable(aTag); +} + +static thread_local gfxFontEntry* tl_grGetFontTableCallbackData = nullptr; + +class gfxFontEntryCallbacks { + public: + static tainted_gr<const void*> GrGetTable( + rlbox_sandbox_gr& sandbox, tainted_gr<const void*> /* aAppFaceHandle */, + tainted_gr<unsigned int> aName, tainted_gr<unsigned int*> aLen) { + gfxFontEntry* fontEntry = tl_grGetFontTableCallbackData; + *aLen = 0; + tainted_gr<const void*> ret = nullptr; + + if (fontEntry) { + unsigned int fontTableKey = aName.unverified_safe_because( + "This is only being used to index into a hashmap, which is robust " + "for any value. No checks needed."); + gfxFontUtils::AutoHBBlob blob(fontEntry->GetFontTable(fontTableKey)); + + if (blob) { + unsigned int blobLength; + const void* tableData = hb_blob_get_data(blob, &blobLength); + // tableData is read-only data shared with the sandbox. + // Making a copy in sandbox memory + tainted_gr<void*> t_tableData = rlbox::sandbox_reinterpret_cast<void*>( + sandbox.malloc_in_sandbox<char>(blobLength)); + if (t_tableData) { + rlbox::memcpy(sandbox, t_tableData, tableData, blobLength); + *aLen = blobLength; + ret = rlbox::sandbox_const_cast<const void*>(t_tableData); + } + } + } + + return ret; + } + + static void GrReleaseTable(rlbox_sandbox_gr& sandbox, + tainted_gr<const void*> /* aAppFaceHandle */, + tainted_gr<const void*> aTableBuffer) { + sandbox.free_in_sandbox(aTableBuffer); + } + + static tainted_gr<float> GrGetAdvance(rlbox_sandbox_gr& sandbox, + tainted_gr<const void*> appFontHandle, + tainted_gr<uint16_t> glyphid) { + tainted_opaque_gr<float> ret = gfxGraphiteShaper::GrGetAdvance( + sandbox, appFontHandle.to_opaque(), glyphid.to_opaque()); + return rlbox::from_opaque(ret); + } +}; + +struct gfxFontEntry::GrSandboxData { + rlbox_sandbox_gr sandbox; + sandbox_callback_gr<const void* (*)(const void*, unsigned int, unsigned int*)> + grGetTableCallback; + sandbox_callback_gr<void (*)(const void*, const void*)> + grReleaseTableCallback; + // Text Shapers register a callback to get glyph advances + sandbox_callback_gr<float (*)(const void*, uint16_t)> + grGetGlyphAdvanceCallback; + + GrSandboxData() { + sandbox.create_sandbox(); + grGetTableCallback = + sandbox.register_callback(gfxFontEntryCallbacks::GrGetTable); + grReleaseTableCallback = + sandbox.register_callback(gfxFontEntryCallbacks::GrReleaseTable); + grGetGlyphAdvanceCallback = + sandbox.register_callback(gfxFontEntryCallbacks::GrGetAdvance); + } + + ~GrSandboxData() { + grGetTableCallback.unregister(); + grReleaseTableCallback.unregister(); + grGetGlyphAdvanceCallback.unregister(); + sandbox.destroy_sandbox(); + } +}; + +rlbox_sandbox_gr* gfxFontEntry::GetGrSandbox() { + AutoReadLock lock(mLock); + MOZ_ASSERT(mSandboxData != nullptr); + return &mSandboxData->sandbox; +} + +sandbox_callback_gr<float (*)(const void*, uint16_t)>* +gfxFontEntry::GetGrSandboxAdvanceCallbackHandle() { + AutoReadLock lock(mLock); + MOZ_ASSERT(mSandboxData != nullptr); + return &mSandboxData->grGetGlyphAdvanceCallback; +} + +tainted_opaque_gr<gr_face*> gfxFontEntry::GetGrFace() { + if (!mGrFaceInitialized) { + // When possible, the below code will use WASM as a sandboxing mechanism. + // At this time the wasm sandbox does not support threads. + // If Thebes is updated to make callst to the sandbox on multiple threaads, + // we need to make sure the underlying sandbox supports threading. + MOZ_ASSERT(NS_IsMainThread()); + + mSandboxData = new GrSandboxData(); + + auto p_faceOps = mSandboxData->sandbox.malloc_in_sandbox<gr_face_ops>(); + if (!p_faceOps) { + MOZ_CRASH("Graphite sandbox memory allocation failed"); + } + p_faceOps->size = sizeof(*p_faceOps); + p_faceOps->get_table = mSandboxData->grGetTableCallback; + p_faceOps->release_table = mSandboxData->grReleaseTableCallback; + + tl_grGetFontTableCallbackData = this; + auto face = sandbox_invoke( + mSandboxData->sandbox, gr_make_face_with_ops, + // For security, we do not pass the callback data to this arg, and use + // a TLS var instead. However, gr_make_face_with_ops expects this to + // be a non null ptr. Therefore, we should pass some dummy non null + // pointer which will be passed to callbacks, but never used. Let's just + // pass p_faceOps again, as this is a non-null tainted pointer. + p_faceOps /* appFaceHandle */, p_faceOps, gr_face_default); + tl_grGetFontTableCallbackData = nullptr; + mGrFace = face.to_opaque(); + mGrFaceInitialized = true; + mSandboxData->sandbox.free_in_sandbox(p_faceOps); + } + ++mGrFaceRefCnt; + return mGrFace; +} + +void gfxFontEntry::ReleaseGrFace(tainted_opaque_gr<gr_face*> aFace) { + MOZ_ASSERT( + (rlbox::from_opaque(aFace) == rlbox::from_opaque(mGrFace)) + .unverified_safe_because( + "This is safe as the only thing we are doing is comparing " + "addresses of two tainted pointers. Furthermore this is used " + "merely as a debugging aid in the debug builds. This function is " + "called only from the trusted Firefox code rather than the " + "untrusted libGraphite.")); // sanity-check + MOZ_ASSERT(mGrFaceRefCnt > 0); + if (--mGrFaceRefCnt == 0) { + auto t_mGrFace = rlbox::from_opaque(mGrFace); + + tl_grGetFontTableCallbackData = this; + sandbox_invoke(mSandboxData->sandbox, gr_face_destroy, t_mGrFace); + tl_grGetFontTableCallbackData = nullptr; + + t_mGrFace = nullptr; + mGrFace = t_mGrFace.to_opaque(); + + delete mSandboxData; + mSandboxData = nullptr; + + mGrFaceInitialized = false; + } +} + +void gfxFontEntry::DisconnectSVG() { + if (mSVGInitialized && mSVGGlyphs) { + mSVGGlyphs = nullptr; + mSVGInitialized = false; + } +} + +bool gfxFontEntry::HasFontTable(uint32_t aTableTag) { + AutoTable table(this, aTableTag); + return table && hb_blob_get_length(table) > 0; +} + +tainted_boolean_hint gfxFontEntry::HasGraphiteSpaceContextuals() { + LazyFlag flag = mHasGraphiteSpaceContextuals; + if (flag == LazyFlag::Uninitialized) { + auto face = GetGrFace(); + auto t_face = rlbox::from_opaque(face); + if (t_face) { + tainted_gr<const gr_faceinfo*> faceInfo = + sandbox_invoke(mSandboxData->sandbox, gr_face_info, t_face, 0); + // Comparison with a value in sandboxed memory returns a + // tainted_boolean_hint, i.e. a "hint", since the value could be changed + // maliciously at any moment. + tainted_boolean_hint is_not_none = + faceInfo->space_contextuals != gr_faceinfo::gr_space_none; + flag = is_not_none.unverified_safe_because( + "Note ideally mHasGraphiteSpaceContextuals would be " + "tainted_boolean_hint, but RLBox does not yet support " + "bitfields, so it is not wrapped. However, its value is only " + "ever accessed through this function which returns a " + "tainted_boolean_hint, so unwrapping temporarily is safe. " + "We remove the wrapper now and re-add it below.") + ? LazyFlag::Yes + : LazyFlag::No; + } + ReleaseGrFace(face); // always balance GetGrFace, even if face is null + mHasGraphiteSpaceContextuals = flag; + } + + return tainted_boolean_hint(flag == LazyFlag::Yes); +} + +#define FEATURE_SCRIPT_MASK 0x000000ff // script index replaces low byte of tag + +static_assert(int(intl::Script::NUM_SCRIPT_CODES) <= FEATURE_SCRIPT_MASK, + "Too many script codes"); + +// high-order three bytes of tag with script in low-order byte +#define SCRIPT_FEATURE(s, tag) \ + (((~FEATURE_SCRIPT_MASK) & (tag)) | \ + ((FEATURE_SCRIPT_MASK) & static_cast<uint32_t>(s))) + +bool gfxFontEntry::SupportsOpenTypeFeature(Script aScript, + uint32_t aFeatureTag) { + MutexAutoLock lock(mFeatureInfoLock); + if (!mSupportedFeatures) { + mSupportedFeatures = MakeUnique<nsTHashMap<nsUint32HashKey, bool>>(); + } + + // note: high-order three bytes *must* be unique for each feature + // listed below (see SCRIPT_FEATURE macro def'n) + NS_ASSERTION(aFeatureTag == HB_TAG('s', 'm', 'c', 'p') || + aFeatureTag == HB_TAG('c', '2', 's', 'c') || + aFeatureTag == HB_TAG('p', 'c', 'a', 'p') || + aFeatureTag == HB_TAG('c', '2', 'p', 'c') || + aFeatureTag == HB_TAG('s', 'u', 'p', 's') || + aFeatureTag == HB_TAG('s', 'u', 'b', 's') || + aFeatureTag == HB_TAG('v', 'e', 'r', 't'), + "use of unknown feature tag"); + + // note: graphite feature support uses the last script index + NS_ASSERTION(int(aScript) < FEATURE_SCRIPT_MASK - 1, + "need to bump the size of the feature shift"); + + uint32_t scriptFeature = SCRIPT_FEATURE(aScript, aFeatureTag); + return mSupportedFeatures->LookupOrInsertWith(scriptFeature, [&] { + bool result = false; + auto face(GetHBFace()); + + if (hb_ot_layout_has_substitution(face)) { + hb_script_t hbScript = + gfxHarfBuzzShaper::GetHBScriptUsedForShaping(aScript); + + // Get the OpenType tag(s) that match this script code + unsigned int scriptCount = 4; + hb_tag_t scriptTags[4]; + hb_ot_tags_from_script_and_language(hbScript, HB_LANGUAGE_INVALID, + &scriptCount, scriptTags, nullptr, + nullptr); + + // Append DEFAULT to the returned tags, if room + if (scriptCount < 4) { + scriptTags[scriptCount++] = HB_OT_TAG_DEFAULT_SCRIPT; + } + + // Now check for 'smcp' under the first of those scripts that is present + const hb_tag_t kGSUB = HB_TAG('G', 'S', 'U', 'B'); + result = std::any_of(scriptTags, scriptTags + scriptCount, + [&](const hb_tag_t& scriptTag) { + unsigned int scriptIndex; + return hb_ot_layout_table_find_script( + face, kGSUB, scriptTag, &scriptIndex) && + hb_ot_layout_language_find_feature( + face, kGSUB, scriptIndex, + HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX, + aFeatureTag, nullptr); + }); + } + + return result; + }); +} + +const hb_set_t* gfxFontEntry::InputsForOpenTypeFeature(Script aScript, + uint32_t aFeatureTag) { + MutexAutoLock lock(mFeatureInfoLock); + if (!mFeatureInputs) { + mFeatureInputs = MakeUnique<nsTHashMap<nsUint32HashKey, hb_set_t*>>(); + } + + NS_ASSERTION(aFeatureTag == HB_TAG('s', 'u', 'p', 's') || + aFeatureTag == HB_TAG('s', 'u', 'b', 's') || + aFeatureTag == HB_TAG('v', 'e', 'r', 't'), + "use of unknown feature tag"); + + uint32_t scriptFeature = SCRIPT_FEATURE(aScript, aFeatureTag); + hb_set_t* inputGlyphs; + if (mFeatureInputs->Get(scriptFeature, &inputGlyphs)) { + return inputGlyphs; + } + + inputGlyphs = hb_set_create(); + + auto face(GetHBFace()); + + if (hb_ot_layout_has_substitution(face)) { + hb_script_t hbScript = + gfxHarfBuzzShaper::GetHBScriptUsedForShaping(aScript); + + // Get the OpenType tag(s) that match this script code + unsigned int scriptCount = 4; + hb_tag_t scriptTags[5]; // space for null terminator + hb_ot_tags_from_script_and_language(hbScript, HB_LANGUAGE_INVALID, + &scriptCount, scriptTags, nullptr, + nullptr); + + // Append DEFAULT to the returned tags, if room + if (scriptCount < 4) { + scriptTags[scriptCount++] = HB_OT_TAG_DEFAULT_SCRIPT; + } + scriptTags[scriptCount++] = 0; + + const hb_tag_t kGSUB = HB_TAG('G', 'S', 'U', 'B'); + hb_tag_t features[2] = {aFeatureTag, HB_TAG_NONE}; + hb_set_t* featurelookups = hb_set_create(); + hb_ot_layout_collect_lookups(face, kGSUB, scriptTags, nullptr, features, + featurelookups); + hb_codepoint_t index = -1; + while (hb_set_next(featurelookups, &index)) { + hb_ot_layout_lookup_collect_glyphs(face, kGSUB, index, nullptr, + inputGlyphs, nullptr, nullptr); + } + hb_set_destroy(featurelookups); + } + + mFeatureInputs->InsertOrUpdate(scriptFeature, inputGlyphs); + return inputGlyphs; +} + +bool gfxFontEntry::SupportsGraphiteFeature(uint32_t aFeatureTag) { + MutexAutoLock lock(mFeatureInfoLock); + + if (!mSupportedFeatures) { + mSupportedFeatures = MakeUnique<nsTHashMap<nsUint32HashKey, bool>>(); + } + + // note: high-order three bytes *must* be unique for each feature + // listed below (see SCRIPT_FEATURE macro def'n) + NS_ASSERTION(aFeatureTag == HB_TAG('s', 'm', 'c', 'p') || + aFeatureTag == HB_TAG('c', '2', 's', 'c') || + aFeatureTag == HB_TAG('p', 'c', 'a', 'p') || + aFeatureTag == HB_TAG('c', '2', 'p', 'c') || + aFeatureTag == HB_TAG('s', 'u', 'p', 's') || + aFeatureTag == HB_TAG('s', 'u', 'b', 's'), + "use of unknown feature tag"); + + // graphite feature check uses the last script slot + uint32_t scriptFeature = SCRIPT_FEATURE(FEATURE_SCRIPT_MASK, aFeatureTag); + bool result; + if (mSupportedFeatures->Get(scriptFeature, &result)) { + return result; + } + + auto face = GetGrFace(); + auto t_face = rlbox::from_opaque(face); + result = t_face ? sandbox_invoke(mSandboxData->sandbox, gr_face_find_fref, + t_face, aFeatureTag) != nullptr + : false; + ReleaseGrFace(face); + + mSupportedFeatures->InsertOrUpdate(scriptFeature, result); + + return result; +} + +void gfxFontEntry::GetFeatureInfo(nsTArray<gfxFontFeatureInfo>& aFeatureInfo) { + // TODO: implement alternative code path for graphite fonts + + auto autoFace(GetHBFace()); + // Expose the raw hb_face_t to be captured by the lambdas (not the + // AutoHBFace wrapper). + hb_face_t* face = autoFace; + + // Get the list of features for a specific <script,langSys> pair and + // append them to aFeatureInfo. + auto collectForLang = [=, &aFeatureInfo]( + hb_tag_t aTableTag, unsigned int aScript, + hb_tag_t aScriptTag, unsigned int aLang, + hb_tag_t aLangTag) { + unsigned int featCount = hb_ot_layout_language_get_feature_tags( + face, aTableTag, aScript, aLang, 0, nullptr, nullptr); + AutoTArray<hb_tag_t, 32> featTags; + featTags.SetLength(featCount); + hb_ot_layout_language_get_feature_tags(face, aTableTag, aScript, aLang, 0, + &featCount, featTags.Elements()); + MOZ_ASSERT(featCount <= featTags.Length()); + // Just in case HB didn't fill featTags (i.e. in case it returned fewer + // tags than it promised), we truncate at the length it says it filled: + featTags.SetLength(featCount); + for (hb_tag_t t : featTags) { + aFeatureInfo.AppendElement(gfxFontFeatureInfo{t, aScriptTag, aLangTag}); + } + }; + + // Iterate over the language systems supported by a given script, + // and call collectForLang for each of them. + auto collectForScript = [=](hb_tag_t aTableTag, unsigned int aScript, + hb_tag_t aScriptTag) { + collectForLang(aTableTag, aScript, aScriptTag, + HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX, + HB_TAG('d', 'f', 'l', 't')); + unsigned int langCount = hb_ot_layout_script_get_language_tags( + face, aTableTag, aScript, 0, nullptr, nullptr); + AutoTArray<hb_tag_t, 32> langTags; + langTags.SetLength(langCount); + hb_ot_layout_script_get_language_tags(face, aTableTag, aScript, 0, + &langCount, langTags.Elements()); + MOZ_ASSERT(langCount <= langTags.Length()); + langTags.SetLength(langCount); + for (unsigned int lang = 0; lang < langCount; ++lang) { + collectForLang(aTableTag, aScript, aScriptTag, lang, langTags[lang]); + } + }; + + // Iterate over the scripts supported by a table (GSUB or GPOS), and call + // collectForScript for each of them. + auto collectForTable = [=](hb_tag_t aTableTag) { + unsigned int scriptCount = hb_ot_layout_table_get_script_tags( + face, aTableTag, 0, nullptr, nullptr); + AutoTArray<hb_tag_t, 32> scriptTags; + scriptTags.SetLength(scriptCount); + hb_ot_layout_table_get_script_tags(face, aTableTag, 0, &scriptCount, + scriptTags.Elements()); + MOZ_ASSERT(scriptCount <= scriptTags.Length()); + scriptTags.SetLength(scriptCount); + for (unsigned int script = 0; script < scriptCount; ++script) { + collectForScript(aTableTag, script, scriptTags[script]); + } + }; + + // Collect all OpenType Layout features, both substitution and positioning, + // supported by the font resource. + collectForTable(HB_TAG('G', 'S', 'U', 'B')); + collectForTable(HB_TAG('G', 'P', 'O', 'S')); +} + +typedef struct { + AutoSwap_PRUint32 version; + AutoSwap_PRUint16 format; + AutoSwap_PRUint16 horizOffset; + AutoSwap_PRUint16 vertOffset; + AutoSwap_PRUint16 reserved; + // TrackData horizData; + // TrackData vertData; +} TrakHeader; + +typedef struct { + AutoSwap_PRUint16 nTracks; + AutoSwap_PRUint16 nSizes; + AutoSwap_PRUint32 sizeTableOffset; + // trackTableEntry trackTable[]; + // fixed32 sizeTable[]; +} TrackData; + +typedef struct { + AutoSwap_PRUint32 track; + AutoSwap_PRUint16 nameIndex; + AutoSwap_PRUint16 offset; +} TrackTableEntry; + +bool gfxFontEntry::HasTrackingTable() { + if (!TrakTableInitialized()) { + hb_blob_t* trak = GetFontTable(TRUETYPE_TAG('t', 'r', 'a', 'k')); + if (trak) { + // mTrakTable itself is atomic, but we also want to set the auxiliary + // pointers mTrakValues and mTrakSizeTable, so we take a lock here to + // avoid racing with another thread also initializing the same values. + AutoWriteLock lock(mLock); + if (!mTrakTable.compareExchange(kTrakTableUninitialized, trak)) { + hb_blob_destroy(trak); + } else if (!ParseTrakTable()) { + hb_blob_destroy(mTrakTable.exchange(nullptr)); + } + } else { + mTrakTable.exchange(nullptr); + } + } + return GetTrakTable() != nullptr; +} + +bool gfxFontEntry::ParseTrakTable() { + // Check table validity and set up the subtable pointers we need; + // if 'trak' table is invalid, or doesn't contain a 'normal' track, + // return false to tell the caller not to try using it. + unsigned int len; + const char* data = hb_blob_get_data(GetTrakTable(), &len); + if (len < sizeof(TrakHeader)) { + return false; + } + auto trak = reinterpret_cast<const TrakHeader*>(data); + uint16_t horizOffset = trak->horizOffset; + if (trak->version != 0x00010000 || uint16_t(trak->format) != 0 || + horizOffset == 0 || uint16_t(trak->reserved) != 0) { + return false; + } + // Find the horizontal trackData, and check it doesn't overrun the buffer. + if (horizOffset > len - sizeof(TrackData)) { + return false; + } + auto trackData = reinterpret_cast<const TrackData*>(data + horizOffset); + uint16_t nTracks = trackData->nTracks; + mNumTrakSizes = trackData->nSizes; + if (nTracks == 0 || mNumTrakSizes < 2) { + return false; + } + uint32_t sizeTableOffset = trackData->sizeTableOffset; + // Find the trackTable, and check it doesn't overrun the buffer. + if (horizOffset > + len - (sizeof(TrackData) + nTracks * sizeof(TrackTableEntry))) { + return false; + } + auto trackTable = reinterpret_cast<const TrackTableEntry*>( + data + horizOffset + sizeof(TrackData)); + // Look for 'normal' tracking, bail out if no such track is present. + unsigned trackIndex; + for (trackIndex = 0; trackIndex < nTracks; ++trackIndex) { + if (trackTable[trackIndex].track == 0x00000000) { + break; + } + } + if (trackIndex == nTracks) { + return false; + } + // Find list of tracking values, and check they won't overrun. + uint16_t offset = trackTable[trackIndex].offset; + if (offset > len - mNumTrakSizes * sizeof(uint16_t)) { + return false; + } + mTrakValues = reinterpret_cast<const AutoSwap_PRInt16*>(data + offset); + // Find the size subtable, and check it doesn't overrun the buffer. + mTrakSizeTable = + reinterpret_cast<const AutoSwap_PRInt32*>(data + sizeTableOffset); + if (mTrakSizeTable + mNumTrakSizes > + reinterpret_cast<const AutoSwap_PRInt32*>(data + len)) { + return false; + } + return true; +} + +gfxFloat gfxFontEntry::TrackingForCSSPx(gfxFloat aSize) const { + // No locking because this does read-only access of fields that are inert + // once initialized. + MOZ_ASSERT(TrakTableInitialized() && mTrakTable && mTrakValues && + mTrakSizeTable); + + // Find index of first sizeTable entry that is >= the requested size. + int32_t fixedSize = int32_t(aSize * 65536.0); // float -> 16.16 fixed-point + unsigned sizeIndex; + for (sizeIndex = 0; sizeIndex < mNumTrakSizes; ++sizeIndex) { + if (mTrakSizeTable[sizeIndex] >= fixedSize) { + break; + } + } + // Return the tracking value for the requested size, or an interpolated + // value if the exact size isn't found. + if (sizeIndex == mNumTrakSizes) { + // Request is larger than last entry in the table, so just use that. + // (We don't attempt to extrapolate more extreme tracking values than + // the largest or smallest present in the table.) + return int16_t(mTrakValues[mNumTrakSizes - 1]); + } + if (sizeIndex == 0 || mTrakSizeTable[sizeIndex] == fixedSize) { + // Found an exact match, or size was smaller than the first entry. + return int16_t(mTrakValues[sizeIndex]); + } + // Requested size falls between two entries: interpolate value. + double s0 = mTrakSizeTable[sizeIndex - 1] / 65536.0; // 16.16 -> float + double s1 = mTrakSizeTable[sizeIndex] / 65536.0; + double t = (aSize - s0) / (s1 - s0); + return (1.0 - t) * int16_t(mTrakValues[sizeIndex - 1]) + + t * int16_t(mTrakValues[sizeIndex]); +} + +void gfxFontEntry::SetupVariationRanges() { + // No locking because this is done during initialization before any other + // thread has access to the entry. + if (!gfxPlatform::HasVariationFontSupport() || + !StaticPrefs::layout_css_font_variations_enabled() || !HasVariations() || + IsUserFont()) { + return; + } + AutoTArray<gfxFontVariationAxis, 4> axes; + GetVariationAxes(axes); + for (const auto& axis : axes) { + switch (axis.mTag) { + case HB_TAG('w', 'g', 'h', 't'): + // If the axis range looks like it doesn't fit the CSS font-weight + // scale, we don't hook up the high-level property, and we mark + // the face (in mRangeFlags) as having non-standard weight. This + // means we won't map CSS font-weight to the axis. Setting 'wght' + // with font-variation-settings will still work. + // Strictly speaking, the min value should be checked against 1.0, + // not 0.0, but we'll allow font makers that amount of leeway, as + // in practice a number of fonts seem to use 0..1000. + if (axis.mMinValue >= 0.0f && axis.mMaxValue <= 1000.0f && + // If axis.mMaxValue is less than the default weight we already + // set up, assume the axis has a non-standard range (like Skia) + // and don't try to map it. + Weight().Min() <= FontWeight::FromFloat(axis.mMaxValue)) { + if (FontWeight::FromFloat(axis.mDefaultValue) != Weight().Min()) { + mStandardFace = false; + } + mWeightRange = + WeightRange(FontWeight::FromFloat(std::max(1.0f, axis.mMinValue)), + FontWeight::FromFloat(axis.mMaxValue)); + } else { + mRangeFlags |= RangeFlags::eNonCSSWeight; + } + break; + + case HB_TAG('w', 'd', 't', 'h'): + if (axis.mMinValue >= 0.0f && axis.mMaxValue <= 1000.0f && + Stretch().Min() <= FontStretch::FromFloat(axis.mMaxValue)) { + if (FontStretch::FromFloat(axis.mDefaultValue) != Stretch().Min()) { + mStandardFace = false; + } + mStretchRange = StretchRange(FontStretch::FromFloat(axis.mMinValue), + FontStretch::FromFloat(axis.mMaxValue)); + } else { + mRangeFlags |= RangeFlags::eNonCSSStretch; + } + break; + + case HB_TAG('s', 'l', 'n', 't'): + if (axis.mMinValue >= -90.0f && axis.mMaxValue <= 90.0f) { + if (FontSlantStyle::FromFloat(axis.mDefaultValue) != + SlantStyle().Min()) { + mStandardFace = false; + } + // OpenType and CSS measure angles in opposite directions, so we + // have to flip signs and swap min/max when setting up the CSS + // font-style range here. + mStyleRange = + SlantStyleRange(FontSlantStyle::FromFloat(-axis.mMaxValue), + FontSlantStyle::FromFloat(-axis.mMinValue)); + } + break; + + case HB_TAG('i', 't', 'a', 'l'): + if (axis.mMinValue <= 0.0f && axis.mMaxValue >= 1.0f) { + if (axis.mDefaultValue != 0.0f) { + mStandardFace = false; + } + mStyleRange = + SlantStyleRange(FontSlantStyle::NORMAL, FontSlantStyle::ITALIC); + } + break; + + default: + continue; + } + } +} + +void gfxFontEntry::CheckForVariationAxes() { + if (mCheckedForVariationAxes) { + return; + } + mCheckedForVariationAxes = true; + if (HasVariations()) { + AutoTArray<gfxFontVariationAxis, 4> axes; + GetVariationAxes(axes); + for (const auto& axis : axes) { + if (axis.mTag == HB_TAG('w', 'g', 'h', 't') && axis.mMaxValue >= 600.0f) { + mRangeFlags |= RangeFlags::eBoldVariableWeight; + } else if (axis.mTag == HB_TAG('i', 't', 'a', 'l') && + axis.mMaxValue >= 1.0f) { + mRangeFlags |= RangeFlags::eItalicVariation; + } else if (axis.mTag == HB_TAG('s', 'l', 'n', 't')) { + mRangeFlags |= RangeFlags::eSlantVariation; + } else if (axis.mTag == HB_TAG('o', 'p', 's', 'z')) { + mRangeFlags |= RangeFlags::eOpticalSize; + } + } + } +} + +bool gfxFontEntry::HasBoldVariableWeight() { + MOZ_ASSERT(!mIsUserFontContainer, + "should not be called for user-font containers!"); + CheckForVariationAxes(); + return bool(mRangeFlags & RangeFlags::eBoldVariableWeight); +} + +bool gfxFontEntry::HasItalicVariation() { + MOZ_ASSERT(!mIsUserFontContainer, + "should not be called for user-font containers!"); + CheckForVariationAxes(); + return bool(mRangeFlags & RangeFlags::eItalicVariation); +} + +bool gfxFontEntry::HasSlantVariation() { + MOZ_ASSERT(!mIsUserFontContainer, + "should not be called for user-font containers!"); + CheckForVariationAxes(); + return bool(mRangeFlags & RangeFlags::eSlantVariation); +} + +bool gfxFontEntry::HasOpticalSize() { + MOZ_ASSERT(!mIsUserFontContainer, + "should not be called for user-font containers!"); + CheckForVariationAxes(); + return bool(mRangeFlags & RangeFlags::eOpticalSize); +} + +void gfxFontEntry::GetVariationsForStyle(nsTArray<gfxFontVariation>& aResult, + const gfxFontStyle& aStyle) { + if (!gfxPlatform::HasVariationFontSupport() || + !StaticPrefs::layout_css_font_variations_enabled()) { + return; + } + + if (!HasVariations()) { + return; + } + + // Resolve high-level CSS properties from the requested style + // (font-{style,weight,stretch}) to the appropriate variations. + // The value used is clamped to the range available in the font face, + // unless the face is a user font where no explicit descriptor was + // given, indicated by the corresponding 'auto' range-flag. + + // We don't do these mappings if the font entry has weight and/or stretch + // ranges that do not appear to use the CSS property scale. Some older + // fonts created for QuickDrawGX/AAT may use "normalized" values where the + // standard variation is 1.0 rather than 400.0 (weight) or 100.0 (stretch). + + if (!(mRangeFlags & RangeFlags::eNonCSSWeight)) { + float weight = (IsUserFont() && (mRangeFlags & RangeFlags::eAutoWeight)) + ? aStyle.weight.ToFloat() + : Weight().Clamp(aStyle.weight).ToFloat(); + aResult.AppendElement(gfxFontVariation{HB_TAG('w', 'g', 'h', 't'), weight}); + } + + if (!(mRangeFlags & RangeFlags::eNonCSSStretch)) { + float stretch = (IsUserFont() && (mRangeFlags & RangeFlags::eAutoStretch)) + ? aStyle.stretch.ToFloat() + : Stretch().Clamp(aStyle.stretch).ToFloat(); + aResult.AppendElement( + gfxFontVariation{HB_TAG('w', 'd', 't', 'h'), stretch}); + } + + if (aStyle.style.IsItalic() && SupportsItalic()) { + // The 'ital' axis is normally a binary toggle; intermediate values + // can only be set using font-variation-settings. + aResult.AppendElement(gfxFontVariation{HB_TAG('i', 't', 'a', 'l'), 1.0f}); + } else if (aStyle.style != StyleFontStyle::NORMAL && HasSlantVariation()) { + // Figure out what slant angle we should try to match from the + // requested style. + float angle = aStyle.style.SlantAngle(); + // Clamp to the available range, unless the face is a user font + // with no explicit descriptor. + if (!(IsUserFont() && (mRangeFlags & RangeFlags::eAutoSlantStyle))) { + angle = SlantStyle().Clamp(FontSlantStyle::FromFloat(angle)).SlantAngle(); + } + // OpenType and CSS measure angles in opposite directions, so we have to + // invert the sign of the CSS oblique value when setting OpenType 'slnt'. + aResult.AppendElement(gfxFontVariation{HB_TAG('s', 'l', 'n', 't'), -angle}); + } + + struct TagEquals { + bool Equals(const gfxFontVariation& aIter, uint32_t aTag) const { + return aIter.mTag == aTag; + } + }; + + auto replaceOrAppend = [&aResult](const gfxFontVariation& aSetting) { + auto index = aResult.IndexOf(aSetting.mTag, 0, TagEquals()); + if (index == aResult.NoIndex) { + aResult.AppendElement(aSetting); + } else { + aResult[index].mValue = aSetting.mValue; + } + }; + + // The low-level font-variation-settings descriptor from @font-face, + // if present, takes precedence over automatic variation settings + // from high-level properties. + for (const auto& v : mVariationSettings) { + replaceOrAppend(v); + } + + // And the low-level font-variation-settings property takes precedence + // over the descriptor. + for (const auto& v : aStyle.variationSettings) { + replaceOrAppend(v); + } + + // If there's no explicit opsz in the settings, apply 'auto' value. + if (HasOpticalSize() && aStyle.autoOpticalSize >= 0.0f) { + const uint32_t kOpszTag = HB_TAG('o', 'p', 's', 'z'); + auto index = aResult.IndexOf(kOpszTag, 0, TagEquals()); + if (index == aResult.NoIndex) { + float value = aStyle.autoOpticalSize * mSizeAdjust; + aResult.AppendElement(gfxFontVariation{kOpszTag, value}); + } + } +} + +size_t gfxFontEntry::FontTableHashEntry::SizeOfExcludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + if (mBlob) { + n += aMallocSizeOf(mBlob); + } + if (mSharedBlobData) { + n += mSharedBlobData->SizeOfIncludingThis(aMallocSizeOf); + } + return n; +} + +void gfxFontEntry::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + aSizes->mFontListSize += mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + + // cmaps are shared so only non-shared cmaps are included here + if (mCharacterMap && GetCharacterMap()->mBuildOnTheFly) { + aSizes->mCharMapsSize += + GetCharacterMap()->SizeOfIncludingThis(aMallocSizeOf); + } + { + AutoReadLock lock(mLock); + if (mFontTableCache) { + aSizes->mFontTableCacheSize += + GetFontTableCache()->SizeOfIncludingThis(aMallocSizeOf); + } + } + + // If the font has UVS data, we count that as part of the character map. + if (mUVSData) { + aSizes->mCharMapsSize += aMallocSizeOf(GetUVSData()); + } + + // The following, if present, are essentially cached forms of font table + // data, so we'll accumulate them together with the basic table cache. + if (mUserFontData) { + aSizes->mFontTableCacheSize += + mUserFontData->SizeOfIncludingThis(aMallocSizeOf); + } + if (mSVGGlyphs) { + aSizes->mFontTableCacheSize += + GetSVGGlyphs()->SizeOfIncludingThis(aMallocSizeOf); + } + + { + MutexAutoLock lock(mFeatureInfoLock); + if (mSupportedFeatures) { + aSizes->mFontTableCacheSize += + mSupportedFeatures->ShallowSizeOfIncludingThis(aMallocSizeOf); + } + if (mFeatureInputs) { + aSizes->mFontTableCacheSize += + mFeatureInputs->ShallowSizeOfIncludingThis(aMallocSizeOf); + // XXX Can't this simply be + // aSizes->mFontTableCacheSize += 8192 * mFeatureInputs->Count(); + for (auto iter = mFeatureInputs->ConstIter(); !iter.Done(); iter.Next()) { + // There's no API to get the real size of an hb_set, so we'll use + // an approximation based on knowledge of the implementation. + aSizes->mFontTableCacheSize += 8192; // vector of 64K bits + } + } + } + // We don't include the size of mCOLR/mCPAL here, because (depending on the + // font backend implementation) they will either wrap blocks of data owned + // by the system (and potentially shared), or tables that are in our font + // table cache and therefore already counted. +} + +void gfxFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +// This is used to report the size of an individual downloaded font in the +// user font cache. (Fonts that are part of the platform font list accumulate +// their sizes to the font list's reporter using the AddSizeOf... methods +// above.) +size_t gfxFontEntry::ComputedSizeOfExcludingThis( + MallocSizeOf aMallocSizeOf) const { + FontListSizes s = {0}; + AddSizeOfExcludingThis(aMallocSizeOf, &s); + + // When reporting memory used for the main platform font list, + // where we're typically summing the totals for a few hundred font faces, + // we report the fields of FontListSizes separately. + // But for downloaded user fonts, the actual resource data (added below) + // will dominate, and the minor overhead of these pieces isn't worth + // splitting out for an individual font. + size_t result = s.mFontListSize + s.mFontTableCacheSize + s.mCharMapsSize; + + if (mIsDataUserFont) { + MOZ_ASSERT(mComputedSizeOfUserFont > 0, "user font with no data?"); + result += mComputedSizeOfUserFont; + } + + return result; +} + +////////////////////////////////////////////////////////////////////////////// +// +// class gfxFontFamily +// +////////////////////////////////////////////////////////////////////////////// + +// we consider faces with mStandardFace == true to be "less than" those with +// false, because during style matching, earlier entries are tried first +class FontEntryStandardFaceComparator { + public: + bool Equals(const RefPtr<gfxFontEntry>& a, + const RefPtr<gfxFontEntry>& b) const { + return a->mStandardFace == b->mStandardFace; + } + bool LessThan(const RefPtr<gfxFontEntry>& a, + const RefPtr<gfxFontEntry>& b) const { + return (a->mStandardFace == true && b->mStandardFace == false); + } +}; + +void gfxFontFamily::SortAvailableFonts() { + MOZ_ASSERT(mLock.LockedForWritingByCurrentThread()); + mAvailableFonts.Sort(FontEntryStandardFaceComparator()); +} + +bool gfxFontFamily::HasOtherFamilyNames() { + // need to read in other family names to determine this + if (!mOtherFamilyNamesInitialized) { + ReadOtherFamilyNames( + gfxPlatformFontList::PlatformFontList()); // sets mHasOtherFamilyNames + } + return mHasOtherFamilyNames; +} + +gfxFontEntry* gfxFontFamily::FindFontForStyle(const gfxFontStyle& aFontStyle, + bool aIgnoreSizeTolerance) { + AutoTArray<gfxFontEntry*, 4> matched; + FindAllFontsForStyle(aFontStyle, matched, aIgnoreSizeTolerance); + if (!matched.IsEmpty()) { + return matched[0]; + } + return nullptr; +} + +static inline double WeightStyleStretchDistance( + gfxFontEntry* aFontEntry, const gfxFontStyle& aTargetStyle) { + double stretchDist = + StretchDistance(aFontEntry->Stretch(), aTargetStyle.stretch); + double styleDist = + StyleDistance(aFontEntry->SlantStyle(), aTargetStyle.style); + double weightDist = WeightDistance(aFontEntry->Weight(), aTargetStyle.weight); + + // Sanity-check that the distances are within the expected range + // (update if implementation of the distance functions is changed). + MOZ_ASSERT(stretchDist >= 0.0 && stretchDist <= 2000.0); + MOZ_ASSERT(styleDist >= 0.0 && styleDist <= 500.0); + MOZ_ASSERT(weightDist >= 0.0 && weightDist <= 1600.0); + + // weight/style/stretch priority: stretch >> style >> weight + // so we multiply the stretch and style values to make them dominate + // the result + return stretchDist * kStretchFactor + styleDist * kStyleFactor + + weightDist * kWeightFactor; +} + +void gfxFontFamily::FindAllFontsForStyle( + const gfxFontStyle& aFontStyle, nsTArray<gfxFontEntry*>& aFontEntryList, + bool aIgnoreSizeTolerance) { + if (!mHasStyles) { + FindStyleVariations(); // collect faces for the family, if not already + // done + } + + AutoReadLock lock(mLock); + + NS_ASSERTION(mAvailableFonts.Length() > 0, "font family with no faces!"); + NS_ASSERTION(aFontEntryList.IsEmpty(), "non-empty fontlist passed in"); + + gfxFontEntry* fe = nullptr; + + // If the family has only one face, we simply return it; no further + // checking needed + uint32_t count = mAvailableFonts.Length(); + if (count == 1) { + fe = mAvailableFonts[0]; + aFontEntryList.AppendElement(fe); + return; + } + + // Most families are "simple", having just Regular/Bold/Italic/BoldItalic, + // or some subset of these. In this case, we have exactly 4 entries in + // mAvailableFonts, stored in the above order; note that some of the entries + // may be nullptr. We can then pick the required entry based on whether the + // request is for bold or non-bold, italic or non-italic, without running the + // more complex matching algorithm used for larger families with many weights + // and/or widths. + + if (mIsSimpleFamily) { + // Family has no more than the "standard" 4 faces, at fixed indexes; + // calculate which one we want. + // Note that we cannot simply return it as not all 4 faces are necessarily + // present. + bool wantBold = aFontStyle.weight >= FontWeight::FromInt(600); + bool wantItalic = !aFontStyle.style.IsNormal(); + uint8_t faceIndex = + (wantItalic ? kItalicMask : 0) | (wantBold ? kBoldMask : 0); + + // if the desired style is available, return it directly + fe = mAvailableFonts[faceIndex]; + if (fe) { + aFontEntryList.AppendElement(fe); + return; + } + + // order to check fallback faces in a simple family, depending on requested + // style + static const uint8_t simpleFallbacks[4][3] = { + {kBoldFaceIndex, kItalicFaceIndex, + kBoldItalicFaceIndex}, // fallbacks for Regular + {kRegularFaceIndex, kBoldItalicFaceIndex, kItalicFaceIndex}, // Bold + {kBoldItalicFaceIndex, kRegularFaceIndex, kBoldFaceIndex}, // Italic + {kItalicFaceIndex, kBoldFaceIndex, kRegularFaceIndex} // BoldItalic + }; + const uint8_t* order = simpleFallbacks[faceIndex]; + + for (uint8_t trial = 0; trial < 3; ++trial) { + // check remaining faces in order of preference to find the first that + // actually exists + fe = mAvailableFonts[order[trial]]; + if (fe) { + aFontEntryList.AppendElement(fe); + return; + } + } + + // this can't happen unless we have totally broken the font-list manager! + MOZ_ASSERT_UNREACHABLE("no face found in simple font family!"); + } + + // Pick the font(s) that are closest to the desired weight, style, and + // stretch. Iterate over all fonts, measuring the weight/style distance. + // Because of unicode-range values, there may be more than one font for a + // given but the 99% use case is only a single font entry per + // weight/style/stretch distance value. To optimize this, only add entries + // to the matched font array when another entry already has the same + // weight/style/stretch distance and add the last matched font entry. For + // normal platform fonts with a single font entry for each + // weight/style/stretch combination, only the last matched font entry will + // be added. + + double minDistance = INFINITY; + gfxFontEntry* matched = nullptr; + // iterate in forward order so that faces like 'Bold' are matched before + // matching style distance faces such as 'Bold Outline' (see bug 1185812) + for (uint32_t i = 0; i < count; i++) { + fe = mAvailableFonts[i]; + // weight/style/stretch priority: stretch >> style >> weight + double distance = WeightStyleStretchDistance(fe, aFontStyle); + if (distance < minDistance) { + matched = fe; + if (!aFontEntryList.IsEmpty()) { + aFontEntryList.Clear(); + } + minDistance = distance; + } else if (distance == minDistance) { + if (matched) { + aFontEntryList.AppendElement(matched); + } + matched = fe; + } + } + + NS_ASSERTION(matched, "didn't match a font within a family"); + + if (matched) { + aFontEntryList.AppendElement(matched); + } +} + +void gfxFontFamily::CheckForSimpleFamily() { + MOZ_ASSERT(mLock.LockedForWritingByCurrentThread()); + // already checked this family + if (mIsSimpleFamily) { + return; + } + + uint32_t count = mAvailableFonts.Length(); + if (count > 4 || count == 0) { + return; // can't be "simple" if there are >4 faces; + // if none then the family is unusable anyway + } + + if (count == 1) { + mIsSimpleFamily = true; + return; + } + + StretchRange firstStretch = mAvailableFonts[0]->Stretch(); + if (!firstStretch.IsSingle()) { + return; // family with variation fonts is not considered "simple" + } + + gfxFontEntry* faces[4] = {0}; + for (uint8_t i = 0; i < count; ++i) { + gfxFontEntry* fe = mAvailableFonts[i]; + if (fe->Stretch() != firstStretch || fe->IsOblique()) { + // simple families don't have varying font-stretch or oblique + return; + } + if (!fe->Weight().IsSingle() || !fe->SlantStyle().IsSingle()) { + return; // family with variation fonts is not considered "simple" + } + uint8_t faceIndex = (fe->IsItalic() ? kItalicMask : 0) | + (fe->SupportsBold() ? kBoldMask : 0); + if (faces[faceIndex]) { + return; // two faces resolve to the same slot; family isn't "simple" + } + faces[faceIndex] = fe; + } + + // we have successfully slotted the available faces into the standard + // 4-face framework + mAvailableFonts.SetLength(4); + for (uint8_t i = 0; i < 4; ++i) { + if (mAvailableFonts[i].get() != faces[i]) { + mAvailableFonts[i].swap(faces[i]); + } + } + + mIsSimpleFamily = true; +} + +#ifdef DEBUG +bool gfxFontFamily::ContainsFace(gfxFontEntry* aFontEntry) { + AutoReadLock lock(mLock); + + uint32_t i, numFonts = mAvailableFonts.Length(); + for (i = 0; i < numFonts; i++) { + if (mAvailableFonts[i] == aFontEntry) { + return true; + } + // userfonts contain the actual real font entry + if (mAvailableFonts[i] && mAvailableFonts[i]->mIsUserFontContainer) { + gfxUserFontEntry* ufe = + static_cast<gfxUserFontEntry*>(mAvailableFonts[i].get()); + if (ufe->GetPlatformFontEntry() == aFontEntry) { + return true; + } + } + } + return false; +} +#endif + +void gfxFontFamily::LocalizedName(nsACString& aLocalizedName) { + // just return the primary name; subclasses should override + aLocalizedName = mName; +} + +void gfxFontFamily::FindFontForChar(GlobalFontMatch* aMatchData) { + gfxPlatformFontList::PlatformFontList()->mLock.AssertCurrentThreadIn(); + + { + AutoReadLock lock(mLock); + if (mFamilyCharacterMapInitialized && !TestCharacterMap(aMatchData->mCh)) { + // none of the faces in the family support the required char, + // so bail out immediately + return; + } + } + + nsCString charAndName; + if (profiler_thread_is_being_profiled( + Combine(ThreadProfilingFeatures::Sampling, + ThreadProfilingFeatures::Markers))) { + charAndName = nsPrintfCString("\\u%x %s", aMatchData->mCh, mName.get()); + } + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("gfxFontFamily::FindFontForChar", + LAYOUT, charAndName); + + AutoTArray<gfxFontEntry*, 4> entries; + FindAllFontsForStyle(aMatchData->mStyle, entries, + /*aIgnoreSizeTolerance*/ true); + if (entries.IsEmpty()) { + return; + } + + gfxFontEntry* fe = nullptr; + float distance = INFINITY; + + for (auto e : entries) { + if (e->SkipDuringSystemFallback()) { + continue; + } + + aMatchData->mCmapsTested++; + if (e->HasCharacter(aMatchData->mCh)) { + aMatchData->mCount++; + + LogModule* log = gfxPlatform::GetLog(eGfxLog_textrun); + + if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Debug))) { + intl::Script script = + intl::UnicodeProperties::GetScriptCode(aMatchData->mCh); + MOZ_LOG(log, LogLevel::Debug, + ("(textrun-systemfallback-fonts) char: u+%6.6x " + "script: %d match: [%s]\n", + aMatchData->mCh, int(script), e->Name().get())); + } + + fe = e; + distance = WeightStyleStretchDistance(fe, aMatchData->mStyle); + if (aMatchData->mPresentation != eFontPresentation::Any) { + RefPtr<gfxFont> font = fe->FindOrMakeFont(&aMatchData->mStyle); + if (!font) { + continue; + } + bool hasColorGlyph = + font->HasColorGlyphFor(aMatchData->mCh, aMatchData->mNextCh); + if (hasColorGlyph != PrefersColor(aMatchData->mPresentation)) { + distance += kPresentationMismatch; + } + } + break; + } + } + + if (!fe && !aMatchData->mStyle.IsNormalStyle()) { + // If style/weight/stretch was not Normal, see if we can + // fall back to a next-best face (e.g. Arial Black -> Bold, + // or Arial Narrow -> Regular). + GlobalFontMatch data(aMatchData->mCh, aMatchData->mNextCh, + aMatchData->mStyle, aMatchData->mPresentation); + SearchAllFontsForChar(&data); + if (!data.mBestMatch) { + return; + } + fe = data.mBestMatch; + distance = data.mMatchDistance; + } + + if (!fe) { + return; + } + + if (distance < aMatchData->mMatchDistance || + (distance == aMatchData->mMatchDistance && + Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0)) { + aMatchData->mBestMatch = fe; + aMatchData->mMatchedFamily = this; + aMatchData->mMatchDistance = distance; + } +} + +void gfxFontFamily::SearchAllFontsForChar(GlobalFontMatch* aMatchData) { + if (!mFamilyCharacterMapInitialized) { + ReadAllCMAPs(); + } + AutoReadLock lock(mLock); + if (!mFamilyCharacterMap.test(aMatchData->mCh)) { + return; + } + uint32_t i, numFonts = mAvailableFonts.Length(); + for (i = 0; i < numFonts; i++) { + gfxFontEntry* fe = mAvailableFonts[i]; + if (fe && fe->HasCharacter(aMatchData->mCh)) { + float distance = WeightStyleStretchDistance(fe, aMatchData->mStyle); + if (aMatchData->mPresentation != eFontPresentation::Any) { + RefPtr<gfxFont> font = fe->FindOrMakeFont(&aMatchData->mStyle); + if (!font) { + continue; + } + bool hasColorGlyph = + font->HasColorGlyphFor(aMatchData->mCh, aMatchData->mNextCh); + if (hasColorGlyph != PrefersColor(aMatchData->mPresentation)) { + distance += kPresentationMismatch; + } + } + if (distance < aMatchData->mMatchDistance || + (distance == aMatchData->mMatchDistance && + Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0)) { + aMatchData->mBestMatch = fe; + aMatchData->mMatchedFamily = this; + aMatchData->mMatchDistance = distance; + } + } + } +} + +/*virtual*/ +gfxFontFamily::~gfxFontFamily() { + // Should not be dropped by stylo, but the InitFontList thread might use + // a transient gfxFontFamily and that's OK. + MOZ_ASSERT(!gfxFontUtils::IsInServoTraversal()); +} + +// returns true if other names were found, false otherwise +bool gfxFontFamily::ReadOtherFamilyNamesForFace( + gfxPlatformFontList* aPlatformFontList, hb_blob_t* aNameTable, + bool useFullName) { + uint32_t dataLength; + const char* nameData = hb_blob_get_data(aNameTable, &dataLength); + AutoTArray<nsCString, 4> otherFamilyNames; + + gfxFontUtils::ReadOtherFamilyNamesForFace(mName, nameData, dataLength, + otherFamilyNames, useFullName); + + if (!otherFamilyNames.IsEmpty()) { + aPlatformFontList->AddOtherFamilyNames(this, otherFamilyNames); + } + + return !otherFamilyNames.IsEmpty(); +} + +void gfxFontFamily::ReadOtherFamilyNames( + gfxPlatformFontList* aPlatformFontList) { + AutoWriteLock lock(mLock); + if (mOtherFamilyNamesInitialized) { + return; + } + + mOtherFamilyNamesInitialized = true; + + FindStyleVariationsLocked(); + + // read in other family names for the first face in the list + uint32_t i, numFonts = mAvailableFonts.Length(); + const uint32_t kNAME = TRUETYPE_TAG('n', 'a', 'm', 'e'); + + for (i = 0; i < numFonts; ++i) { + gfxFontEntry* fe = mAvailableFonts[i]; + if (!fe) { + continue; + } + gfxFontEntry::AutoTable nameTable(fe, kNAME); + if (!nameTable) { + continue; + } + mHasOtherFamilyNames = + ReadOtherFamilyNamesForFace(aPlatformFontList, nameTable); + break; + } + + // read in other names for the first face in the list with the assumption + // that if extra names don't exist in that face then they don't exist in + // other faces for the same font + if (!mHasOtherFamilyNames) { + return; + } + + // read in names for all faces, needed to catch cases where fonts have + // family names for individual weights (e.g. Hiragino Kaku Gothic Pro W6) + for (; i < numFonts; i++) { + gfxFontEntry* fe = mAvailableFonts[i]; + if (!fe) { + continue; + } + gfxFontEntry::AutoTable nameTable(fe, kNAME); + if (!nameTable) { + continue; + } + ReadOtherFamilyNamesForFace(aPlatformFontList, nameTable); + } +} + +static bool LookForLegacyFamilyName(const nsACString& aCanonicalName, + const char* aNameData, uint32_t aDataLength, + nsACString& aLegacyName /* outparam */) { + const gfxFontUtils::NameHeader* nameHeader = + reinterpret_cast<const gfxFontUtils::NameHeader*>(aNameData); + + uint32_t nameCount = nameHeader->count; + if (nameCount * sizeof(gfxFontUtils::NameRecord) > aDataLength) { + NS_WARNING("invalid font (name records)"); + return false; + } + + const gfxFontUtils::NameRecord* nameRecord = + reinterpret_cast<const gfxFontUtils::NameRecord*>( + aNameData + sizeof(gfxFontUtils::NameHeader)); + uint32_t stringsBase = uint32_t(nameHeader->stringOffset); + + for (uint32_t i = 0; i < nameCount; i++, nameRecord++) { + uint32_t nameLen = nameRecord->length; + uint32_t nameOff = nameRecord->offset; + + if (stringsBase + nameOff + nameLen > aDataLength) { + NS_WARNING("invalid font (name table strings)"); + return false; + } + + if (uint16_t(nameRecord->nameID) == gfxFontUtils::NAME_ID_FAMILY) { + bool ok = gfxFontUtils::DecodeFontName( + aNameData + stringsBase + nameOff, nameLen, + uint32_t(nameRecord->platformID), uint32_t(nameRecord->encodingID), + uint32_t(nameRecord->languageID), aLegacyName); + // It's only a legacy name if it case-insensitively differs from the + // canonical name (otherwise it would map to the same key). + if (ok && !aLegacyName.Equals(aCanonicalName, + nsCaseInsensitiveCStringComparator)) { + return true; + } + } + } + return false; +} + +bool gfxFontFamily::CheckForLegacyFamilyNames(gfxPlatformFontList* aFontList) { + aFontList->mLock.AssertCurrentThreadIn(); + if (mCheckedForLegacyFamilyNames) { + // we already did this, so there's nothing more to add + return false; + } + mCheckedForLegacyFamilyNames = true; + bool added = false; + const uint32_t kNAME = TRUETYPE_TAG('n', 'a', 'm', 'e'); + AutoTArray<RefPtr<gfxFontEntry>, 16> faces; + { + // Take a local copy of the array of font entries, because it's possible + // AddWithLegacyFamilyName will mutate it (and it needs to be able to take + // an exclusive lock on the family to do so, so we release the read lock + // here). + AutoReadLock lock(mLock); + faces.AppendElements(mAvailableFonts); + } + for (const auto& fe : faces) { + if (!fe) { + continue; + } + gfxFontEntry::AutoTable nameTable(fe, kNAME); + if (!nameTable) { + continue; + } + nsAutoCString legacyName; + uint32_t dataLength; + const char* nameData = hb_blob_get_data(nameTable, &dataLength); + if (LookForLegacyFamilyName(Name(), nameData, dataLength, legacyName)) { + if (aFontList->AddWithLegacyFamilyName(legacyName, fe, mVisibility)) { + added = true; + } + } + } + return added; +} + +void gfxFontFamily::ReadFaceNames(gfxPlatformFontList* aPlatformFontList, + bool aNeedFullnamePostscriptNames, + FontInfoData* aFontInfoData) { + aPlatformFontList->mLock.AssertCurrentThreadIn(); + + // if all needed names have already been read, skip + if (mOtherFamilyNamesInitialized && + (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) { + return; + } + + AutoWriteLock lock(mLock); + + bool asyncFontLoaderDisabled = false; + + if (!mOtherFamilyNamesInitialized && aFontInfoData && + aFontInfoData->mLoadOtherNames && !asyncFontLoaderDisabled) { + const auto* otherFamilyNames = aFontInfoData->GetOtherFamilyNames(mName); + if (otherFamilyNames && otherFamilyNames->Length()) { + aPlatformFontList->AddOtherFamilyNames(this, *otherFamilyNames); + } + mOtherFamilyNamesInitialized = true; + } + + // if all needed data has been initialized, return + if (mOtherFamilyNamesInitialized && + (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) { + return; + } + + FindStyleVariationsLocked(aFontInfoData); + + // check again, as style enumeration code may have loaded names + if (mOtherFamilyNamesInitialized && + (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) { + return; + } + + uint32_t i, numFonts = mAvailableFonts.Length(); + const uint32_t kNAME = TRUETYPE_TAG('n', 'a', 'm', 'e'); + + bool firstTime = true, readAllFaces = false; + for (i = 0; i < numFonts; ++i) { + gfxFontEntry* fe = mAvailableFonts[i]; + if (!fe) { + continue; + } + + nsAutoCString fullname, psname; + bool foundFaceNames = false; + if (!mFaceNamesInitialized && aNeedFullnamePostscriptNames && + aFontInfoData && aFontInfoData->mLoadFaceNames) { + aFontInfoData->GetFaceNames(fe->Name(), fullname, psname); + if (!fullname.IsEmpty()) { + aPlatformFontList->AddFullnameLocked(fe, fullname); + } + if (!psname.IsEmpty()) { + aPlatformFontList->AddPostscriptNameLocked(fe, psname); + } + foundFaceNames = true; + + // found everything needed? skip to next font + if (mOtherFamilyNamesInitialized) { + continue; + } + } + + // load directly from the name table + gfxFontEntry::AutoTable nameTable(fe, kNAME); + if (!nameTable) { + continue; + } + + if (aNeedFullnamePostscriptNames && !foundFaceNames) { + if (gfxFontUtils::ReadCanonicalName(nameTable, gfxFontUtils::NAME_ID_FULL, + fullname) == NS_OK) { + aPlatformFontList->AddFullnameLocked(fe, fullname); + } + + if (gfxFontUtils::ReadCanonicalName( + nameTable, gfxFontUtils::NAME_ID_POSTSCRIPT, psname) == NS_OK) { + aPlatformFontList->AddPostscriptNameLocked(fe, psname); + } + } + + if (!mOtherFamilyNamesInitialized && (firstTime || readAllFaces)) { + bool foundOtherName = + ReadOtherFamilyNamesForFace(aPlatformFontList, nameTable); + + // if the first face has a different name, scan all faces, otherwise + // assume the family doesn't have other names + if (firstTime && foundOtherName) { + mHasOtherFamilyNames = true; + readAllFaces = true; + } + firstTime = false; + } + + // if not reading in any more names, skip other faces + if (!readAllFaces && !aNeedFullnamePostscriptNames) { + break; + } + } + + mFaceNamesInitialized = true; + mOtherFamilyNamesInitialized = true; +} + +gfxFontEntry* gfxFontFamily::FindFont(const nsACString& aFontName, + const nsCStringComparator& aCmp) const { + // find the font using a simple linear search + AutoReadLock lock(mLock); + uint32_t numFonts = mAvailableFonts.Length(); + for (uint32_t i = 0; i < numFonts; i++) { + gfxFontEntry* fe = mAvailableFonts[i].get(); + if (fe && fe->Name().Equals(aFontName, aCmp)) { + return fe; + } + } + return nullptr; +} + +void gfxFontFamily::ReadAllCMAPs(FontInfoData* aFontInfoData) { + AutoWriteLock lock(mLock); + FindStyleVariationsLocked(aFontInfoData); + + uint32_t i, numFonts = mAvailableFonts.Length(); + for (i = 0; i < numFonts; i++) { + gfxFontEntry* fe = mAvailableFonts[i]; + // don't try to load cmaps for downloadable fonts not yet loaded + if (!fe || fe->mIsUserFontContainer) { + continue; + } + fe->ReadCMAP(aFontInfoData); + mFamilyCharacterMap.Union(*(fe->GetCharacterMap())); + } + mFamilyCharacterMap.Compact(); + mFamilyCharacterMapInitialized = true; +} + +void gfxFontFamily::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + AutoReadLock lock(mLock); + aSizes->mFontListSize += mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + aSizes->mCharMapsSize += + mFamilyCharacterMap.SizeOfExcludingThis(aMallocSizeOf); + + aSizes->mFontListSize += + mAvailableFonts.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (uint32_t i = 0; i < mAvailableFonts.Length(); ++i) { + gfxFontEntry* fe = mAvailableFonts[i]; + if (fe) { + fe->AddSizeOfIncludingThis(aMallocSizeOf, aSizes); + } + } +} + +void gfxFontFamily::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} diff --git a/gfx/thebes/gfxFontEntry.h b/gfx/thebes/gfxFontEntry.h new file mode 100644 index 0000000000..888b85f2c9 --- /dev/null +++ b/gfx/thebes/gfxFontEntry.h @@ -0,0 +1,1253 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FONTENTRY_H +#define GFX_FONTENTRY_H + +#include <limits> +#include <math.h> +#include <new> +#include <utility> +#include "COLRFonts.h" +#include "ThebesRLBoxTypes.h" +#include "gfxFontUtils.h" +#include "gfxFontVariations.h" +#include "gfxRect.h" +#include "gfxTypes.h" +#include "harfbuzz/hb.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/RWLock.h" +#include "mozilla/TypedEnumBits.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/intl/UnicodeScriptCodes.h" +#include "nsTHashMap.h" +#include "nsDebug.h" +#include "nsHashKeys.h" +#include "nsISupports.h" +#include "nsStringFwd.h" +#include "nsTArray.h" +#include "nscore.h" + +class FontInfoData; +class gfxContext; +class gfxFont; +class gfxFontFamily; +class gfxPlatformFontList; +class gfxSVGGlyphs; +class gfxUserFontData; +class nsAtom; +struct FontListSizes; +struct gfxFontFeature; +struct gfxFontStyle; +enum class eFontPresentation : uint8_t; + +namespace IPC { +template <class P> +struct ParamTraits; +} + +namespace mozilla { +class SVGContextPaint; +namespace fontlist { +struct Face; +struct Family; +} // namespace fontlist +} // namespace mozilla + +typedef struct gr_face gr_face; +typedef struct FT_MM_Var_ FT_MM_Var; + +#define NO_FONT_LANGUAGE_OVERRIDE 0 + +class gfxCharacterMap : public gfxSparseBitSet { + public: + // gfxCharacterMap instances may be shared across multiple threads via a + // global table managed by gfxPlatformFontList. Once a gfxCharacterMap is + // inserted in the global table, its mShared flag will be TRUE, and we + // cannot safely delete it except from gfxPlatformFontList (which will + // use a lock to ensure entries are removed from its table and deleted + // safely). + + // AddRef() is pretty much standard. We don't return the refcount as our + // users don't care about it. + void AddRef() { + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(gfxCharacterMap); + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); + [[maybe_unused]] nsrefcnt count = ++mRefCnt; + NS_LOG_ADDREF(this, count, "gfxCharacterMap", sizeof(*this)); + } + + // Custom Release(): if the object is referenced from the global shared + // table, and we're releasing the last *other* reference to it, then we + // notify the global table to consider also releasing its ref. (That may + // not actually happen, if another thread is racing with us and takes a + // new reference, or completes the release first!) + void Release() { + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); + // We can't safely read this after we've decremented mRefCnt, so save it + // in a local variable here. Note that the value is never reset to false + // once it has been set to true (when recording the cmap in the shared + // table), so there's no risk of this resulting in a "false positive" when + // tested later. A "false negative" is possible but harmless; it would + // just mean we miss an opportunity to release a reference from the shared + // cmap table. + bool isShared = mShared; + + // Ensure we only access mRefCnt once, for consistency if the object is + // being used by multiple threads. + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "gfxCharacterMap"); + + // If isShared was true, this object has been shared across threads. In + // that case, if the refcount went to 1, we notify the shared table so + // it can drop its reference and delete the object. + if (isShared) { + MOZ_ASSERT(count > 0); + if (count == 1) { + NotifyMaybeReleased(this); + } + return; + } + + // Otherwise, this object hasn't been shared and we can safely delete it + // as we must have been holding the only reference. (Note that if we were + // holding the only reference, there's no other owner who can have set + // mShared to true since we read it above.) + if (count == 0) { + delete this; + } + } + + gfxCharacterMap() = default; + + explicit gfxCharacterMap(const gfxSparseBitSet& aOther) + : gfxSparseBitSet(aOther) {} + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return gfxSparseBitSet::SizeOfExcludingThis(aMallocSizeOf); + } + + // hash of the cmap bitvector + uint32_t mHash = 0; + + // if cmap is built on the fly it's never shared + bool mBuildOnTheFly = false; + + // Character map is shared globally. This can only be set by the thread that + // originally created the map, as no other thread can get a reference until + // it has been shared via the global table. + bool mShared = false; + + protected: + friend class gfxPlatformFontList; + + // Destructor should not be called except via Release(). + // (Note that our "friend" gfxPlatformFontList also accesses this from its + // MaybeRemoveCmap method.) + ~gfxCharacterMap() = default; + + nsrefcnt RefCount() const { return mRefCnt; } + + void CalcHash() { mHash = GetChecksum(); } + + static void NotifyMaybeReleased(gfxCharacterMap* aCmap); + + // Only used when clearing the shared-cmap hashtable during shutdown. + void ClearSharedFlag() { + MOZ_ASSERT(NS_IsMainThread()); + mShared = false; + } + + mozilla::ThreadSafeAutoRefCnt mRefCnt; + + private: + gfxCharacterMap(const gfxCharacterMap&) = delete; + gfxCharacterMap& operator=(const gfxCharacterMap&) = delete; +}; + +// Info on an individual font feature, for reporting available features +// to DevTools via the GetFeatureInfo method. +struct gfxFontFeatureInfo { + uint32_t mTag; + uint32_t mScript; + uint32_t mLangSys; +}; + +class gfxFontEntryCallbacks; + +class gfxFontEntry { + public: + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::intl::Script Script; + typedef mozilla::FontWeight FontWeight; + typedef mozilla::FontSlantStyle FontSlantStyle; + typedef mozilla::FontStretch FontStretch; + typedef mozilla::WeightRange WeightRange; + typedef mozilla::SlantStyleRange SlantStyleRange; + typedef mozilla::StretchRange StretchRange; + + // Used by stylo + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxFontEntry) + + explicit gfxFontEntry(const nsACString& aName, bool aIsStandardFace = false); + + gfxFontEntry() = delete; + gfxFontEntry(const gfxFontEntry&) = delete; + gfxFontEntry& operator=(const gfxFontEntry&) = delete; + + // Create a new entry that refers to the same font as this, but without + // additional state that may have been set up (such as family name). + // (This is only to be used for system fonts in the platform font list, + // not user fonts.) + virtual gfxFontEntry* Clone() const = 0; + + // unique name for the face, *not* the family; not necessarily the + // "real" or user-friendly name, may be an internal identifier + const nsCString& Name() const { return mName; } + + // family name + const nsCString& FamilyName() const { return mFamilyName; } + + // The following two methods may be relatively expensive, as they + // will (usually, except on Linux) load and parse the 'name' table; + // they are intended only for the font-inspection API, not for + // perf-critical layout/drawing work. + + // The "real" name of the face, if available from the font resource; + // returns Name() if nothing better is available. + virtual nsCString RealFaceName(); + + WeightRange Weight() const { return mWeightRange; } + StretchRange Stretch() const { return mStretchRange; } + SlantStyleRange SlantStyle() const { return mStyleRange; } + + bool IsUserFont() const { return mIsDataUserFont || mIsLocalUserFont; } + bool IsLocalUserFont() const { return mIsLocalUserFont; } + bool IsFixedPitch() const { return mFixedPitch; } + bool IsItalic() const { return SlantStyle().Min().IsItalic(); } + bool IsOblique() const { return SlantStyle().Min().IsOblique(); } + bool IsUpright() const { return SlantStyle().Min().IsNormal(); } + inline bool SupportsItalic(); // defined below, because of RangeFlags use + inline bool SupportsBold(); + inline bool MayUseSyntheticSlant(); + bool IgnoreGDEF() const { return mIgnoreGDEF; } + bool IgnoreGSUB() const { return mIgnoreGSUB; } + + // Return whether the face corresponds to "normal" CSS style properties: + // font-style: normal; + // font-weight: normal; + // font-stretch: normal; + // If this is false, we might want to fall back to a different face and + // possibly apply synthetic styling. + bool IsNormalStyle() const { + return IsUpright() && Weight().Min() <= FontWeight::NORMAL && + Weight().Max() >= FontWeight::NORMAL && + Stretch().Min() <= FontStretch::NORMAL && + Stretch().Max() >= FontStretch::NORMAL; + } + + // whether a feature is supported by the font (limited to a small set + // of features for which some form of fallback needs to be implemented) + virtual bool SupportsOpenTypeFeature(Script aScript, uint32_t aFeatureTag); + bool SupportsGraphiteFeature(uint32_t aFeatureTag); + + // returns a set containing all input glyph ids for a given feature + const hb_set_t* InputsForOpenTypeFeature(Script aScript, + uint32_t aFeatureTag); + + virtual bool HasFontTable(uint32_t aTableTag); + + inline bool HasGraphiteTables() { + LazyFlag flag = mHasGraphiteTables; + if (flag == LazyFlag::Uninitialized) { + flag = CheckForGraphiteTables() ? LazyFlag::Yes : LazyFlag::No; + mHasGraphiteTables = flag; + } + return flag == LazyFlag::Yes; + } + + inline bool HasCmapTable() { + if (!mCharacterMap && !mShmemCharacterMap) { + ReadCMAP(); + NS_ASSERTION(mCharacterMap || mShmemCharacterMap, + "failed to initialize character map"); + } + return mHasCmapTable; + } + + inline bool HasCharacter(uint32_t ch) { + if (mShmemCharacterMap) { + return GetShmemCharacterMap()->test(ch); + } + if (mCharacterMap) { + if (mShmemFace && TrySetShmemCharacterMap()) { + // Forget our temporary local copy, now we can use the shared cmap + auto* oldCmap = mCharacterMap.exchange(nullptr); + NS_IF_RELEASE(oldCmap); + return GetShmemCharacterMap()->test(ch); + } + if (GetCharacterMap()->test(ch)) { + return true; + } + } + return TestCharacterMap(ch); + } + + virtual bool SkipDuringSystemFallback() { return false; } + void EnsureUVSMapInitialized(); + uint16_t GetUVSGlyph(uint32_t aCh, uint32_t aVS); + + // All concrete gfxFontEntry subclasses (except gfxUserFontEntry) need + // to override this, otherwise the font will never be used as it will + // be considered to support no characters. + // ReadCMAP() must *always* set the mCharacterMap pointer to a valid + // gfxCharacterMap, even if empty, as other code assumes this pointer + // can be safely dereferenced. + virtual nsresult ReadCMAP(FontInfoData* aFontInfoData = nullptr); + + bool TryGetSVGData(const gfxFont* aFont); + bool HasSVGGlyph(uint32_t aGlyphId); + bool GetSVGGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphId, + gfxFloat aSize, gfxRect* aResult); + void RenderSVGGlyph(gfxContext* aContext, uint32_t aGlyphId, + mozilla::SVGContextPaint* aContextPaint); + // Call this when glyph geometry or rendering has changed + // (e.g. animated SVG glyphs) + void NotifyGlyphsChanged(); + + bool TryGetColorGlyphs(); + + bool HasColorBitmapTable() { + LazyFlag flag = mHasColorBitmapTable; + if (flag == LazyFlag::Uninitialized) { + flag = HasFontTable(TRUETYPE_TAG('C', 'B', 'D', 'T')) || + HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x')) + ? LazyFlag::Yes + : LazyFlag::No; + mHasColorBitmapTable = flag; + } + return flag == LazyFlag::Yes; + } + + // Access to raw font table data (needed for Harfbuzz): + // returns a pointer to data owned by the fontEntry or the OS, + // which will remain valid until the blob is destroyed. + // The data MUST be treated as read-only; we may be getting a + // reference to a shared system font cache. + // + // The default implementation uses CopyFontTable to get the data + // into a byte array, and maintains a cache of loaded tables. + // + // Subclasses should override this if they can provide more efficient + // access than copying table data into our own buffers. + // + // Get blob that encapsulates a specific font table, or nullptr if + // the table doesn't exist in the font. + // + // Caller is responsible to call hb_blob_destroy() on the returned blob + // (if non-nullptr) when no longer required. For transient access to a + // table, use of AutoTable (below) is generally preferred. + virtual hb_blob_t* GetFontTable(uint32_t aTag); + + // Stack-based utility to return a specified table, automatically releasing + // the blob when the AutoTable goes out of scope. + class AutoTable { + public: + AutoTable(gfxFontEntry* aFontEntry, uint32_t aTag) { + mBlob = aFontEntry->GetFontTable(aTag); + } + ~AutoTable() { hb_blob_destroy(mBlob); } + operator hb_blob_t*() const { return mBlob; } + + private: + hb_blob_t* mBlob; + // not implemented: + AutoTable(const AutoTable&) = delete; + AutoTable& operator=(const AutoTable&) = delete; + }; + + // Return a font instance for a particular style. This may be a newly- + // created instance, or a font already in the global cache. + // We can't return a UniquePtr here, because we may be returning a shared + // cached instance; but we also don't return already_AddRefed, because + // the caller may only need to use the font temporarily and doesn't need + // a strong reference. + already_AddRefed<gfxFont> FindOrMakeFont( + const gfxFontStyle* aStyle, gfxCharacterMap* aUnicodeRangeMap = nullptr); + + // Get an existing font table cache entry in aBlob if it has been + // registered, or return false if not. Callers must call + // hb_blob_destroy on aBlob if true is returned. + // + // Note that some gfxFont implementations may not call this at all, + // if it is more efficient to get the table from the OS at that level. + bool GetExistingFontTable(uint32_t aTag, hb_blob_t** aBlob); + + // Elements of aTable are transferred (not copied) to and returned in a + // new hb_blob_t which is registered on the gfxFontEntry, but the initial + // reference is owned by the caller. Removing the last reference + // unregisters the table from the font entry. + // + // Pass nullptr for aBuffer to indicate that the table is not present and + // nullptr will be returned. Also returns nullptr on OOM. + hb_blob_t* ShareFontTableAndGetBlob(uint32_t aTag, nsTArray<uint8_t>* aTable); + + // Get the font's unitsPerEm from the 'head' table, in the case of an + // sfnt resource. Will return kInvalidUPEM for non-sfnt fonts, + // if present on the platform. + uint16_t UnitsPerEm(); + enum { + kMinUPEM = 16, // Limits on valid unitsPerEm range, from the + kMaxUPEM = 16384, // OpenType spec + kInvalidUPEM = uint16_t(-1) + }; + + // Shaper face accessors: + // NOTE that harfbuzz and graphite handle ownership/lifetime of the face + // object in completely different ways. + + // Create a HarfBuzz face corresponding to this font file. + // Our reference to the underlying hb_face_t will be released when the + // returned AutoHBFace goes out of scope, but the hb_face_t itself may + // be kept alive by other references (e.g. if an hb_font_t has been + // instantiated for it). + class MOZ_STACK_CLASS AutoHBFace { + public: + explicit AutoHBFace(hb_face_t* aFace) : mFace(aFace) {} + ~AutoHBFace() { hb_face_destroy(mFace); } + + operator hb_face_t*() const { return mFace; } + + // Not default-constructible, not copyable. + AutoHBFace() = delete; + AutoHBFace(const AutoHBFace&) = delete; + AutoHBFace& operator=(const AutoHBFace&) = delete; + + private: + hb_face_t* mFace; + }; + + AutoHBFace GetHBFace() { + return AutoHBFace(hb_face_create_for_tables(HBGetTable, this, nullptr)); + } + + // Get the sandbox instance that graphite is running in. + rlbox_sandbox_gr* GetGrSandbox(); + + // Register and get the callback handle for the glyph advance firefox callback + // Since the sandbox instance is shared with multiple test shapers, callback + // registration must be handled centrally to ensure multiple instances don't + // register the same callback. + sandbox_callback_gr<float (*)(const void*, uint16_t)>* + GetGrSandboxAdvanceCallbackHandle(); + + // Get Graphite face corresponding to this font file. + // Caller must call gfxFontEntry::ReleaseGrFace when finished with it. + // Graphite is run in a sandbox + tainted_opaque_gr<gr_face*> GetGrFace(); + void ReleaseGrFace(tainted_opaque_gr<gr_face*> aFace); + + // Does the font have graphite contextuals that involve the space glyph + // (and therefore we should bypass the word cache)? + // Since this function inspects data from libGraphite stored in sandbox memory + // it can only return a "hint" to the correct return value. This is because + // a compromised libGraphite could change the sandbox memory maliciously at + // any moment. The caller must ensure the calling code performs safe actions + // independent of the value returned, to unwrap this return. + tainted_boolean_hint HasGraphiteSpaceContextuals(); + + // Release any SVG-glyphs document this font may have loaded. + void DisconnectSVG(); + + // Called to notify that aFont is being destroyed. Needed when we're tracking + // the fonts belonging to this font entry. + void NotifyFontDestroyed(gfxFont* aFont); + + // For memory reporting of the platform font list. + virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const; + virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const; + + // Used for reporting on individual font entries in the user font cache, + // which are not present in the platform font list. + size_t ComputedSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + // Used when checking for complex script support, to mask off cmap ranges + struct ScriptRange { + uint32_t rangeStart; + uint32_t rangeEnd; + uint32_t numTags; // number of entries in the tags[] array + hb_tag_t tags[3]; // up to three OpenType script tags to check + }; + + bool SupportsScriptInGSUB(const hb_tag_t* aScriptTags, uint32_t aNumTags); + + /** + * Font-variation query methods. + * + * Font backends that don't support variations should provide empty + * implementations. + */ + virtual bool HasVariations() = 0; + + virtual void GetVariationAxes( + nsTArray<gfxFontVariationAxis>& aVariationAxes) = 0; + + virtual void GetVariationInstances( + nsTArray<gfxFontVariationInstance>& aInstances) = 0; + + bool HasBoldVariableWeight(); + bool HasItalicVariation(); + bool HasSlantVariation(); + bool HasOpticalSize(); + + void CheckForVariationAxes(); + + // Set up the entry's weight/stretch/style ranges according to axes found + // by GetVariationAxes (for installed fonts; do NOT call this for user + // fonts, where the ranges are provided by @font-face descriptors). + void SetupVariationRanges(); + + // Get variation axis settings that should be used to implement a particular + // font style using this resource. + void GetVariationsForStyle(nsTArray<gfxFontVariation>& aResult, + const gfxFontStyle& aStyle); + + // Get the font's list of features (if any) for DevTools support. + void GetFeatureInfo(nsTArray<gfxFontFeatureInfo>& aFeatureInfo); + + // This is only called on platforms where we use FreeType. + virtual FT_MM_Var* GetMMVar() { return nullptr; } + + // Return true if the font has a 'trak' table (and we can successfully + // interpret it), otherwise false. This will load and cache the table + // the first time it is called. + bool HasTrackingTable(); + + // Return the tracking (in font units) to be applied for the given size. + // (This is a floating-point number because of possible interpolation.) + gfxFloat TrackingForCSSPx(gfxFloat aSize) const; + + mozilla::gfx::Rect GetFontExtents(float aFUnitScaleFactor) const { + // Flip the y-axis here to match the orientation of Gecko's coordinates. + return mozilla::gfx::Rect(float(mXMin) * aFUnitScaleFactor, + float(-mYMax) * aFUnitScaleFactor, + float(mXMax - mXMin) * aFUnitScaleFactor, + float(mYMax - mYMin) * aFUnitScaleFactor); + } + + nsCString mName; + nsCString mFamilyName; + + // These are mutable so that we can take a read lock within a const method. + mutable mozilla::RWLock mLock; + mutable mozilla::Mutex mFeatureInfoLock; + + mozilla::Atomic<gfxCharacterMap*> mCharacterMap; // strong ref + gfxCharacterMap* GetCharacterMap() const { return mCharacterMap; } + + mozilla::fontlist::Face* mShmemFace = nullptr; + const mozilla::fontlist::Family* mShmemFamily = nullptr; + + mozilla::Atomic<const SharedBitSet*> mShmemCharacterMap; + const SharedBitSet* GetShmemCharacterMap() const { + return mShmemCharacterMap; + } + + mozilla::Atomic<const uint8_t*> mUVSData; + const uint8_t* GetUVSData() const { return mUVSData; } + + mozilla::UniquePtr<gfxUserFontData> mUserFontData; + + mozilla::Atomic<gfxSVGGlyphs*> mSVGGlyphs; + gfxSVGGlyphs* GetSVGGlyphs() const { return mSVGGlyphs; } + + // list of gfxFonts that are using SVG glyphs + nsTArray<const gfxFont*> mFontsUsingSVGGlyphs MOZ_GUARDED_BY(mLock); + nsTArray<gfxFontFeature> mFeatureSettings; + nsTArray<gfxFontVariation> mVariationSettings; + + mozilla::UniquePtr<nsTHashMap<nsUint32HashKey, bool>> mSupportedFeatures + MOZ_GUARDED_BY(mFeatureInfoLock); + mozilla::UniquePtr<nsTHashMap<nsUint32HashKey, hb_set_t*>> mFeatureInputs + MOZ_GUARDED_BY(mFeatureInfoLock); + + // Color Layer font support. These tables are inert once loaded, so we don't + // need to hold a lock when reading them. + mozilla::Atomic<hb_blob_t*> mCOLR; + mozilla::Atomic<hb_blob_t*> mCPAL; + hb_blob_t* GetCOLR() const { return mCOLR; } + hb_blob_t* GetCPAL() const { return mCPAL; } + + // bitvector of substitution space features per script, one each + // for default and non-default features + uint32_t mDefaultSubSpaceFeatures[(int(Script::NUM_SCRIPT_CODES) + 31) / 32]; + uint32_t + mNonDefaultSubSpaceFeatures[(int(Script::NUM_SCRIPT_CODES) + 31) / 32]; + + mozilla::Atomic<uint32_t> mUVSOffset; + + uint32_t mLanguageOverride = NO_FONT_LANGUAGE_OVERRIDE; + + WeightRange mWeightRange = WeightRange(FontWeight::FromInt(500)); + StretchRange mStretchRange = StretchRange(FontStretch::NORMAL); + SlantStyleRange mStyleRange = SlantStyleRange(FontSlantStyle::NORMAL); + + // Font metrics overrides (as multiples of used font size); negative values + // indicate no override to be applied. + float mAscentOverride = -1.0; + float mDescentOverride = -1.0; + float mLineGapOverride = -1.0; + + // Scaling factor to be applied to the font size. + float mSizeAdjust = 1.0; + + // For user fonts (only), we need to record whether or not weight/stretch/ + // slant variations should be clamped to the range specified in the entry + // properties. When the @font-face rule omitted one or more of these + // descriptors, it is treated as the initial value for font-matching (and + // so that is what we record in the font entry), but when rendering the + // range is NOT clamped. + enum class RangeFlags : uint16_t { + eNoFlags = 0, + eAutoWeight = (1 << 0), + eAutoStretch = (1 << 1), + eAutoSlantStyle = (1 << 2), + + // Flag to record whether the face has a variable "wght" axis + // that supports "bold" values, used to disable the application + // of synthetic-bold effects. + eBoldVariableWeight = (1 << 3), + // Whether the face has an 'ital' axis. + eItalicVariation = (1 << 4), + // Whether the face has a 'slnt' axis. + eSlantVariation = (1 << 5), + + // Flags to record if the face uses a non-CSS-compatible scale + // for weight and/or stretch, in which case we won't map the + // properties to the variation axes (though they can still be + // explicitly set using font-variation-settings). + eNonCSSWeight = (1 << 6), + eNonCSSStretch = (1 << 7), + + // Whether the font has an 'opsz' axis. + eOpticalSize = (1 << 8) + }; + RangeFlags mRangeFlags = RangeFlags::eNoFlags; + + bool mFixedPitch : 1; + bool mIsBadUnderlineFont : 1; + bool mIsUserFontContainer : 1; // userfont entry + bool mIsDataUserFont : 1; // platform font entry (data) + bool mIsLocalUserFont : 1; // platform font entry (local) + bool mStandardFace : 1; + bool mIgnoreGDEF : 1; + bool mIgnoreGSUB : 1; + bool mSkipDefaultFeatureSpaceCheck : 1; + + mozilla::Atomic<bool> mSVGInitialized; + mozilla::Atomic<bool> mHasCmapTable; + mozilla::Atomic<bool> mGrFaceInitialized; + mozilla::Atomic<bool> mCheckedForColorGlyph; + mozilla::Atomic<bool> mCheckedForVariationAxes; + + // Atomic flags that are lazily evaluated - initially set to UNINITIALIZED, + // changed to NO or YES once we determine the actual value. + enum class LazyFlag : uint8_t { Uninitialized = 0xff, No = 0, Yes = 1 }; + + std::atomic<LazyFlag> mSpaceGlyphIsInvisible; + std::atomic<LazyFlag> mHasGraphiteTables; + std::atomic<LazyFlag> mHasGraphiteSpaceContextuals; + std::atomic<LazyFlag> mHasColorBitmapTable; + + enum class SpaceFeatures : uint8_t { + Uninitialized = 0xff, + None = 0, + HasFeatures = 1 << 0, + Kerning = 1 << 1, + NonKerning = 1 << 2 + }; + + std::atomic<SpaceFeatures> mHasSpaceFeatures; + + protected: + friend class gfxPlatformFontList; + friend class gfxFontFamily; + friend class gfxUserFontEntry; + + // Protected destructor, to discourage deletion outside of Release(): + virtual ~gfxFontEntry(); + + virtual gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle) = 0; + + inline bool CheckForGraphiteTables() { + return HasFontTable(TRUETYPE_TAG('S', 'i', 'l', 'f')); + } + + // Copy a font table into aBuffer. + // The caller will be responsible for ownership of the data. + virtual nsresult CopyFontTable(uint32_t aTableTag, + nsTArray<uint8_t>& aBuffer) { + MOZ_ASSERT_UNREACHABLE( + "forgot to override either GetFontTable or " + "CopyFontTable?"); + return NS_ERROR_FAILURE; + } + + // Helper for HasTrackingTable; check/parse the table and cache pointers + // to the subtables we need. Returns false on failure, in which case the + // table is unusable. + bool ParseTrakTable() MOZ_REQUIRES(mLock); + + // lookup the cmap in cached font data + virtual already_AddRefed<gfxCharacterMap> GetCMAPFromFontInfo( + FontInfoData* aFontInfoData, uint32_t& aUVSOffset); + + // helper for HasCharacter(), which is what client code should call + virtual bool TestCharacterMap(uint32_t aCh); + + // Try to set mShmemCharacterMap, based on the char map in mShmemFace; + // return true if successful, false if it remains null (maybe the parent + // hasn't handled our SetCharacterMap message yet). + bool TrySetShmemCharacterMap(); + + // Helper for gfxPlatformFontList::CreateFontEntry methods: set properties + // of the gfxFontEntry based on shared Face and Family records. + void InitializeFrom(mozilla::fontlist::Face* aFace, + const mozilla::fontlist::Family* aFamily); + + // Shaper-specific face objects, shared by all instantiations of the same + // physical font, regardless of size. + // Usually, only one of these will actually be created for any given font + // entry, depending on the font tables that are present. + + // hb_face_t is refcounted internally, so each shaper that's using it will + // bump the ref count when it acquires the face, and "destroy" (release) it + // in its destructor. The font entry has only this non-owning reference to + // the face; when the face is deleted, it will tell the font entry to forget + // it, so that a new face will be created next time it is needed. + mozilla::Atomic<hb_face_t*> mHBFace; + + static hb_blob_t* HBGetTable(hb_face_t* face, uint32_t aTag, void* aUserData); + + // Callback that the hb_face will use to tell us when it is being deleted. + static void HBFaceDeletedCallback(void* aUserData); + + // All libGraphite functionality is sandboxed in an rlbox sandbox. This + // contains data for the sandbox instance. + // Currently graphite shaping is only supported on the main thread. + struct GrSandboxData; + GrSandboxData* mSandboxData = nullptr; + + // gr_face is -not- refcounted, so it will be owned directly by the font + // entry, and we'll keep a count of how many references we've handed out; + // each shaper is responsible to call ReleaseGrFace on its entry when + // finished with it, so that we know when it can be deleted. + tainted_opaque_gr<gr_face*> mGrFace; + + // For AAT font, a strong reference to the 'trak' table (if present). + hb_blob_t* const kTrakTableUninitialized = (hb_blob_t*)(intptr_t(-1)); + mozilla::Atomic<hb_blob_t*> mTrakTable; + hb_blob_t* GetTrakTable() const { return mTrakTable; } + bool TrakTableInitialized() const { + return mTrakTable != kTrakTableUninitialized; + } + + // Cached pointers to tables within 'trak', initialized by ParseTrakTable. + // This data is inert once loaded, so locking is not required to read it. + const mozilla::AutoSwap_PRInt16* mTrakValues = nullptr; + const mozilla::AutoSwap_PRInt32* mTrakSizeTable = nullptr; + + // number of current users of this entry's mGrFace + nsrefcnt mGrFaceRefCnt = 0; + + friend class gfxFontEntryCallbacks; + + // For memory reporting: size of user-font data belonging to this entry. + // We record this in the font entry because the actual data block may be + // handed over to platform APIs, so that it would become difficult (and + // platform-specific) to measure it directly at report-gathering time. + uint32_t mComputedSizeOfUserFont = 0; + + // Font's unitsPerEm from the 'head' table, if available (will be set to + // kInvalidUPEM for non-sfnt font formats) + uint16_t mUnitsPerEm = 0; + + uint16_t mNumTrakSizes = 0; + + // Font extents in FUnits. (To be set from the 'head' table; default to + // "huge" to avoid any clipping if real extents not available.) + int16_t mXMin = std::numeric_limits<int16_t>::min(); + int16_t mYMin = std::numeric_limits<int16_t>::min(); + int16_t mXMax = std::numeric_limits<int16_t>::max(); + int16_t mYMax = std::numeric_limits<int16_t>::max(); + + private: + /** + * Font table hashtable, to support GetFontTable for harfbuzz. + * + * The harfbuzz shaper (and potentially other clients) needs access to raw + * font table data. This needs to be cached so that it can be used + * repeatedly (each time we construct a text run; in some cases, for + * each character/glyph within the run) without re-fetching large tables + * every time. + * + * Because we may instantiate many gfxFonts for the same physical font + * file (at different sizes), we should ensure that they can share a + * single cached copy of the font tables. To do this, we implement table + * access and sharing on the fontEntry rather than the font itself. + * + * The default implementation uses GetFontTable() to read font table + * data into byte arrays, and wraps them in blobs which are registered in + * a hashtable. The hashtable can then return pre-existing blobs to + * harfbuzz. + * + * Harfbuzz will "destroy" the blobs when it is finished with them. When + * the last blob reference is removed, the FontTableBlobData user data + * will remove the blob from the hashtable if still registered. + */ + + class FontTableBlobData; + + /** + * FontTableHashEntry manages the entries of hb_blob_t's containing font + * table data. + * + * This is used to share font tables across fonts with the same + * font entry (but different sizes) for use by HarfBuzz. The hashtable + * does not own a strong reference to the blob, but keeps a weak pointer, + * managed by FontTableBlobData. Similarly FontTableBlobData keeps only a + * weak pointer to the hashtable, managed by FontTableHashEntry. + */ + + class FontTableHashEntry : public nsUint32HashKey { + public: + // Declarations for nsTHashtable + + typedef nsUint32HashKey KeyClass; + typedef KeyClass::KeyType KeyType; + typedef KeyClass::KeyTypePointer KeyTypePointer; + + explicit FontTableHashEntry(KeyTypePointer aTag) + : KeyClass(aTag), mSharedBlobData(nullptr), mBlob(nullptr) {} + + // NOTE: This assumes the new entry belongs to the same hashtable as + // the old, because the mHashtable pointer in mSharedBlobData (if + // present) will not be updated. + FontTableHashEntry(FontTableHashEntry&& toMove) + : KeyClass(std::move(toMove)), + mSharedBlobData(std::move(toMove.mSharedBlobData)), + mBlob(std::move(toMove.mBlob)) { + toMove.mSharedBlobData = nullptr; + toMove.mBlob = nullptr; + } + + ~FontTableHashEntry() { Clear(); } + + // FontTable/Blob API + + // Transfer (not copy) elements of aTable to a new hb_blob_t and + // return ownership to the caller. A weak reference to the blob is + // recorded in the hashtable entry so that others may use the same + // table. + hb_blob_t* ShareTableAndGetBlob( + nsTArray<uint8_t>&& aTable, + nsTHashtable<FontTableHashEntry>* aHashtable); + + // Return a strong reference to the blob. + // Callers must hb_blob_destroy the returned blob. + hb_blob_t* GetBlob() const; + + void Clear(); + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + private: + static void DeleteFontTableBlobData(void* aBlobData); + // not implemented + FontTableHashEntry& operator=(FontTableHashEntry& toCopy); + + FontTableBlobData* mSharedBlobData; + hb_blob_t* mBlob; + }; + + using FontTableCache = nsTHashtable<FontTableHashEntry>; + mozilla::Atomic<FontTableCache*> mFontTableCache; + FontTableCache* GetFontTableCache() const { return mFontTableCache; } +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(gfxFontEntry::RangeFlags) +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(gfxFontEntry::SpaceFeatures) + +inline bool gfxFontEntry::SupportsItalic() { + return SlantStyle().Max().IsItalic() || + ((mRangeFlags & RangeFlags::eAutoSlantStyle) == + RangeFlags::eAutoSlantStyle && + HasItalicVariation()); +} + +inline bool gfxFontEntry::SupportsBold() { + // bold == weights 600 and above + // We return true if the face has a max weight descriptor >= 600, + // OR if it's a user font with auto-weight (no descriptor) and has + // a weight axis that supports values >= 600 + return Weight().Max().IsBold() || + ((mRangeFlags & RangeFlags::eAutoWeight) == RangeFlags::eAutoWeight && + HasBoldVariableWeight()); +} + +inline bool gfxFontEntry::MayUseSyntheticSlant() { + if (!IsUpright()) { + return false; // The resource is already non-upright. + } + if (HasSlantVariation()) { + if (mRangeFlags & RangeFlags::eAutoSlantStyle) { + return false; + } + if (!SlantStyle().IsSingle()) { + return false; // The resource has a 'slnt' axis, and has not been + // clamped to just its upright setting. + } + } + return true; +} + +// used when iterating over all fonts looking for a match for a given character +struct GlobalFontMatch { + GlobalFontMatch(uint32_t aCharacter, uint32_t aNextCh, + const gfxFontStyle& aStyle, eFontPresentation aPresentation) + : mStyle(aStyle), + mCh(aCharacter), + mNextCh(aNextCh), + mPresentation(aPresentation) {} + + RefPtr<gfxFontEntry> mBestMatch; // current best match + RefPtr<gfxFontFamily> mMatchedFamily; // the family it belongs to + mozilla::fontlist::Family* mMatchedSharedFamily = nullptr; + const gfxFontStyle& mStyle; // style to match + const uint32_t mCh; // codepoint to be matched + const uint32_t mNextCh; // following codepoint (or zero) + eFontPresentation mPresentation; + uint32_t mCount = 0; // number of fonts matched + uint32_t mCmapsTested = 0; // number of cmaps tested + double mMatchDistance = INFINITY; // metric indicating closest match +}; + +class gfxFontFamily { + public: + // Used by stylo + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxFontFamily) + + gfxFontFamily(const nsACString& aName, FontVisibility aVisibility) + : mName(aName), + mLock("gfxFontFamily lock"), + mVisibility(aVisibility), + mIsSimpleFamily(false), + mIsBadUnderlineFamily(false), + mSkipDefaultFeatureSpaceCheck(false), + mCheckForFallbackFaces(false) {} + + const nsCString& Name() const { return mName; } + + virtual void LocalizedName(nsACString& aLocalizedName); + virtual bool HasOtherFamilyNames(); + + // See https://bugzilla.mozilla.org/show_bug.cgi?id=835204: + // check the font's 'name' table to see if it has a legacy family name + // that would have been used by GDI (e.g. to split extra-bold or light + // faces in a large family into separate "styled families" because of + // GDI's 4-faces-per-family limitation). If found, the styled family + // name will be added to the font list's "other family names" table. + // Note that the caller must already hold the gfxPlatformFontList lock. + bool CheckForLegacyFamilyNames(gfxPlatformFontList* aFontList); + + // Callers must hold a read-lock for as long as they're using the list. + const nsTArray<RefPtr<gfxFontEntry>>& GetFontList() + MOZ_REQUIRES_SHARED(mLock) { + return mAvailableFonts; + } + void ReadLock() MOZ_ACQUIRE_SHARED(mLock) { mLock.ReadLock(); } + void ReadUnlock() MOZ_RELEASE_SHARED(mLock) { mLock.ReadUnlock(); } + + uint32_t FontListLength() const { + mozilla::AutoReadLock lock(mLock); + return mAvailableFonts.Length(); + } + + void AddFontEntry(RefPtr<gfxFontEntry> aFontEntry) { + mozilla::AutoWriteLock lock(mLock); + AddFontEntryLocked(aFontEntry); + } + + void AddFontEntryLocked(RefPtr<gfxFontEntry> aFontEntry) MOZ_REQUIRES(mLock) { + // Avoid potentially duplicating entries. + if (mAvailableFonts.Contains(aFontEntry)) { + return; + } + // bug 589682 - set the IgnoreGDEF flag on entries for Italic faces + // of Times New Roman, because of buggy table in those fonts + if (aFontEntry->IsItalic() && !aFontEntry->IsUserFont() && + Name().EqualsLiteral("Times New Roman")) { + aFontEntry->mIgnoreGDEF = true; + } + if (aFontEntry->mFamilyName.IsEmpty()) { + aFontEntry->mFamilyName = Name(); + } else { + MOZ_ASSERT(aFontEntry->mFamilyName.Equals(Name())); + } + aFontEntry->mSkipDefaultFeatureSpaceCheck = mSkipDefaultFeatureSpaceCheck; + mAvailableFonts.AppendElement(aFontEntry); + + // If we're adding a face to a family that has been marked as "simple", + // we need to ensure any null entries are removed, as well as clearing + // the flag (which may be set again later). + if (mIsSimpleFamily) { + mAvailableFonts.RemoveElementsBy([](const auto& font) { return !font; }); + mIsSimpleFamily = false; + } + } + + // note that the styles for this family have been added + bool HasStyles() const { return mHasStyles; } + void SetHasStyles(bool aHasStyles) { mHasStyles = aHasStyles; } + + void SetCheckedForLegacyFamilyNames(bool aChecked) { + mCheckedForLegacyFamilyNames = aChecked; + } + + // choose a specific face to match a style using CSS font matching + // rules (weight matching occurs here). may return a face that doesn't + // precisely match (e.g. normal face when no italic face exists). + gfxFontEntry* FindFontForStyle(const gfxFontStyle& aFontStyle, + bool aIgnoreSizeTolerance = false); + + virtual void FindAllFontsForStyle(const gfxFontStyle& aFontStyle, + nsTArray<gfxFontEntry*>& aFontEntryList, + bool aIgnoreSizeTolerance = false); + + // Checks for a matching font within the family; used as part of the font + // fallback process. + // Note that when this is called, the caller must already be holding the + // gfxPlatformFontList lock. + void FindFontForChar(GlobalFontMatch* aMatchData); + + // checks all fonts for a matching font within the family + void SearchAllFontsForChar(GlobalFontMatch* aMatchData); + + // read in other family names, if any, and use functor to add each into cache + virtual void ReadOtherFamilyNames(gfxPlatformFontList* aPlatformFontList); + + // set when other family names have been read in + void SetOtherFamilyNamesInitialized() { mOtherFamilyNamesInitialized = true; } + + // Read in other localized family names, fullnames and Postscript names + // for all faces and append to lookup tables. + // Note that when this is called, the caller must already be holding the + // gfxPlatformFontList lock. + virtual void ReadFaceNames(gfxPlatformFontList* aPlatformFontList, + bool aNeedFullnamePostscriptNames, + FontInfoData* aFontInfoData = nullptr); + + // Find faces belonging to this family (platform implementations override). + // This is a no-op in cases where the family is explicitly populated by other + // means, rather than being asked to find its faces via system API. + virtual void FindStyleVariationsLocked(FontInfoData* aFontInfoData = nullptr) + MOZ_REQUIRES(mLock){}; + void FindStyleVariations(FontInfoData* aFontInfoData = nullptr) { + if (mHasStyles) { + return; + } + mozilla::AutoWriteLock lock(mLock); + FindStyleVariationsLocked(aFontInfoData); + } + + // search for a specific face using the Postscript name + gfxFontEntry* FindFont(const nsACString& aFontName, + const nsCStringComparator& aCmp) const; + + // Read in cmaps for all the faces. + // Note that when this is called, the caller must already be holding the + // gfxPlatformFontList lock. + void ReadAllCMAPs(FontInfoData* aFontInfoData = nullptr); + + bool TestCharacterMap(uint32_t aCh) { + if (!mFamilyCharacterMapInitialized) { + ReadAllCMAPs(); + } + mozilla::AutoReadLock lock(mLock); + return mFamilyCharacterMap.test(aCh); + } + + void ResetCharacterMap() MOZ_REQUIRES(mLock) { + mFamilyCharacterMap.reset(); + mFamilyCharacterMapInitialized = false; + } + + // mark this family as being in the "bad" underline offset blocklist + void SetBadUnderlineFamily() { + mozilla::AutoWriteLock lock(mLock); + mIsBadUnderlineFamily = true; + if (mHasStyles) { + SetBadUnderlineFonts(); + } + } + + virtual bool IsSingleFaceFamily() const { return false; } + + bool IsBadUnderlineFamily() const { return mIsBadUnderlineFamily; } + bool CheckForFallbackFaces() const { return mCheckForFallbackFaces; } + + // sort available fonts to put preferred (standard) faces towards the end + void SortAvailableFonts() MOZ_REQUIRES(mLock); + + // check whether the family fits into the simple 4-face model, + // so we can use simplified style-matching; + // if so set the mIsSimpleFamily flag (defaults to False before we've checked) + void CheckForSimpleFamily() MOZ_REQUIRES(mLock); + + // For memory reporter + virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const; + virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const; + +#ifdef DEBUG + // Only used for debugging checks - does a linear search + bool ContainsFace(gfxFontEntry* aFontEntry); +#endif + + void SetSkipSpaceFeatureCheck(bool aSkipCheck) { + mSkipDefaultFeatureSpaceCheck = aSkipCheck; + } + + // Check whether this family is appropriate to include in the Preferences + // font list for the given langGroup and CSS generic, if the platform lets + // us determine this. + // Return true if the family should be included in the list, false to omit. + // Default implementation returns true for everything, so no filtering + // will occur; individual platforms may override. + virtual bool FilterForFontList(nsAtom* aLangGroup, + const nsACString& aGeneric) const { + return true; + } + + FontVisibility Visibility() const { return mVisibility; } + bool IsHidden() const { return Visibility() == FontVisibility::Hidden; } + bool IsWebFontFamily() const { + return Visibility() == FontVisibility::Webfont; + } + + protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~gfxFontFamily(); + + bool ReadOtherFamilyNamesForFace(gfxPlatformFontList* aPlatformFontList, + hb_blob_t* aNameTable, + bool useFullName = false); + + // set whether this font family is in "bad" underline offset blocklist. + void SetBadUnderlineFonts() MOZ_REQUIRES(mLock) { + for (auto& f : mAvailableFonts) { + if (f) { + f->mIsBadUnderlineFont = true; + } + } + } + + nsCString mName; + nsTArray<RefPtr<gfxFontEntry>> mAvailableFonts MOZ_GUARDED_BY(mLock); + gfxSparseBitSet mFamilyCharacterMap MOZ_GUARDED_BY(mLock); + + mutable mozilla::RWLock mLock; + + FontVisibility mVisibility; + + mozilla::Atomic<bool> mOtherFamilyNamesInitialized; + mozilla::Atomic<bool> mFaceNamesInitialized; + mozilla::Atomic<bool> mHasStyles; + mozilla::Atomic<bool> mFamilyCharacterMapInitialized; + mozilla::Atomic<bool> mCheckedForLegacyFamilyNames; + mozilla::Atomic<bool> mHasOtherFamilyNames; + + bool mIsSimpleFamily : 1 MOZ_GUARDED_BY(mLock); + bool mIsBadUnderlineFamily : 1; + bool mSkipDefaultFeatureSpaceCheck : 1; + bool mCheckForFallbackFaces : 1; // check other faces for character + + enum { + // for "simple" families, the faces are stored in mAvailableFonts + // with fixed positions: + kRegularFaceIndex = 0, + kBoldFaceIndex = 1, + kItalicFaceIndex = 2, + kBoldItalicFaceIndex = 3, + // mask values for selecting face with bold and/or italic attributes + kBoldMask = 0x01, + kItalicMask = 0x02 + }; +}; + +// Wrapper for either a raw pointer to a mozilla::fontlist::Family in the shared +// font list or a strong pointer to an unshared gfxFontFamily that belongs just +// to the current process. +struct FontFamily { + FontFamily() = default; + FontFamily(const FontFamily& aOther) = default; + + explicit FontFamily(RefPtr<gfxFontFamily>&& aFamily) + : mUnshared(std::move(aFamily)) {} + + explicit FontFamily(gfxFontFamily* aFamily) : mUnshared(aFamily) {} + + explicit FontFamily(mozilla::fontlist::Family* aFamily) : mShared(aFamily) {} + + bool operator==(const FontFamily& aOther) const { + return mShared == aOther.mShared && mUnshared == aOther.mUnshared; + } + + bool IsNull() const { return !mShared && !mUnshared; } + + RefPtr<gfxFontFamily> mUnshared; + mozilla::fontlist::Family* mShared = nullptr; +}; + +// Struct used in the gfxFontGroup font list to keep track of a font family +// together with the CSS generic (if any) that was mapped to it in this +// particular case (so it can be reported to the DevTools font inspector). +struct FamilyAndGeneric final { + FamilyAndGeneric() : mGeneric(mozilla::StyleGenericFontFamily(0)) {} + FamilyAndGeneric(const FamilyAndGeneric& aOther) = default; + explicit FamilyAndGeneric(gfxFontFamily* aFamily, + mozilla::StyleGenericFontFamily aGeneric = + mozilla::StyleGenericFontFamily(0)) + : mFamily(aFamily), mGeneric(aGeneric) {} + explicit FamilyAndGeneric(RefPtr<gfxFontFamily>&& aFamily, + mozilla::StyleGenericFontFamily aGeneric = + mozilla::StyleGenericFontFamily(0)) + : mFamily(std::move(aFamily)), mGeneric(aGeneric) {} + explicit FamilyAndGeneric(mozilla::fontlist::Family* aFamily, + mozilla::StyleGenericFontFamily aGeneric = + mozilla::StyleGenericFontFamily(0)) + : mFamily(aFamily), mGeneric(aGeneric) {} + explicit FamilyAndGeneric(const FontFamily& aFamily, + mozilla::StyleGenericFontFamily aGeneric = + mozilla::StyleGenericFontFamily(0)) + : mFamily(aFamily), mGeneric(aGeneric) {} + + bool operator==(const FamilyAndGeneric& aOther) const { + return mFamily == aOther.mFamily && mGeneric == aOther.mGeneric; + } + + FontFamily mFamily; + mozilla::StyleGenericFontFamily mGeneric; +}; + +#endif diff --git a/gfx/thebes/gfxFontFeatures.cpp b/gfx/thebes/gfxFontFeatures.cpp new file mode 100644 index 0000000000..4d1d82927a --- /dev/null +++ b/gfx/thebes/gfxFontFeatures.cpp @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxFontFeatures.h" +#include "nsAtom.h" +#include "nsUnicharUtils.h" +#include "nsHashKeys.h" + +using namespace mozilla; + +gfxFontFeatureValueSet::gfxFontFeatureValueSet() : mFontFeatureValues(8) {} + +Span<const uint32_t> gfxFontFeatureValueSet::GetFontFeatureValuesFor( + const nsACString& aFamily, uint32_t aVariantProperty, nsAtom* aName) const { + nsAutoCString family(aFamily); + ToLowerCase(family); + FeatureValueHashKey key(family, aVariantProperty, aName); + FeatureValueHashEntry* entry = mFontFeatureValues.GetEntry(key); + if (!entry) { + return {}; + } + NS_ASSERTION(entry->mValues.Length() > 0, + "null array of font feature values"); + return {entry->mValues}; +} + +nsTArray<uint32_t>* gfxFontFeatureValueSet::AppendFeatureValueHashEntry( + const nsACString& aFamily, nsAtom* aName, uint32_t aAlternate) { + FeatureValueHashKey key(aFamily, aAlternate, aName); + FeatureValueHashEntry* entry = mFontFeatureValues.PutEntry(key); + entry->mKey = key; + return &entry->mValues; +} + +bool gfxFontFeatureValueSet::FeatureValueHashEntry::KeyEquals( + const KeyTypePointer aKey) const { + return aKey->mPropVal == mKey.mPropVal && aKey->mName == mKey.mName && + aKey->mFamily.Equals(mKey.mFamily); +} + +PLDHashNumber gfxFontFeatureValueSet::FeatureValueHashEntry::HashKey( + const KeyTypePointer aKey) { + return HashString(aKey->mFamily) + aKey->mName->hash() + + aKey->mPropVal * uint32_t(0xdeadbeef); +} diff --git a/gfx/thebes/gfxFontFeatures.h b/gfx/thebes/gfxFontFeatures.h new file mode 100644 index 0000000000..24ece02d36 --- /dev/null +++ b/gfx/thebes/gfxFontFeatures.h @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FONT_FEATURES_H +#define GFX_FONT_FEATURES_H + +#include "nsAtom.h" +#include "nsTHashtable.h" +#include "nsTArray.h" +#include "nsString.h" + +// An OpenType feature tag and value pair +struct gfxFontFeature { + uint32_t + mTag; // see http://www.microsoft.com/typography/otspec/featuretags.htm + uint32_t mValue; // 0 = off, 1 = on, larger values may be used as parameters + // to features that select among multiple alternatives +}; + +inline bool operator<(const gfxFontFeature& a, const gfxFontFeature& b) { + return (a.mTag < b.mTag) || ((a.mTag == b.mTag) && (a.mValue < b.mValue)); +} + +inline bool operator==(const gfxFontFeature& a, const gfxFontFeature& b) { + return (a.mTag == b.mTag) && (a.mValue == b.mValue); +} + +class nsAtom; + +class gfxFontFeatureValueSet final { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxFontFeatureValueSet) + + gfxFontFeatureValueSet(); + + struct ValueList { + ValueList(const nsAString& aName, const nsTArray<uint32_t>& aSelectors) + : name(aName), featureSelectors(aSelectors.Clone()) {} + nsString name; + nsTArray<uint32_t> featureSelectors; + }; + + struct FeatureValues { + uint32_t alternate; + nsTArray<ValueList> valuelist; + }; + + mozilla::Span<const uint32_t> GetFontFeatureValuesFor( + const nsACString& aFamily, uint32_t aVariantProperty, + nsAtom* aName) const; + + // Appends a new hash entry with given key values and returns a pointer to + // mValues array to fill. This should be filled first. + nsTArray<uint32_t>* AppendFeatureValueHashEntry(const nsACString& aFamily, + nsAtom* aName, + uint32_t aAlternate); + + private: + // Private destructor, to discourage deletion outside of Release(): + ~gfxFontFeatureValueSet() = default; + + struct FeatureValueHashKey { + nsCString mFamily; + uint32_t mPropVal; + RefPtr<nsAtom> mName; + + FeatureValueHashKey() : mPropVal(0) {} + FeatureValueHashKey(const nsACString& aFamily, uint32_t aPropVal, + nsAtom* aName) + : mFamily(aFamily), mPropVal(aPropVal), mName(aName) {} + FeatureValueHashKey(const FeatureValueHashKey& aKey) = default; + }; + + class FeatureValueHashEntry : public PLDHashEntryHdr { + public: + typedef const FeatureValueHashKey& KeyType; + typedef const FeatureValueHashKey* KeyTypePointer; + + explicit FeatureValueHashEntry(KeyTypePointer aKey) {} + FeatureValueHashEntry(FeatureValueHashEntry&& other) + : PLDHashEntryHdr(std::move(other)), + mKey(std::move(other.mKey)), + mValues(std::move(other.mValues)) { + NS_ERROR("Should not be called"); + } + ~FeatureValueHashEntry() = default; + + bool KeyEquals(const KeyTypePointer aKey) const; + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(const KeyTypePointer aKey); + enum { ALLOW_MEMMOVE = true }; + + FeatureValueHashKey mKey; + nsTArray<uint32_t> mValues; + }; + + nsTHashtable<FeatureValueHashEntry> mFontFeatureValues; +}; + +#endif diff --git a/gfx/thebes/gfxFontInfoLoader.cpp b/gfx/thebes/gfxFontInfoLoader.cpp new file mode 100644 index 0000000000..41cb519164 --- /dev/null +++ b/gfx/thebes/gfxFontInfoLoader.cpp @@ -0,0 +1,318 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxFontInfoLoader.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/AppShutdown.h" +#include "nsCRT.h" +#include "nsIObserverService.h" +#include "nsThreadUtils.h" // for nsRunnable +#include "gfxPlatformFontList.h" + +#ifdef XP_WIN +# include <windows.h> +#endif + +using namespace mozilla; +using services::GetObserverService; + +#define LOG_FONTINIT(args) \ + MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug, args) +#define LOG_FONTINIT_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug) + +void FontInfoData::Load() { + TimeStamp start = TimeStamp::Now(); + + uint32_t i, n = mFontFamiliesToLoad.Length(); + mLoadStats.families = n; + for (i = 0; i < n && !mCanceled; i++) { + // font file memory mapping sometimes causes exceptions - bug 1100949 + MOZ_SEH_TRY { LoadFontFamilyData(mFontFamiliesToLoad[i]); } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + gfxCriticalError() << "Exception occurred reading font data for " + << mFontFamiliesToLoad[i].get(); + } + } + + mLoadTime = TimeStamp::Now() - start; +} + +class FontInfoLoadCompleteEvent : public Runnable { + virtual ~FontInfoLoadCompleteEvent() = default; + + public: + NS_INLINE_DECL_REFCOUNTING_INHERITED(FontInfoLoadCompleteEvent, Runnable) + + explicit FontInfoLoadCompleteEvent(FontInfoData* aFontInfo) + : mozilla::Runnable("FontInfoLoadCompleteEvent"), mFontInfo(aFontInfo) {} + + NS_IMETHOD Run() override; + + private: + RefPtr<FontInfoData> mFontInfo; +}; + +class AsyncFontInfoLoader : public Runnable { + virtual ~AsyncFontInfoLoader() = default; + + public: + NS_INLINE_DECL_REFCOUNTING_INHERITED(AsyncFontInfoLoader, Runnable) + + explicit AsyncFontInfoLoader(FontInfoData* aFontInfo) + : mozilla::Runnable("AsyncFontInfoLoader"), mFontInfo(aFontInfo) { + mCompleteEvent = new FontInfoLoadCompleteEvent(aFontInfo); + } + + NS_IMETHOD Run() override; + + private: + RefPtr<FontInfoData> mFontInfo; + RefPtr<FontInfoLoadCompleteEvent> mCompleteEvent; +}; + +class ShutdownThreadEvent : public Runnable { + virtual ~ShutdownThreadEvent() = default; + + public: + NS_INLINE_DECL_REFCOUNTING_INHERITED(ShutdownThreadEvent, Runnable) + + explicit ShutdownThreadEvent(nsIThread* aThread) + : mozilla::Runnable("ShutdownThreadEvent"), mThread(aThread) {} + NS_IMETHOD Run() override { + mThread->Shutdown(); + return NS_OK; + } + + private: + nsCOMPtr<nsIThread> mThread; +}; + +// runs on main thread after async font info loading is done +nsresult FontInfoLoadCompleteEvent::Run() { + gfxFontInfoLoader* loader = + static_cast<gfxFontInfoLoader*>(gfxPlatformFontList::PlatformFontList()); + + loader->FinalizeLoader(mFontInfo); + + return NS_OK; +} + +// runs on separate thread +nsresult AsyncFontInfoLoader::Run() { + // load platform-specific font info + mFontInfo->Load(); + + // post a completion event that transfer the data to the fontlist + NS_DispatchToMainThread(mCompleteEvent); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(gfxFontInfoLoader::ShutdownObserver, nsIObserver) + +NS_IMETHODIMP +gfxFontInfoLoader::ShutdownObserver::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* someData) { + if (!nsCRT::strcmp(aTopic, "quit-application") || + !nsCRT::strcmp(aTopic, "xpcom-shutdown")) { + mLoader->CancelLoader(); + } else { + MOZ_ASSERT_UNREACHABLE("unexpected notification topic"); + } + return NS_OK; +} + +// StartLoader is usually called at startup with a (prefs-derived) delay value, +// so that the async loader runs shortly after startup, to avoid competing for +// disk i/o etc with other more critical operations. +// However, it may be called with aDelay=0 if we find that the font info (e.g. +// localized names) is needed for layout. In this case we start the loader +// immediately; however, it is still an async process and we may use fallback +// fonts to satisfy layout until it completes. +void gfxFontInfoLoader::StartLoader(uint32_t aDelay) { + if (aDelay == 0 && (mState == stateTimerOff || mState == stateAsyncLoad)) { + // We were asked to load (async) without delay, but have already started, + // so just return and let the loader proceed. + return; + } + + // We observe for "quit-application" above, so avoid initialization after it. + if (NS_WARN_IF(AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown))) { + MOZ_ASSERT(!aDelay, "Delayed gfxFontInfoLoader startup after AppShutdown?"); + return; + } + + // sanity check + if (mState != stateInitial && mState != stateTimerOff && + mState != stateTimerOnDelay) { + CancelLoader(); + } + + // Create mFontInfo when we're initially called to set up the delay, rather + // than when called by the DelayedStartCallback, because on the initial call + // we know we'll be holding the gfxPlatformFontList lock. + if (!mFontInfo) { + mFontInfo = CreateFontInfoData(); + if (!mFontInfo) { + // The platform doesn't want anything loaded, so just bail out. + mState = stateTimerOff; + return; + } + } + + AddShutdownObserver(); + + // Caller asked for a delay? ==> start async thread after a delay + if (aDelay) { + // Set up delay timer, or if there is already a timer in place, just + // leave it to do its thing. (This can happen if a StartLoader runnable + // was posted to the main thread from the InitFontList thread, but then + // before it had a chance to run and call StartLoader, the main thread + // re-initialized the list due to a platform notification and called + // StartLoader directly.) + if (mTimer) { + return; + } + mTimer = NS_NewTimer(); + mTimer->InitWithNamedFuncCallback(DelayedStartCallback, this, aDelay, + nsITimer::TYPE_ONE_SHOT, + "gfxFontInfoLoader::StartLoader"); + mState = stateTimerOnDelay; + return; + } + + // Either we've been called back by the DelayedStartCallback when its timer + // fired, or a layout caller has passed aDelay=0 to ask the loader to run + // without further delay. + + // Cancel the delay timer, if any. + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + + // initialize + InitLoader(); + + // start async load + nsresult rv = NS_NewNamedThread("Font Loader", + getter_AddRefs(mFontLoaderThread), nullptr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + PRThread* prThread; + if (NS_SUCCEEDED(mFontLoaderThread->GetPRThread(&prThread))) { + PR_SetThreadPriority(prThread, PR_PRIORITY_LOW); + } + + mState = stateAsyncLoad; + + nsCOMPtr<nsIRunnable> loadEvent = new AsyncFontInfoLoader(mFontInfo); + + mFontLoaderThread->Dispatch(loadEvent.forget(), NS_DISPATCH_NORMAL); + + if (LOG_FONTINIT_ENABLED()) { + LOG_FONTINIT( + ("(fontinit) fontloader started (fontinfo: %p)\n", mFontInfo.get())); + } +} + +class FinalizeLoaderRunnable : public Runnable { + virtual ~FinalizeLoaderRunnable() = default; + + public: + NS_INLINE_DECL_REFCOUNTING_INHERITED(FinalizeLoaderRunnable, Runnable) + + explicit FinalizeLoaderRunnable(gfxFontInfoLoader* aLoader) + : mozilla::Runnable("FinalizeLoaderRunnable"), mLoader(aLoader) {} + + NS_IMETHOD Run() override { + nsresult rv; + if (mLoader->LoadFontInfo()) { + mLoader->CancelLoader(); + rv = NS_OK; + } else { + nsCOMPtr<nsIRunnable> runnable = this; + rv = NS_DispatchToCurrentThreadQueue( + runnable.forget(), PR_INTERVAL_NO_TIMEOUT, EventQueuePriority::Idle); + } + return rv; + } + + private: + gfxFontInfoLoader* mLoader; +}; + +void gfxFontInfoLoader::FinalizeLoader(FontInfoData* aFontInfo) { + // Avoid loading data if loader has already been canceled. + // This should mean that CancelLoader() ran and the Load + // thread has already Shutdown(), and likely before processing + // the Shutdown event it handled the load event and sent back + // our Completion event, thus we end up here. + if (mState != stateAsyncLoad || mFontInfo != aFontInfo) { + return; + } + + mLoadTime = mFontInfo->mLoadTime; + + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIRunnable> runnable = new FinalizeLoaderRunnable(this); + if (NS_FAILED(NS_DispatchToCurrentThreadQueue(runnable.forget(), + PR_INTERVAL_NO_TIMEOUT, + EventQueuePriority::Idle))) { + NS_WARNING("Failed to finalize async font info"); + } +} + +void gfxFontInfoLoader::CancelLoader() { + if (mState == stateInitial) { + return; + } + mState = stateTimerOff; + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + if (mFontInfo) // null during any initial delay + mFontInfo->mCanceled = true; + if (mFontLoaderThread) { + NS_DispatchToMainThread(new ShutdownThreadEvent(mFontLoaderThread)); + mFontLoaderThread = nullptr; + } + RemoveShutdownObserver(); + CleanupLoader(); +} + +gfxFontInfoLoader::~gfxFontInfoLoader() { + RemoveShutdownObserver(); + MOZ_COUNT_DTOR(gfxFontInfoLoader); +} + +void gfxFontInfoLoader::AddShutdownObserver() { + if (mObserver) { + return; + } + + nsCOMPtr<nsIObserverService> obs = GetObserverService(); + if (obs) { + mObserver = new ShutdownObserver(this); + obs->AddObserver(mObserver, "quit-application", false); + obs->AddObserver(mObserver, "xpcom-shutdown", false); + } +} + +void gfxFontInfoLoader::RemoveShutdownObserver() { + if (mObserver) { + nsCOMPtr<nsIObserverService> obs = GetObserverService(); + if (obs) { + obs->RemoveObserver(mObserver, "quit-application"); + obs->RemoveObserver(mObserver, "xpcom-shutdown"); + mObserver = nullptr; + } + } +} diff --git a/gfx/thebes/gfxFontInfoLoader.h b/gfx/thebes/gfxFontInfoLoader.h new file mode 100644 index 0000000000..230cdb25b8 --- /dev/null +++ b/gfx/thebes/gfxFontInfoLoader.h @@ -0,0 +1,214 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FONT_INFO_LOADER_H +#define GFX_FONT_INFO_LOADER_H + +#include "nsCOMPtr.h" +#include "nsIObserver.h" +#include "nsITimer.h" +#include "nsIThread.h" +#include "nsString.h" +#include "gfxFontEntry.h" +#include "mozilla/Atomics.h" +#include "mozilla/TimeStamp.h" +#include "nsISupports.h" + +// data retrieved for a given face + +struct FontFaceData { + nsCString mFullName; + nsCString mPostscriptName; + RefPtr<gfxCharacterMap> mCharacterMap; + uint32_t mUVSOffset = 0; +}; + +// base class used to contain cached system-wide font info. +// methods in this class are called on off-main threads so +// all methods use only static methods or other thread-safe +// font data access API's. specifically, no use is made of +// gfxPlatformFontList, gfxFontFamily, gfxFamily or any +// harfbuzz API methods within FontInfoData subclasses. + +class FontInfoData { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FontInfoData) + + FontInfoData(bool aLoadOtherNames, bool aLoadFaceNames, bool aLoadCmaps) + : mCanceled(false), + mLoadOtherNames(aLoadOtherNames), + mLoadFaceNames(aLoadFaceNames), + mLoadCmaps(aLoadCmaps) { + MOZ_COUNT_CTOR(FontInfoData); + } + + protected: + // Protected destructor, to discourage deletion outside of Release(): + MOZ_COUNTED_DTOR_VIRTUAL(FontInfoData) + + public: + virtual void Load(); + + // loads font data for all fonts of a given family + // (called on async thread) + virtual void LoadFontFamilyData(const nsACString& aFamilyName) = 0; + + // -- methods overriden by platform-specific versions -- + + // fetches cmap data for a particular font from cached font data + virtual already_AddRefed<gfxCharacterMap> GetCMAP(const nsACString& aFontName, + uint32_t& aUVSOffset) { + FontFaceData faceData; + if (!mFontFaceData.Get(aFontName, &faceData) || !faceData.mCharacterMap) { + return nullptr; + } + + aUVSOffset = faceData.mUVSOffset; + RefPtr<gfxCharacterMap> cmap = faceData.mCharacterMap; + return cmap.forget(); + } + + // fetches fullname/postscript names from cached font data + virtual void GetFaceNames(const nsACString& aFontName, nsACString& aFullName, + nsACString& aPostscriptName) { + FontFaceData faceData; + if (!mFontFaceData.Get(aFontName, &faceData)) { + return; + } + + aFullName = faceData.mFullName; + aPostscriptName = faceData.mPostscriptName; + } + + // fetches localized family name data from cached font data + const nsTArray<nsCString>* GetOtherFamilyNames( + const nsACString& aFamilyName) { + return mOtherFamilyNames.Lookup(aFamilyName).DataPtrOrNull(); + } + + nsTArray<nsCString> mFontFamiliesToLoad; + + // currently non-issue but beware, + // this is also set during cleanup after finishing + mozilla::Atomic<bool> mCanceled; + + // time spent on the loader thread + mozilla::TimeDuration mLoadTime; + + struct FontCounts { + uint32_t families; + uint32_t fonts; + uint32_t cmaps; + uint32_t facenames; + uint32_t othernames; + }; + + FontCounts mLoadStats; + + bool mLoadOtherNames; + bool mLoadFaceNames; + bool mLoadCmaps; + + // face name ==> per-face data + nsTHashMap<nsCStringHashKey, FontFaceData> mFontFaceData; + + // canonical family name ==> array of localized family names + nsTHashMap<nsCStringHashKey, CopyableTArray<nsCString> > mOtherFamilyNames; +}; + +// gfxFontInfoLoader - helper class for loading font info on async thread +// For large, "all fonts on system" data, data needed on a given platform +// (e.g. localized names, face names, cmaps) are loaded async. + +// helper class for loading in font info on a separate async thread +// once async thread completes, completion process is run on the main +// thread's idle queue in short slices + +class gfxFontInfoLoader { + public: + // state transitions: + // initial ---StartLoader with delay---> timer on delay + // initial ---StartLoader without delay---> timer off + // timer on delay ---LoaderTimerFire---> timer off + // timer on delay ---CancelLoader---> timer off + // timer off ---StartLoader with delay---> timer on delay + // timer off ---StartLoader without delay---> timer off + typedef enum { + stateInitial, + stateTimerOnDelay, + stateAsyncLoad, + stateTimerOff + } TimerState; + + gfxFontInfoLoader() : mState(stateInitial) { + MOZ_COUNT_CTOR(gfxFontInfoLoader); + } + + virtual ~gfxFontInfoLoader(); + + // start timer with an initial delay + void StartLoader(uint32_t aDelay); + + // Finalize - async load complete, transfer data (on idle) + virtual void FinalizeLoader(FontInfoData* aFontInfo); + + // cancel the timer and cleanup + void CancelLoader(); + + protected: + friend class FinalizeLoaderRunnable; + + class ShutdownObserver : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + explicit ShutdownObserver(gfxFontInfoLoader* aLoader) : mLoader(aLoader) {} + + protected: + virtual ~ShutdownObserver() = default; + + gfxFontInfoLoader* mLoader; + }; + + // CreateFontInfo - create platform-specific object used + // to load system-wide font info + virtual already_AddRefed<FontInfoData> CreateFontInfoData() { + return nullptr; + } + + // Init - initialization before async loader thread runs + virtual void InitLoader() = 0; + + // LoadFontInfo - transfer font info data within a time limit, return + // true when done + virtual bool LoadFontInfo() = 0; + + // Cleanup - finish and cleanup after done, including possible reflows + virtual void CleanupLoader() { mFontInfo = nullptr; } + + static void DelayedStartCallback(nsITimer* aTimer, void* aThis) { + gfxFontInfoLoader* loader = static_cast<gfxFontInfoLoader*>(aThis); + loader->StartLoader(0); + } + + void LoadFontInfoTimerFire(); + + void AddShutdownObserver(); + void RemoveShutdownObserver(); + + nsCOMPtr<nsITimer> mTimer; + nsCOMPtr<nsIObserver> mObserver; + nsCOMPtr<nsIThread> mFontLoaderThread; + TimerState mState; + + // after async font loader completes, data is stored here + RefPtr<FontInfoData> mFontInfo; + + // time spent on the loader thread + mozilla::TimeDuration mLoadTime; +}; + +#endif /* GFX_FONT_INFO_LOADER_H */ diff --git a/gfx/thebes/gfxFontMissingGlyphs.cpp b/gfx/thebes/gfxFontMissingGlyphs.cpp new file mode 100644 index 0000000000..949a71228c --- /dev/null +++ b/gfx/thebes/gfxFontMissingGlyphs.cpp @@ -0,0 +1,542 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxFontMissingGlyphs.h" + +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Helpers.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/LinkedList.h" +#include "mozilla/RefPtr.h" +#include "nsDeviceContext.h" +#include "nsLayoutUtils.h" +#include "TextDrawTarget.h" +#include "LayerUserData.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +#define X 255 +static const uint8_t gMiniFontData[] = { + 0, X, 0, 0, X, 0, X, X, X, X, X, X, X, 0, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, 0, 0, X, X, X, X, 0, X, X, X, X, X, X, + X, 0, X, 0, X, 0, 0, 0, X, 0, 0, X, X, 0, X, X, 0, 0, X, 0, 0, 0, 0, X, + X, 0, X, X, 0, X, X, 0, X, X, 0, X, X, 0, 0, X, 0, X, X, 0, 0, X, 0, 0, + X, 0, X, 0, X, 0, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, 0, 0, X, + X, X, X, X, X, X, X, X, X, X, X, 0, X, 0, 0, X, 0, X, X, X, X, X, X, X, + X, 0, X, 0, X, 0, X, 0, 0, 0, 0, X, 0, 0, X, 0, 0, X, X, 0, X, 0, 0, X, + X, 0, X, 0, 0, X, X, 0, X, X, 0, X, X, 0, 0, X, 0, X, X, 0, 0, X, 0, 0, + 0, X, 0, 0, X, 0, X, X, X, X, X, X, 0, 0, X, X, X, X, X, X, X, 0, 0, X, + X, X, X, 0, 0, X, X, 0, X, X, X, 0, 0, X, X, X, X, 0, X, X, X, X, 0, 0, +}; +#undef X + +/* Parameters that control the rendering of hexboxes. They look like this: + + BMP codepoints non-BMP codepoints + (U+0000 - U+FFFF) (U+10000 - U+10FFFF) + + +---------+ +-------------+ + | | | | + | HHH HHH | | HHH HHH HHH | + | HHH HHH | | HHH HHH HHH | + | HHH HHH | | HHH HHH HHH | + | HHH HHH | | HHH HHH HHH | + | HHH HHH | | HHH HHH HHH | + | | | | + | HHH HHH | | HHH HHH HHH | + | HHH HHH | | HHH HHH HHH | + | HHH HHH | | HHH HHH HHH | + | HHH HHH | | HHH HHH HHH | + | HHH HHH | | HHH HHH HHH | + | | | | + +---------+ +-------------+ +*/ + +/** Width of a minifont glyph (see above) */ +static const int MINIFONT_WIDTH = 3; +/** Height of a minifont glyph (see above) */ +static const int MINIFONT_HEIGHT = 5; +/** + * Gap between minifont glyphs (both horizontal and vertical) and also + * the minimum desired gap between the box border and the glyphs + */ +static const int HEX_CHAR_GAP = 1; +/** + * The amount of space between the vertical edge of the glyphbox and the + * box border. We make this nonzero so that when multiple missing glyphs + * occur consecutively there's a gap between their rendered boxes. + */ +static const int BOX_HORIZONTAL_INSET = 1; +/** The width of the border */ +static const int BOX_BORDER_WIDTH = 1; +/** + * The scaling factor for the border opacity; this is multiplied by the current + * opacity being used to draw the text. + */ +static const Float BOX_BORDER_OPACITY = 0.5; + +#ifndef MOZ_GFX_OPTIMIZE_MOBILE + +class GlyphAtlas { + public: + GlyphAtlas(RefPtr<SourceSurface>&& aSurface, const DeviceColor& aColor) + : mSurface(std::move(aSurface)), mColor(aColor) {} + ~GlyphAtlas() = default; + + already_AddRefed<SourceSurface> Surface() const { + RefPtr surface = mSurface; + return surface.forget(); + } + DeviceColor Color() const { return mColor; } + + private: + RefPtr<SourceSurface> mSurface; + DeviceColor mColor; +}; + +// This is an owning reference that we will manage via exchange() and +// explicit new/delete operations. +static std::atomic<GlyphAtlas*> gGlyphAtlas; + +/** + * Generates a new colored mini-font atlas from the mini-font mask. + */ +static GlyphAtlas* MakeGlyphAtlas(const DeviceColor& aColor) { + RefPtr<DrawTarget> glyphDrawTarget = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( + IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT), + SurfaceFormat::B8G8R8A8); + if (!glyphDrawTarget) { + return nullptr; + } + RefPtr<SourceSurface> glyphMask = + glyphDrawTarget->CreateSourceSurfaceFromData( + const_cast<uint8_t*>(gMiniFontData), + IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT), MINIFONT_WIDTH * 16, + SurfaceFormat::A8); + if (!glyphMask) { + return nullptr; + } + glyphDrawTarget->MaskSurface(ColorPattern(aColor), glyphMask, Point(0, 0), + DrawOptions(1.0f, CompositionOp::OP_SOURCE)); + RefPtr<SourceSurface> surface = glyphDrawTarget->Snapshot(); + if (!surface) { + return nullptr; + } + return new GlyphAtlas(std::move(surface), aColor); +} + +/** + * Reuse the current mini-font atlas if the color matches, otherwise regenerate + * it. + */ +static inline already_AddRefed<SourceSurface> GetGlyphAtlas( + const DeviceColor& aColor) { + // Get the opaque color, ignoring any transparency which will be handled + // later. + DeviceColor color(aColor.r, aColor.g, aColor.b); + + // Atomically grab the current GlyphAtlas pointer (if any). Because we + // exchange with nullptr here, no other thread will be able to touch the + // currAtlas record while we're using it; if they try, they'll just see + // the null that we stored. + GlyphAtlas* currAtlas = gGlyphAtlas.exchange(nullptr); + + if (currAtlas && currAtlas->Color() == color) { + // If its color is right, grab a reference to its surface. + RefPtr<SourceSurface> surface = currAtlas->Surface(); + // Now put the currAtlas record back in the global. If some other thread + // has stored an atlas there in the meantime, we just discard it. + delete gGlyphAtlas.exchange(currAtlas); + return surface.forget(); + } + + // Make a new atlas in the color we want. + GlyphAtlas* atlas = MakeGlyphAtlas(color); + RefPtr<SourceSurface> surface = atlas ? atlas->Surface() : nullptr; + + // Store the newly-created atlas in the global; release any other. + delete gGlyphAtlas.exchange(atlas); + return surface.forget(); +} + +/** + * Clear any cached glyph atlas resources. + */ +static void PurgeGlyphAtlas() { delete gGlyphAtlas.exchange(nullptr); } + +// WebRender layer manager user data that will get signaled when the layer +// manager is destroyed. +class WRUserData : public layers::LayerUserData, + public LinkedListElement<WRUserData> { + public: + explicit WRUserData(layers::WebRenderLayerManager* aManager); + + ~WRUserData(); + + static void Assign(layers::WebRenderLayerManager* aManager) { + if (!aManager->HasUserData(&sWRUserDataKey)) { + aManager->SetUserData(&sWRUserDataKey, new WRUserData(aManager)); + } + } + + void Remove() { mManager->RemoveUserData(&sWRUserDataKey); } + + layers::WebRenderLayerManager* mManager; + + static UserDataKey sWRUserDataKey; +}; + +static void DestroyImageKey(void* aClosure) { + auto* key = static_cast<wr::ImageKey*>(aClosure); + delete key; +} + +static RefPtr<SourceSurface> gWRGlyphAtlas[8]; +static LinkedList<WRUserData> gWRUsers; +UserDataKey WRUserData::sWRUserDataKey; + +/** + * Generates a transformed WebRender mini-font atlas for a given orientation. + */ +static already_AddRefed<SourceSurface> MakeWRGlyphAtlas(const Matrix* aMat) { + IntSize size(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT); + // If the orientation is transposed, width/height are swapped. + if (aMat && aMat->_11 == 0) { + std::swap(size.width, size.height); + } + RefPtr<DrawTarget> ref = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + RefPtr<DrawTarget> dt = + gfxPlatform::GetPlatform()->CreateSimilarSoftwareDrawTarget( + ref, size, SurfaceFormat::B8G8R8A8); + if (!dt) { + return nullptr; + } + if (aMat) { + // Select appropriate transform matrix based on whether the + // orientation is transposed. + dt->SetTransform(aMat->_11 == 0 + ? Matrix(0.0f, copysign(1.0f, aMat->_12), + copysign(1.0f, aMat->_21), 0.0f, + aMat->_21 < 0 ? MINIFONT_HEIGHT : 0.0f, + aMat->_12 < 0 ? MINIFONT_WIDTH * 16 : 0.0f) + : Matrix(copysign(1.0f, aMat->_11), 0.0f, 0.0f, + copysign(1.0f, aMat->_22), + aMat->_11 < 0 ? MINIFONT_WIDTH * 16 : 0.0f, + aMat->_22 < 0 ? MINIFONT_HEIGHT : 0.0f)); + } + RefPtr<SourceSurface> mask = dt->CreateSourceSurfaceFromData( + const_cast<uint8_t*>(gMiniFontData), + IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT), MINIFONT_WIDTH * 16, + SurfaceFormat::A8); + if (!mask) { + return nullptr; + } + dt->MaskSurface(ColorPattern(DeviceColor::MaskOpaqueWhite()), mask, + Point(0, 0)); + return dt->Snapshot(); +} + +/** + * Clear any cached WebRender glyph atlas resources. + */ +static void PurgeWRGlyphAtlas() { + // For each WR layer manager, we need go through each atlas orientation + // and see if it has a stashed image key. If it does, remove the image + // from the layer manager. + for (WRUserData* user : gWRUsers) { + auto* manager = user->mManager; + for (size_t i = 0; i < 8; i++) { + if (gWRGlyphAtlas[i]) { + auto* key = static_cast<wr::ImageKey*>(gWRGlyphAtlas[i]->GetUserData( + reinterpret_cast<UserDataKey*>(manager))); + if (key) { + manager->GetRenderRootStateManager()->AddImageKeyForDiscard(*key); + } + } + } + } + // Remove the layer managers' destroy notifications only after processing + // so as not to mess up gWRUsers iteration. + while (!gWRUsers.isEmpty()) { + gWRUsers.popFirst()->Remove(); + } + // Finally, clear out the atlases. + for (size_t i = 0; i < 8; i++) { + gWRGlyphAtlas[i] = nullptr; + } +} + +WRUserData::WRUserData(layers::WebRenderLayerManager* aManager) + : mManager(aManager) { + gWRUsers.insertFront(this); +} + +WRUserData::~WRUserData() { + // When the layer manager is destroyed, we need go through each + // atlas and remove any assigned image keys. + if (isInList()) { + for (size_t i = 0; i < 8; i++) { + if (gWRGlyphAtlas[i]) { + gWRGlyphAtlas[i]->RemoveUserData( + reinterpret_cast<UserDataKey*>(mManager)); + } + } + } +} + +static already_AddRefed<SourceSurface> GetWRGlyphAtlas(DrawTarget& aDrawTarget, + const Matrix* aMat) { + uint32_t key = 0; + // Encode orientation in the key. + if (aMat) { + if (aMat->_11 == 0) { + key |= 4 | (aMat->_12 < 0 ? 1 : 0) | (aMat->_21 < 0 ? 2 : 0); + } else { + key |= (aMat->_11 < 0 ? 1 : 0) | (aMat->_22 < 0 ? 2 : 0); + } + } + + // Check if an atlas was already created, or create one if necessary. + RefPtr<SourceSurface> atlas = gWRGlyphAtlas[key]; + if (!atlas) { + atlas = MakeWRGlyphAtlas(aMat); + gWRGlyphAtlas[key] = atlas; + } + + // The atlas may exist, but an image key may not be assigned for it to + // the given layer manager, or it may no longer be valid. + auto* tdt = static_cast<layout::TextDrawTarget*>(&aDrawTarget); + auto* manager = tdt->WrLayerManager(); + auto* imageKey = static_cast<wr::ImageKey*>( + atlas->GetUserData(reinterpret_cast<UserDataKey*>(manager))); + if (!imageKey || !manager->WrBridge()->MatchesNamespace(*imageKey)) { + // No image key, so we need to map the atlas' data for transfer to WR. + RefPtr<DataSourceSurface> dataSurface = atlas->GetDataSurface(); + if (!dataSurface) { + return nullptr; + } + DataSourceSurface::ScopedMap map(dataSurface, DataSourceSurface::READ); + if (!map.IsMapped()) { + return nullptr; + } + // Transfer the data and get an image key for it. + Maybe<wr::ImageKey> result = tdt->DefineImage( + atlas->GetSize(), map.GetStride(), atlas->GetFormat(), map.GetData()); + if (!result.isSome()) { + return nullptr; + } + // Assign the image key to the atlas. + atlas->AddUserData(reinterpret_cast<UserDataKey*>(manager), + new wr::ImageKey(result.ref()), DestroyImageKey); + // Create a user data notification for when the layer manager is + // destroyed so we can clean up any assigned image keys. + WRUserData::Assign(manager); + } + return atlas.forget(); +} + +static void DrawHexChar(uint32_t aDigit, Float aLeft, Float aTop, + DrawTarget& aDrawTarget, SourceSurface* aAtlas, + const DeviceColor& aColor, + const Matrix* aMat = nullptr) { + Rect dest(aLeft, aTop, MINIFONT_WIDTH, MINIFONT_HEIGHT); + if (aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT) { + // For WR, we need to get the image key assigned to the given WR layer + // manager for referencing the image. + auto* tdt = static_cast<layout::TextDrawTarget*>(&aDrawTarget); + auto* manager = tdt->WrLayerManager(); + auto* key = static_cast<wr::ImageKey*>( + aAtlas->GetUserData(reinterpret_cast<UserDataKey*>(manager))); + MOZ_ASSERT(key); + // Transform the bounds of the atlas into the given orientation, and then + // also transform a small clip rect which will be used to select the given + // digit from the atlas. + Rect bounds(aLeft - aDigit * MINIFONT_WIDTH, aTop, MINIFONT_WIDTH * 16, + MINIFONT_HEIGHT); + if (aMat) { + // Width and height may be negative after the transform, so move the rect + // if necessary and fix size. + bounds = aMat->TransformRect(bounds); + bounds.x += std::min(bounds.width, 0.0f); + bounds.y += std::min(bounds.height, 0.0f); + bounds.width = fabs(bounds.width); + bounds.height = fabs(bounds.height); + dest = aMat->TransformRect(dest); + dest.x += std::min(dest.width, 0.0f); + dest.y += std::min(dest.height, 0.0f); + dest.width = fabs(dest.width); + dest.height = fabs(dest.height); + } + // Finally, push the colored image with point filtering. + tdt->PushImage(*key, bounds, dest, wr::ImageRendering::Pixelated, + wr::ToColorF(aColor)); + } else { + // For the normal case, just draw the given digit from the atlas. Point + // filtering is used to ensure the mini-font rectangles stay sharp with any + // scaling. Handle any transparency here as well. + aDrawTarget.DrawSurface( + aAtlas, dest, + Rect(aDigit * MINIFONT_WIDTH, 0, MINIFONT_WIDTH, MINIFONT_HEIGHT), + DrawSurfaceOptions(SamplingFilter::POINT), + DrawOptions(aColor.a, CompositionOp::OP_OVER, AntialiasMode::NONE)); + } +} + +void gfxFontMissingGlyphs::Purge() { + PurgeGlyphAtlas(); + PurgeWRGlyphAtlas(); +} + +#else // MOZ_GFX_OPTIMIZE_MOBILE + +void gfxFontMissingGlyphs::Purge() {} + +#endif + +void gfxFontMissingGlyphs::Shutdown() { Purge(); } + +void gfxFontMissingGlyphs::DrawMissingGlyph(uint32_t aChar, const Rect& aRect, + DrawTarget& aDrawTarget, + const Pattern& aPattern, + const Matrix* aMat) { + Rect rect(aRect); + // If there is an orientation transform, reorient the bounding rect. + if (aMat) { + rect.MoveBy(-aRect.BottomLeft()); + rect = aMat->TransformBounds(rect); + rect.MoveBy(aRect.BottomLeft()); + } + + // If we're currently drawing with some kind of pattern, we just draw the + // missing-glyph data in black. + DeviceColor color = aPattern.GetType() == PatternType::COLOR + ? static_cast<const ColorPattern&>(aPattern).mColor + : ToDeviceColor(sRGBColor::OpaqueBlack()); + + // Stroke a rectangle so that the stroke's left edge is inset one pixel + // from the left edge of the glyph box and the stroke's right edge + // is inset one pixel from the right edge of the glyph box. + Float halfBorderWidth = BOX_BORDER_WIDTH / 2.0; + Float borderLeft = rect.X() + BOX_HORIZONTAL_INSET + halfBorderWidth; + Float borderRight = rect.XMost() - BOX_HORIZONTAL_INSET - halfBorderWidth; + Rect borderStrokeRect(borderLeft, rect.Y() + halfBorderWidth, + borderRight - borderLeft, + rect.Height() - 2.0 * halfBorderWidth); + if (!borderStrokeRect.IsEmpty()) { + ColorPattern adjustedColor(color); + adjustedColor.mColor.a *= BOX_BORDER_OPACITY; +#ifdef MOZ_GFX_OPTIMIZE_MOBILE + aDrawTarget.FillRect(borderStrokeRect, adjustedColor); +#else + StrokeOptions strokeOptions(BOX_BORDER_WIDTH); + aDrawTarget.StrokeRect(borderStrokeRect, adjustedColor, strokeOptions); +#endif + } + +#ifndef MOZ_GFX_OPTIMIZE_MOBILE + RefPtr<SourceSurface> atlas = + aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT + ? GetWRGlyphAtlas(aDrawTarget, aMat) + : GetGlyphAtlas(color); + if (!atlas) { + return; + } + + Point center = rect.Center(); + Float halfGap = HEX_CHAR_GAP / 2.f; + Float top = -(MINIFONT_HEIGHT + halfGap); + + // Figure out a scaling factor that will fit the glyphs in the target rect + // both horizontally and vertically. + Float width = HEX_CHAR_GAP + MINIFONT_WIDTH + HEX_CHAR_GAP + MINIFONT_WIDTH + + ((aChar < 0x10000) ? 0 : HEX_CHAR_GAP + MINIFONT_WIDTH) + + HEX_CHAR_GAP; + Float height = HEX_CHAR_GAP + MINIFONT_HEIGHT + HEX_CHAR_GAP + + MINIFONT_HEIGHT + HEX_CHAR_GAP; + Float scaling = std::min(rect.Height() / height, rect.Width() / width); + + // We always want integer scaling, otherwise the "bitmap" glyphs will look + // even uglier than usual when scaled to the target. + int32_t devPixelsPerCSSPx = std::max<int32_t>(1, std::floor(scaling)); + + Matrix tempMat; + if (aMat) { + // If there is an orientation transform, since draw target transforms may + // not be supported, scale and translate it so that it can be directly used + // for rendering the mini font without changing the draw target transform. + tempMat = Matrix(*aMat) + .PostScale(devPixelsPerCSSPx, devPixelsPerCSSPx) + .PostTranslate(center); + aMat = &tempMat; + } else { + // Otherwise, scale and translate the draw target transform assuming it + // supports that. + tempMat = aDrawTarget.GetTransform(); + aDrawTarget.SetTransform(Matrix(tempMat).PreTranslate(center).PreScale( + devPixelsPerCSSPx, devPixelsPerCSSPx)); + } + + if (aChar < 0x10000) { + if (rect.Width() >= 2 * (MINIFONT_WIDTH + HEX_CHAR_GAP) && + rect.Height() >= 2 * MINIFONT_HEIGHT + HEX_CHAR_GAP) { + // Draw 4 digits for BMP + Float left = -(MINIFONT_WIDTH + halfGap); + DrawHexChar((aChar >> 12) & 0xF, left, top, aDrawTarget, atlas, color, + aMat); + DrawHexChar((aChar >> 8) & 0xF, halfGap, top, aDrawTarget, atlas, color, + aMat); + DrawHexChar((aChar >> 4) & 0xF, left, halfGap, aDrawTarget, atlas, color, + aMat); + DrawHexChar(aChar & 0xF, halfGap, halfGap, aDrawTarget, atlas, color, + aMat); + } + } else { + if (rect.Width() >= 3 * (MINIFONT_WIDTH + HEX_CHAR_GAP) && + rect.Height() >= 2 * MINIFONT_HEIGHT + HEX_CHAR_GAP) { + // Draw 6 digits for non-BMP + Float first = -(MINIFONT_WIDTH * 1.5 + HEX_CHAR_GAP); + Float second = -(MINIFONT_WIDTH / 2.0); + Float third = (MINIFONT_WIDTH / 2.0 + HEX_CHAR_GAP); + DrawHexChar((aChar >> 20) & 0xF, first, top, aDrawTarget, atlas, color, + aMat); + DrawHexChar((aChar >> 16) & 0xF, second, top, aDrawTarget, atlas, color, + aMat); + DrawHexChar((aChar >> 12) & 0xF, third, top, aDrawTarget, atlas, color, + aMat); + DrawHexChar((aChar >> 8) & 0xF, first, halfGap, aDrawTarget, atlas, color, + aMat); + DrawHexChar((aChar >> 4) & 0xF, second, halfGap, aDrawTarget, atlas, + color, aMat); + DrawHexChar(aChar & 0xF, third, halfGap, aDrawTarget, atlas, color, aMat); + } + } + + if (!aMat) { + // The draw target transform was changed, so it must be restored to + // the original value. + aDrawTarget.SetTransform(tempMat); + } +#endif +} + +Float gfxFontMissingGlyphs::GetDesiredMinWidth(uint32_t aChar, + uint32_t aAppUnitsPerDevPixel) { + /** + * The minimum desired width for a missing-glyph glyph box. I've laid it out + * like this so you can see what goes where. + */ + Float width = BOX_HORIZONTAL_INSET + BOX_BORDER_WIDTH + HEX_CHAR_GAP + + MINIFONT_WIDTH + HEX_CHAR_GAP + MINIFONT_WIDTH + + ((aChar < 0x10000) ? 0 : HEX_CHAR_GAP + MINIFONT_WIDTH) + + HEX_CHAR_GAP + BOX_BORDER_WIDTH + BOX_HORIZONTAL_INSET; + // Note that this will give us floating-point division, so the width will + // -not- be snapped to integer multiples of its basic pixel value + width *= Float(AppUnitsPerCSSPixel()) / aAppUnitsPerDevPixel; + return width; +} diff --git a/gfx/thebes/gfxFontMissingGlyphs.h b/gfx/thebes/gfxFontMissingGlyphs.h new file mode 100644 index 0000000000..711e96f79e --- /dev/null +++ b/gfx/thebes/gfxFontMissingGlyphs.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FONTMISSINGGLYPHS_H +#define GFX_FONTMISSINGGLYPHS_H + +#include "mozilla/Attributes.h" +#include "mozilla/gfx/MatrixFwd.h" +#include "mozilla/gfx/Rect.h" + +namespace mozilla { +namespace gfx { +class DrawTarget; +class Pattern; +} // namespace gfx +} // namespace mozilla + +/** + * This class should not be instantiated. It's just a container + * for some helper functions. + */ +class gfxFontMissingGlyphs final { + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::Float Float; + typedef mozilla::gfx::Matrix Matrix; + typedef mozilla::gfx::Pattern Pattern; + typedef mozilla::gfx::Rect Rect; + + gfxFontMissingGlyphs() = delete; // prevent instantiation + + public: + /** + * Draw hexboxes for a missing glyph. + * @param aChar the UTF16 codepoint for the character + * @param aRect the glyph-box for the glyph that is missing + * @param aDrawTarget the DrawTarget to draw to + * @param aPattern the pattern currently being used to paint + * @param aMat optional local-space orientation matrix + */ + static void DrawMissingGlyph(uint32_t aChar, const Rect& aRect, + DrawTarget& aDrawTarget, const Pattern& aPattern, + const Matrix* aMat = nullptr); + /** + * @return the desired minimum width for a glyph-box that will allow + * the hexboxes to be drawn reasonably. + */ + static Float GetDesiredMinWidth(uint32_t aChar, uint32_t aAppUnitsPerDevUnit); + + static void Purge(); + + static void Shutdown(); +}; + +#endif diff --git a/gfx/thebes/gfxFontPrefLangList.h b/gfx/thebes/gfxFontPrefLangList.h new file mode 100644 index 0000000000..5ca1311cb0 --- /dev/null +++ b/gfx/thebes/gfxFontPrefLangList.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// this needs to match the list of pref font.default.xx entries listed in +// all.js! + +FONT_PREF_LANG(Western, "x-western", x_western), + FONT_PREF_LANG(Japanese, "ja", Japanese), + FONT_PREF_LANG(ChineseTW, "zh-TW", Taiwanese), + FONT_PREF_LANG(ChineseCN, "zh-CN", Chinese), + FONT_PREF_LANG(ChineseHK, "zh-HK", HongKongChinese), + FONT_PREF_LANG(Korean, "ko", ko), + FONT_PREF_LANG(Cyrillic, "x-cyrillic", x_cyrillic), + FONT_PREF_LANG(Greek, "el", el), FONT_PREF_LANG(Thai, "th", th), + FONT_PREF_LANG(Hebrew, "he", he), FONT_PREF_LANG(Arabic, "ar", ar), + FONT_PREF_LANG(Devanagari, "x-devanagari", x_devanagari), + FONT_PREF_LANG(Tamil, "x-tamil", x_tamil), + FONT_PREF_LANG(Armenian, "x-armn", x_armn), + FONT_PREF_LANG(Bengali, "x-beng", x_beng), + FONT_PREF_LANG(Canadian, "x-cans", x_cans), + FONT_PREF_LANG(Ethiopic, "x-ethi", x_ethi), + FONT_PREF_LANG(Georgian, "x-geor", x_geor), + FONT_PREF_LANG(Gujarati, "x-gujr", x_gujr), + FONT_PREF_LANG(Gurmukhi, "x-guru", x_guru), + FONT_PREF_LANG(Khmer, "x-khmr", x_khmr), + FONT_PREF_LANG(Malayalam, "x-mlym", x_mlym), + FONT_PREF_LANG(Mathematics, "x-math", x_math), + FONT_PREF_LANG(Oriya, "x-orya", x_orya), + FONT_PREF_LANG(Telugu, "x-telu", x_telu), + FONT_PREF_LANG(Kannada, "x-knda", x_knda), + FONT_PREF_LANG(Sinhala, "x-sinh", x_sinh), + FONT_PREF_LANG(Tibetan, "x-tibt", x_tibt), + FONT_PREF_LANG(Others, "x-unicode", Unicode) diff --git a/gfx/thebes/gfxFontSrcPrincipal.cpp b/gfx/thebes/gfxFontSrcPrincipal.cpp new file mode 100644 index 0000000000..97ddc5ffe7 --- /dev/null +++ b/gfx/thebes/gfxFontSrcPrincipal.cpp @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxFontSrcPrincipal.h" + +#include "nsURIHashKey.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/HashFunctions.h" + +using mozilla::BasePrincipal; + +gfxFontSrcPrincipal::gfxFontSrcPrincipal(nsIPrincipal* aNodePrincipal, + nsIPrincipal* aStoragePrincipal) + : mNodePrincipal(aNodePrincipal), + mStoragePrincipal(mozilla::StaticPrefs::privacy_partition_network_state() + ? aStoragePrincipal + : aNodePrincipal) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aNodePrincipal); + MOZ_ASSERT(aStoragePrincipal); + + nsAutoCString suffix; + mStoragePrincipal->GetOriginSuffix(suffix); + + mHash = mozilla::AddToHash(mStoragePrincipal->GetHashValue(), + mozilla::HashString(suffix)); +} + +gfxFontSrcPrincipal::~gfxFontSrcPrincipal() = default; + +bool gfxFontSrcPrincipal::Equals(gfxFontSrcPrincipal* aOther) { + return BasePrincipal::Cast(mStoragePrincipal) + ->FastEquals(BasePrincipal::Cast(aOther->mStoragePrincipal)); +} diff --git a/gfx/thebes/gfxFontSrcPrincipal.h b/gfx/thebes/gfxFontSrcPrincipal.h new file mode 100644 index 0000000000..44170701a1 --- /dev/null +++ b/gfx/thebes/gfxFontSrcPrincipal.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_FONTSRCPRINCIPAL_H +#define MOZILLA_GFX_FONTSRCPRINCIPAL_H + +#include "nsCOMPtr.h" +#include "PLDHashTable.h" + +class nsIPrincipal; + +namespace mozilla { +namespace net { +class nsSimpleURI; +} // namespace net +} // namespace mozilla + +/** + * A wrapper for an nsIPrincipal that can be used OMT, which has cached + * information useful for the gfxUserFontSet. + * + * TODO(emilio): This has grown a bit more complex, but nsIPrincipal is now + * thread-safe, re-evaluate the existence of this class. + */ +class gfxFontSrcPrincipal { + public: + explicit gfxFontSrcPrincipal(nsIPrincipal* aNodePrincipal, + nsIPrincipal* aStoragePrincipal); + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxFontSrcPrincipal) + + nsIPrincipal* NodePrincipal() const { return mNodePrincipal; } + + nsIPrincipal* StoragePrincipal() const { return mStoragePrincipal; } + + bool Equals(gfxFontSrcPrincipal* aOther); + + PLDHashNumber Hash() const { return mHash; } + + private: + ~gfxFontSrcPrincipal(); + + // The principal of the node. + nsCOMPtr<nsIPrincipal> mNodePrincipal; + + // The principal used for storage. + nsCOMPtr<nsIPrincipal> mStoragePrincipal; + + // Precomputed hash for mStoragePrincipal. + PLDHashNumber mHash; +}; + +#endif // MOZILLA_GFX_FONTSRCPRINCIPAL_H diff --git a/gfx/thebes/gfxFontSrcURI.cpp b/gfx/thebes/gfxFontSrcURI.cpp new file mode 100644 index 0000000000..3153d2abce --- /dev/null +++ b/gfx/thebes/gfxFontSrcURI.cpp @@ -0,0 +1,113 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxFontSrcURI.h" + +#include "mozilla/ServoStyleSet.h" +#include "nsIProtocolHandler.h" +#include "nsProxyRelease.h" +#include "nsNetUtil.h" +#include "nsSimpleURI.h" +#include "nsURIHashKey.h" + +static bool HasFlag(nsIURI* aURI, uint32_t aFlag) { + nsresult rv; + bool value = false; + rv = NS_URIChainHasFlags(aURI, aFlag, &value); + return NS_SUCCEEDED(rv) && value; +} + +gfxFontSrcURI::gfxFontSrcURI(nsIURI* aURI) : mURI(aURI) { + MOZ_ASSERT(aURI); + + // If we have a data: URI, we know that it is backed by an nsSimpleURI, + // and that we don't need to serialize it ahead of time. + nsCString scheme; + mURI->GetScheme(scheme); + + if (scheme.EqualsLiteral("data")) { + // We know that nsSimpleURI::From returns us a pointer to the same object, + // and we hold a strong reference to the object in mURI, so no need to + // hold it strongly here as well. (And we'd have to + // NS_ReleaseOnMainThread it in our destructor anyway.) + RefPtr<mozilla::net::nsSimpleURI> simpleURI = + mozilla::net::nsSimpleURI::From(aURI); + mSimpleURI = simpleURI; + + NS_ASSERTION(mSimpleURI, + "Why aren't our data: URLs backed by nsSimpleURI?"); + } else { + mSimpleURI = nullptr; + } + + if (!mSimpleURI) { + mURI->GetSpec(mSpec); + } + + mHash = nsURIHashKey::HashKey(mURI); +} + +gfxFontSrcURI::~gfxFontSrcURI() = default; + +void gfxFontSrcURI::EnsureInitialized() { + MOZ_ASSERT(NS_IsMainThread() || mozilla::ServoStyleSet::IsInServoTraversal()); + + if (mInitialized) { + return; + } + + mInheritsSecurityContext = + HasFlag(mURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT); + mSyncLoadIsOK = HasFlag(mURI, nsIProtocolHandler::URI_SYNC_LOAD_IS_OK); + mInitialized = true; +} + +bool gfxFontSrcURI::Equals(gfxFontSrcURI* aOther) { + if (mSimpleURI) { + if (aOther->mSimpleURI) { + return mSimpleURI->Equals(aOther->mSimpleURI); + } + + // The two URIs are probably different. Do a quick check on the + // schemes before deciding to serialize mSimpleURI (which might be + // quite large). + { + nsCString thisScheme; + mSimpleURI->GetScheme(thisScheme); + + nsCString otherScheme; + if (!StringBeginsWith(aOther->mSpec, thisScheme)) { + return false; + } + } + + nsCString thisSpec; + mSimpleURI->GetSpec(thisSpec); + return thisSpec == aOther->mSpec; + } + + if (aOther->mSimpleURI) { + return aOther->Equals(this); + } + + return mSpec == aOther->mSpec; +} + +nsresult gfxFontSrcURI::GetSpec(nsACString& aResult) { + if (mSimpleURI) { + return mSimpleURI->GetSpec(aResult); + } + + aResult = mSpec; + return NS_OK; +} + +nsCString gfxFontSrcURI::GetSpecOrDefault() { + if (mSimpleURI) { + return mSimpleURI->GetSpecOrDefault(); + } + + return mSpec; +} diff --git a/gfx/thebes/gfxFontSrcURI.h b/gfx/thebes/gfxFontSrcURI.h new file mode 100644 index 0000000000..994575d7fc --- /dev/null +++ b/gfx/thebes/gfxFontSrcURI.h @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_FONTSRCURI_H +#define MOZILLA_GFX_FONTSRCURI_H + +#include "nsCOMPtr.h" +#include "nsTString.h" +#include "PLDHashTable.h" + +class nsIURI; + +namespace mozilla { +namespace net { +class nsSimpleURI; +} // namespace net +} // namespace mozilla + +/** + * A wrapper for an nsIURI that can be used OMT, which has cached information + * useful for the gfxUserFontSet. + */ +class gfxFontSrcURI final { + public: + explicit gfxFontSrcURI(nsIURI* aURI); + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxFontSrcURI) + + nsIURI* get() { return mURI; } + + bool Equals(gfxFontSrcURI* aOther); + nsresult GetSpec(nsACString& aResult); + nsCString GetSpecOrDefault(); + + PLDHashNumber Hash() const { return mHash; } + + bool InheritsSecurityContext() { + EnsureInitialized(); + return mInheritsSecurityContext; + } + + bool SyncLoadIsOK() { + EnsureInitialized(); + return mSyncLoadIsOK; + } + + private: + ~gfxFontSrcURI(); + + void EnsureInitialized(); + + // The URI. + nsCOMPtr<nsIURI> mURI; + + // If the nsIURI is an nsSimpleURI for a data: URL, this is a pointer to it. + // (Just a weak reference since mURI holds the strong reference.) + // + // We store this so that we don't duplicate the URL spec for data: URLs, + // which can be much larger than other URLs. + mozilla::net::nsSimpleURI* mSimpleURI; + + // If the nsIURI is not an nsSimpleURI, this is its spec. + nsCString mSpec; + + // Precomputed hash for mURI. + PLDHashNumber mHash; + + // Whether the font has been initialized on the main thread. + bool mInitialized = false; + + // Whether the nsIURI's protocol handler has the URI_INHERITS_SECURITY_CONTEXT + // flag. + bool mInheritsSecurityContext = false; + + // Whether the nsIURI's protocol handler has teh URI_SYNC_LOAD_IS_OK flag. + bool mSyncLoadIsOK = false; +}; + +#endif // MOZILLA_GFX_FONTSRCURI_H diff --git a/gfx/thebes/gfxFontUtils.cpp b/gfx/thebes/gfxFontUtils.cpp new file mode 100644 index 0000000000..02543607cd --- /dev/null +++ b/gfx/thebes/gfxFontUtils.cpp @@ -0,0 +1,1791 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/BinarySearch.h" + +#include "gfxFontUtils.h" +#include "gfxFontEntry.h" +#include "gfxFontVariations.h" +#include "gfxUtils.h" + +#include "nsServiceManagerUtils.h" + +#include "mozilla/Preferences.h" +#include "mozilla/BinarySearch.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Unused.h" + +#include "nsCOMPtr.h" +#include "nsIUUIDGenerator.h" +#include "mozilla/Encoding.h" + +#include "mozilla/ServoStyleSet.h" +#include "mozilla/dom/WorkerCommon.h" + +#include "plbase64.h" +#include "mozilla/Logging.h" + +#ifdef XP_MACOSX +# include <CoreFoundation/CoreFoundation.h> +#endif + +#define LOG(log, args) MOZ_LOG(gfxPlatform::GetLog(log), LogLevel::Debug, args) + +#define UNICODE_BMP_LIMIT 0x10000 + +using namespace mozilla; + +#pragma pack(1) + +typedef struct { + AutoSwap_PRUint16 format; + AutoSwap_PRUint16 reserved; + AutoSwap_PRUint32 length; + AutoSwap_PRUint32 language; + AutoSwap_PRUint32 startCharCode; + AutoSwap_PRUint32 numChars; +} Format10CmapHeader; + +typedef struct { + AutoSwap_PRUint16 format; + AutoSwap_PRUint16 reserved; + AutoSwap_PRUint32 length; + AutoSwap_PRUint32 language; + AutoSwap_PRUint32 numGroups; +} Format12CmapHeader; + +typedef struct { + AutoSwap_PRUint32 startCharCode; + AutoSwap_PRUint32 endCharCode; + AutoSwap_PRUint32 startGlyphId; +} Format12Group; + +#pragma pack() + +void gfxSparseBitSet::Dump(const char* aPrefix, eGfxLog aWhichLog) const { + uint32_t numBlocks = mBlockIndex.Length(); + + for (uint32_t b = 0; b < numBlocks; b++) { + if (mBlockIndex[b] == NO_BLOCK) { + continue; + } + const Block* block = &mBlocks[mBlockIndex[b]]; + const int BUFSIZE = 256; + char outStr[BUFSIZE]; + int index = 0; + index += snprintf(&outStr[index], BUFSIZE - index, "%s u+%6.6x [", aPrefix, + (b * BLOCK_SIZE_BITS)); + for (int i = 0; i < 32; i += 4) { + for (int j = i; j < i + 4; j++) { + uint8_t bits = block->mBits[j]; + uint8_t flip1 = ((bits & 0xaa) >> 1) | ((bits & 0x55) << 1); + uint8_t flip2 = ((flip1 & 0xcc) >> 2) | ((flip1 & 0x33) << 2); + uint8_t flipped = ((flip2 & 0xf0) >> 4) | ((flip2 & 0x0f) << 4); + + index += snprintf(&outStr[index], BUFSIZE - index, "%2.2x", flipped); + } + if (i + 4 != 32) index += snprintf(&outStr[index], BUFSIZE - index, " "); + } + Unused << snprintf(&outStr[index], BUFSIZE - index, "]"); + LOG(aWhichLog, ("%s", outStr)); + } +} + +nsresult gfxFontUtils::ReadCMAPTableFormat10(const uint8_t* aBuf, + uint32_t aLength, + gfxSparseBitSet& aCharacterMap) { + // Ensure table is large enough that we can safely read the header + NS_ENSURE_TRUE(aLength >= sizeof(Format10CmapHeader), + NS_ERROR_GFX_CMAP_MALFORMED); + + // Sanity-check header fields + const Format10CmapHeader* cmap10 = + reinterpret_cast<const Format10CmapHeader*>(aBuf); + NS_ENSURE_TRUE(uint16_t(cmap10->format) == 10, NS_ERROR_GFX_CMAP_MALFORMED); + NS_ENSURE_TRUE(uint16_t(cmap10->reserved) == 0, NS_ERROR_GFX_CMAP_MALFORMED); + + uint32_t tablelen = cmap10->length; + NS_ENSURE_TRUE(tablelen >= sizeof(Format10CmapHeader) && tablelen <= aLength, + NS_ERROR_GFX_CMAP_MALFORMED); + + NS_ENSURE_TRUE(cmap10->language == 0, NS_ERROR_GFX_CMAP_MALFORMED); + + uint32_t numChars = cmap10->numChars; + NS_ENSURE_TRUE( + tablelen == sizeof(Format10CmapHeader) + numChars * sizeof(uint16_t), + NS_ERROR_GFX_CMAP_MALFORMED); + + uint32_t charCode = cmap10->startCharCode; + NS_ENSURE_TRUE(charCode <= CMAP_MAX_CODEPOINT && + charCode + numChars <= CMAP_MAX_CODEPOINT, + NS_ERROR_GFX_CMAP_MALFORMED); + + // glyphs[] array immediately follows the subtable header + const AutoSwap_PRUint16* glyphs = + reinterpret_cast<const AutoSwap_PRUint16*>(cmap10 + 1); + + for (uint32_t i = 0; i < numChars; ++i) { + if (uint16_t(*glyphs) != 0) { + aCharacterMap.set(charCode); + } + ++charCode; + ++glyphs; + } + + aCharacterMap.Compact(); + + return NS_OK; +} + +nsresult gfxFontUtils::ReadCMAPTableFormat12or13( + const uint8_t* aBuf, uint32_t aLength, gfxSparseBitSet& aCharacterMap) { + // Format 13 has the same structure as format 12, the only difference is + // the interpretation of the glyphID field. So we can share the code here + // that reads the table and just records character coverage. + + // Ensure table is large enough that we can safely read the header + NS_ENSURE_TRUE(aLength >= sizeof(Format12CmapHeader), + NS_ERROR_GFX_CMAP_MALFORMED); + + // Sanity-check header fields + const Format12CmapHeader* cmap12 = + reinterpret_cast<const Format12CmapHeader*>(aBuf); + NS_ENSURE_TRUE( + uint16_t(cmap12->format) == 12 || uint16_t(cmap12->format) == 13, + NS_ERROR_GFX_CMAP_MALFORMED); + NS_ENSURE_TRUE(uint16_t(cmap12->reserved) == 0, NS_ERROR_GFX_CMAP_MALFORMED); + + uint32_t tablelen = cmap12->length; + NS_ENSURE_TRUE(tablelen >= sizeof(Format12CmapHeader) && tablelen <= aLength, + NS_ERROR_GFX_CMAP_MALFORMED); + + NS_ENSURE_TRUE(cmap12->language == 0, NS_ERROR_GFX_CMAP_MALFORMED); + + // Check that the table is large enough for the group array + const uint32_t numGroups = cmap12->numGroups; + NS_ENSURE_TRUE( + (tablelen - sizeof(Format12CmapHeader)) / sizeof(Format12Group) >= + numGroups, + NS_ERROR_GFX_CMAP_MALFORMED); + + // The array of groups immediately follows the subtable header. + const Format12Group* group = + reinterpret_cast<const Format12Group*>(aBuf + sizeof(Format12CmapHeader)); + + // Check that groups are in correct order and do not overlap, + // and record character coverage in aCharacterMap. + uint32_t prevEndCharCode = 0; + for (uint32_t i = 0; i < numGroups; i++, group++) { + uint32_t startCharCode = group->startCharCode; + const uint32_t endCharCode = group->endCharCode; + NS_ENSURE_TRUE((prevEndCharCode < startCharCode || i == 0) && + startCharCode <= endCharCode && + endCharCode <= CMAP_MAX_CODEPOINT, + NS_ERROR_GFX_CMAP_MALFORMED); + // don't include a character that maps to glyph ID 0 (.notdef) + if (group->startGlyphId == 0) { + startCharCode++; + } + if (startCharCode <= endCharCode) { + aCharacterMap.SetRange(startCharCode, endCharCode); + } + prevEndCharCode = endCharCode; + } + + aCharacterMap.Compact(); + + return NS_OK; +} + +nsresult gfxFontUtils::ReadCMAPTableFormat4(const uint8_t* aBuf, + uint32_t aLength, + gfxSparseBitSet& aCharacterMap, + bool aIsSymbolFont) { + enum { + OffsetFormat = 0, + OffsetLength = 2, + OffsetLanguage = 4, + OffsetSegCountX2 = 6 + }; + + NS_ENSURE_TRUE(ReadShortAt(aBuf, OffsetFormat) == 4, + NS_ERROR_GFX_CMAP_MALFORMED); + uint16_t tablelen = ReadShortAt(aBuf, OffsetLength); + NS_ENSURE_TRUE(tablelen <= aLength, NS_ERROR_GFX_CMAP_MALFORMED); + NS_ENSURE_TRUE(tablelen > 16, NS_ERROR_GFX_CMAP_MALFORMED); + + // This field should normally (except for Mac platform subtables) be zero + // according to the OT spec, but some buggy fonts have lang = 1 (which would + // be English for MacOS). E.g. Arial Narrow Bold, v. 1.1 (Tiger), Arial + // Unicode MS (see bug 530614). So accept either zero or one here; the error + // should be harmless. + NS_ENSURE_TRUE((ReadShortAt(aBuf, OffsetLanguage) & 0xfffe) == 0, + NS_ERROR_GFX_CMAP_MALFORMED); + + uint16_t segCountX2 = ReadShortAt(aBuf, OffsetSegCountX2); + NS_ENSURE_TRUE(tablelen >= 16 + (segCountX2 * 4), + NS_ERROR_GFX_CMAP_MALFORMED); + + const uint16_t segCount = segCountX2 / 2; + + const uint16_t* endCounts = reinterpret_cast<const uint16_t*>(aBuf + 14); + const uint16_t* startCounts = + endCounts + 1 /* skip one uint16_t for reservedPad */ + segCount; + const uint16_t* idDeltas = startCounts + segCount; + const uint16_t* idRangeOffsets = idDeltas + segCount; + uint16_t prevEndCount = 0; + for (uint16_t i = 0; i < segCount; i++) { + const uint16_t endCount = ReadShortAt16(endCounts, i); + const uint16_t startCount = ReadShortAt16(startCounts, i); + const uint16_t idRangeOffset = ReadShortAt16(idRangeOffsets, i); + + // sanity-check range + // This permits ranges to overlap by 1 character, which is strictly + // incorrect but occurs in Baskerville on OS X 10.7 (see bug 689087), + // and appears to be harmless in practice + NS_ENSURE_TRUE(startCount >= prevEndCount && startCount <= endCount, + NS_ERROR_GFX_CMAP_MALFORMED); + prevEndCount = endCount; + + if (idRangeOffset == 0) { + // figure out if there's a code in the range that would map to + // glyph ID 0 (.notdef); if so, we need to skip setting that + // character code in the map + const uint16_t skipCode = 65536 - ReadShortAt16(idDeltas, i); + if (startCount < skipCode) { + aCharacterMap.SetRange(startCount, + std::min<uint16_t>(skipCode - 1, endCount)); + } + if (skipCode < endCount) { + aCharacterMap.SetRange(std::max<uint16_t>(startCount, skipCode + 1), + endCount); + } + } else { + // Unused: self-documenting. + // const uint16_t idDelta = ReadShortAt16(idDeltas, i); + for (uint32_t c = startCount; c <= endCount; ++c) { + if (c == 0xFFFF) break; + + const uint16_t* gdata = + (idRangeOffset / 2 + (c - startCount) + &idRangeOffsets[i]); + + NS_ENSURE_TRUE( + (uint8_t*)gdata > aBuf && (uint8_t*)gdata < aBuf + aLength, + NS_ERROR_GFX_CMAP_MALFORMED); + + // make sure we have a glyph + if (*gdata != 0) { + // The glyph index at this point is: + uint16_t glyph = ReadShortAt16(idDeltas, i) + *gdata; + if (glyph) { + aCharacterMap.set(c); + } + } + } + } + } + + if (aIsSymbolFont) { + // For fonts with "MS Symbol" encoding, we duplicate character mappings in + // the U+F0xx range down to U+00xx codepoints, so as to support fonts such + // as Wingdings. + // Note that if the font actually has cmap coverage for the U+00xx range + // (either duplicating the PUA codepoints or mapping to separate glyphs), + // this will not affect it. + for (uint32_t c = 0x0020; c <= 0x00ff; ++c) { + if (aCharacterMap.test(0xf000 + c)) { + aCharacterMap.set(c); + } + } + } + + aCharacterMap.Compact(); + + return NS_OK; +} + +nsresult gfxFontUtils::ReadCMAPTableFormat14(const uint8_t* aBuf, + uint32_t aLength, + const uint8_t*& aTable) { + enum { + OffsetFormat = 0, + OffsetTableLength = 2, + OffsetNumVarSelectorRecords = 6, + OffsetVarSelectorRecords = 10, + + SizeOfVarSelectorRecord = 11, + VSRecOffsetVarSelector = 0, + VSRecOffsetDefUVSOffset = 3, + VSRecOffsetNonDefUVSOffset = 7, + + SizeOfDefUVSTable = 4, + DefUVSOffsetStartUnicodeValue = 0, + DefUVSOffsetAdditionalCount = 3, + + SizeOfNonDefUVSTable = 5, + NonDefUVSOffsetUnicodeValue = 0, + NonDefUVSOffsetGlyphID = 3 + }; + NS_ENSURE_TRUE(aLength >= OffsetVarSelectorRecords, + NS_ERROR_GFX_CMAP_MALFORMED); + + NS_ENSURE_TRUE(ReadShortAt(aBuf, OffsetFormat) == 14, + NS_ERROR_GFX_CMAP_MALFORMED); + + uint32_t tablelen = ReadLongAt(aBuf, OffsetTableLength); + NS_ENSURE_TRUE(tablelen <= aLength, NS_ERROR_GFX_CMAP_MALFORMED); + NS_ENSURE_TRUE(tablelen >= OffsetVarSelectorRecords, + NS_ERROR_GFX_CMAP_MALFORMED); + + const uint32_t numVarSelectorRecords = + ReadLongAt(aBuf, OffsetNumVarSelectorRecords); + NS_ENSURE_TRUE( + (tablelen - OffsetVarSelectorRecords) / SizeOfVarSelectorRecord >= + numVarSelectorRecords, + NS_ERROR_GFX_CMAP_MALFORMED); + + const uint8_t* records = aBuf + OffsetVarSelectorRecords; + for (uint32_t i = 0; i < numVarSelectorRecords; + i++, records += SizeOfVarSelectorRecord) { + const uint32_t varSelector = ReadUint24At(records, VSRecOffsetVarSelector); + const uint32_t defUVSOffset = ReadLongAt(records, VSRecOffsetDefUVSOffset); + const uint32_t nonDefUVSOffset = + ReadLongAt(records, VSRecOffsetNonDefUVSOffset); + NS_ENSURE_TRUE(varSelector <= CMAP_MAX_CODEPOINT && + defUVSOffset <= tablelen - 4 && + nonDefUVSOffset <= tablelen - 4, + NS_ERROR_GFX_CMAP_MALFORMED); + + if (defUVSOffset) { + const uint32_t numUnicodeValueRanges = ReadLongAt(aBuf, defUVSOffset); + NS_ENSURE_TRUE((tablelen - defUVSOffset) / SizeOfDefUVSTable >= + numUnicodeValueRanges, + NS_ERROR_GFX_CMAP_MALFORMED); + const uint8_t* tables = aBuf + defUVSOffset + 4; + uint32_t prevEndUnicode = 0; + for (uint32_t j = 0; j < numUnicodeValueRanges; + j++, tables += SizeOfDefUVSTable) { + const uint32_t startUnicode = + ReadUint24At(tables, DefUVSOffsetStartUnicodeValue); + const uint32_t endUnicode = + startUnicode + tables[DefUVSOffsetAdditionalCount]; + NS_ENSURE_TRUE((prevEndUnicode < startUnicode || j == 0) && + endUnicode <= CMAP_MAX_CODEPOINT, + NS_ERROR_GFX_CMAP_MALFORMED); + prevEndUnicode = endUnicode; + } + } + + if (nonDefUVSOffset) { + const uint32_t numUVSMappings = ReadLongAt(aBuf, nonDefUVSOffset); + NS_ENSURE_TRUE( + (tablelen - nonDefUVSOffset) / SizeOfNonDefUVSTable >= numUVSMappings, + NS_ERROR_GFX_CMAP_MALFORMED); + const uint8_t* tables = aBuf + nonDefUVSOffset + 4; + uint32_t prevUnicode = 0; + for (uint32_t j = 0; j < numUVSMappings; + j++, tables += SizeOfNonDefUVSTable) { + const uint32_t unicodeValue = + ReadUint24At(tables, NonDefUVSOffsetUnicodeValue); + NS_ENSURE_TRUE((prevUnicode < unicodeValue || j == 0) && + unicodeValue <= CMAP_MAX_CODEPOINT, + NS_ERROR_GFX_CMAP_MALFORMED); + prevUnicode = unicodeValue; + } + } + } + + uint8_t* table = new uint8_t[tablelen]; + memcpy(table, aBuf, tablelen); + + aTable = static_cast<const uint8_t*>(table); + + return NS_OK; +} + +// For fonts with two format-4 tables, the first one (Unicode platform) is +// preferred on the Mac; on other platforms we allow the Microsoft-platform +// subtable to replace it. + +#if defined(XP_MACOSX) +# define acceptableFormat4(p, e, k) \ + (((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDMicrosoft && !(k)) || \ + ((p) == PLATFORM_ID_UNICODE)) + +# define acceptableUCS4Encoding(p, e, k) \ + (((p) == PLATFORM_ID_MICROSOFT && \ + (e) == EncodingIDUCS4ForMicrosoftPlatform) && \ + (k) != 12 || \ + ((p) == PLATFORM_ID_UNICODE && ((e) != EncodingIDUVSForUnicodePlatform))) +#else +# define acceptableFormat4(p, e, k) \ + (((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDMicrosoft) || \ + ((p) == PLATFORM_ID_UNICODE)) + +# define acceptableUCS4Encoding(p, e, k) \ + ((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDUCS4ForMicrosoftPlatform) +#endif + +#define acceptablePlatform(p) \ + ((p) == PLATFORM_ID_UNICODE || (p) == PLATFORM_ID_MICROSOFT) +#define isSymbol(p, e) ((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDSymbol) +#define isUVSEncoding(p, e) \ + ((p) == PLATFORM_ID_UNICODE && (e) == EncodingIDUVSForUnicodePlatform) + +uint32_t gfxFontUtils::FindPreferredSubtable(const uint8_t* aBuf, + uint32_t aBufLength, + uint32_t* aTableOffset, + uint32_t* aUVSTableOffset, + bool* aIsSymbolFont) { + enum { + OffsetVersion = 0, + OffsetNumTables = 2, + SizeOfHeader = 4, + + TableOffsetPlatformID = 0, + TableOffsetEncodingID = 2, + TableOffsetOffset = 4, + SizeOfTable = 8, + + SubtableOffsetFormat = 0 + }; + enum { + EncodingIDSymbol = 0, + EncodingIDMicrosoft = 1, + EncodingIDDefaultForUnicodePlatform = 0, + EncodingIDUCS4ForUnicodePlatform = 3, + EncodingIDUVSForUnicodePlatform = 5, + EncodingIDUCS4ForMicrosoftPlatform = 10 + }; + + if (aUVSTableOffset) { + *aUVSTableOffset = 0; + } + if (aIsSymbolFont) { + *aIsSymbolFont = false; + } + + if (!aBuf || aBufLength < SizeOfHeader) { + // cmap table is missing, or too small to contain header fields! + return 0; + } + + // uint16_t version = ReadShortAt(aBuf, OffsetVersion); // Unused: + // self-documenting. + uint16_t numTables = ReadShortAt(aBuf, OffsetNumTables); + if (aBufLength < uint32_t(SizeOfHeader + numTables * SizeOfTable)) { + return 0; + } + + // save the format we want here + uint32_t keepFormat = 0; + + const uint8_t* table = aBuf + SizeOfHeader; + for (uint16_t i = 0; i < numTables; ++i, table += SizeOfTable) { + const uint16_t platformID = ReadShortAt(table, TableOffsetPlatformID); + if (!acceptablePlatform(platformID)) continue; + + const uint16_t encodingID = ReadShortAt(table, TableOffsetEncodingID); + const uint32_t offset = ReadLongAt(table, TableOffsetOffset); + if (aBufLength - 2 < offset) { + // this subtable is not valid - beyond end of buffer + return 0; + } + + const uint8_t* subtable = aBuf + offset; + const uint16_t format = ReadShortAt(subtable, SubtableOffsetFormat); + + if (isSymbol(platformID, encodingID)) { + keepFormat = format; + *aTableOffset = offset; + if (aIsSymbolFont) { + *aIsSymbolFont = true; + } + break; + } else if (format == 4 && + acceptableFormat4(platformID, encodingID, keepFormat)) { + keepFormat = format; + *aTableOffset = offset; + } else if ((format == 10 || format == 12 || format == 13) && + acceptableUCS4Encoding(platformID, encodingID, keepFormat)) { + keepFormat = format; + *aTableOffset = offset; + if (platformID > PLATFORM_ID_UNICODE || !aUVSTableOffset || + *aUVSTableOffset) { + break; // we don't want to try anything else when this format is + // available. + } + } else if (format == 14 && isUVSEncoding(platformID, encodingID) && + aUVSTableOffset) { + *aUVSTableOffset = offset; + if (keepFormat == 10 || keepFormat == 12) { + break; + } + } + } + + return keepFormat; +} + +nsresult gfxFontUtils::ReadCMAP(const uint8_t* aBuf, uint32_t aBufLength, + gfxSparseBitSet& aCharacterMap, + uint32_t& aUVSOffset) { + uint32_t offset; + bool isSymbolFont; + uint32_t format = FindPreferredSubtable(aBuf, aBufLength, &offset, + &aUVSOffset, &isSymbolFont); + + switch (format) { + case 4: + return ReadCMAPTableFormat4(aBuf + offset, aBufLength - offset, + aCharacterMap, isSymbolFont); + + case 10: + return ReadCMAPTableFormat10(aBuf + offset, aBufLength - offset, + aCharacterMap); + + case 12: + case 13: + return ReadCMAPTableFormat12or13(aBuf + offset, aBufLength - offset, + aCharacterMap); + + default: + break; + } + + return NS_ERROR_FAILURE; +} + +#pragma pack(1) + +typedef struct { + AutoSwap_PRUint16 format; + AutoSwap_PRUint16 length; + AutoSwap_PRUint16 language; + AutoSwap_PRUint16 segCountX2; + AutoSwap_PRUint16 searchRange; + AutoSwap_PRUint16 entrySelector; + AutoSwap_PRUint16 rangeShift; + + AutoSwap_PRUint16 arrays[1]; +} Format4Cmap; + +typedef struct Format14Cmap { + AutoSwap_PRUint16 format; + AutoSwap_PRUint32 length; + AutoSwap_PRUint32 numVarSelectorRecords; + + typedef struct { + AutoSwap_PRUint24 varSelector; + AutoSwap_PRUint32 defaultUVSOffset; + AutoSwap_PRUint32 nonDefaultUVSOffset; + } VarSelectorRecord; + + VarSelectorRecord varSelectorRecords[1]; +} Format14Cmap; + +typedef struct NonDefUVSTable { + AutoSwap_PRUint32 numUVSMappings; + + typedef struct { + AutoSwap_PRUint24 unicodeValue; + AutoSwap_PRUint16 glyphID; + } UVSMapping; + + UVSMapping uvsMappings[1]; +} NonDefUVSTable; + +#pragma pack() + +uint32_t gfxFontUtils::MapCharToGlyphFormat4(const uint8_t* aBuf, + uint32_t aLength, char16_t aCh) { + const Format4Cmap* cmap4 = reinterpret_cast<const Format4Cmap*>(aBuf); + + uint16_t segCount = (uint16_t)(cmap4->segCountX2) / 2; + + const AutoSwap_PRUint16* endCodes = &cmap4->arrays[0]; + const AutoSwap_PRUint16* startCodes = &cmap4->arrays[segCount + 1]; + const AutoSwap_PRUint16* idDelta = &startCodes[segCount]; + const AutoSwap_PRUint16* idRangeOffset = &idDelta[segCount]; + + // Sanity-check that the fixed-size arrays don't exceed the buffer. + const uint8_t* const limit = aBuf + aLength; + if ((const uint8_t*)(&idRangeOffset[segCount]) > limit) { + return 0; // broken font, just bail out safely + } + + // For most efficient binary search, we want to work on a range of segment + // indexes that is a power of 2 so that we can always halve it by shifting. + // So we find the largest power of 2 that is <= segCount. + // We will offset this range by segOffset so as to reach the end + // of the table, provided that doesn't put us beyond the target + // value from the outset. + uint32_t powerOf2 = mozilla::FindHighestBit(segCount); + uint32_t segOffset = segCount - powerOf2; + uint32_t idx = 0; + + if (uint16_t(startCodes[segOffset]) <= aCh) { + idx = segOffset; + } + + // Repeatedly halve the size of the range until we find the target group + while (powerOf2 > 1) { + powerOf2 >>= 1; + if (uint16_t(startCodes[idx + powerOf2]) <= aCh) { + idx += powerOf2; + } + } + + if (aCh >= uint16_t(startCodes[idx]) && aCh <= uint16_t(endCodes[idx])) { + uint16_t result; + if (uint16_t(idRangeOffset[idx]) == 0) { + result = aCh; + } else { + uint16_t offset = aCh - uint16_t(startCodes[idx]); + const AutoSwap_PRUint16* glyphIndexTable = + (const AutoSwap_PRUint16*)((const char*)&idRangeOffset[idx] + + uint16_t(idRangeOffset[idx])); + if ((const uint8_t*)(glyphIndexTable + offset + 1) > limit) { + return 0; // broken font, just bail out safely + } + result = glyphIndexTable[offset]; + } + + // Note that this is unsigned 16-bit arithmetic, and may wrap around + // (which is required behavior per spec) + result += uint16_t(idDelta[idx]); + return result; + } + + return 0; +} + +uint32_t gfxFontUtils::MapCharToGlyphFormat10(const uint8_t* aBuf, + uint32_t aCh) { + const Format10CmapHeader* cmap10 = + reinterpret_cast<const Format10CmapHeader*>(aBuf); + + uint32_t startChar = cmap10->startCharCode; + uint32_t numChars = cmap10->numChars; + + if (aCh < startChar || aCh >= startChar + numChars) { + return 0; + } + + const AutoSwap_PRUint16* glyphs = + reinterpret_cast<const AutoSwap_PRUint16*>(cmap10 + 1); + + uint16_t glyph = glyphs[aCh - startChar]; + return glyph; +} + +uint32_t gfxFontUtils::MapCharToGlyphFormat12or13(const uint8_t* aBuf, + uint32_t aCh) { + // The only difference between formats 12 and 13 is the interpretation of + // the glyphId field. So the code here uses the same "Format12" structures, + // etc., to handle both subtable formats. + + const Format12CmapHeader* cmap12 = + reinterpret_cast<const Format12CmapHeader*>(aBuf); + + // We know that numGroups is within range for the subtable size + // because it was checked by ReadCMAPTableFormat12or13. + uint32_t numGroups = cmap12->numGroups; + + // The array of groups immediately follows the subtable header. + const Format12Group* groups = + reinterpret_cast<const Format12Group*>(aBuf + sizeof(Format12CmapHeader)); + + // For most efficient binary search, we want to work on a range that + // is a power of 2 so that we can always halve it by shifting. + // So we find the largest power of 2 that is <= numGroups. + // We will offset this range by rangeOffset so as to reach the end + // of the table, provided that doesn't put us beyond the target + // value from the outset. + uint32_t powerOf2 = mozilla::FindHighestBit(numGroups); + uint32_t rangeOffset = numGroups - powerOf2; + uint32_t range = 0; + uint32_t startCharCode; + + if (groups[rangeOffset].startCharCode <= aCh) { + range = rangeOffset; + } + + // Repeatedly halve the size of the range until we find the target group + while (powerOf2 > 1) { + powerOf2 >>= 1; + if (groups[range + powerOf2].startCharCode <= aCh) { + range += powerOf2; + } + } + + // Check if the character is actually present in the range and return + // the corresponding glyph ID. Here is where formats 12 and 13 interpret + // the startGlyphId (12) or glyphId (13) field differently + startCharCode = groups[range].startCharCode; + if (startCharCode <= aCh && groups[range].endCharCode >= aCh) { + return uint16_t(cmap12->format) == 12 + ? uint16_t(groups[range].startGlyphId) + aCh - startCharCode + : uint16_t(groups[range].startGlyphId); + } + + // Else it's not present, so return the .notdef glyph + return 0; +} + +namespace { + +struct Format14CmapWrapper { + const Format14Cmap& mCmap14; + explicit Format14CmapWrapper(const Format14Cmap& cmap14) : mCmap14(cmap14) {} + uint32_t operator[](size_t index) const { + return mCmap14.varSelectorRecords[index].varSelector; + } +}; + +struct NonDefUVSTableWrapper { + const NonDefUVSTable& mTable; + explicit NonDefUVSTableWrapper(const NonDefUVSTable& table) : mTable(table) {} + uint32_t operator[](size_t index) const { + return mTable.uvsMappings[index].unicodeValue; + } +}; + +} // namespace + +uint16_t gfxFontUtils::MapUVSToGlyphFormat14(const uint8_t* aBuf, uint32_t aCh, + uint32_t aVS) { + using mozilla::BinarySearch; + const Format14Cmap* cmap14 = reinterpret_cast<const Format14Cmap*>(aBuf); + + size_t index; + if (!BinarySearch(Format14CmapWrapper(*cmap14), 0, + cmap14->numVarSelectorRecords, aVS, &index)) { + return 0; + } + + const uint32_t nonDefUVSOffset = + cmap14->varSelectorRecords[index].nonDefaultUVSOffset; + if (!nonDefUVSOffset) { + return 0; + } + + const NonDefUVSTable* table = + reinterpret_cast<const NonDefUVSTable*>(aBuf + nonDefUVSOffset); + + if (BinarySearch(NonDefUVSTableWrapper(*table), 0, table->numUVSMappings, aCh, + &index)) { + return table->uvsMappings[index].glyphID; + } + + return 0; +} + +uint32_t gfxFontUtils::MapCharToGlyph(const uint8_t* aCmapBuf, + uint32_t aBufLength, uint32_t aUnicode, + uint32_t aVarSelector) { + uint32_t offset, uvsOffset; + bool isSymbolFont; + uint32_t format = FindPreferredSubtable(aCmapBuf, aBufLength, &offset, + &uvsOffset, &isSymbolFont); + + uint32_t gid; + switch (format) { + case 4: + gid = aUnicode < UNICODE_BMP_LIMIT + ? MapCharToGlyphFormat4(aCmapBuf + offset, aBufLength - offset, + char16_t(aUnicode)) + : 0; + if (!gid && isSymbolFont) { + if (auto pua = MapLegacySymbolFontCharToPUA(aUnicode)) { + gid = MapCharToGlyphFormat4(aCmapBuf + offset, aBufLength - offset, + pua); + } + } + break; + case 10: + gid = MapCharToGlyphFormat10(aCmapBuf + offset, aUnicode); + break; + case 12: + case 13: + gid = MapCharToGlyphFormat12or13(aCmapBuf + offset, aUnicode); + break; + default: + NS_WARNING("unsupported cmap format, glyphs will be missing"); + gid = 0; + } + + if (aVarSelector && uvsOffset && gid) { + uint32_t varGID = gfxFontUtils::MapUVSToGlyphFormat14( + aCmapBuf + uvsOffset, aUnicode, aVarSelector); + if (!varGID) { + aUnicode = gfxFontUtils::GetUVSFallback(aUnicode, aVarSelector); + if (aUnicode) { + switch (format) { + case 4: + if (aUnicode < UNICODE_BMP_LIMIT) { + varGID = MapCharToGlyphFormat4( + aCmapBuf + offset, aBufLength - offset, char16_t(aUnicode)); + } + break; + case 10: + varGID = MapCharToGlyphFormat10(aCmapBuf + offset, aUnicode); + break; + case 12: + case 13: + varGID = MapCharToGlyphFormat12or13(aCmapBuf + offset, aUnicode); + break; + } + } + } + if (varGID) { + gid = varGID; + } + + // else the variation sequence was not supported, use default mapping + // of the character code alone + } + + return gid; +} + +void gfxFontUtils::ParseFontList(const nsACString& aFamilyList, + nsTArray<nsCString>& aFontList) { + const char kComma = ','; + + // append each font name to the list + nsAutoCString fontname; + const char *p, *p_end; + aFamilyList.BeginReading(p); + aFamilyList.EndReading(p_end); + + while (p < p_end) { + const char* nameStart = p; + while (++p != p_end && *p != kComma) /* nothing */ + ; + + // pull out a single name and clean out leading/trailing whitespace + fontname = Substring(nameStart, p); + fontname.CompressWhitespace(true, true); + + // append it to the list if it's not empty + if (!fontname.IsEmpty()) { + aFontList.AppendElement(fontname); + } + ++p; + } +} + +void gfxFontUtils::GetPrefsFontList(const char* aPrefName, + nsTArray<nsCString>& aFontList, + bool aLocalized) { + aFontList.Clear(); + + nsAutoCString fontlistValue; + nsresult rv = aLocalized + ? Preferences::GetLocalizedCString(aPrefName, fontlistValue) + : Preferences::GetCString(aPrefName, fontlistValue); + if (NS_FAILED(rv)) { + return; + } + + ParseFontList(fontlistValue, aFontList); +} + +// produce a unique font name that is (1) a valid Postscript name and (2) less +// than 31 characters in length. Using AddFontMemResourceEx on Windows fails +// for names longer than 30 characters in length. + +#define MAX_B64_LEN 32 + +nsresult gfxFontUtils::MakeUniqueUserFontName(nsAString& aName) { + nsCOMPtr<nsIUUIDGenerator> uuidgen = + do_GetService("@mozilla.org/uuid-generator;1"); + NS_ENSURE_TRUE(uuidgen, NS_ERROR_OUT_OF_MEMORY); + + nsID guid; + + NS_ASSERTION(sizeof(guid) * 2 <= MAX_B64_LEN, "size of nsID has changed!"); + + nsresult rv = uuidgen->GenerateUUIDInPlace(&guid); + NS_ENSURE_SUCCESS(rv, rv); + + char guidB64[MAX_B64_LEN] = {0}; + + if (!PL_Base64Encode(reinterpret_cast<char*>(&guid), sizeof(guid), guidB64)) + return NS_ERROR_FAILURE; + + // all b64 characters except for '/' are allowed in Postscript names, so + // convert / ==> - + char* p; + for (p = guidB64; *p; p++) { + if (*p == '/') *p = '-'; + } + + aName.AssignLiteral(u"uf"); + aName.AppendASCII(guidB64); + return NS_OK; +} + +// TrueType/OpenType table handling code + +// need byte aligned structs +#pragma pack(1) + +// name table stores set of name record structures, followed by +// large block containing all the strings. name record offset and length +// indicates the offset and length within that block. +// http://www.microsoft.com/typography/otspec/name.htm +struct NameRecordData { + uint32_t offset; + uint32_t length; +}; + +#pragma pack() + +static bool IsValidSFNTVersion(uint32_t version) { + // normally 0x00010000, CFF-style OT fonts == 'OTTO' and Apple TT fonts = + // 'true' 'typ1' is also possible for old Type 1 fonts in a SFNT container but + // not supported + return version == 0x10000 || version == TRUETYPE_TAG('O', 'T', 'T', 'O') || + version == TRUETYPE_TAG('t', 'r', 'u', 'e'); +} + +gfxUserFontType gfxFontUtils::DetermineFontDataType(const uint8_t* aFontData, + uint32_t aFontDataLength) { + // test for OpenType font data + // problem: EOT-Lite with 0x10000 length will look like TrueType! + if (aFontDataLength >= sizeof(SFNTHeader)) { + const SFNTHeader* sfntHeader = + reinterpret_cast<const SFNTHeader*>(aFontData); + uint32_t sfntVersion = sfntHeader->sfntVersion; + if (IsValidSFNTVersion(sfntVersion)) { + return GFX_USERFONT_OPENTYPE; + } + } + + // test for WOFF or WOFF2 + if (aFontDataLength >= sizeof(AutoSwap_PRUint32)) { + const AutoSwap_PRUint32* version = + reinterpret_cast<const AutoSwap_PRUint32*>(aFontData); + if (uint32_t(*version) == TRUETYPE_TAG('w', 'O', 'F', 'F')) { + return GFX_USERFONT_WOFF; + } + if (uint32_t(*version) == TRUETYPE_TAG('w', 'O', 'F', '2')) { + return GFX_USERFONT_WOFF2; + } + } + + // tests for other formats here + + return GFX_USERFONT_UNKNOWN; +} + +static int DirEntryCmp(const void* aKey, const void* aItem) { + int32_t tag = *static_cast<const int32_t*>(aKey); + const TableDirEntry* entry = static_cast<const TableDirEntry*>(aItem); + return tag - int32_t(entry->tag); +} + +/* static */ +TableDirEntry* gfxFontUtils::FindTableDirEntry(const void* aFontData, + uint32_t aTableTag) { + const SFNTHeader* header = reinterpret_cast<const SFNTHeader*>(aFontData); + const TableDirEntry* dir = reinterpret_cast<const TableDirEntry*>(header + 1); + return static_cast<TableDirEntry*>( + bsearch(&aTableTag, dir, uint16_t(header->numTables), + sizeof(TableDirEntry), DirEntryCmp)); +} + +/* static */ +hb_blob_t* gfxFontUtils::GetTableFromFontData(const void* aFontData, + uint32_t aTableTag) { + const TableDirEntry* dir = FindTableDirEntry(aFontData, aTableTag); + if (dir) { + return hb_blob_create( + reinterpret_cast<const char*>(aFontData) + dir->offset, dir->length, + HB_MEMORY_MODE_READONLY, nullptr, nullptr); + } + return nullptr; +} + +nsresult gfxFontUtils::RenameFont(const nsAString& aName, + const uint8_t* aFontData, + uint32_t aFontDataLength, + FallibleTArray<uint8_t>* aNewFont) { + NS_ASSERTION(aNewFont, "null font data array"); + + uint64_t dataLength(aFontDataLength); + + // new name table + static const uint32_t neededNameIDs[] = {NAME_ID_FAMILY, NAME_ID_STYLE, + NAME_ID_UNIQUE, NAME_ID_FULL, + NAME_ID_POSTSCRIPT}; + + // calculate new name table size + uint16_t nameCount = ArrayLength(neededNameIDs); + + // leave room for null-terminator + uint32_t nameStrLength = (aName.Length() + 1) * sizeof(char16_t); + if (nameStrLength > 65535) { + // The name length _in bytes_ must fit in an unsigned short field; + // therefore, a name longer than this cannot be used. + return NS_ERROR_FAILURE; + } + + // round name table size up to 4-byte multiple + uint32_t nameTableSize = + (sizeof(NameHeader) + sizeof(NameRecord) * nameCount + nameStrLength + + 3) & + ~3; + + if (dataLength + nameTableSize > UINT32_MAX) return NS_ERROR_FAILURE; + + // bug 505386 - need to handle unpadded font length + uint32_t paddedFontDataSize = (aFontDataLength + 3) & ~3; + uint32_t adjFontDataSize = paddedFontDataSize + nameTableSize; + + // create new buffer: old font data plus new name table + if (!aNewFont->AppendElements(adjFontDataSize, fallible)) + return NS_ERROR_OUT_OF_MEMORY; + + // copy the old font data + uint8_t* newFontData = reinterpret_cast<uint8_t*>(aNewFont->Elements()); + + // null the last four bytes in case the font length is not a multiple of 4 + memset(newFontData + aFontDataLength, 0, + paddedFontDataSize - aFontDataLength); + + // copy font data + memcpy(newFontData, aFontData, aFontDataLength); + + // null out the last 4 bytes for checksum calculations + memset(newFontData + adjFontDataSize - 4, 0, 4); + + NameHeader* nameHeader = + reinterpret_cast<NameHeader*>(newFontData + paddedFontDataSize); + + // -- name header + nameHeader->format = 0; + nameHeader->count = nameCount; + nameHeader->stringOffset = + sizeof(NameHeader) + nameCount * sizeof(NameRecord); + + // -- name records + uint32_t i; + NameRecord* nameRecord = reinterpret_cast<NameRecord*>(nameHeader + 1); + + for (i = 0; i < nameCount; i++, nameRecord++) { + nameRecord->platformID = PLATFORM_ID_MICROSOFT; + nameRecord->encodingID = ENCODING_ID_MICROSOFT_UNICODEBMP; + nameRecord->languageID = LANG_ID_MICROSOFT_EN_US; + nameRecord->nameID = neededNameIDs[i]; + nameRecord->offset = 0; + nameRecord->length = nameStrLength; + } + + // -- string data, located after the name records, stored in big-endian form + char16_t* strData = reinterpret_cast<char16_t*>(nameRecord); + + mozilla::NativeEndian::copyAndSwapToBigEndian(strData, aName.BeginReading(), + aName.Length()); + strData[aName.Length()] = 0; // add null termination + + // adjust name table header to point to the new name table + SFNTHeader* sfntHeader = reinterpret_cast<SFNTHeader*>(newFontData); + + // table directory entries begin immediately following SFNT header + TableDirEntry* dirEntry = + FindTableDirEntry(newFontData, TRUETYPE_TAG('n', 'a', 'm', 'e')); + // function only called if font validates, so this should always be true + MOZ_ASSERT(dirEntry, "attempt to rename font with no name table"); + + uint32_t numTables = sfntHeader->numTables; + + // note: dirEntry now points to 'name' table record + + // recalculate name table checksum + uint32_t checkSum = 0; + AutoSwap_PRUint32* nameData = + reinterpret_cast<AutoSwap_PRUint32*>(nameHeader); + AutoSwap_PRUint32* nameDataEnd = nameData + (nameTableSize >> 2); + + while (nameData < nameDataEnd) checkSum = checkSum + *nameData++; + + // adjust name table entry to point to new name table + dirEntry->offset = paddedFontDataSize; + dirEntry->length = nameTableSize; + dirEntry->checkSum = checkSum; + + // fix up checksums + uint32_t checksum = 0; + + // checksum for font = (checksum of header) + (checksum of tables) + uint32_t headerLen = sizeof(SFNTHeader) + sizeof(TableDirEntry) * numTables; + const AutoSwap_PRUint32* headerData = + reinterpret_cast<const AutoSwap_PRUint32*>(newFontData); + + // header length is in bytes, checksum calculated in longwords + for (i = 0; i < (headerLen >> 2); i++, headerData++) { + checksum += *headerData; + } + + uint32_t headOffset = 0; + dirEntry = reinterpret_cast<TableDirEntry*>(newFontData + sizeof(SFNTHeader)); + + for (i = 0; i < numTables; i++, dirEntry++) { + if (dirEntry->tag == TRUETYPE_TAG('h', 'e', 'a', 'd')) { + headOffset = dirEntry->offset; + } + checksum += dirEntry->checkSum; + } + + NS_ASSERTION(headOffset != 0, "no head table for font"); + + HeadTable* headData = reinterpret_cast<HeadTable*>(newFontData + headOffset); + + headData->checkSumAdjustment = HeadTable::HEAD_CHECKSUM_CALC_CONST - checksum; + + return NS_OK; +} + +// This is only called after the basic validity of the downloaded sfnt +// data has been checked, so it should never fail to find the name table +// (though it might fail to read it, if memory isn't available); +// other checks here are just for extra paranoia. +nsresult gfxFontUtils::GetFullNameFromSFNT(const uint8_t* aFontData, + uint32_t aLength, + nsACString& aFullName) { + aFullName = "(MISSING NAME)"; // should always get replaced + + const TableDirEntry* dirEntry = + FindTableDirEntry(aFontData, TRUETYPE_TAG('n', 'a', 'm', 'e')); + + // should never fail, as we're only called after font validation succeeded + NS_ENSURE_TRUE(dirEntry, NS_ERROR_NOT_AVAILABLE); + + uint32_t len = dirEntry->length; + NS_ENSURE_TRUE(aLength > len && aLength - len >= dirEntry->offset, + NS_ERROR_UNEXPECTED); + + AutoHBBlob nameBlob(hb_blob_create((const char*)aFontData + dirEntry->offset, + len, HB_MEMORY_MODE_READONLY, nullptr, + nullptr)); + nsresult rv = GetFullNameFromTable(nameBlob, aFullName); + + return rv; +} + +nsresult gfxFontUtils::GetFullNameFromTable(hb_blob_t* aNameTable, + nsACString& aFullName) { + nsAutoCString name; + nsresult rv = gfxFontUtils::ReadCanonicalName( + aNameTable, gfxFontUtils::NAME_ID_FULL, name); + if (NS_SUCCEEDED(rv) && !name.IsEmpty()) { + aFullName = name; + return NS_OK; + } + rv = gfxFontUtils::ReadCanonicalName(aNameTable, gfxFontUtils::NAME_ID_FAMILY, + name); + if (NS_SUCCEEDED(rv) && !name.IsEmpty()) { + nsAutoCString styleName; + rv = gfxFontUtils::ReadCanonicalName( + aNameTable, gfxFontUtils::NAME_ID_STYLE, styleName); + if (NS_SUCCEEDED(rv) && !styleName.IsEmpty()) { + name.Append(' '); + name.Append(styleName); + aFullName = name; + } + return NS_OK; + } + + return NS_ERROR_NOT_AVAILABLE; +} + +nsresult gfxFontUtils::GetFamilyNameFromTable(hb_blob_t* aNameTable, + nsACString& aFamilyName) { + nsAutoCString name; + nsresult rv = gfxFontUtils::ReadCanonicalName( + aNameTable, gfxFontUtils::NAME_ID_FAMILY, name); + if (NS_SUCCEEDED(rv) && !name.IsEmpty()) { + aFamilyName = name; + return NS_OK; + } + return NS_ERROR_NOT_AVAILABLE; +} + +enum { +#if defined(XP_MACOSX) + CANONICAL_LANG_ID = gfxFontUtils::LANG_ID_MAC_ENGLISH, + PLATFORM_ID = gfxFontUtils::PLATFORM_ID_MAC +#else + CANONICAL_LANG_ID = gfxFontUtils::LANG_ID_MICROSOFT_EN_US, + PLATFORM_ID = gfxFontUtils::PLATFORM_ID_MICROSOFT +#endif +}; + +nsresult gfxFontUtils::ReadNames(const char* aNameData, uint32_t aDataLen, + uint32_t aNameID, int32_t aPlatformID, + nsTArray<nsCString>& aNames) { + return ReadNames(aNameData, aDataLen, aNameID, LANG_ALL, aPlatformID, aNames); +} + +nsresult gfxFontUtils::ReadCanonicalName(hb_blob_t* aNameTable, + uint32_t aNameID, nsCString& aName) { + uint32_t nameTableLen; + const char* nameTable = hb_blob_get_data(aNameTable, &nameTableLen); + return ReadCanonicalName(nameTable, nameTableLen, aNameID, aName); +} + +nsresult gfxFontUtils::ReadCanonicalName(const char* aNameData, + uint32_t aDataLen, uint32_t aNameID, + nsCString& aName) { + nsresult rv; + + nsTArray<nsCString> names; + + // first, look for the English name (this will succeed 99% of the time) + rv = ReadNames(aNameData, aDataLen, aNameID, CANONICAL_LANG_ID, PLATFORM_ID, + names); + NS_ENSURE_SUCCESS(rv, rv); + + // otherwise, grab names for all languages + if (names.Length() == 0) { + rv = ReadNames(aNameData, aDataLen, aNameID, LANG_ALL, PLATFORM_ID, names); + NS_ENSURE_SUCCESS(rv, rv); + } + +#if defined(XP_MACOSX) + // may be dealing with font that only has Microsoft name entries + if (names.Length() == 0) { + rv = ReadNames(aNameData, aDataLen, aNameID, LANG_ID_MICROSOFT_EN_US, + PLATFORM_ID_MICROSOFT, names); + NS_ENSURE_SUCCESS(rv, rv); + + // getting really desperate now, take anything! + if (names.Length() == 0) { + rv = ReadNames(aNameData, aDataLen, aNameID, LANG_ALL, + PLATFORM_ID_MICROSOFT, names); + NS_ENSURE_SUCCESS(rv, rv); + } + } +#endif + + // return the first name (99.9% of the time names will + // contain a single English name) + if (names.Length()) { + aName.Assign(names[0]); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +// Charsets to use for decoding Mac platform font names. +// This table is sorted by {encoding, language}, with the wildcard "ANY" being +// greater than any defined values for each field; we use a binary search on +// both fields, and fall back to matching only encoding if necessary + +// Some "redundant" entries for specific combinations are included such as +// encoding=roman, lang=english, in order that common entries will be found +// on the first search. + +const uint16_t ANY = 0xffff; +const gfxFontUtils::MacFontNameCharsetMapping + gfxFontUtils::gMacFontNameCharsets[] = { + {ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_ENGLISH, MACINTOSH_ENCODING}, + {ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_ICELANDIC, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_TURKISH, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_POLISH, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_ROMANIAN, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_CZECH, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_SLOVAK, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_ROMAN, ANY, MACINTOSH_ENCODING}, + {ENCODING_ID_MAC_JAPANESE, LANG_ID_MAC_JAPANESE, SHIFT_JIS_ENCODING}, + {ENCODING_ID_MAC_JAPANESE, ANY, SHIFT_JIS_ENCODING}, + {ENCODING_ID_MAC_TRAD_CHINESE, LANG_ID_MAC_TRAD_CHINESE, BIG5_ENCODING}, + {ENCODING_ID_MAC_TRAD_CHINESE, ANY, BIG5_ENCODING}, + {ENCODING_ID_MAC_KOREAN, LANG_ID_MAC_KOREAN, EUC_KR_ENCODING}, + {ENCODING_ID_MAC_KOREAN, ANY, EUC_KR_ENCODING}, + {ENCODING_ID_MAC_ARABIC, LANG_ID_MAC_ARABIC, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_ARABIC, LANG_ID_MAC_URDU, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_ARABIC, LANG_ID_MAC_FARSI, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_ARABIC, ANY, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_HEBREW, LANG_ID_MAC_HEBREW, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_HEBREW, ANY, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_GREEK, ANY, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_CYRILLIC, ANY, X_MAC_CYRILLIC_ENCODING}, + {ENCODING_ID_MAC_DEVANAGARI, ANY, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_GURMUKHI, ANY, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_GUJARATI, ANY, X_USER_DEFINED_ENCODING}, + {ENCODING_ID_MAC_SIMP_CHINESE, LANG_ID_MAC_SIMP_CHINESE, + GB18030_ENCODING}, + {ENCODING_ID_MAC_SIMP_CHINESE, ANY, GB18030_ENCODING}}; + +const Encoding* gfxFontUtils::gISOFontNameCharsets[] = { + /* 0 */ WINDOWS_1252_ENCODING, /* US-ASCII */ + /* 1 */ nullptr, /* spec says "ISO 10646" but does not specify encoding + form! */ + /* 2 */ WINDOWS_1252_ENCODING /* ISO-8859-1 */ +}; + +const Encoding* gfxFontUtils::gMSFontNameCharsets[] = { + /* [0] ENCODING_ID_MICROSOFT_SYMBOL */ UTF_16BE_ENCODING, + /* [1] ENCODING_ID_MICROSOFT_UNICODEBMP */ UTF_16BE_ENCODING, + /* [2] ENCODING_ID_MICROSOFT_SHIFTJIS */ SHIFT_JIS_ENCODING, + /* [3] ENCODING_ID_MICROSOFT_PRC */ nullptr, + /* [4] ENCODING_ID_MICROSOFT_BIG5 */ BIG5_ENCODING, + /* [5] ENCODING_ID_MICROSOFT_WANSUNG */ nullptr, + /* [6] ENCODING_ID_MICROSOFT_JOHAB */ nullptr, + /* [7] reserved */ nullptr, + /* [8] reserved */ nullptr, + /* [9] reserved */ nullptr, + /*[10] ENCODING_ID_MICROSOFT_UNICODEFULL */ UTF_16BE_ENCODING}; + +struct MacCharsetMappingComparator { + typedef gfxFontUtils::MacFontNameCharsetMapping MacFontNameCharsetMapping; + const MacFontNameCharsetMapping& mSearchValue; + explicit MacCharsetMappingComparator( + const MacFontNameCharsetMapping& aSearchValue) + : mSearchValue(aSearchValue) {} + int operator()(const MacFontNameCharsetMapping& aEntry) const { + if (mSearchValue < aEntry) { + return -1; + } + if (aEntry < mSearchValue) { + return 1; + } + return 0; + } +}; + +// Return the Encoding object we should use to decode a font name +// given the name table attributes. +// Special return values: +// X_USER_DEFINED_ENCODING One of Mac legacy encodings that is not a part +// of Encoding Standard +// nullptr unknown charset, do not attempt conversion +const Encoding* gfxFontUtils::GetCharsetForFontName(uint16_t aPlatform, + uint16_t aScript, + uint16_t aLanguage) { + switch (aPlatform) { + case PLATFORM_ID_UNICODE: + return UTF_16BE_ENCODING; + + case PLATFORM_ID_MAC: { + MacFontNameCharsetMapping searchValue = {aScript, aLanguage, nullptr}; + for (uint32_t i = 0; i < 2; ++i) { + size_t idx; + if (BinarySearchIf(gMacFontNameCharsets, 0, + ArrayLength(gMacFontNameCharsets), + MacCharsetMappingComparator(searchValue), &idx)) { + return gMacFontNameCharsets[idx].mEncoding; + } + + // no match, so try again finding one in any language + searchValue.mLanguage = ANY; + } + } break; + + case PLATFORM_ID_ISO: + if (aScript < ArrayLength(gISOFontNameCharsets)) { + return gISOFontNameCharsets[aScript]; + } + break; + + case PLATFORM_ID_MICROSOFT: + if (aScript < ArrayLength(gMSFontNameCharsets)) { + return gMSFontNameCharsets[aScript]; + } + break; + } + + return nullptr; +} + +template <int N> +static bool StartsWith(const nsACString& string, const char (&prefix)[N]) { + if (N - 1 > string.Length()) { + return false; + } + return memcmp(string.Data(), prefix, N - 1) == 0; +} + +// convert a raw name from the name table to an nsString, if possible; +// return value indicates whether conversion succeeded +bool gfxFontUtils::DecodeFontName(const char* aNameData, int32_t aByteLen, + uint32_t aPlatformCode, uint32_t aScriptCode, + uint32_t aLangCode, nsACString& aName) { + if (aByteLen <= 0) { + NS_WARNING("empty font name"); + aName.SetLength(0); + return true; + } + + auto encoding = GetCharsetForFontName(aPlatformCode, aScriptCode, aLangCode); + + if (!encoding) { + // nullptr -> unknown charset +#ifdef DEBUG + char warnBuf[128]; + if (aByteLen > 64) aByteLen = 64; + SprintfLiteral(warnBuf, + "skipping font name, unknown charset %d:%d:%d for <%.*s>", + aPlatformCode, aScriptCode, aLangCode, aByteLen, aNameData); + NS_WARNING(warnBuf); +#endif + return false; + } + + if (encoding == X_USER_DEFINED_ENCODING) { +#ifdef XP_MACOSX + // Special case for macOS only: support legacy Mac encodings + // that aren't part of the Encoding Standard. + if (aPlatformCode == PLATFORM_ID_MAC) { + CFStringRef str = + CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*)aNameData, + aByteLen, aScriptCode, false); + if (str) { + CFIndex length = CFStringGetLength(str); + nsAutoString name16; + name16.SetLength(length); + CFStringGetCharacters(str, CFRangeMake(0, length), + (UniChar*)name16.BeginWriting()); + CFRelease(str); + CopyUTF16toUTF8(name16, aName); + return true; + } + } +#endif + NS_WARNING("failed to get the decoder for a font name string"); + return false; + } + + auto rv = encoding->DecodeWithoutBOMHandling( + nsDependentCSubstring(aNameData, aByteLen), aName); + return NS_SUCCEEDED(rv); +} + +nsresult gfxFontUtils::ReadNames(const char* aNameData, uint32_t aDataLen, + uint32_t aNameID, int32_t aLangID, + int32_t aPlatformID, + nsTArray<nsCString>& aNames) { + NS_ASSERTION(aDataLen != 0, "null name table"); + + if (!aDataLen) { + return NS_ERROR_FAILURE; + } + + // -- name table data + const NameHeader* nameHeader = reinterpret_cast<const NameHeader*>(aNameData); + + uint32_t nameCount = nameHeader->count; + + // -- sanity check the number of name records + if (uint64_t(nameCount) * sizeof(NameRecord) > aDataLen) { + NS_WARNING("invalid font (name table data)"); + return NS_ERROR_FAILURE; + } + + // -- iterate through name records + const NameRecord* nameRecord = + reinterpret_cast<const NameRecord*>(aNameData + sizeof(NameHeader)); + uint64_t nameStringsBase = uint64_t(nameHeader->stringOffset); + + uint32_t i; + for (i = 0; i < nameCount; i++, nameRecord++) { + uint32_t platformID; + + // skip over unwanted nameID's + if (uint32_t(nameRecord->nameID) != aNameID) { + continue; + } + + // skip over unwanted platform data + platformID = nameRecord->platformID; + if (aPlatformID != PLATFORM_ALL && platformID != uint32_t(aPlatformID)) { + continue; + } + + // skip over unwanted languages + if (aLangID != LANG_ALL && + uint32_t(nameRecord->languageID) != uint32_t(aLangID)) { + continue; + } + + // add name to names array + + // -- calculate string location + uint32_t namelen = nameRecord->length; + uint32_t nameoff = + nameRecord->offset; // offset from base of string storage + + if (nameStringsBase + uint64_t(nameoff) + uint64_t(namelen) > aDataLen) { + NS_WARNING("invalid font (name table strings)"); + return NS_ERROR_FAILURE; + } + + // -- decode if necessary and make nsString + nsAutoCString name; + + DecodeFontName(aNameData + nameStringsBase + nameoff, namelen, platformID, + uint32_t(nameRecord->encodingID), + uint32_t(nameRecord->languageID), name); + + uint32_t k, numNames; + bool foundName = false; + + numNames = aNames.Length(); + for (k = 0; k < numNames; k++) { + if (name.Equals(aNames[k])) { + foundName = true; + break; + } + } + + if (!foundName) aNames.AppendElement(name); + } + + return NS_OK; +} + +void gfxFontUtils::GetVariationData( + gfxFontEntry* aFontEntry, nsTArray<gfxFontVariationAxis>* aAxes, + nsTArray<gfxFontVariationInstance>* aInstances) { + MOZ_ASSERT(!aAxes || aAxes->IsEmpty()); + MOZ_ASSERT(!aInstances || aInstances->IsEmpty()); + + if (!aFontEntry->HasVariations()) { + return; + } + + // Some platforms don't offer a simple API to return the list of instances, + // so we have to interpret the 'fvar' table ourselves. + + // https://www.microsoft.com/typography/otspec/fvar.htm#fvarHeader + struct FvarHeader { + AutoSwap_PRUint16 majorVersion; + AutoSwap_PRUint16 minorVersion; + AutoSwap_PRUint16 axesArrayOffset; + AutoSwap_PRUint16 reserved; + AutoSwap_PRUint16 axisCount; + AutoSwap_PRUint16 axisSize; + AutoSwap_PRUint16 instanceCount; + AutoSwap_PRUint16 instanceSize; + }; + + // https://www.microsoft.com/typography/otspec/fvar.htm#variationAxisRecord + struct AxisRecord { + AutoSwap_PRUint32 axisTag; + AutoSwap_PRInt32 minValue; + AutoSwap_PRInt32 defaultValue; + AutoSwap_PRInt32 maxValue; + AutoSwap_PRUint16 flags; + AutoSwap_PRUint16 axisNameID; + }; + const uint16_t HIDDEN_AXIS = 0x0001; // AxisRecord flags value + + // https://www.microsoft.com/typography/otspec/fvar.htm#instanceRecord + struct InstanceRecord { + AutoSwap_PRUint16 subfamilyNameID; + AutoSwap_PRUint16 flags; + AutoSwap_PRInt32 coordinates[1]; // variable-size array [axisCount] + // The variable-length 'coordinates' array may be followed by an + // optional extra field 'postScriptNameID'. We can't directly + // represent this in the struct, because its offset varies depending + // on the number of axes present. + // (Not currently used by our code here anyhow.) + // AutoSwap_PRUint16 postScriptNameID; + }; + + // Load the two font tables we need as harfbuzz blobs; if either is absent, + // just bail out. + AutoHBBlob fvarTable( + aFontEntry->GetFontTable(TRUETYPE_TAG('f', 'v', 'a', 'r'))); + AutoHBBlob nameTable( + aFontEntry->GetFontTable(TRUETYPE_TAG('n', 'a', 'm', 'e'))); + if (!fvarTable || !nameTable) { + return; + } + unsigned int len; + const char* data = hb_blob_get_data(fvarTable, &len); + if (len < sizeof(FvarHeader)) { + return; + } + // Read the fields of the table header; bail out if it looks broken. + auto fvar = reinterpret_cast<const FvarHeader*>(data); + if (uint16_t(fvar->majorVersion) != 1 || uint16_t(fvar->minorVersion) != 0 || + uint16_t(fvar->reserved) != 2) { + return; + } + uint16_t axisCount = fvar->axisCount; + uint16_t axisSize = fvar->axisSize; + uint16_t instanceCount = fvar->instanceCount; + uint16_t instanceSize = fvar->instanceSize; + if (axisCount == + 0 || // no axes? + // https://www.microsoft.com/typography/otspec/fvar.htm#axisSize + axisSize != 20 || // required value for current table version + // https://www.microsoft.com/typography/otspec/fvar.htm#instanceSize + (instanceSize != axisCount * sizeof(int32_t) + 4 && + instanceSize != axisCount * sizeof(int32_t) + 6)) { + return; + } + // Check that axis array will not exceed table size + uint16_t axesOffset = fvar->axesArrayOffset; + if (axesOffset + uint32_t(axisCount) * axisSize > len) { + return; + } + // Get pointer to the array of axis records + auto axes = reinterpret_cast<const AxisRecord*>(data + axesOffset); + // Get address of instance array, and check it doesn't overflow table size. + // https://www.microsoft.com/typography/otspec/fvar.htm#axisAndInstanceArrays + auto instData = data + axesOffset + axisCount * axisSize; + if (instData + uint32_t(instanceCount) * instanceSize > data + len) { + return; + } + if (aInstances) { + aInstances->SetCapacity(instanceCount); + for (unsigned i = 0; i < instanceCount; ++i, instData += instanceSize) { + // Typed pointer to the current instance record, to read its fields. + auto inst = reinterpret_cast<const InstanceRecord*>(instData); + // Pointer to the coordinates array within the instance record. + // This array has axisCount elements, and is included in instanceSize + // (which depends on axisCount, and was validated above) so we know + // access to coords[j] below will not be outside the table bounds. + auto coords = &inst->coordinates[0]; + gfxFontVariationInstance instance; + uint16_t nameID = inst->subfamilyNameID; + nsresult rv = ReadCanonicalName(nameTable, nameID, instance.mName); + if (NS_FAILED(rv)) { + // If no name was available for the instance, ignore it. + continue; + } + instance.mValues.SetCapacity(axisCount); + for (unsigned j = 0; j < axisCount; ++j) { + gfxFontVariationValue value = {axes[j].axisTag, + int32_t(coords[j]) / 65536.0f}; + instance.mValues.AppendElement(value); + } + aInstances->AppendElement(std::move(instance)); + } + } + if (aAxes) { + aAxes->SetCapacity(axisCount); + for (unsigned i = 0; i < axisCount; ++i) { + if (uint16_t(axes[i].flags) & HIDDEN_AXIS) { + continue; + } + gfxFontVariationAxis axis; + axis.mTag = axes[i].axisTag; + uint16_t nameID = axes[i].axisNameID; + nsresult rv = ReadCanonicalName(nameTable, nameID, axis.mName); + if (NS_FAILED(rv)) { + axis.mName.Truncate(0); + } + // Convert values from 16.16 fixed-point to float + axis.mMinValue = int32_t(axes[i].minValue) / 65536.0f; + axis.mDefaultValue = int32_t(axes[i].defaultValue) / 65536.0f; + axis.mMaxValue = int32_t(axes[i].maxValue) / 65536.0f; + aAxes->AppendElement(axis); + } + } +} + +void gfxFontUtils::ReadOtherFamilyNamesForFace( + const nsACString& aFamilyName, const char* aNameData, uint32_t aDataLength, + nsTArray<nsCString>& aOtherFamilyNames, bool useFullName) { + const NameHeader* nameHeader = reinterpret_cast<const NameHeader*>(aNameData); + + uint32_t nameCount = nameHeader->count; + if (nameCount * sizeof(NameRecord) > aDataLength) { + NS_WARNING("invalid font (name records)"); + return; + } + + const NameRecord* nameRecord = + reinterpret_cast<const NameRecord*>(aNameData + sizeof(NameHeader)); + uint32_t stringsBase = uint32_t(nameHeader->stringOffset); + + for (uint32_t i = 0; i < nameCount; i++, nameRecord++) { + uint32_t nameLen = nameRecord->length; + uint32_t nameOff = + nameRecord->offset; // offset from base of string storage + + if (stringsBase + nameOff + nameLen > aDataLength) { + NS_WARNING("invalid font (name table strings)"); + return; + } + + uint16_t nameID = nameRecord->nameID; + if ((useFullName && nameID == NAME_ID_FULL) || + (!useFullName && + (nameID == NAME_ID_FAMILY || nameID == NAME_ID_PREFERRED_FAMILY))) { + nsAutoCString otherFamilyName; + bool ok = DecodeFontName( + aNameData + stringsBase + nameOff, nameLen, + uint32_t(nameRecord->platformID), uint32_t(nameRecord->encodingID), + uint32_t(nameRecord->languageID), otherFamilyName); + // add if not same as canonical family name + if (ok && otherFamilyName != aFamilyName && + !aOtherFamilyNames.Contains(otherFamilyName)) { + aOtherFamilyNames.AppendElement(otherFamilyName); + } + } + } +} + +#ifdef XP_WIN + +/* static */ +bool gfxFontUtils::IsCffFont(const uint8_t* aFontData) { + // this is only called after aFontData has passed basic validation, + // so we know there is enough data present to allow us to read the version! + const SFNTHeader* sfntHeader = reinterpret_cast<const SFNTHeader*>(aFontData); + return (sfntHeader->sfntVersion == TRUETYPE_TAG('O', 'T', 'T', 'O')); +} + +#endif + +/* static */ bool gfxFontUtils::IsInServoTraversal() { + if (NS_IsMainThread()) { + return ServoStyleSet::IsInServoTraversal(); + } + + if (dom::GetCurrentThreadWorkerPrivate()) { + return false; + } + + // The only permissible threads are the main thread, the worker thread, the + // servo threads. If the latter, we must be traversing. + bool traversing = ServoStyleSet::IsInServoTraversal(); + MOZ_ASSERT(traversing); + return traversing; +} + +/* static */ ServoStyleSet* gfxFontUtils::CurrentServoStyleSet() { + // If we are on a worker thread, we must not check for the current set since + // the main/servo threads may be busy in parallel. + if (dom::GetCurrentThreadWorkerPrivate()) { + return nullptr; + } + + return ServoStyleSet::Current(); +} + +#ifdef DEBUG +/* static */ void gfxFontUtils::AssertSafeThreadOrServoFontMetricsLocked() { + if (!dom::GetCurrentThreadWorkerPrivate()) { + AssertIsMainThreadOrServoFontMetricsLocked(); + } +} +#endif + +#undef acceptablePlatform +#undef isSymbol +#undef isUVSEncoding +#undef LOG +#undef LOG_ENABLED diff --git a/gfx/thebes/gfxFontUtils.h b/gfx/thebes/gfxFontUtils.h new file mode 100644 index 0000000000..ce11c6943d --- /dev/null +++ b/gfx/thebes/gfxFontUtils.h @@ -0,0 +1,1467 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FONT_UTILS_H +#define GFX_FONT_UTILS_H + +#include <string.h> +#include <algorithm> +#include <new> +#include <utility> +#include "gfxPlatform.h" +#include "harfbuzz/hb.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/Casting.h" +#include "mozilla/EndianUtils.h" +#include "mozilla/ServoStyleConstsInlines.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/UniquePtr.h" +#include "nsStringFwd.h" +#include "nsTArray.h" +#include "nscore.h" +#include "zlib.h" + +class PickleIterator; +class gfxFontEntry; +struct gfxFontVariationAxis; +struct gfxFontVariationInstance; + +namespace mozilla { +class Encoding; +class ServoStyleSet; +} // namespace mozilla + +/* Bug 341128 - w32api defines min/max which causes problems with <bitset> */ +#ifdef __MINGW32__ +# undef min +# undef max +#endif + +#undef ERROR /* defined by Windows.h, conflicts with some generated bindings \ + code when this gets indirectly included via shared font list \ + */ + +typedef struct hb_blob_t hb_blob_t; + +class SharedBitSet; + +namespace IPC { +template <typename T> +struct ParamTraits; +} + +class gfxSparseBitSet { + private: + friend class SharedBitSet; + + enum { BLOCK_SIZE = 32 }; // ==> 256 codepoints per block + enum { BLOCK_SIZE_BITS = BLOCK_SIZE * 8 }; + enum { NO_BLOCK = 0xffff }; // index value indicating missing (empty) block + + struct Block { + explicit Block(unsigned char memsetValue = 0) { + memset(mBits, memsetValue, BLOCK_SIZE); + } + uint8_t mBits[BLOCK_SIZE]; + }; + + friend struct IPC::ParamTraits<gfxSparseBitSet>; + friend struct IPC::ParamTraits<Block>; + + public: + gfxSparseBitSet() = default; + + bool Equals(const gfxSparseBitSet* aOther) const { + if (mBlockIndex.Length() != aOther->mBlockIndex.Length()) { + return false; + } + size_t n = mBlockIndex.Length(); + for (size_t i = 0; i < n; ++i) { + uint32_t b1 = mBlockIndex[i]; + uint32_t b2 = aOther->mBlockIndex[i]; + if ((b1 == NO_BLOCK) != (b2 == NO_BLOCK)) { + return false; + } + if (b1 == NO_BLOCK) { + continue; + } + if (memcmp(&mBlocks[b1].mBits, &aOther->mBlocks[b2].mBits, BLOCK_SIZE) != + 0) { + return false; + } + } + return true; + } + + bool test(uint32_t aIndex) const { + uint32_t i = aIndex / BLOCK_SIZE_BITS; + if (i >= mBlockIndex.Length() || mBlockIndex[i] == NO_BLOCK) { + return false; + } + const Block& block = mBlocks[mBlockIndex[i]]; + return ((block.mBits[(aIndex >> 3) & (BLOCK_SIZE - 1)]) & + (1 << (aIndex & 0x7))) != 0; + } + + // dump out contents of bitmap + void Dump(const char* aPrefix, eGfxLog aWhichLog) const; + + bool TestRange(uint32_t aStart, uint32_t aEnd) { + // start point is beyond the end of the block array? return false + // immediately + uint32_t startBlock = aStart / BLOCK_SIZE_BITS; + uint32_t blockLen = mBlockIndex.Length(); + if (startBlock >= blockLen) { + return false; + } + + // check for blocks in range, if none, return false + bool hasBlocksInRange = false; + uint32_t endBlock = aEnd / BLOCK_SIZE_BITS; + for (uint32_t bi = startBlock; bi <= endBlock; bi++) { + if (bi < blockLen && mBlockIndex[bi] != NO_BLOCK) { + hasBlocksInRange = true; + break; + } + } + if (!hasBlocksInRange) { + return false; + } + + // first block, check bits + if (mBlockIndex[startBlock] != NO_BLOCK) { + const Block& block = mBlocks[mBlockIndex[startBlock]]; + uint32_t start = aStart; + uint32_t end = std::min(aEnd, ((startBlock + 1) * BLOCK_SIZE_BITS) - 1); + for (uint32_t i = start; i <= end; i++) { + if ((block.mBits[(i >> 3) & (BLOCK_SIZE - 1)]) & (1 << (i & 0x7))) { + return true; + } + } + } + if (endBlock == startBlock) { + return false; + } + + // [2..n-1] blocks check bytes + for (uint32_t i = startBlock + 1; i < endBlock; i++) { + if (i >= blockLen || mBlockIndex[i] == NO_BLOCK) { + continue; + } + const Block& block = mBlocks[mBlockIndex[i]]; + for (uint32_t index = 0; index < BLOCK_SIZE; index++) { + if (block.mBits[index]) { + return true; + } + } + } + + // last block, check bits + if (endBlock < blockLen && mBlockIndex[endBlock] != NO_BLOCK) { + const Block& block = mBlocks[mBlockIndex[endBlock]]; + uint32_t start = endBlock * BLOCK_SIZE_BITS; + uint32_t end = aEnd; + for (uint32_t i = start; i <= end; i++) { + if ((block.mBits[(i >> 3) & (BLOCK_SIZE - 1)]) & (1 << (i & 0x7))) { + return true; + } + } + } + + return false; + } + + void set(uint32_t aIndex) { + uint32_t i = aIndex / BLOCK_SIZE_BITS; + while (i >= mBlockIndex.Length()) { + mBlockIndex.AppendElement(NO_BLOCK); + } + if (mBlockIndex[i] == NO_BLOCK) { + mBlocks.AppendElement(); + MOZ_ASSERT(mBlocks.Length() < 0xffff, "block index overflow!"); + mBlockIndex[i] = static_cast<uint16_t>(mBlocks.Length() - 1); + } + Block& block = mBlocks[mBlockIndex[i]]; + block.mBits[(aIndex >> 3) & (BLOCK_SIZE - 1)] |= 1 << (aIndex & 0x7); + } + + void set(uint32_t aIndex, bool aValue) { + if (aValue) { + set(aIndex); + } else { + clear(aIndex); + } + } + + void SetRange(uint32_t aStart, uint32_t aEnd) { + const uint32_t startIndex = aStart / BLOCK_SIZE_BITS; + const uint32_t endIndex = aEnd / BLOCK_SIZE_BITS; + + while (endIndex >= mBlockIndex.Length()) { + mBlockIndex.AppendElement(NO_BLOCK); + } + + for (uint32_t i = startIndex; i <= endIndex; ++i) { + const uint32_t blockFirstBit = i * BLOCK_SIZE_BITS; + const uint32_t blockLastBit = blockFirstBit + BLOCK_SIZE_BITS - 1; + + if (mBlockIndex[i] == NO_BLOCK) { + bool fullBlock = (aStart <= blockFirstBit && aEnd >= blockLastBit); + mBlocks.AppendElement(Block(fullBlock ? 0xFF : 0)); + MOZ_ASSERT(mBlocks.Length() < 0xffff, "block index overflow!"); + mBlockIndex[i] = static_cast<uint16_t>(mBlocks.Length() - 1); + if (fullBlock) { + continue; + } + } + + Block& block = mBlocks[mBlockIndex[i]]; + const uint32_t start = + aStart > blockFirstBit ? aStart - blockFirstBit : 0; + const uint32_t end = + std::min<uint32_t>(aEnd - blockFirstBit, BLOCK_SIZE_BITS - 1); + + for (uint32_t bit = start; bit <= end; ++bit) { + block.mBits[bit >> 3] |= 1 << (bit & 0x7); + } + } + } + + void clear(uint32_t aIndex) { + uint32_t i = aIndex / BLOCK_SIZE_BITS; + if (i >= mBlockIndex.Length()) { + return; + } + if (mBlockIndex[i] == NO_BLOCK) { + mBlocks.AppendElement(); + MOZ_ASSERT(mBlocks.Length() < 0xffff, "block index overflow!"); + mBlockIndex[i] = static_cast<uint16_t>(mBlocks.Length() - 1); + } + Block& block = mBlocks[mBlockIndex[i]]; + block.mBits[(aIndex >> 3) & (BLOCK_SIZE - 1)] &= ~(1 << (aIndex & 0x7)); + } + + void ClearRange(uint32_t aStart, uint32_t aEnd) { + const uint32_t startIndex = aStart / BLOCK_SIZE_BITS; + const uint32_t endIndex = aEnd / BLOCK_SIZE_BITS; + + for (uint32_t i = startIndex; i <= endIndex; ++i) { + if (i >= mBlockIndex.Length()) { + return; + } + if (mBlockIndex[i] == NO_BLOCK) { + continue; + } + + const uint32_t blockFirstBit = i * BLOCK_SIZE_BITS; + Block& block = mBlocks[mBlockIndex[i]]; + + const uint32_t start = + aStart > blockFirstBit ? aStart - blockFirstBit : 0; + const uint32_t end = + std::min<uint32_t>(aEnd - blockFirstBit, BLOCK_SIZE_BITS - 1); + + for (uint32_t bit = start; bit <= end; ++bit) { + block.mBits[bit >> 3] &= ~(1 << (bit & 0x7)); + } + } + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return mBlocks.ShallowSizeOfExcludingThis(aMallocSizeOf) + + mBlockIndex.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + + // clear out all blocks in the array + void reset() { + mBlocks.Clear(); + mBlockIndex.Clear(); + } + + // set this bitset to the union of its current contents and another + void Union(const gfxSparseBitSet& aBitset) { + // ensure mBlocks is large enough + uint32_t blockCount = aBitset.mBlockIndex.Length(); + while (blockCount > mBlockIndex.Length()) { + mBlockIndex.AppendElement(NO_BLOCK); + } + // for each block that may be present in aBitset... + for (uint32_t i = 0; i < blockCount; ++i) { + // if it is missing (implicitly empty), just skip + if (aBitset.mBlockIndex[i] == NO_BLOCK) { + continue; + } + // if the block is missing in this set, just copy the other + if (mBlockIndex[i] == NO_BLOCK) { + mBlocks.AppendElement(aBitset.mBlocks[aBitset.mBlockIndex[i]]); + MOZ_ASSERT(mBlocks.Length() < 0xffff, "block index overflow!"); + mBlockIndex[i] = static_cast<uint16_t>(mBlocks.Length() - 1); + continue; + } + // else set existing block to the union of both + uint32_t* dst = + reinterpret_cast<uint32_t*>(&mBlocks[mBlockIndex[i]].mBits); + const uint32_t* src = reinterpret_cast<const uint32_t*>( + &aBitset.mBlocks[aBitset.mBlockIndex[i]].mBits); + for (uint32_t j = 0; j < BLOCK_SIZE / 4; ++j) { + dst[j] |= src[j]; + } + } + } + + inline void Union(const SharedBitSet& aBitset); + + void Compact() { + // TODO: Discard any empty blocks, and adjust index accordingly. + // (May not be worth doing, though, because we so rarely clear bits + // that were previously set.) + mBlocks.Compact(); + mBlockIndex.Compact(); + } + + uint32_t GetChecksum() const { + uint32_t check = + adler32(0, reinterpret_cast<const uint8_t*>(mBlockIndex.Elements()), + mBlockIndex.Length() * sizeof(uint16_t)); + check = adler32(check, reinterpret_cast<const uint8_t*>(mBlocks.Elements()), + mBlocks.Length() * sizeof(Block)); + return check; + } + + private: + CopyableTArray<uint16_t> mBlockIndex; + CopyableTArray<Block> mBlocks; +}; + +/** + * SharedBitSet is a version of gfxSparseBitSet that is intended to be used + * in a shared-memory block, and can be used regardless of the address at which + * the block has been mapped. The SharedBitSet cannot be modified once it has + * been created. + * + * Max size of a SharedBitSet = 4352 * 32 ; blocks + * + 4352 * 2 ; index + * + 4 ; counts + * = 147972 bytes + * + * Therefore, SharedFontList must be able to allocate a contiguous block of at + * least this size. + */ +class SharedBitSet { + private: + // We use the same Block type as gfxSparseBitSet. + typedef gfxSparseBitSet::Block Block; + + enum { BLOCK_SIZE = gfxSparseBitSet::BLOCK_SIZE }; + enum { BLOCK_SIZE_BITS = gfxSparseBitSet::BLOCK_SIZE_BITS }; + enum { NO_BLOCK = gfxSparseBitSet::NO_BLOCK }; + + public: + static const size_t kMaxSize = 147972; // see above + + // Returns the size needed for a SharedBitSet version of the given + // gfxSparseBitSet. + static size_t RequiredSize(const gfxSparseBitSet& aBitset) { + size_t total = sizeof(SharedBitSet); + size_t len = aBitset.mBlockIndex.Length(); + total += len * sizeof(uint16_t); // add size for index array + // add size for blocks, excluding any missing ones + for (uint16_t i = 0; i < len; i++) { + if (aBitset.mBlockIndex[i] != NO_BLOCK) { + total += sizeof(Block); + } + } + MOZ_ASSERT(total <= kMaxSize); + return total; + } + + // Create a SharedBitSet in the provided buffer, initializing it with the + // contents of aBitset. + static SharedBitSet* Create(void* aBuffer, size_t aBufSize, + const gfxSparseBitSet& aBitset) { + MOZ_ASSERT(aBufSize >= RequiredSize(aBitset)); + return new (aBuffer) SharedBitSet(aBitset); + } + + bool test(uint32_t aIndex) const { + const auto i = static_cast<uint16_t>(aIndex / BLOCK_SIZE_BITS); + if (i >= mBlockIndexCount) { + return false; + } + const uint16_t* const blockIndex = + reinterpret_cast<const uint16_t*>(this + 1); + if (blockIndex[i] == NO_BLOCK) { + return false; + } + const Block* const blocks = + reinterpret_cast<const Block*>(blockIndex + mBlockIndexCount); + const Block& block = blocks[blockIndex[i]]; + return ((block.mBits[(aIndex >> 3) & (BLOCK_SIZE - 1)]) & + (1 << (aIndex & 0x7))) != 0; + } + + bool Equals(const gfxSparseBitSet* aOther) const { + if (mBlockIndexCount != aOther->mBlockIndex.Length()) { + return false; + } + const uint16_t* const blockIndex = + reinterpret_cast<const uint16_t*>(this + 1); + const Block* const blocks = + reinterpret_cast<const Block*>(blockIndex + mBlockIndexCount); + for (uint16_t i = 0; i < mBlockIndexCount; ++i) { + uint16_t index = blockIndex[i]; + uint16_t otherIndex = aOther->mBlockIndex[i]; + if ((index == NO_BLOCK) != (otherIndex == NO_BLOCK)) { + return false; + } + if (index == NO_BLOCK) { + continue; + } + const Block& b1 = blocks[index]; + const Block& b2 = aOther->mBlocks[otherIndex]; + if (memcmp(&b1.mBits, &b2.mBits, BLOCK_SIZE) != 0) { + return false; + } + } + return true; + } + + private: + friend class gfxSparseBitSet; + SharedBitSet() = delete; + + explicit SharedBitSet(const gfxSparseBitSet& aBitset) + : mBlockIndexCount( + mozilla::AssertedCast<uint16_t>(aBitset.mBlockIndex.Length())), + mBlockCount(0) { + uint16_t* blockIndex = reinterpret_cast<uint16_t*>(this + 1); + Block* blocks = reinterpret_cast<Block*>(blockIndex + mBlockIndexCount); + for (uint16_t i = 0; i < mBlockIndexCount; i++) { + if (aBitset.mBlockIndex[i] != NO_BLOCK) { + const Block& srcBlock = aBitset.mBlocks[aBitset.mBlockIndex[i]]; + std::memcpy(&blocks[mBlockCount], &srcBlock, sizeof(Block)); + blockIndex[i] = mBlockCount; + mBlockCount++; + } else { + blockIndex[i] = NO_BLOCK; + } + } + } + + // We never manage SharedBitSet as a "normal" object, it's a view onto a + // buffer of shared memory. So we should never be trying to call this. + ~SharedBitSet() = delete; + + uint16_t mBlockIndexCount; + uint16_t mBlockCount; + + // After the two "header" fields above, we have a block index array + // of uint16_t[mBlockIndexCount], followed by mBlockCount Block records. +}; + +// Union the contents of a SharedBitSet with the target gfxSparseBitSet +inline void gfxSparseBitSet::Union(const SharedBitSet& aBitset) { + // ensure mBlockIndex is large enough + while (mBlockIndex.Length() < aBitset.mBlockIndexCount) { + mBlockIndex.AppendElement(NO_BLOCK); + } + auto blockIndex = reinterpret_cast<const uint16_t*>(&aBitset + 1); + auto blocks = + reinterpret_cast<const Block*>(blockIndex + aBitset.mBlockIndexCount); + for (uint32_t i = 0; i < aBitset.mBlockIndexCount; ++i) { + // if it is missing (implicitly empty) in source, just skip + if (blockIndex[i] == NO_BLOCK) { + continue; + } + // if the block is missing, just copy from source bitset + if (mBlockIndex[i] == NO_BLOCK) { + mBlocks.AppendElement(blocks[blockIndex[i]]); + MOZ_ASSERT(mBlocks.Length() < 0xffff, "block index overflow"); + mBlockIndex[i] = uint16_t(mBlocks.Length() - 1); + continue; + } + // Else set existing target block to the union of both. + // Note that blocks in SharedBitSet may not be 4-byte aligned, so we don't + // try to optimize by casting to uint32_t* here and processing 4 bytes at + // once, as this could result in misaligned access. + uint8_t* dst = reinterpret_cast<uint8_t*>(&mBlocks[mBlockIndex[i]].mBits); + const uint8_t* src = + reinterpret_cast<const uint8_t*>(&blocks[blockIndex[i]].mBits); + for (uint32_t j = 0; j < BLOCK_SIZE; ++j) { + dst[j] |= src[j]; + } + } +} + +#define TRUETYPE_TAG(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d)) + +namespace mozilla { + +// Byte-swapping types and name table structure definitions moved from +// gfxFontUtils.cpp to .h file so that gfxFont.cpp can also refer to them +#pragma pack(1) + +struct AutoSwap_PRUint16 { +#ifdef __SUNPRO_CC + AutoSwap_PRUint16& operator=(const uint16_t aValue) { + this->value = mozilla::NativeEndian::swapToBigEndian(aValue); + return *this; + } +#else + MOZ_IMPLICIT AutoSwap_PRUint16(uint16_t aValue) { + value = mozilla::NativeEndian::swapToBigEndian(aValue); + } +#endif + operator uint16_t() const { + return mozilla::NativeEndian::swapFromBigEndian(value); + } + + operator uint32_t() const { + return mozilla::NativeEndian::swapFromBigEndian(value); + } + + operator uint64_t() const { + return mozilla::NativeEndian::swapFromBigEndian(value); + } + + private: + uint16_t value; +}; + +struct AutoSwap_PRInt16 { +#ifdef __SUNPRO_CC + AutoSwap_PRInt16& operator=(const int16_t aValue) { + this->value = mozilla::NativeEndian::swapToBigEndian(aValue); + return *this; + } +#else + MOZ_IMPLICIT AutoSwap_PRInt16(int16_t aValue) { + value = mozilla::NativeEndian::swapToBigEndian(aValue); + } +#endif + operator int16_t() const { + return mozilla::NativeEndian::swapFromBigEndian(value); + } + + operator uint32_t() const { + return mozilla::NativeEndian::swapFromBigEndian(value); + } + + private: + int16_t value; +}; + +struct AutoSwap_PRUint32 { +#ifdef __SUNPRO_CC + AutoSwap_PRUint32& operator=(const uint32_t aValue) { + this->value = mozilla::NativeEndian::swapToBigEndian(aValue); + return *this; + } +#else + MOZ_IMPLICIT AutoSwap_PRUint32(uint32_t aValue) { + value = mozilla::NativeEndian::swapToBigEndian(aValue); + } +#endif + operator uint32_t() const { + return mozilla::NativeEndian::swapFromBigEndian(value); + } + + private: + uint32_t value; +}; + +struct AutoSwap_PRInt32 { +#ifdef __SUNPRO_CC + AutoSwap_PRInt32& operator=(const int32_t aValue) { + this->value = mozilla::NativeEndian::swapToBigEndian(aValue); + return *this; + } +#else + MOZ_IMPLICIT AutoSwap_PRInt32(int32_t aValue) { + value = mozilla::NativeEndian::swapToBigEndian(aValue); + } +#endif + operator int32_t() const { + return mozilla::NativeEndian::swapFromBigEndian(value); + } + + private: + int32_t value; +}; + +struct AutoSwap_PRUint64 { +#ifdef __SUNPRO_CC + AutoSwap_PRUint64& operator=(const uint64_t aValue) { + this->value = mozilla::NativeEndian::swapToBigEndian(aValue); + return *this; + } +#else + MOZ_IMPLICIT AutoSwap_PRUint64(uint64_t aValue) { + value = mozilla::NativeEndian::swapToBigEndian(aValue); + } +#endif + operator uint64_t() const { + return mozilla::NativeEndian::swapFromBigEndian(value); + } + + private: + uint64_t value; +}; + +struct AutoSwap_PRUint24 { + operator uint32_t() const { + return value[0] << 16 | value[1] << 8 | value[2]; + } + + private: + AutoSwap_PRUint24() = default; + uint8_t value[3]; +}; + +struct SFNTHeader { + AutoSwap_PRUint32 sfntVersion; // Fixed, 0x00010000 for version 1.0. + AutoSwap_PRUint16 numTables; // Number of tables. + AutoSwap_PRUint16 searchRange; // (Maximum power of 2 <= numTables) x 16. + AutoSwap_PRUint16 entrySelector; // Log2(maximum power of 2 <= numTables). + AutoSwap_PRUint16 rangeShift; // NumTables x 16-searchRange. +}; + +struct TTCHeader { + AutoSwap_PRUint32 ttcTag; // 4 -byte identifier 'ttcf'. + AutoSwap_PRUint16 majorVersion; + AutoSwap_PRUint16 minorVersion; + AutoSwap_PRUint32 numFonts; + // followed by: + // AutoSwap_PRUint32 offsetTable[numFonts] +}; + +struct TableDirEntry { + AutoSwap_PRUint32 tag; // 4 -byte identifier. + AutoSwap_PRUint32 checkSum; // CheckSum for this table. + AutoSwap_PRUint32 offset; // Offset from beginning of TrueType font file. + AutoSwap_PRUint32 length; // Length of this table. +}; + +struct HeadTable { + enum { + HEAD_VERSION = 0x00010000, + HEAD_MAGIC_NUMBER = 0x5F0F3CF5, + HEAD_CHECKSUM_CALC_CONST = 0xB1B0AFBA + }; + + AutoSwap_PRUint32 tableVersionNumber; // Fixed, 0x00010000 for version 1.0. + AutoSwap_PRUint32 fontRevision; // Set by font manufacturer. + AutoSwap_PRUint32 + checkSumAdjustment; // To compute: set it to 0, sum the entire font as + // ULONG, then store 0xB1B0AFBA - sum. + AutoSwap_PRUint32 magicNumber; // Set to 0x5F0F3CF5. + AutoSwap_PRUint16 flags; + AutoSwap_PRUint16 + unitsPerEm; // Valid range is from 16 to 16384. This value should be a + // power of 2 for fonts that have TrueType outlines. + AutoSwap_PRUint64 created; // Number of seconds since 12:00 midnight, January + // 1, 1904. 64-bit integer + AutoSwap_PRUint64 modified; // Number of seconds since 12:00 midnight, + // January 1, 1904. 64-bit integer + AutoSwap_PRInt16 xMin; // For all glyph bounding boxes. + AutoSwap_PRInt16 yMin; // For all glyph bounding boxes. + AutoSwap_PRInt16 xMax; // For all glyph bounding boxes. + AutoSwap_PRInt16 yMax; // For all glyph bounding boxes. + AutoSwap_PRUint16 macStyle; // Bit 0: Bold (if set to 1); + AutoSwap_PRUint16 lowestRecPPEM; // Smallest readable size in pixels. + AutoSwap_PRInt16 fontDirectionHint; + AutoSwap_PRInt16 indexToLocFormat; + AutoSwap_PRInt16 glyphDataFormat; +}; + +struct OS2Table { + AutoSwap_PRUint16 version; // 0004 = OpenType 1.5 + AutoSwap_PRInt16 xAvgCharWidth; + AutoSwap_PRUint16 usWeightClass; + AutoSwap_PRUint16 usWidthClass; + AutoSwap_PRUint16 fsType; + AutoSwap_PRInt16 ySubscriptXSize; + AutoSwap_PRInt16 ySubscriptYSize; + AutoSwap_PRInt16 ySubscriptXOffset; + AutoSwap_PRInt16 ySubscriptYOffset; + AutoSwap_PRInt16 ySuperscriptXSize; + AutoSwap_PRInt16 ySuperscriptYSize; + AutoSwap_PRInt16 ySuperscriptXOffset; + AutoSwap_PRInt16 ySuperscriptYOffset; + AutoSwap_PRInt16 yStrikeoutSize; + AutoSwap_PRInt16 yStrikeoutPosition; + AutoSwap_PRInt16 sFamilyClass; + uint8_t panose[10]; + AutoSwap_PRUint32 unicodeRange1; + AutoSwap_PRUint32 unicodeRange2; + AutoSwap_PRUint32 unicodeRange3; + AutoSwap_PRUint32 unicodeRange4; + uint8_t achVendID[4]; + AutoSwap_PRUint16 fsSelection; + AutoSwap_PRUint16 usFirstCharIndex; + AutoSwap_PRUint16 usLastCharIndex; + AutoSwap_PRInt16 sTypoAscender; + AutoSwap_PRInt16 sTypoDescender; + AutoSwap_PRInt16 sTypoLineGap; + AutoSwap_PRUint16 usWinAscent; + AutoSwap_PRUint16 usWinDescent; + AutoSwap_PRUint32 codePageRange1; + AutoSwap_PRUint32 codePageRange2; + AutoSwap_PRInt16 sxHeight; + AutoSwap_PRInt16 sCapHeight; + AutoSwap_PRUint16 usDefaultChar; + AutoSwap_PRUint16 usBreakChar; + AutoSwap_PRUint16 usMaxContext; +}; + +struct PostTable { + AutoSwap_PRUint32 version; + AutoSwap_PRInt32 italicAngle; + AutoSwap_PRInt16 underlinePosition; + AutoSwap_PRUint16 underlineThickness; + AutoSwap_PRUint32 isFixedPitch; + AutoSwap_PRUint32 minMemType42; + AutoSwap_PRUint32 maxMemType42; + AutoSwap_PRUint32 minMemType1; + AutoSwap_PRUint32 maxMemType1; +}; + +// This structure is used for both 'hhea' and 'vhea' tables. +// The field names here are those of the horizontal version; the +// vertical table just exchanges vertical and horizontal coordinates. +struct MetricsHeader { + AutoSwap_PRUint32 version; + AutoSwap_PRInt16 ascender; + AutoSwap_PRInt16 descender; + AutoSwap_PRInt16 lineGap; + AutoSwap_PRUint16 advanceWidthMax; + AutoSwap_PRInt16 minLeftSideBearing; + AutoSwap_PRInt16 minRightSideBearing; + AutoSwap_PRInt16 xMaxExtent; + AutoSwap_PRInt16 caretSlopeRise; + AutoSwap_PRInt16 caretSlopeRun; + AutoSwap_PRInt16 caretOffset; + AutoSwap_PRInt16 reserved1; + AutoSwap_PRInt16 reserved2; + AutoSwap_PRInt16 reserved3; + AutoSwap_PRInt16 reserved4; + AutoSwap_PRInt16 metricDataFormat; + AutoSwap_PRUint16 numOfLongMetrics; +}; + +struct MaxpTableHeader { + AutoSwap_PRUint32 version; // CFF: 0x00005000; TrueType: 0x00010000 + AutoSwap_PRUint16 numGlyphs; + // truetype version has additional fields that we don't currently use +}; + +// old 'kern' table, supported on Windows +// see http://www.microsoft.com/typography/otspec/kern.htm +struct KernTableVersion0 { + AutoSwap_PRUint16 version; // 0x0000 + AutoSwap_PRUint16 nTables; +}; + +struct KernTableSubtableHeaderVersion0 { + AutoSwap_PRUint16 version; + AutoSwap_PRUint16 length; + AutoSwap_PRUint16 coverage; +}; + +// newer Mac-only 'kern' table, ignored by Windows +// see http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6kern.html +struct KernTableVersion1 { + AutoSwap_PRUint32 version; // 0x00010000 + AutoSwap_PRUint32 nTables; +}; + +struct KernTableSubtableHeaderVersion1 { + AutoSwap_PRUint32 length; + AutoSwap_PRUint16 coverage; + AutoSwap_PRUint16 tupleIndex; +}; + +#pragma pack() + +// Return just the highest bit of the given value, i.e., the highest +// power of 2 that is <= value, or zero if the input value is zero. +inline uint32_t FindHighestBit(uint32_t value) { + // propagate highest bit into all lower bits of the value + value |= (value >> 1); + value |= (value >> 2); + value |= (value >> 4); + value |= (value >> 8); + value |= (value >> 16); + // isolate the leftmost bit + return (value & ~(value >> 1)); +} + +} // namespace mozilla + +// used for overlaying name changes without touching original font data +struct FontDataOverlay { + // overlaySrc != 0 ==> use overlay + uint32_t overlaySrc; // src offset from start of font data + uint32_t overlaySrcLen; // src length + uint32_t overlayDest; // dest offset from start of font data +}; + +enum gfxUserFontType { + GFX_USERFONT_UNKNOWN = 0, + GFX_USERFONT_OPENTYPE = 1, + GFX_USERFONT_SVG = 2, + GFX_USERFONT_WOFF = 3, + GFX_USERFONT_WOFF2 = 4 +}; + +extern const uint8_t sCJKCompatSVSTable[]; + +class gfxFontUtils { + public: + // these are public because gfxFont.cpp also looks into the name table + enum { + NAME_ID_FAMILY = 1, + NAME_ID_STYLE = 2, + NAME_ID_UNIQUE = 3, + NAME_ID_FULL = 4, + NAME_ID_VERSION = 5, + NAME_ID_POSTSCRIPT = 6, + NAME_ID_PREFERRED_FAMILY = 16, + NAME_ID_PREFERRED_STYLE = 17, + + PLATFORM_ALL = -1, + PLATFORM_ID_UNICODE = 0, // Mac OS uses this typically + PLATFORM_ID_MAC = 1, + PLATFORM_ID_ISO = 2, + PLATFORM_ID_MICROSOFT = 3, + + ENCODING_ID_MAC_ROMAN = 0, // traditional Mac OS script manager encodings + ENCODING_ID_MAC_JAPANESE = + 1, // (there are others defined, but some were never + ENCODING_ID_MAC_TRAD_CHINESE = + 2, // implemented by Apple, and I have never seen them + ENCODING_ID_MAC_KOREAN = 3, // used in font names) + ENCODING_ID_MAC_ARABIC = 4, + ENCODING_ID_MAC_HEBREW = 5, + ENCODING_ID_MAC_GREEK = 6, + ENCODING_ID_MAC_CYRILLIC = 7, + ENCODING_ID_MAC_DEVANAGARI = 9, + ENCODING_ID_MAC_GURMUKHI = 10, + ENCODING_ID_MAC_GUJARATI = 11, + ENCODING_ID_MAC_SIMP_CHINESE = 25, + + ENCODING_ID_MICROSOFT_SYMBOL = 0, // Microsoft platform encoding IDs + ENCODING_ID_MICROSOFT_UNICODEBMP = 1, + ENCODING_ID_MICROSOFT_SHIFTJIS = 2, + ENCODING_ID_MICROSOFT_PRC = 3, + ENCODING_ID_MICROSOFT_BIG5 = 4, + ENCODING_ID_MICROSOFT_WANSUNG = 5, + ENCODING_ID_MICROSOFT_JOHAB = 6, + ENCODING_ID_MICROSOFT_UNICODEFULL = 10, + + LANG_ALL = -1, + LANG_ID_MAC_ENGLISH = 0, // many others are defined, but most don't affect + LANG_ID_MAC_HEBREW = + 10, // the charset; should check all the central/eastern + LANG_ID_MAC_JAPANESE = 11, // european codes, though + LANG_ID_MAC_ARABIC = 12, + LANG_ID_MAC_ICELANDIC = 15, + LANG_ID_MAC_TURKISH = 17, + LANG_ID_MAC_TRAD_CHINESE = 19, + LANG_ID_MAC_URDU = 20, + LANG_ID_MAC_KOREAN = 23, + LANG_ID_MAC_POLISH = 25, + LANG_ID_MAC_FARSI = 31, + LANG_ID_MAC_SIMP_CHINESE = 33, + LANG_ID_MAC_ROMANIAN = 37, + LANG_ID_MAC_CZECH = 38, + LANG_ID_MAC_SLOVAK = 39, + + LANG_ID_MICROSOFT_EN_US = + 0x0409, // with Microsoft platformID, EN US lang code + + CMAP_MAX_CODEPOINT = 0x10ffff // maximum possible Unicode codepoint + // contained in a cmap + }; + + // name table has a header, followed by name records, followed by string data + struct NameHeader { + mozilla::AutoSwap_PRUint16 format; // Format selector (=0). + mozilla::AutoSwap_PRUint16 count; // Number of name records. + mozilla::AutoSwap_PRUint16 stringOffset; // Offset to start of string + // storage (from start of table) + }; + + struct NameRecord { + mozilla::AutoSwap_PRUint16 platformID; // Platform ID + mozilla::AutoSwap_PRUint16 encodingID; // Platform-specific encoding ID + mozilla::AutoSwap_PRUint16 languageID; // Language ID + mozilla::AutoSwap_PRUint16 nameID; // Name ID. + mozilla::AutoSwap_PRUint16 length; // String length (in bytes). + mozilla::AutoSwap_PRUint16 offset; // String offset from start of storage + // (in bytes). + }; + + // Helper to ensure we free a font table when we return. + class AutoHBBlob { + public: + explicit AutoHBBlob(hb_blob_t* aBlob) : mBlob(aBlob) {} + + ~AutoHBBlob() { hb_blob_destroy(mBlob); } + + operator hb_blob_t*() { return mBlob; } + + private: + hb_blob_t* const mBlob; + }; + + // for reading big-endian font data on either big or little-endian platforms + + static inline uint16_t ReadShortAt(const uint8_t* aBuf, uint32_t aIndex) { + return static_cast<uint16_t>(aBuf[aIndex] << 8) | aBuf[aIndex + 1]; + } + + static inline uint16_t ReadShortAt16(const uint16_t* aBuf, uint32_t aIndex) { + const uint8_t* buf = reinterpret_cast<const uint8_t*>(aBuf); + uint32_t index = aIndex << 1; + return static_cast<uint16_t>(buf[index] << 8) | buf[index + 1]; + } + + static inline uint32_t ReadUint24At(const uint8_t* aBuf, uint32_t aIndex) { + return ((aBuf[aIndex] << 16) | (aBuf[aIndex + 1] << 8) | + (aBuf[aIndex + 2])); + } + + static inline uint32_t ReadLongAt(const uint8_t* aBuf, uint32_t aIndex) { + return ((aBuf[aIndex] << 24) | (aBuf[aIndex + 1] << 16) | + (aBuf[aIndex + 2] << 8) | (aBuf[aIndex + 3])); + } + + static nsresult ReadCMAPTableFormat10(const uint8_t* aBuf, uint32_t aLength, + gfxSparseBitSet& aCharacterMap); + + static nsresult ReadCMAPTableFormat12or13(const uint8_t* aBuf, + uint32_t aLength, + gfxSparseBitSet& aCharacterMap); + + static nsresult ReadCMAPTableFormat4(const uint8_t* aBuf, uint32_t aLength, + gfxSparseBitSet& aCharacterMap, + bool aIsSymbolFont); + + static nsresult ReadCMAPTableFormat14(const uint8_t* aBuf, uint32_t aLength, + const uint8_t*& aTable); + + static uint32_t FindPreferredSubtable(const uint8_t* aBuf, + uint32_t aBufLength, + uint32_t* aTableOffset, + uint32_t* aUVSTableOffset, + bool* aIsSymbolFont); + + static nsresult ReadCMAP(const uint8_t* aBuf, uint32_t aBufLength, + gfxSparseBitSet& aCharacterMap, + uint32_t& aUVSOffset); + + static uint32_t MapCharToGlyphFormat4(const uint8_t* aBuf, uint32_t aLength, + char16_t aCh); + + static uint32_t MapCharToGlyphFormat10(const uint8_t* aBuf, uint32_t aCh); + + static uint32_t MapCharToGlyphFormat12or13(const uint8_t* aBuf, uint32_t aCh); + + static uint16_t MapUVSToGlyphFormat14(const uint8_t* aBuf, uint32_t aCh, + uint32_t aVS); + + // sCJKCompatSVSTable is a 'cmap' format 14 subtable that maps + // <char + var-selector> pairs to the corresponding Unicode + // compatibility ideograph codepoints. + static MOZ_ALWAYS_INLINE uint32_t GetUVSFallback(uint32_t aCh, uint32_t aVS) { + aCh = MapUVSToGlyphFormat14(sCJKCompatSVSTable, aCh, aVS); + return aCh >= 0xFB00 ? aCh + (0x2F800 - 0xFB00) : aCh; + } + + static uint32_t MapCharToGlyph(const uint8_t* aCmapBuf, uint32_t aBufLength, + uint32_t aUnicode, uint32_t aVarSelector = 0); + + // For legacy MS Symbol fonts, we try mapping 8-bit character codes to the + // Private Use range at U+F0xx used by the cmaps in these fonts. + static MOZ_ALWAYS_INLINE uint32_t MapLegacySymbolFontCharToPUA(uint32_t aCh) { + return aCh >= 0x20 && aCh <= 0xff ? 0xf000 + aCh : 0; + } + +#ifdef XP_WIN + // determine whether a font (which has already been sanitized, so is known + // to be a valid sfnt) is CFF format rather than TrueType + static bool IsCffFont(const uint8_t* aFontData); +#endif + + // determine the format of font data + static gfxUserFontType DetermineFontDataType(const uint8_t* aFontData, + uint32_t aFontDataLength); + + // Read the fullname from the sfnt data (used to save the original name + // prior to renaming the font for installation). + // This is called with sfnt data that has already been validated, + // so it should always succeed in finding the name table. + static nsresult GetFullNameFromSFNT(const uint8_t* aFontData, + uint32_t aLength, nsACString& aFullName); + + // helper to get fullname from name table, constructing from family+style + // if no explicit fullname is present + static nsresult GetFullNameFromTable(hb_blob_t* aNameTable, + nsACString& aFullName); + + // helper to get family name from name table + static nsresult GetFamilyNameFromTable(hb_blob_t* aNameTable, + nsACString& aFamilyName); + + // Find the table directory entry for a given table tag, in a (validated) + // buffer of 'sfnt' data. Returns null if the tag is not present. + static mozilla::TableDirEntry* FindTableDirEntry(const void* aFontData, + uint32_t aTableTag); + + // Return a blob that wraps a table found within a buffer of font data. + // The blob does NOT own its data; caller guarantees that the buffer + // will remain valid at least as long as the blob. + // Returns null if the specified table is not found. + // This method assumes aFontData is valid 'sfnt' data; before using this, + // caller is responsible to do any sanitization/validation necessary. + static hb_blob_t* GetTableFromFontData(const void* aFontData, + uint32_t aTableTag); + + // create a new name table and build a new font with that name table + // appended on the end, returns true on success + static nsresult RenameFont(const nsAString& aName, const uint8_t* aFontData, + uint32_t aFontDataLength, + FallibleTArray<uint8_t>* aNewFont); + + // read all names matching aNameID, returning in aNames array + static nsresult ReadNames(const char* aNameData, uint32_t aDataLen, + uint32_t aNameID, int32_t aPlatformID, + nsTArray<nsCString>& aNames); + + // reads English or first name matching aNameID, returning in aName + // platform based on OS + static nsresult ReadCanonicalName(hb_blob_t* aNameTable, uint32_t aNameID, + nsCString& aName); + + static nsresult ReadCanonicalName(const char* aNameData, uint32_t aDataLen, + uint32_t aNameID, nsCString& aName); + + // convert a name from the raw name table data into an nsString, + // provided we know how; return true if successful, or false + // if we can't handle the encoding + static bool DecodeFontName(const char* aBuf, int32_t aLength, + uint32_t aPlatformCode, uint32_t aScriptCode, + uint32_t aLangCode, nsACString& dest); + + static inline bool IsJoinCauser(uint32_t ch) { return (ch == 0x200D); } + + // We treat Combining Grapheme Joiner (U+034F) together with the join + // controls (ZWJ, ZWNJ) here, because (like them) it is an invisible + // char that will be handled by the shaper even if not explicitly + // supported by the font. (See bug 1408366.) + static inline bool IsJoinControl(uint32_t ch) { + return (ch == 0x200C || ch == 0x200D || ch == 0x034f); + } + + enum { + kUnicodeVS1 = 0xFE00, + kUnicodeVS16 = 0xFE0F, + kUnicodeVS17 = 0xE0100, + kUnicodeVS256 = 0xE01EF + }; + + static inline bool IsVarSelector(uint32_t ch) { + return (ch >= kUnicodeVS1 && ch <= kUnicodeVS16) || + (ch >= kUnicodeVS17 && ch <= kUnicodeVS256); + } + + enum { + kUnicodeRegionalIndicatorA = 0x1F1E6, + kUnicodeRegionalIndicatorZ = 0x1F1FF + }; + + static inline bool IsRegionalIndicator(uint32_t aCh) { + return aCh >= kUnicodeRegionalIndicatorA && + aCh <= kUnicodeRegionalIndicatorZ; + } + + static inline bool IsEmojiFlagAndTag(uint32_t aCh, uint32_t aNext) { + constexpr uint32_t kBlackFlag = 0x1F3F4; + constexpr uint32_t kTagLetterA = 0xE0061; + constexpr uint32_t kTagLetterZ = 0xE007A; + + return aCh == kBlackFlag && aNext >= kTagLetterA && aNext <= kTagLetterZ; + } + + static inline bool IsInvalid(uint32_t ch) { return (ch == 0xFFFD); } + + // Font code may want to know if there is the potential for bidi behavior + // to be triggered by any of the characters in a text run; this can be + // used to test that possibility. + enum { + kUnicodeBidiScriptsStart = 0x0590, + kUnicodeBidiScriptsEnd = 0x08FF, + kUnicodeBidiPresentationStart = 0xFB1D, + kUnicodeBidiPresentationEnd = 0xFEFC, + kUnicodeFirstHighSurrogateBlock = 0xD800, + kUnicodeRLM = 0x200F, + kUnicodeRLE = 0x202B, + kUnicodeRLO = 0x202E + }; + + static inline bool PotentialRTLChar(char16_t aCh) { + if (aCh >= kUnicodeBidiScriptsStart && aCh <= kUnicodeBidiScriptsEnd) + // bidi scripts Hebrew, Arabic, Syriac, Thaana, N'Ko are all encoded + // together + return true; + + if (aCh == kUnicodeRLM || aCh == kUnicodeRLE || aCh == kUnicodeRLO) + // directional controls that trigger bidi layout + return true; + + if (aCh >= kUnicodeBidiPresentationStart && + aCh <= kUnicodeBidiPresentationEnd) + // presentation forms of Arabic and Hebrew letters + return true; + + if ((aCh & 0xFF00) == kUnicodeFirstHighSurrogateBlock) + // surrogate that could be part of a bidi supplementary char + // (Cypriot, Aramaic, Phoenecian, etc) + return true; + + // otherwise we know this char cannot trigger bidi reordering + return false; + } + + // parse a simple list of font family names into + // an array of strings + static void ParseFontList(const nsACString& aFamilyList, + nsTArray<nsCString>& aFontList); + + // for a given pref name, initialize a list of font names + static void GetPrefsFontList(const char* aPrefName, + nsTArray<nsCString>& aFontList, + bool aLocalized = false); + + // generate a unique font name + static nsresult MakeUniqueUserFontName(nsAString& aName); + + // Helper used to implement gfxFontEntry::GetVariation{Axes,Instances} for + // platforms where the native font APIs don't provide the info we want + // in a convenient form, or when native APIs are too expensive. + // (Not used on platforms -- currently, freetype -- where the font APIs + // expose variation instance details directly.) + static void GetVariationData(gfxFontEntry* aFontEntry, + nsTArray<gfxFontVariationAxis>* aAxes, + nsTArray<gfxFontVariationInstance>* aInstances); + + // Helper method for reading localized family names from the name table + // of a single face. + static void ReadOtherFamilyNamesForFace( + const nsACString& aFamilyName, const char* aNameData, + uint32_t aDataLength, nsTArray<nsCString>& aOtherFamilyNames, + bool useFullName); + + // Main, DOM worker or servo thread safe method to check if we are performing + // Servo traversal. + static bool IsInServoTraversal(); + + // Main, DOM worker or servo thread safe method to get the current + // ServoTypeSet. Always returns nullptr for DOM worker threads. + static mozilla::ServoStyleSet* CurrentServoStyleSet(); + + static void AssertSafeThreadOrServoFontMetricsLocked() +#ifdef DEBUG + ; +#else + { + } +#endif + + protected: + friend struct MacCharsetMappingComparator; + + static nsresult ReadNames(const char* aNameData, uint32_t aDataLen, + uint32_t aNameID, int32_t aLangID, + int32_t aPlatformID, nsTArray<nsCString>& aNames); + + // convert opentype name-table platform/encoding/language values to an + // Encoding object we can use to convert the name data to unicode + static const mozilla::Encoding* GetCharsetForFontName(uint16_t aPlatform, + uint16_t aScript, + uint16_t aLanguage); + + struct MacFontNameCharsetMapping { + uint16_t mScript; + uint16_t mLanguage; + const mozilla::Encoding* mEncoding; + + bool operator<(const MacFontNameCharsetMapping& rhs) const { + return (mScript < rhs.mScript) || + ((mScript == rhs.mScript) && (mLanguage < rhs.mLanguage)); + } + }; + static const MacFontNameCharsetMapping gMacFontNameCharsets[]; + static const mozilla::Encoding* gISOFontNameCharsets[]; + static const mozilla::Encoding* gMSFontNameCharsets[]; +}; + +// Factors used to weight the distances between the available and target font +// properties during font-matching. These ensure that we respect the CSS-fonts +// requirement that font-stretch >> font-style >> font-weight; and in addition, +// a mismatch between the desired and actual glyph presentation (emoji vs text) +// will take precedence over any of the style attributes. +constexpr double kPresentationMismatch = 1.0e12; +constexpr double kStretchFactor = 1.0e8; +constexpr double kStyleFactor = 1.0e4; +constexpr double kWeightFactor = 1.0e0; + +// style distance ==> [0,500] +static inline double StyleDistance(const mozilla::SlantStyleRange& aRange, + mozilla::FontSlantStyle aTargetStyle) { + const mozilla::FontSlantStyle minStyle = aRange.Min(); + if (aTargetStyle == minStyle) { + return 0.0; // styles match exactly ==> 0 + } + + // bias added to angle difference when searching in the non-preferred + // direction from a target angle + const double kReverse = 100.0; + + // bias added when we've crossed from positive to negative angles or + // vice versa + const double kNegate = 200.0; + + if (aTargetStyle.IsNormal()) { + if (minStyle.IsOblique()) { + // to distinguish oblique 0deg from normal, we add 1.0 to the angle + const double minAngle = minStyle.ObliqueAngle(); + if (minAngle >= 0.0) { + return 1.0 + minAngle; + } + const mozilla::FontSlantStyle maxStyle = aRange.Max(); + const double maxAngle = maxStyle.ObliqueAngle(); + if (maxAngle >= 0.0) { + // [min,max] range includes 0.0, so just return our minimum + return 1.0; + } + // negative oblique is even worse than italic + return kNegate - maxAngle; + } + // must be italic, which is worse than any non-negative oblique; + // treat as a match in the wrong search direction + MOZ_ASSERT(minStyle.IsItalic()); + return kReverse; + } + + const double kDefaultAngle = mozilla::FontSlantStyle::DEFAULT_OBLIQUE_DEGREES; + + if (aTargetStyle.IsItalic()) { + if (minStyle.IsOblique()) { + const double minAngle = minStyle.ObliqueAngle(); + if (minAngle >= kDefaultAngle) { + return 1.0 + (minAngle - kDefaultAngle); + } + const mozilla::FontSlantStyle maxStyle = aRange.Max(); + const double maxAngle = maxStyle.ObliqueAngle(); + if (maxAngle >= kDefaultAngle) { + return 1.0; + } + if (maxAngle > 0.0) { + // wrong direction but still > 0, add bias of 100 + return kReverse + (kDefaultAngle - maxAngle); + } + // negative oblique angle, add bias of 300 + return kReverse + kNegate + (kDefaultAngle - maxAngle); + } + // normal is worse than oblique > 0, but better than oblique <= 0 + MOZ_ASSERT(minStyle.IsNormal()); + return kNegate; + } + + // target is oblique <angle>: four different cases depending on + // the value of the <angle>, which determines the preferred direction + // of search + const double targetAngle = aTargetStyle.ObliqueAngle(); + if (targetAngle >= kDefaultAngle) { + if (minStyle.IsOblique()) { + const double minAngle = minStyle.ObliqueAngle(); + if (minAngle >= targetAngle) { + return minAngle - targetAngle; + } + const mozilla::FontSlantStyle maxStyle = aRange.Max(); + const double maxAngle = maxStyle.ObliqueAngle(); + if (maxAngle >= targetAngle) { + return 0.0; + } + if (maxAngle > 0.0) { + return kReverse + (targetAngle - maxAngle); + } + return kReverse + kNegate + (targetAngle - maxAngle); + } + if (minStyle.IsItalic()) { + return kReverse + kNegate; + } + return kReverse + kNegate + 1.0; + } + + if (targetAngle <= -kDefaultAngle) { + if (minStyle.IsOblique()) { + const mozilla::FontSlantStyle maxStyle = aRange.Max(); + const double maxAngle = maxStyle.ObliqueAngle(); + if (maxAngle <= targetAngle) { + return targetAngle - maxAngle; + } + const double minAngle = minStyle.ObliqueAngle(); + if (minAngle <= targetAngle) { + return 0.0; + } + if (minAngle < 0.0) { + return kReverse + (minAngle - targetAngle); + } + return kReverse + kNegate + (minAngle - targetAngle); + } + if (minStyle.IsItalic()) { + return kReverse + kNegate; + } + return kReverse + kNegate + 1.0; + } + + if (targetAngle >= 0.0) { + if (minStyle.IsOblique()) { + const double minAngle = minStyle.ObliqueAngle(); + if (minAngle > targetAngle) { + return kReverse + (minAngle - targetAngle); + } + const mozilla::FontSlantStyle maxStyle = aRange.Max(); + const double maxAngle = maxStyle.ObliqueAngle(); + if (maxAngle >= targetAngle) { + return 0.0; + } + if (maxAngle > 0.0) { + return targetAngle - maxAngle; + } + return kReverse + kNegate + (targetAngle - maxAngle); + } + if (minStyle.IsItalic()) { + return kReverse + kNegate - 2.0; + } + return kReverse + kNegate - 1.0; + } + + // last case: (targetAngle < 0.0 && targetAngle > kDefaultAngle) + if (minStyle.IsOblique()) { + const mozilla::FontSlantStyle maxStyle = aRange.Max(); + const double maxAngle = maxStyle.ObliqueAngle(); + if (maxAngle < targetAngle) { + return kReverse + (targetAngle - maxAngle); + } + const double minAngle = minStyle.ObliqueAngle(); + if (minAngle <= targetAngle) { + return 0.0; + } + if (minAngle < 0.0) { + return minAngle - targetAngle; + } + return kReverse + kNegate + (minAngle - targetAngle); + } + if (minStyle.IsItalic()) { + return kReverse + kNegate - 2.0; + } + return kReverse + kNegate - 1.0; +} + +// stretch distance ==> [0,2000] +static inline double StretchDistance(const mozilla::StretchRange& aRange, + mozilla::FontStretch aTargetStretch) { + const double kReverseDistance = 1000.0; + + mozilla::FontStretch minStretch = aRange.Min(); + mozilla::FontStretch maxStretch = aRange.Max(); + + // The stretch value is a (non-negative) percentage; currently we support + // values in the range 0 .. 1000. (If the upper limit is ever increased, + // the kReverseDistance value used here may need to be adjusted.) + // If aTargetStretch is >100, we prefer larger values if available; + // if <=100, we prefer smaller values if available. + if (aTargetStretch < minStretch) { + if (aTargetStretch > mozilla::FontStretch::NORMAL) { + return minStretch.ToFloat() - aTargetStretch.ToFloat(); + } + return (minStretch.ToFloat() - aTargetStretch.ToFloat()) + kReverseDistance; + } + if (aTargetStretch > maxStretch) { + if (aTargetStretch <= mozilla::FontStretch::NORMAL) { + return aTargetStretch.ToFloat() - maxStretch.ToFloat(); + } + return (aTargetStretch.ToFloat() - maxStretch.ToFloat()) + kReverseDistance; + } + return 0.0; +} + +// Calculate weight distance with values in the range (0..1000). In general, +// heavier weights match towards even heavier weights while lighter weights +// match towards even lighter weights. Target weight values in the range +// [400..500] are special, since they will first match up to 500, then down +// towards 0, then up again towards 999. +// +// Example: with target 600 and font weight 800, distance will be 200. With +// target 300 and font weight 600, distance will be 900, since heavier +// weights are farther away than lighter weights. If the target is 5 and the +// font weight 995, the distance would be 1590 for the same reason. + +// weight distance ==> [0,1600] +static inline double WeightDistance(const mozilla::WeightRange& aRange, + mozilla::FontWeight aTargetWeight) { + const double kNotWithinCentralRange = 100.0; + const double kReverseDistance = 600.0; + + mozilla::FontWeight minWeight = aRange.Min(); + mozilla::FontWeight maxWeight = aRange.Max(); + + if (aTargetWeight >= minWeight && aTargetWeight <= maxWeight) { + // Target is within the face's range, so it's a perfect match + return 0.0; + } + + if (aTargetWeight < mozilla::FontWeight::NORMAL) { + // Requested a lighter-than-400 weight + if (maxWeight < aTargetWeight) { + return aTargetWeight.ToFloat() - maxWeight.ToFloat(); + } + // Add reverse-search penalty for bolder faces + return (minWeight.ToFloat() - aTargetWeight.ToFloat()) + kReverseDistance; + } + + if (aTargetWeight > mozilla::FontWeight::FromInt(500)) { + // Requested a bolder-than-500 weight + if (minWeight > aTargetWeight) { + return minWeight.ToFloat() - aTargetWeight.ToFloat(); + } + // Add reverse-search penalty for lighter faces + return (aTargetWeight.ToFloat() - maxWeight.ToFloat()) + kReverseDistance; + } + + // Special case for requested weight in the [400..500] range + if (minWeight > aTargetWeight) { + if (minWeight <= mozilla::FontWeight::FromInt(500)) { + // Bolder weight up to 500 is first choice + return minWeight.ToFloat() - aTargetWeight.ToFloat(); + } + // Other bolder weights get a reverse-search penalty + return (minWeight.ToFloat() - aTargetWeight.ToFloat()) + kReverseDistance; + } + // Lighter weights are not as good as bolder ones within [400..500] + return (aTargetWeight.ToFloat() - maxWeight.ToFloat()) + + kNotWithinCentralRange; +} + +#endif /* GFX_FONT_UTILS_H */ diff --git a/gfx/thebes/gfxFontVariations.h b/gfx/thebes/gfxFontVariations.h new file mode 100644 index 0000000000..e589ffc8cb --- /dev/null +++ b/gfx/thebes/gfxFontVariations.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FONT_VARIATIONS_H +#define GFX_FONT_VARIATIONS_H + +#include "mozilla/gfx/FontVariation.h" +#include "nsString.h" +#include "nsTArray.h" + +typedef mozilla::gfx::FontVariation gfxFontVariation; + +// Structure that describes a single axis of variation in an +// OpenType Variation or Multiple-Master font. +struct gfxFontVariationAxis { + uint32_t mTag; + nsCString mName; // may be empty + float mMinValue; + float mMaxValue; + float mDefaultValue; +}; + +// A single <axis, value> pair that may be applied to a variation font. +struct gfxFontVariationValue { + uint32_t mAxis; + float mValue; +}; + +// Structure that describes a named instance of a variation font: +// a name like "Light Condensed" or "Black Ultra Extended" etc., +// and a list of the corresponding <variation-axis, value> pairs +// to be used. +struct gfxFontVariationInstance { + nsCString mName; + CopyableTArray<gfxFontVariationValue> mValues; +}; + +#endif diff --git a/gfx/thebes/gfxGDIFont.cpp b/gfx/thebes/gfxGDIFont.cpp new file mode 100644 index 0000000000..2053b33727 --- /dev/null +++ b/gfx/thebes/gfxGDIFont.cpp @@ -0,0 +1,553 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxGDIFont.h" + +#include "mozilla/MemoryReporting.h" +#include "mozilla/Sprintf.h" +#include "mozilla/WindowsVersion.h" + +#include <algorithm> +#include "gfxWindowsPlatform.h" +#include "gfxContext.h" +#include "mozilla/Preferences.h" +#include "nsUnicodeProperties.h" +#include "gfxFontConstants.h" +#include "gfxHarfBuzzShaper.h" +#include "gfxTextRun.h" + +#include "cairo-win32.h" + +#define ROUND(x) floor((x) + 0.5) + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::unicode; + +gfxGDIFont::gfxGDIFont(GDIFontEntry* aFontEntry, const gfxFontStyle* aFontStyle, + AntialiasOption anAAOption) + : gfxFont(nullptr, aFontEntry, aFontStyle, anAAOption), + mFont(nullptr), + mMetrics(nullptr), + mIsBitmap(false), + mScriptCache(nullptr) { + mNeedsSyntheticBold = aFontStyle->NeedsSyntheticBold(aFontEntry); + + Initialize(); + + if (mFont) { + mUnscaledFont = aFontEntry->LookupUnscaledFont(mFont); + } +} + +gfxGDIFont::~gfxGDIFont() { + if (mFont) { + ::DeleteObject(mFont); + } + if (mScriptCache) { + ScriptFreeCache(&mScriptCache); + } + delete mMetrics; +} + +gfxFont* gfxGDIFont::CopyWithAntialiasOption(AntialiasOption anAAOption) const { + auto entry = static_cast<GDIFontEntry*>(mFontEntry.get()); + return new gfxGDIFont(entry, &mStyle, anAAOption); +} + +bool gfxGDIFont::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, + RoundingFlags aRounding, + gfxShapedText* aShapedText) { + if (!mIsValid) { + NS_WARNING("invalid font! expect incorrect text rendering"); + return false; + } + + return gfxFont::ShapeText(aDrawTarget, aText, aOffset, aLength, aScript, + aLanguage, aVertical, aRounding, aShapedText); +} + +already_AddRefed<ScaledFont> gfxGDIFont::GetScaledFont( + const TextRunDrawParams& aRunParams) { + if (ScaledFont* scaledFont = mAzureScaledFont) { + return do_AddRef(scaledFont); + } + + LOGFONT lf; + GetObject(GetHFONT(), sizeof(LOGFONT), &lf); + + RefPtr<ScaledFont> newScaledFont = Factory::CreateScaledFontForGDIFont( + &lf, GetUnscaledFont(), GetAdjustedSize()); + if (!newScaledFont) { + return nullptr; + } + + InitializeScaledFont(newScaledFont); + + if (mAzureScaledFont.compareExchange(nullptr, newScaledFont.get())) { + Unused << newScaledFont.forget(); + } + ScaledFont* scaledFont = mAzureScaledFont; + return do_AddRef(scaledFont); +} + +gfxFont::RunMetrics gfxGDIFont::Measure(const gfxTextRun* aTextRun, + uint32_t aStart, uint32_t aEnd, + BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, + Spacing* aSpacing, + gfx::ShapedTextFlags aOrientation) { + gfxFont::RunMetrics metrics = + gfxFont::Measure(aTextRun, aStart, aEnd, aBoundingBoxType, aRefDrawTarget, + aSpacing, aOrientation); + + // if aBoundingBoxType is LOOSE_INK_EXTENTS + // and the underlying cairo font may be antialiased, + // we can't trust Windows to have considered all the pixels + // so we need to add "padding" to the bounds. + // (see bugs 475968, 439831, compare also bug 445087) + if (aBoundingBoxType == LOOSE_INK_EXTENTS && + mAntialiasOption != kAntialiasNone && metrics.mBoundingBox.Width() > 0) { + metrics.mBoundingBox.MoveByX(-aTextRun->GetAppUnitsPerDevUnit()); + metrics.mBoundingBox.SetWidth(metrics.mBoundingBox.Width() + + aTextRun->GetAppUnitsPerDevUnit() * 3); + } + + return metrics; +} + +void gfxGDIFont::Initialize() { + NS_ASSERTION(!mMetrics, "re-creating metrics? this will leak"); + + LOGFONTW logFont; + + if (mAdjustedSize == 0.0) { + mAdjustedSize = GetAdjustedSize(); + if (FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) != + FontSizeAdjust::Tag::None) { + if (mStyle.sizeAdjust > 0.0 && mAdjustedSize > 0.0) { + // to implement font-size-adjust, we first create the "unadjusted" font + FillLogFont(logFont, mAdjustedSize); + mFont = ::CreateFontIndirectW(&logFont); + + // initialize its metrics so we can calculate size adjustment + Initialize(); + + // Unless the font was so small that GDI metrics rounded to zero, + // calculate the properly adjusted size, and then proceed + // to recreate mFont and recalculate metrics + if (mMetrics->emHeight > 0.0) { + gfxFloat aspect; + switch (FontSizeAdjust::Tag(mStyle.sizeAdjustBasis)) { + default: + MOZ_ASSERT_UNREACHABLE("unhandled sizeAdjustBasis?"); + aspect = 0.0; + break; + case FontSizeAdjust::Tag::ExHeight: + aspect = mMetrics->xHeight / mMetrics->emHeight; + break; + case FontSizeAdjust::Tag::CapHeight: + aspect = mMetrics->capHeight / mMetrics->emHeight; + break; + case FontSizeAdjust::Tag::ChWidth: { + gfxFloat advance = GetCharAdvance('0'); + aspect = advance > 0.0 ? advance / mMetrics->emHeight : 0.5; + break; + } + case FontSizeAdjust::Tag::IcWidth: + case FontSizeAdjust::Tag::IcHeight: { + bool vertical = FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) == + FontSizeAdjust::Tag::IcHeight; + gfxFloat advance = GetCharAdvance(kWaterIdeograph, vertical); + aspect = advance > 0.0 ? advance / mMetrics->emHeight : 1.0; + break; + } + } + if (aspect > 0.0) { + // If we created a shaper above (to measure glyphs), discard it so + // we get a new one for the adjusted scaling. + delete mHarfBuzzShaper.exchange(nullptr); + mAdjustedSize = mStyle.GetAdjustedSize(aspect); + } + } + + // delete the temporary font and metrics + ::DeleteObject(mFont); + mFont = nullptr; + delete mMetrics; + mMetrics = nullptr; + } else { + mAdjustedSize = 0.0; + } + } + } + + // (bug 724231) for local user fonts, we don't use GDI's synthetic bold, + // as it could lead to a different, incompatible face being used + // but instead do our own multi-striking + if (mNeedsSyntheticBold && GetFontEntry()->IsLocalUserFont()) { + mApplySyntheticBold = true; + } + + // this may end up being zero + mAdjustedSize = ROUND(mAdjustedSize); + FillLogFont(logFont, mAdjustedSize); + mFont = ::CreateFontIndirectW(&logFont); + + mMetrics = new gfxFont::Metrics; + ::memset(mMetrics, 0, sizeof(*mMetrics)); + + if (!mFont) { + NS_WARNING("Failed creating GDI font"); + mIsValid = false; + return; + } + + AutoDC dc; + SetGraphicsMode(dc.GetDC(), GM_ADVANCED); + AutoSelectFont selectFont(dc.GetDC(), mFont); + + // Get font metrics if size > 0 + if (mAdjustedSize > 0.0) { + OUTLINETEXTMETRIC oMetrics; + TEXTMETRIC& metrics = oMetrics.otmTextMetrics; + + if (0 < GetOutlineTextMetrics(dc.GetDC(), sizeof(oMetrics), &oMetrics)) { + mMetrics->strikeoutSize = (double)oMetrics.otmsStrikeoutSize; + mMetrics->strikeoutOffset = (double)oMetrics.otmsStrikeoutPosition; + mMetrics->underlineSize = (double)oMetrics.otmsUnderscoreSize; + mMetrics->underlineOffset = (double)oMetrics.otmsUnderscorePosition; + + const MAT2 kIdentityMatrix = {{0, 1}, {0, 0}, {0, 0}, {0, 1}}; + GLYPHMETRICS gm; + DWORD len = GetGlyphOutlineW(dc.GetDC(), char16_t('x'), GGO_METRICS, &gm, + 0, nullptr, &kIdentityMatrix); + if (len == GDI_ERROR || gm.gmptGlyphOrigin.y <= 0) { + // 56% of ascent, best guess for true type + mMetrics->xHeight = + ROUND((double)metrics.tmAscent * DEFAULT_XHEIGHT_FACTOR); + } else { + mMetrics->xHeight = gm.gmptGlyphOrigin.y; + } + len = GetGlyphOutlineW(dc.GetDC(), char16_t('H'), GGO_METRICS, &gm, 0, + nullptr, &kIdentityMatrix); + if (len == GDI_ERROR || gm.gmptGlyphOrigin.y <= 0) { + mMetrics->capHeight = metrics.tmAscent - metrics.tmInternalLeading; + } else { + mMetrics->capHeight = gm.gmptGlyphOrigin.y; + } + mMetrics->emHeight = metrics.tmHeight - metrics.tmInternalLeading; + gfxFloat typEmHeight = + (double)oMetrics.otmAscent - (double)oMetrics.otmDescent; + mMetrics->emAscent = + ROUND(mMetrics->emHeight * (double)oMetrics.otmAscent / typEmHeight); + mMetrics->emDescent = mMetrics->emHeight - mMetrics->emAscent; + if (oMetrics.otmEMSquare > 0) { + mFUnitsConvFactor = float(mAdjustedSize / oMetrics.otmEMSquare); + } + } else { + // Make a best-effort guess at extended metrics + // this is based on general typographic guidelines + + // GetTextMetrics can fail if the font file has been removed + // or corrupted recently. + BOOL result = GetTextMetrics(dc.GetDC(), &metrics); + if (!result) { + NS_WARNING("Missing or corrupt font data, fasten your seatbelt"); + mIsValid = false; + memset(mMetrics, 0, sizeof(*mMetrics)); + return; + } + + mMetrics->xHeight = + ROUND((float)metrics.tmAscent * DEFAULT_XHEIGHT_FACTOR); + mMetrics->strikeoutSize = 1; + mMetrics->strikeoutOffset = + ROUND(mMetrics->xHeight * 0.5f); // 50% of xHeight + mMetrics->underlineSize = 1; + mMetrics->underlineOffset = + -ROUND((float)metrics.tmDescent * 0.30f); // 30% of descent + mMetrics->emHeight = metrics.tmHeight - metrics.tmInternalLeading; + mMetrics->emAscent = metrics.tmAscent - metrics.tmInternalLeading; + mMetrics->emDescent = metrics.tmDescent; + mMetrics->capHeight = mMetrics->emAscent; + } + + mMetrics->internalLeading = metrics.tmInternalLeading; + mMetrics->externalLeading = metrics.tmExternalLeading; + mMetrics->maxHeight = metrics.tmHeight; + mMetrics->maxAscent = metrics.tmAscent; + mMetrics->maxDescent = metrics.tmDescent; + mMetrics->maxAdvance = metrics.tmMaxCharWidth; + mMetrics->aveCharWidth = std::max<gfxFloat>(1, metrics.tmAveCharWidth); + // The font is monospace when TMPF_FIXED_PITCH is *not* set! + // See http://msdn2.microsoft.com/en-us/library/ms534202(VS.85).aspx + if (!(metrics.tmPitchAndFamily & TMPF_FIXED_PITCH)) { + mMetrics->maxAdvance = mMetrics->aveCharWidth; + } + + mIsBitmap = !(metrics.tmPitchAndFamily & TMPF_VECTOR); + + // For fonts with USE_TYPO_METRICS set in the fsSelection field, + // let the OS/2 sTypo* metrics override the previous values. + // (see http://www.microsoft.com/typography/otspec/os2.htm#fss) + // Using the equivalent values from oMetrics provides inconsistent + // results with CFF fonts, so we instead rely on OS2Table. + gfxFontEntry::AutoTable os2Table(mFontEntry, + TRUETYPE_TAG('O', 'S', '/', '2')); + if (os2Table) { + uint32_t len; + const OS2Table* os2 = + reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len)); + if (len >= offsetof(OS2Table, sTypoLineGap) + sizeof(int16_t)) { + const uint16_t kUseTypoMetricsMask = 1 << 7; + if ((uint16_t(os2->fsSelection) & kUseTypoMetricsMask)) { + double ascent = int16_t(os2->sTypoAscender); + double descent = int16_t(os2->sTypoDescender); + double lineGap = int16_t(os2->sTypoLineGap); + mMetrics->maxAscent = ROUND(ascent * mFUnitsConvFactor); + mMetrics->maxDescent = -ROUND(descent * mFUnitsConvFactor); + mMetrics->maxHeight = mMetrics->maxAscent + mMetrics->maxDescent; + mMetrics->internalLeading = mMetrics->maxHeight - mMetrics->emHeight; + gfxFloat lineHeight = + ROUND((ascent - descent + lineGap) * mFUnitsConvFactor); + lineHeight = std::max(lineHeight, mMetrics->maxHeight); + mMetrics->externalLeading = lineHeight - mMetrics->maxHeight; + } + } + // although sxHeight and sCapHeight are signed fields, we consider + // negative values to be erroneous and just ignore them + if (uint16_t(os2->version) >= 2) { + // version 2 and later includes the x-height and cap-height fields + if (len >= offsetof(OS2Table, sxHeight) + sizeof(int16_t) && + int16_t(os2->sxHeight) > 0) { + mMetrics->xHeight = ROUND(int16_t(os2->sxHeight) * mFUnitsConvFactor); + } + if (len >= offsetof(OS2Table, sCapHeight) + sizeof(int16_t) && + int16_t(os2->sCapHeight) > 0) { + mMetrics->capHeight = + ROUND(int16_t(os2->sCapHeight) * mFUnitsConvFactor); + } + } + } + + WORD glyph; + SIZE size; + DWORD ret = GetGlyphIndicesW(dc.GetDC(), L" ", 1, &glyph, + GGI_MARK_NONEXISTING_GLYPHS); + if (ret != GDI_ERROR && glyph != 0xFFFF) { + mSpaceGlyph = glyph; + // Cache the width of a single space. + GetTextExtentPoint32W(dc.GetDC(), L" ", 1, &size); + mMetrics->spaceWidth = ROUND(size.cx); + } else { + mMetrics->spaceWidth = mMetrics->aveCharWidth; + } + + // Cache the width of digit zero, if available. + ret = GetGlyphIndicesW(dc.GetDC(), L"0", 1, &glyph, + GGI_MARK_NONEXISTING_GLYPHS); + if (ret != GDI_ERROR && glyph != 0xFFFF) { + GetTextExtentPoint32W(dc.GetDC(), L"0", 1, &size); + mMetrics->zeroWidth = ROUND(size.cx); + } else { + mMetrics->zeroWidth = -1.0; // indicates not found + } + + wchar_t ch = kWaterIdeograph; + ret = GetGlyphIndicesW(dc.GetDC(), &ch, 1, &glyph, + GGI_MARK_NONEXISTING_GLYPHS); + if (ret != GDI_ERROR && glyph != 0xFFFF) { + GetTextExtentPoint32W(dc.GetDC(), &ch, 1, &size); + mMetrics->ideographicWidth = ROUND(size.cx); + } else { + mMetrics->ideographicWidth = -1.0; + } + + SanitizeMetrics(mMetrics, GetFontEntry()->mIsBadUnderlineFont); + } else { + mFUnitsConvFactor = 0.0; // zero-sized font: all values scale to zero + } + + if (ApplySyntheticBold()) { + auto delta = GetSyntheticBoldOffset(); + mMetrics->spaceWidth += delta; + mMetrics->aveCharWidth += delta; + mMetrics->maxAdvance += delta; + if (mMetrics->zeroWidth > 0) { + mMetrics->zeroWidth += delta; + } + if (mMetrics->ideographicWidth > 0) { + mMetrics->ideographicWidth += delta; + } + } + +#if 0 + printf("Font: %p (%s) size: %f adjusted size: %f valid: %s\n", this, + GetName().get(), mStyle.size, mAdjustedSize, (mIsValid ? "yes" : "no")); + printf(" emHeight: %f emAscent: %f emDescent: %f\n", mMetrics->emHeight, mMetrics->emAscent, mMetrics->emDescent); + printf(" maxAscent: %f maxDescent: %f maxAdvance: %f\n", mMetrics->maxAscent, mMetrics->maxDescent, mMetrics->maxAdvance); + printf(" internalLeading: %f externalLeading: %f\n", mMetrics->internalLeading, mMetrics->externalLeading); + printf(" spaceWidth: %f aveCharWidth: %f\n", mMetrics->spaceWidth, mMetrics->aveCharWidth); + printf(" xHeight: %f capHeight: %f\n", mMetrics->xHeight, mMetrics->capHeight); + printf(" uOff: %f uSize: %f stOff: %f stSize: %f\n", + mMetrics->underlineOffset, mMetrics->underlineSize, mMetrics->strikeoutOffset, mMetrics->strikeoutSize); +#endif +} + +void gfxGDIFont::FillLogFont(LOGFONTW& aLogFont, gfxFloat aSize) { + GDIFontEntry* fe = static_cast<GDIFontEntry*>(GetFontEntry()); + + // Figure out the lfWeight value to use for GDI font selection, + // or zero to use the entry's current LOGFONT value. + LONG weight; + if (fe->IsUserFont()) { + if (fe->IsLocalUserFont()) { + // for local user fonts, don't change the original weight + // in the entry's logfont, because that could alter the + // choice of actual face used (bug 724231) + weight = 0; + } else { + // avoid GDI synthetic bold which occurs when weight + // specified is >= font data weight + 200 + weight = mNeedsSyntheticBold ? 700 : 200; + } + } else { + // GDI doesn't support variation fonts, so for system fonts we know + // that the entry has only a single weight, not a range. + MOZ_ASSERT(fe->Weight().IsSingle()); + weight = mNeedsSyntheticBold ? 700 : fe->Weight().Min().ToIntRounded(); + } + + fe->FillLogFont(&aLogFont, weight, aSize); +} + +uint32_t gfxGDIFont::GetGlyph(uint32_t aUnicode, uint32_t aVarSelector) { + // Callback used only for fonts that lack a 'cmap' table. + + // We don't support variation selector sequences or non-BMP characters + // in the legacy bitmap, vector or postscript fonts that might use + // this code path. + if (aUnicode > 0xffff || aVarSelector) { + return 0; + } + + if (!mGlyphIDs) { + mGlyphIDs = MakeUnique<nsTHashMap<nsUint32HashKey, uint32_t>>(64); + } + + uint32_t gid; + if (mGlyphIDs->Get(aUnicode, &gid)) { + return gid; + } + + wchar_t ch = aUnicode; + WORD glyph; + HRESULT ret = ScriptGetCMap(nullptr, &mScriptCache, &ch, 1, 0, &glyph); + if (ret != S_OK) { + AutoDC dc; + AutoSelectFont fs(dc.GetDC(), GetHFONT()); + if (ret == E_PENDING) { + // Try ScriptGetCMap again now that we've set up the font. + ret = ScriptGetCMap(dc.GetDC(), &mScriptCache, &ch, 1, 0, &glyph); + } + if (ret != S_OK) { + // If ScriptGetCMap still failed, fall back to GetGlyphIndicesW + // (see bug 1105807). + DWORD ret = GetGlyphIndicesW(dc.GetDC(), &ch, 1, &glyph, + GGI_MARK_NONEXISTING_GLYPHS); + if (ret == GDI_ERROR || glyph == 0xFFFF) { + glyph = 0; + } + } + } + + mGlyphIDs->InsertOrUpdate(aUnicode, glyph); + return glyph; +} + +int32_t gfxGDIFont::GetGlyphWidth(uint16_t aGID) { + if (!mGlyphWidths) { + mGlyphWidths = MakeUnique<nsTHashMap<nsUint32HashKey, int32_t>>(128); + } + + return mGlyphWidths->WithEntryHandle(aGID, [&](auto&& entry) { + if (!entry) { + DCForMetrics dc; + AutoSelectFont fs(dc, GetHFONT()); + + int devWidth; + if (!GetCharWidthI(dc, aGID, 1, nullptr, &devWidth)) { + return -1; + } + // clamp value to range [0..0x7fff], and convert to 16.16 fixed-point + devWidth = std::min(std::max(0, devWidth), 0x7fff); + entry.Insert(devWidth << 16); + } + return *entry; + }); +} + +bool gfxGDIFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) { + DCForMetrics dc; + AutoSelectFont fs(dc, GetHFONT()); + + if (mIsBitmap) { + int devWidth; + if (!GetCharWidthI(dc, aGID, 1, nullptr, &devWidth)) { + return false; + } + devWidth = std::min(std::max(0, devWidth), 0x7fff); + + *aBounds = gfxRect(0, -mMetrics->maxAscent, devWidth, + mMetrics->maxAscent + mMetrics->maxDescent); + return true; + } + + const MAT2 kIdentityMatrix = {{0, 1}, {0, 0}, {0, 0}, {0, 1}}; + GLYPHMETRICS gm; + if (GetGlyphOutlineW(dc, aGID, GGO_METRICS | GGO_GLYPH_INDEX, &gm, 0, nullptr, + &kIdentityMatrix) == GDI_ERROR) { + return false; + } + + if (gm.gmBlackBoxX == 1 && gm.gmBlackBoxY == 1 && + !GetGlyphOutlineW(dc, aGID, GGO_NATIVE | GGO_GLYPH_INDEX, &gm, 0, nullptr, + &kIdentityMatrix)) { + // Workaround for GetGlyphOutline returning 1x1 bounding box + // for <space> glyph that is in fact empty. + gm.gmBlackBoxX = 0; + gm.gmBlackBoxY = 0; + } else if (gm.gmBlackBoxX > 0 && !aTight) { + // The bounding box reported by Windows supposedly contains the glyph's + // "black" area; however, antialiasing (especially with ClearType) means + // that the actual image that needs to be rendered may "bleed" into the + // adjacent pixels, mainly on the right side. + gm.gmptGlyphOrigin.x -= 1; + gm.gmBlackBoxX += 3; + } + + *aBounds = gfxRect(gm.gmptGlyphOrigin.x, -gm.gmptGlyphOrigin.y, + gm.gmBlackBoxX, gm.gmBlackBoxY); + return true; +} + +void gfxGDIFont::AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const { + gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); + aSizes->mFontInstances += aMallocSizeOf(mMetrics); + if (mGlyphWidths) { + aSizes->mFontInstances += + mGlyphWidths->ShallowSizeOfIncludingThis(aMallocSizeOf); + } +} + +void gfxGDIFont::AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const { + aSizes->mFontInstances += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} diff --git a/gfx/thebes/gfxGDIFont.h b/gfx/thebes/gfxGDIFont.h new file mode 100644 index 0000000000..505f731a79 --- /dev/null +++ b/gfx/thebes/gfxGDIFont.h @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_GDIFONT_H +#define GFX_GDIFONT_H + +#include "mozilla/MemoryReporting.h" +#include "gfxFont.h" +#include "gfxGDIFontList.h" + +#include "nsTHashMap.h" +#include "nsHashKeys.h" + +#include "usp10.h" + +class gfxGDIFont final : public gfxFont { + public: + gfxGDIFont(GDIFontEntry* aFontEntry, const gfxFontStyle* aFontStyle, + AntialiasOption anAAOption = kAntialiasDefault); + + HFONT GetHFONT() const { return mFont; } + + already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont( + const TextRunDrawParams& aRunParams) override; + + /* override Measure to add padding for antialiasing */ + RunMetrics Measure(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd, + BoundingBoxType aBoundingBoxType, + DrawTarget* aDrawTargetForTightBoundingBox, + Spacing* aSpacing, + mozilla::gfx::ShapedTextFlags aOrientation) override; + + /* required for MathML to suppress effects of ClearType "padding" */ + gfxFont* CopyWithAntialiasOption(AntialiasOption anAAOption) const override; + + // If the font has a cmap table, we handle it purely with harfbuzz; + // but if not (e.g. .fon fonts), we'll use a GDI callback to get glyphs. + bool ProvidesGetGlyph() const override { return !mFontEntry->HasCmapTable(); } + + uint32_t GetGlyph(uint32_t aUnicode, uint32_t aVarSelector) override; + + bool ProvidesGlyphWidths() const override { return true; } + + // get hinted glyph width in pixels as 16.16 fixed-point value + int32_t GetGlyphWidth(uint16_t aGID) override; + + bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override; + + void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const; + void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const; + + FontType GetType() const override { return FONT_TYPE_GDI; } + + protected: + ~gfxGDIFont() override; + + const Metrics& GetHorizontalMetrics() const override { return *mMetrics; } + + bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, RoundingFlags aRounding, + gfxShapedText* aShapedText) override; + + void Initialize(); // creates metrics and Cairo fonts + + // Fill the given LOGFONT record according to our size. + // (Synthetic italic is *not* handled here, because GDI may not reliably + // use the face we expect if we tweak the lfItalic field, and because we + // have generic support for this in gfxFont::Draw instead.) + void FillLogFont(LOGFONTW& aLogFont, gfxFloat aSize); + + HFONT mFont; + + Metrics* mMetrics; + bool mIsBitmap; + + bool mNeedsSyntheticBold; + + // cache of glyph IDs (used for non-sfnt fonts only) + mozilla::UniquePtr<nsTHashMap<nsUint32HashKey, uint32_t> > mGlyphIDs; + SCRIPT_CACHE mScriptCache; + + // cache of glyph widths in 16.16 fixed-point pixels + mozilla::UniquePtr<nsTHashMap<nsUint32HashKey, int32_t> > mGlyphWidths; +}; + +#endif /* GFX_GDIFONT_H */ diff --git a/gfx/thebes/gfxGDIFontList.cpp b/gfx/thebes/gfxGDIFontList.cpp new file mode 100644 index 0000000000..3c4c7bce07 --- /dev/null +++ b/gfx/thebes/gfxGDIFontList.cpp @@ -0,0 +1,1107 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/DebugOnly.h" +#include <algorithm> + +#include "mozilla/Logging.h" +#include "mozilla/TextUtils.h" +#include "mozilla/Sprintf.h" + +#include "gfxGDIFontList.h" +#include "gfxWindowsPlatform.h" +#include "gfxUserFontSet.h" +#include "gfxFontUtils.h" +#include "gfxGDIFont.h" + +#include "nsServiceManagerUtils.h" +#include "nsTArray.h" +#include "nsUnicharUtils.h" + +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsPresContext.h" +#include "gfxFontConstants.h" + +#include "mozilla/MemoryReporting.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/Telemetry.h" + +#include <usp10.h> + +using namespace mozilla; +using namespace mozilla::gfx; + +#define ROUND(x) floor((x) + 0.5) + +#define LOG_FONTLIST(args) \ + MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug, args) +#define LOG_FONTLIST_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug) + +#define LOG_CMAPDATA_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_cmapdata), LogLevel::Debug) + +static __inline void BuildKeyNameFromFontName(nsAString& aName) { + if (aName.Length() >= LF_FACESIZE) aName.Truncate(LF_FACESIZE - 1); + ToLowerCase(aName); +} + +// Implementation of gfxPlatformFontList for Win32 GDI, +// using GDI font enumeration APIs to get the list of fonts + +class WinUserFontData : public gfxUserFontData { + public: + explicit WinUserFontData(HANDLE aFontRef) : mFontRef(aFontRef) {} + + virtual ~WinUserFontData() { + DebugOnly<BOOL> success; + success = RemoveFontMemResourceEx(mFontRef); +#if DEBUG + if (!success) { + char buf[256]; + SprintfLiteral( + buf, + "error deleting font handle (%p) - RemoveFontMemResourceEx failed", + mFontRef); + NS_ASSERTION(success, buf); + } +#endif + } + + HANDLE mFontRef; +}; + +BYTE FontTypeToOutPrecision(uint8_t fontType) { + BYTE ret; + switch (fontType) { + case GFX_FONT_TYPE_TT_OPENTYPE: + case GFX_FONT_TYPE_TRUETYPE: + ret = OUT_TT_ONLY_PRECIS; + break; + case GFX_FONT_TYPE_PS_OPENTYPE: + ret = OUT_PS_ONLY_PRECIS; + break; + case GFX_FONT_TYPE_TYPE1: + ret = OUT_OUTLINE_PRECIS; + break; + case GFX_FONT_TYPE_RASTER: + ret = OUT_RASTER_PRECIS; + break; + case GFX_FONT_TYPE_DEVICE: + ret = OUT_DEVICE_PRECIS; + break; + default: + ret = OUT_DEFAULT_PRECIS; + } + return ret; +} + +/*************************************************************** + * + * GDIFontEntry + * + */ + +GDIFontEntry::GDIFontEntry(const nsACString& aFaceName, + gfxWindowsFontType aFontType, SlantStyleRange aStyle, + WeightRange aWeight, StretchRange aStretch, + gfxUserFontData* aUserFontData) + : gfxFontEntry(aFaceName), mFontType(aFontType), mForceGDI(false) { + mUserFontData.reset(aUserFontData); + mStyleRange = aStyle; + mWeightRange = aWeight; + mStretchRange = aStretch; + if (IsType1()) { + mForceGDI = true; + } + mIsDataUserFont = aUserFontData != nullptr; + + InitLogFont(aFaceName, aFontType); +} + +gfxFontEntry* GDIFontEntry::Clone() const { + MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!"); + return new GDIFontEntry(Name(), mFontType, SlantStyle(), Weight(), Stretch(), + nullptr); +} + +nsresult GDIFontEntry::ReadCMAP(FontInfoData* aFontInfoData) { + AUTO_PROFILER_LABEL("GDIFontEntry::ReadCMAP", OTHER); + + // attempt this once, if errors occur leave a blank cmap + if (mCharacterMap) { + return NS_OK; + } + + // skip non-SFNT fonts completely + if (mFontType != GFX_FONT_TYPE_PS_OPENTYPE && + mFontType != GFX_FONT_TYPE_TT_OPENTYPE && + mFontType != GFX_FONT_TYPE_TRUETYPE) { + RefPtr<gfxCharacterMap> cmap = new gfxCharacterMap(); + cmap->mBuildOnTheFly = true; + if (mCharacterMap.compareExchange(nullptr, cmap.get())) { + Unused << cmap.forget(); + } + return NS_ERROR_FAILURE; + } + + RefPtr<gfxCharacterMap> charmap; + nsresult rv; + + uint32_t uvsOffset = 0; + if (aFontInfoData && + (charmap = GetCMAPFromFontInfo(aFontInfoData, uvsOffset))) { + rv = NS_OK; + } else { + uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p'); + charmap = new gfxCharacterMap(); + AutoTArray<uint8_t, 16384> cmap; + rv = CopyFontTable(kCMAP, cmap); + + if (NS_SUCCEEDED(rv)) { + rv = gfxFontUtils::ReadCMAP(cmap.Elements(), cmap.Length(), *charmap, + uvsOffset); + } + } + + if (NS_SUCCEEDED(rv)) { + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + charmap = pfl->FindCharMap(charmap); + mHasCmapTable = true; + } else { + // if error occurred, initialize to null cmap + charmap = new gfxCharacterMap(); + // For fonts where we failed to read the character map, + // we can take a slow path to look up glyphs character by character + charmap->mBuildOnTheFly = true; + } + if (mCharacterMap.compareExchange(nullptr, charmap.get())) { + charmap.get()->AddRef(); + } + + LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %zd hash: %8.8x%s\n", + mName.get(), charmap->SizeOfIncludingThis(moz_malloc_size_of), + charmap->mHash, mCharacterMap == charmap ? " new" : "")); + if (LOG_CMAPDATA_ENABLED()) { + char prefix[256]; + SprintfLiteral(prefix, "(cmapdata) name: %.220s", mName.get()); + charmap->Dump(prefix, eGfxLog_cmapdata); + } + + return rv; +} + +gfxFont* GDIFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle) { + return new gfxGDIFont(this, aFontStyle); +} + +nsresult GDIFontEntry::CopyFontTable(uint32_t aTableTag, + nsTArray<uint8_t>& aBuffer) { + if (!IsTrueType()) { + return NS_ERROR_FAILURE; + } + + AutoDC dc; + AutoSelectFont font(dc.GetDC(), &mLogFont); + if (font.IsValid()) { + uint32_t tableSize = ::GetFontData( + dc.GetDC(), NativeEndian::swapToBigEndian(aTableTag), 0, nullptr, 0); + if (tableSize != GDI_ERROR) { + if (aBuffer.SetLength(tableSize, fallible)) { + ::GetFontData(dc.GetDC(), NativeEndian::swapToBigEndian(aTableTag), 0, + aBuffer.Elements(), tableSize); + return NS_OK; + } + return NS_ERROR_OUT_OF_MEMORY; + } + } + return NS_ERROR_FAILURE; +} + +already_AddRefed<UnscaledFontGDI> GDIFontEntry::LookupUnscaledFont( + HFONT aFont) { + RefPtr<UnscaledFontGDI> unscaledFont(mUnscaledFont); + if (!unscaledFont) { + LOGFONT lf; + GetObject(aFont, sizeof(LOGFONT), &lf); + unscaledFont = new UnscaledFontGDI(lf); + mUnscaledFont = unscaledFont; + } + + return unscaledFont.forget(); +} + +void GDIFontEntry::FillLogFont(LOGFONTW* aLogFont, LONG aWeight, + gfxFloat aSize) { + memcpy(aLogFont, &mLogFont, sizeof(LOGFONTW)); + + aLogFont->lfHeight = (LONG)-ROUND(aSize); + + if (aLogFont->lfHeight == 0) { + aLogFont->lfHeight = -1; + } + + // If a non-zero weight is passed in, use this to override the original + // weight in the entry's logfont. This is used to control synthetic bolding + // for installed families with no bold face, and for downloaded fonts + // (but NOT for local user fonts, because it could cause a different, + // glyph-incompatible face to be used) + if (aWeight != 0) { + aLogFont->lfWeight = aWeight; + } + + // for non-local() user fonts, we never want to apply italics here; + // if the face is described as italic, we should use it as-is, + // and if it's not, but then the element is styled italic, we'll use + // a cairo transform to create fake italic (oblique) + if (mIsDataUserFont) { + aLogFont->lfItalic = 0; + } +} + +#define MISSING_GLYPH \ + 0x1F // glyph index returned for missing characters + // on WinXP with .fon fonts, but not Type1 (.pfb) + +bool GDIFontEntry::TestCharacterMap(uint32_t aCh) { + if (!mCharacterMap) { + ReadCMAP(); + NS_ASSERTION(mCharacterMap, "failed to initialize a character map"); + } + + if (GetCharacterMap()->mBuildOnTheFly) { + if (aCh > 0xFFFF) return false; + + // previous code was using the group style + gfxFontStyle fakeStyle; + if (!IsUpright()) { + fakeStyle.style = FontSlantStyle::ITALIC; + } + fakeStyle.weight = Weight().Min(); + + RefPtr<gfxFont> tempFont = FindOrMakeFont(&fakeStyle, nullptr); + if (!tempFont || !tempFont->Valid()) return false; + gfxGDIFont* font = static_cast<gfxGDIFont*>(tempFont.get()); + + HDC dc = GetDC((HWND) nullptr); + SetGraphicsMode(dc, GM_ADVANCED); + HFONT hfont = font->GetHFONT(); + HFONT oldFont = (HFONT)SelectObject(dc, hfont); + + wchar_t str[1] = {(wchar_t)aCh}; + WORD glyph[1]; + + bool hasGlyph = false; + + // Bug 573038 - in some cases GetGlyphIndicesW returns 0xFFFF for a + // missing glyph or 0x1F in other cases to indicate the "invalid" + // glyph. Map both cases to "not found" + if (IsType1() || mForceGDI) { + // Type1 fonts and uniscribe APIs don't get along. + // ScriptGetCMap will return E_HANDLE + DWORD ret = + GetGlyphIndicesW(dc, str, 1, glyph, GGI_MARK_NONEXISTING_GLYPHS); + if (ret != GDI_ERROR && glyph[0] != 0xFFFF && + (IsType1() || glyph[0] != MISSING_GLYPH)) { + hasGlyph = true; + } + } else { + // ScriptGetCMap works better than GetGlyphIndicesW + // for things like bitmap/vector fonts + SCRIPT_CACHE sc = nullptr; + HRESULT rv = ScriptGetCMap(dc, &sc, str, 1, 0, glyph); + if (rv == S_OK) hasGlyph = true; + } + + SelectObject(dc, oldFont); + ReleaseDC(nullptr, dc); + + if (hasGlyph) { + GetCharacterMap()->set(aCh); + return true; + } + } else { + // font had a cmap so simply check that + return GetCharacterMap()->test(aCh); + } + + return false; +} + +void GDIFontEntry::InitLogFont(const nsACString& aName, + gfxWindowsFontType aFontType) { +#define CLIP_TURNOFF_FONTASSOCIATION 0x40 + + mLogFont.lfHeight = -1; + + // Fill in logFont structure + mLogFont.lfWidth = 0; + mLogFont.lfEscapement = 0; + mLogFont.lfOrientation = 0; + mLogFont.lfUnderline = FALSE; + mLogFont.lfStrikeOut = FALSE; + mLogFont.lfCharSet = DEFAULT_CHARSET; + mLogFont.lfOutPrecision = FontTypeToOutPrecision(aFontType); + mLogFont.lfClipPrecision = CLIP_TURNOFF_FONTASSOCIATION; + mLogFont.lfQuality = DEFAULT_QUALITY; + mLogFont.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE; + // always force lfItalic if we want it. Font selection code will + // do its best to give us an italic font entry, but if no face exists + // it may give us a regular one based on weight. Windows should + // do fake italic for us in that case. + mLogFont.lfItalic = !IsUpright(); + mLogFont.lfWeight = Weight().Min().ToIntRounded(); + + NS_ConvertUTF8toUTF16 name(aName); + int len = std::min<int>(name.Length(), LF_FACESIZE - 1); + memcpy(&mLogFont.lfFaceName, name.BeginReading(), len * sizeof(char16_t)); + mLogFont.lfFaceName[len] = '\0'; +} + +GDIFontEntry* GDIFontEntry::CreateFontEntry(const nsACString& aName, + gfxWindowsFontType aFontType, + SlantStyleRange aStyle, + WeightRange aWeight, + StretchRange aStretch, + gfxUserFontData* aUserFontData) { + // jtdfix - need to set charset, pitch/family + + return new GDIFontEntry(aName, aFontType, aStyle, aWeight, aStretch, + aUserFontData); +} + +void GDIFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +/*************************************************************** + * + * GDIFontFamily + * + */ + +static bool ShouldIgnoreItalicStyle(const nsACString& aName) { + // Ignore italic style's "Meiryo" because "Meiryo (Bold) Italic" has + // non-italic style glyphs as Japanese characters. However, using it + // causes serious problem if web pages wants some elements to be + // different style from others only with font-style. For example, + // <em> and <i> should be rendered as italic in the default style. + return aName.EqualsLiteral("Meiryo") || + aName.EqualsLiteral( + "\xe3\x83\xa1\xe3\x82\xa4\xe3\x83\xaa\xe3\x82\xaa"); +} + +int CALLBACK GDIFontFamily::FamilyAddStylesProc( + const ENUMLOGFONTEXW* lpelfe, const NEWTEXTMETRICEXW* nmetrics, + DWORD fontType, LPARAM data) MOZ_NO_THREAD_SAFETY_ANALYSIS { + const NEWTEXTMETRICW& metrics = nmetrics->ntmTm; + LOGFONTW logFont = lpelfe->elfLogFont; + GDIFontFamily* ff = reinterpret_cast<GDIFontFamily*>(data); + MOZ_ASSERT(ff->mLock.LockedForWritingByCurrentThread()); + + if (logFont.lfItalic && ShouldIgnoreItalicStyle(ff->mName)) { + return 1; + } + + // Some fonts claim to support things > 900, but we don't so clamp the sizes + logFont.lfWeight = clamped(logFont.lfWeight, LONG(100), LONG(900)); + + gfxWindowsFontType feType = + GDIFontEntry::DetermineFontType(metrics, fontType); + + GDIFontEntry* fe = nullptr; + for (uint32_t i = 0; i < ff->mAvailableFonts.Length(); ++i) { + fe = static_cast<GDIFontEntry*>(ff->mAvailableFonts[i].get()); + if (feType > fe->mFontType) { + // if the new type is better than the old one, remove the old entries + ff->mAvailableFonts.RemoveElementAt(i); + --i; + } else if (feType < fe->mFontType) { + // otherwise if the new type is worse, skip it + return 1; + } + } + + for (uint32_t i = 0; i < ff->mAvailableFonts.Length(); ++i) { + fe = static_cast<GDIFontEntry*>(ff->mAvailableFonts[i].get()); + // check if we already know about this face + if (fe->Weight().Min() == FontWeight::FromInt(int32_t(logFont.lfWeight)) && + fe->IsItalic() == (logFont.lfItalic == 0xFF)) { + // update the charset bit here since this could be different + // XXX Can we still do this now that we store mCharset + // on the font family rather than the font entry? + ff->mCharset.set(metrics.tmCharSet); + return 1; + } + } + + // We can't set the hasItalicFace flag correctly here, + // because we might not have seen the family's italic face(s) yet. + // So we'll set that flag for all members after loading all the faces. + auto italicStyle = (logFont.lfItalic == 0xFF ? FontSlantStyle::ITALIC + : FontSlantStyle::NORMAL); + fe = GDIFontEntry::CreateFontEntry( + NS_ConvertUTF16toUTF8(lpelfe->elfFullName), feType, + SlantStyleRange(italicStyle), + WeightRange(FontWeight::FromInt(int32_t(logFont.lfWeight))), + StretchRange(FontStretch::NORMAL), nullptr); + if (!fe) { + return 1; + } + + ff->AddFontEntryLocked(fe); + + if (LOG_FONTLIST_ENABLED()) { + LOG_FONTLIST( + ("(fontlist) added (%s) to family (%s)" + " with style: %s weight: %ld stretch: normal", + fe->Name().get(), ff->Name().get(), + (logFont.lfItalic == 0xff) ? "italic" : "normal", logFont.lfWeight)); + } + return 1; +} + +void GDIFontFamily::FindStyleVariationsLocked(FontInfoData* aFontInfoData) { + if (mHasStyles) { + return; + } + mHasStyles = true; + + HDC hdc = GetDC(nullptr); + SetGraphicsMode(hdc, GM_ADVANCED); + + LOGFONTW logFont; + memset(&logFont, 0, sizeof(LOGFONTW)); + logFont.lfCharSet = DEFAULT_CHARSET; + logFont.lfPitchAndFamily = 0; + NS_ConvertUTF8toUTF16 name(mName); + uint32_t l = std::min<uint32_t>(name.Length(), LF_FACESIZE - 1); + memcpy(logFont.lfFaceName, name.get(), l * sizeof(char16_t)); + + EnumFontFamiliesExW(hdc, &logFont, + (FONTENUMPROCW)GDIFontFamily::FamilyAddStylesProc, + (LPARAM)this, 0); + if (LOG_FONTLIST_ENABLED() && mAvailableFonts.Length() == 0) { + LOG_FONTLIST( + ("(fontlist) no styles available in family \"%s\"", mName.get())); + } + + ReleaseDC(nullptr, hdc); + + if (mIsBadUnderlineFamily) { + SetBadUnderlineFonts(); + } + + CheckForSimpleFamily(); +} + +/*************************************************************** + * + * gfxGDIFontList + * + */ + +gfxGDIFontList::gfxGDIFontList() : mFontSubstitutes(32) { +#ifdef MOZ_BUNDLED_FONTS + if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) { + TimeStamp start = TimeStamp::Now(); + ActivateBundledFonts(); + TimeStamp end = TimeStamp::Now(); + Telemetry::Accumulate(Telemetry::FONTLIST_BUNDLEDFONTS_ACTIVATE, + (end - start).ToMilliseconds()); + } +#endif +} + +static void RemoveCharsetFromFontSubstitute(nsAString& aName) { + int32_t comma = aName.FindChar(char16_t(',')); + if (comma >= 0) aName.Truncate(comma); +} + +#define MAX_VALUE_NAME 512 +#define MAX_VALUE_DATA 512 + +nsresult gfxGDIFontList::GetFontSubstitutes() { + HKEY hKey; + DWORD i, rv, lenAlias, lenActual, valueType; + WCHAR aliasName[MAX_VALUE_NAME]; + WCHAR actualName[MAX_VALUE_DATA]; + + if (RegOpenKeyExW( + HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\FontSubstitutes", + 0, KEY_READ, &hKey) != ERROR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + for (i = 0, rv = ERROR_SUCCESS; rv != ERROR_NO_MORE_ITEMS; i++) { + aliasName[0] = 0; + lenAlias = ArrayLength(aliasName); + actualName[0] = 0; + lenActual = sizeof(actualName); + rv = RegEnumValueW(hKey, i, aliasName, &lenAlias, nullptr, &valueType, + (LPBYTE)actualName, &lenActual); + + if (rv != ERROR_SUCCESS || valueType != REG_SZ || lenAlias == 0) { + continue; + } + + if (aliasName[0] == WCHAR('@')) { + continue; + } + + nsAutoString substituteName((char16_t*)aliasName); + nsAutoString actualFontName((char16_t*)actualName); + RemoveCharsetFromFontSubstitute(substituteName); + BuildKeyNameFromFontName(substituteName); + RemoveCharsetFromFontSubstitute(actualFontName); + BuildKeyNameFromFontName(actualFontName); + gfxFontFamily* ff; + NS_ConvertUTF16toUTF8 substitute(substituteName); + NS_ConvertUTF16toUTF8 actual(actualFontName); + if (!actual.IsEmpty() && (ff = mFontFamilies.GetWeak(actual))) { + mFontSubstitutes.InsertOrUpdate(substitute, RefPtr{ff}); + } else { + mNonExistingFonts.AppendElement(substitute); + } + } + + // "Courier" on a default Windows install is an ugly bitmap font. + // If there is no substitution for Courier in the registry + // substitute "Courier" with "Courier New". + nsAutoString substituteName; + substituteName.AssignLiteral("Courier"); + BuildKeyNameFromFontName(substituteName); + NS_ConvertUTF16toUTF8 substitute(substituteName); + if (!mFontSubstitutes.GetWeak(substitute)) { + gfxFontFamily* ff; + nsAutoString actualFontName; + actualFontName.AssignLiteral("Courier New"); + BuildKeyNameFromFontName(actualFontName); + NS_ConvertUTF16toUTF8 actual(actualFontName); + ff = mFontFamilies.GetWeak(actual); + if (ff) { + mFontSubstitutes.InsertOrUpdate(substitute, RefPtr{ff}); + } + } + return NS_OK; +} + +nsresult gfxGDIFontList::InitFontListForPlatform() { + Telemetry::AutoTimer<Telemetry::GDI_INITFONTLIST_TOTAL> timer; + + mFontSubstitutes.Clear(); + mNonExistingFonts.Clear(); + + // iterate over available families + LOGFONTW logfont; + memset(&logfont, 0, sizeof(logfont)); + logfont.lfCharSet = DEFAULT_CHARSET; + + AutoDC hdc; + (void)EnumFontFamiliesExW(hdc.GetDC(), &logfont, + (FONTENUMPROCW)&EnumFontFamExProc, 0, 0); + + GetFontSubstitutes(); + + GetPrefsAndStartLoader(); + + return NS_OK; +} + +int CALLBACK gfxGDIFontList::EnumFontFamExProc(ENUMLOGFONTEXW* lpelfe, + NEWTEXTMETRICEXW* lpntme, + DWORD fontType, LPARAM lParam) { + const NEWTEXTMETRICW& metrics = lpntme->ntmTm; + const LOGFONTW& lf = lpelfe->elfLogFont; + + if (lf.lfFaceName[0] == '@') { + return 1; + } + + nsAutoString name(lf.lfFaceName); + BuildKeyNameFromFontName(name); + + NS_ConvertUTF16toUTF8 key(name); + + gfxGDIFontList* fontList = PlatformFontList(); + fontList->mLock.AssertCurrentThreadIn(); + + if (!fontList->mFontFamilies.Contains(key)) { + NS_ConvertUTF16toUTF8 faceName(lf.lfFaceName); + FontVisibility visibility = FontVisibility::Unknown; // TODO + RefPtr<GDIFontFamily> family = new GDIFontFamily(faceName, visibility); + fontList->mFontFamilies.InsertOrUpdate(key, RefPtr{family}); + + // if locale is such that CJK font names are the default coming from + // GDI, then if a family name is non-ASCII immediately read in other + // family names. This assures that MS Gothic, MS Mincho are all found + // before lookups begin. + if (!IsAscii(faceName)) { + family->ReadOtherFamilyNames(gfxPlatformFontList::PlatformFontList()); + } + + if (fontList->mBadUnderlineFamilyNames.ContainsSorted(key)) { + family->SetBadUnderlineFamily(); + } + + family->mWindowsFamily = lf.lfPitchAndFamily & 0xF0; + family->mWindowsPitch = lf.lfPitchAndFamily & 0x0F; + + // mark the charset bit + family->mCharset.set(metrics.tmCharSet); + } + + return 1; +} + +gfxFontEntry* gfxGDIFontList::LookupLocalFont(nsPresContext* aPresContext, + const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) { + AutoLock lock(mLock); + + gfxFontEntry* lookup = LookupInFaceNameLists(aFontName); + if (!lookup) { + return nullptr; + } + + bool isCFF = false; // jtdfix -- need to determine this + + // use the face name from the lookup font entry, which will be the localized + // face name which GDI mapping tables use (e.g. with the system locale set to + // Dutch, a fullname of 'Arial Bold' will find a font entry with the face name + // 'Arial Vet' which can be used as a key in GDI font lookups). + GDIFontEntry* fe = GDIFontEntry::CreateFontEntry( + lookup->Name(), + gfxWindowsFontType(isCFF ? GFX_FONT_TYPE_PS_OPENTYPE + : GFX_FONT_TYPE_TRUETYPE) /*type*/, + lookup->SlantStyle(), lookup->Weight(), aStretchForEntry, nullptr); + + if (!fe) return nullptr; + + fe->mIsLocalUserFont = true; + + // make the new font entry match the userfont entry style characteristics + fe->mWeightRange = aWeightForEntry; + fe->mStyleRange = aStyleForEntry; + fe->mStretchRange = aStretchForEntry; + + return fe; +} + +// If aFontData contains only a MS/Symbol cmap subtable, not MS/Unicode, +// we modify the subtable header to mark it as Unicode instead, because +// otherwise GDI will refuse to load the font. +// NOTE that this function does not bounds-check every access to the font data. +// This is OK because we only use it on data that has already been validated +// by OTS, and therefore we will not hit out-of-bounds accesses here. +static bool FixupSymbolEncodedFont(uint8_t* aFontData, uint32_t aLength) { + struct CmapHeader { + AutoSwap_PRUint16 version; + AutoSwap_PRUint16 numTables; + }; + struct CmapEncodingRecord { + AutoSwap_PRUint16 platformID; + AutoSwap_PRUint16 encodingID; + AutoSwap_PRUint32 offset; + }; + const uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p'); + const TableDirEntry* dir = gfxFontUtils::FindTableDirEntry(aFontData, kCMAP); + if (dir && uint32_t(dir->length) >= sizeof(CmapHeader)) { + CmapHeader* cmap = + reinterpret_cast<CmapHeader*>(aFontData + uint32_t(dir->offset)); + CmapEncodingRecord* encRec = + reinterpret_cast<CmapEncodingRecord*>(cmap + 1); + int32_t symbolSubtable = -1; + for (uint32_t i = 0; i < (uint16_t)cmap->numTables; ++i) { + if (uint16_t(encRec[i].platformID) != + gfxFontUtils::PLATFORM_ID_MICROSOFT) { + continue; // only interested in MS platform + } + if (uint16_t(encRec[i].encodingID) == + gfxFontUtils::ENCODING_ID_MICROSOFT_UNICODEBMP) { + // We've got a Microsoft/Unicode table, so don't interfere. + symbolSubtable = -1; + break; + } + if (uint16_t(encRec[i].encodingID) == + gfxFontUtils::ENCODING_ID_MICROSOFT_SYMBOL) { + // Found a symbol subtable; remember it for possible fixup, + // but if we subsequently find a Microsoft/Unicode subtable, + // we'll cancel this. + symbolSubtable = i; + } + } + if (symbolSubtable != -1) { + // We found a windows/symbol cmap table, and no windows/unicode one; + // change the encoding ID so that AddFontMemResourceEx will accept it + encRec[symbolSubtable].encodingID = + gfxFontUtils::ENCODING_ID_MICROSOFT_UNICODEBMP; + return true; + } + } + return false; +} + +gfxFontEntry* gfxGDIFontList::MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, + uint32_t aLength) { + // MakePlatformFont is responsible for deleting the font data with free + // so we set up a stack object to ensure it is freed even if we take an + // early exit + struct FontDataDeleter { + explicit FontDataDeleter(const uint8_t* aFontData) : mFontData(aFontData) {} + ~FontDataDeleter() { free((void*)mFontData); } + const uint8_t* mFontData; + }; + FontDataDeleter autoDelete(aFontData); + + bool isCFF = gfxFontUtils::IsCffFont(aFontData); + + nsresult rv; + HANDLE fontRef = nullptr; + + nsAutoString uniqueName; + rv = gfxFontUtils::MakeUniqueUserFontName(uniqueName); + if (NS_FAILED(rv)) return nullptr; + + FallibleTArray<uint8_t> newFontData; + + rv = gfxFontUtils::RenameFont(uniqueName, aFontData, aLength, &newFontData); + + if (NS_FAILED(rv)) return nullptr; + + DWORD numFonts = 0; + + uint8_t* fontData = reinterpret_cast<uint8_t*>(newFontData.Elements()); + uint32_t fontLength = newFontData.Length(); + NS_ASSERTION(fontData, "null font data after renaming"); + + // http://msdn.microsoft.com/en-us/library/ms533942(VS.85).aspx + // "A font that is added by AddFontMemResourceEx is always private + // to the process that made the call and is not enumerable." + fontRef = + AddFontMemResourceEx(fontData, fontLength, 0 /* reserved */, &numFonts); + if (!fontRef) { + if (FixupSymbolEncodedFont(fontData, fontLength)) { + fontRef = AddFontMemResourceEx(fontData, fontLength, 0, &numFonts); + } + } + if (!fontRef) { + return nullptr; + } + + // only load fonts with a single face contained in the data + // AddFontMemResourceEx generates an additional face name for + // vertical text if the font supports vertical writing but since + // the font is referenced via the name this can be ignored + if (fontRef && numFonts > 2) { + RemoveFontMemResourceEx(fontRef); + return nullptr; + } + + // make a new font entry using the unique name + WinUserFontData* winUserFontData = new WinUserFontData(fontRef); + GDIFontEntry* fe = GDIFontEntry::CreateFontEntry( + NS_ConvertUTF16toUTF8(uniqueName), + gfxWindowsFontType(isCFF ? GFX_FONT_TYPE_PS_OPENTYPE + : GFX_FONT_TYPE_TRUETYPE) /*type*/, + aStyleForEntry, aWeightForEntry, aStretchForEntry, winUserFontData); + + if (fe) { + fe->mIsDataUserFont = true; + } + + return fe; +} + +bool gfxGDIFontList::FindAndAddFamiliesLocked( + nsPresContext* aPresContext, StyleGenericFontFamily aGeneric, + const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput, + FindFamiliesFlags aFlags, gfxFontStyle* aStyle, nsAtom* aLanguage, + gfxFloat aDevToCssSize) { + NS_ConvertUTF8toUTF16 key16(aFamily); + BuildKeyNameFromFontName(key16); + NS_ConvertUTF16toUTF8 keyName(key16); + + gfxFontFamily* ff = mFontSubstitutes.GetWeak(keyName); + FontVisibility level = + aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User; + if (ff && IsVisibleToCSS(*ff, level)) { + aOutput->AppendElement(FamilyAndGeneric(ff, aGeneric)); + return true; + } + + if (mNonExistingFonts.Contains(keyName)) { + return false; + } + + return gfxPlatformFontList::FindAndAddFamiliesLocked( + aPresContext, aGeneric, aFamily, aOutput, aFlags, aStyle, aLanguage, + aDevToCssSize); +} + +FontFamily gfxGDIFontList::GetDefaultFontForPlatform( + nsPresContext* aPresContext, const gfxFontStyle* aStyle, + nsAtom* aLanguage) { + FontFamily ff; + + // this really shouldn't fail to find a font.... + NONCLIENTMETRICSW ncm; + ncm.cbSize = sizeof(ncm); + BOOL status = + ::SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0); + if (status) { + ff = FindFamily(aPresContext, + NS_ConvertUTF16toUTF8(ncm.lfMessageFont.lfFaceName)); + if (!ff.IsNull()) { + return ff; + } + } + + // ...but just in case, try another (long-deprecated) approach as well + HGDIOBJ hGDI = ::GetStockObject(DEFAULT_GUI_FONT); + LOGFONTW logFont; + if (hGDI && ::GetObjectW(hGDI, sizeof(logFont), &logFont)) { + ff = FindFamily(aPresContext, NS_ConvertUTF16toUTF8(logFont.lfFaceName)); + } + + return ff; +} + +void gfxGDIFontList::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + gfxPlatformFontList::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); + + AutoLock lock(mLock); + + aSizes->mFontListSize += + SizeOfFontFamilyTableExcludingThis(mFontSubstitutes, aMallocSizeOf); + aSizes->mFontListSize += + mNonExistingFonts.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (uint32_t i = 0; i < mNonExistingFonts.Length(); ++i) { + aSizes->mFontListSize += + mNonExistingFonts[i].SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } +} + +void gfxGDIFontList::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +// used to load system-wide font info on off-main thread +class GDIFontInfo : public FontInfoData { + public: + GDIFontInfo(bool aLoadOtherNames, bool aLoadFaceNames, bool aLoadCmaps) + : FontInfoData(aLoadOtherNames, aLoadFaceNames, aLoadCmaps) {} + + virtual ~GDIFontInfo() = default; + + virtual void Load() { + mHdc = GetDC(nullptr); + SetGraphicsMode(mHdc, GM_ADVANCED); + FontInfoData::Load(); + ReleaseDC(nullptr, mHdc); + } + + // loads font data for all members of a given family + virtual void LoadFontFamilyData(const nsACString& aFamilyName); + + // callback for GDI EnumFontFamiliesExW call + static int CALLBACK EnumerateFontsForFamily(const ENUMLOGFONTEXW* lpelfe, + const NEWTEXTMETRICEXW* nmetrics, + DWORD fontType, LPARAM data); + + HDC mHdc; +}; + +struct EnumerateFontsForFamilyData { + EnumerateFontsForFamilyData(const nsACString& aFamilyName, + GDIFontInfo& aFontInfo) + : mFamilyName(aFamilyName), mFontInfo(aFontInfo) {} + + nsCString mFamilyName; + nsTArray<nsCString> mOtherFamilyNames; + GDIFontInfo& mFontInfo; + nsCString mPreviousFontName; +}; + +int CALLBACK GDIFontInfo::EnumerateFontsForFamily( + const ENUMLOGFONTEXW* lpelfe, const NEWTEXTMETRICEXW* nmetrics, + DWORD fontType, LPARAM data) { + EnumerateFontsForFamilyData* famData = + reinterpret_cast<EnumerateFontsForFamilyData*>(data); + HDC hdc = famData->mFontInfo.mHdc; + LOGFONTW logFont = lpelfe->elfLogFont; + const NEWTEXTMETRICW& metrics = nmetrics->ntmTm; + + AutoSelectFont font(hdc, &logFont); + if (!font.IsValid()) { + return 1; + } + + FontFaceData fontData; + NS_ConvertUTF16toUTF8 fontName(lpelfe->elfFullName); + + // callback called for each style-charset so return if style already seen + if (fontName.Equals(famData->mPreviousFontName)) { + return 1; + } + famData->mPreviousFontName = fontName; + famData->mFontInfo.mLoadStats.fonts++; + + // read name table info + bool nameDataLoaded = false; + if (famData->mFontInfo.mLoadFaceNames || famData->mFontInfo.mLoadOtherNames) { + uint32_t kNAME = + NativeEndian::swapToBigEndian(TRUETYPE_TAG('n', 'a', 'm', 'e')); + uint32_t nameSize; + AutoTArray<uint8_t, 1024> nameData; + + nameSize = ::GetFontData(hdc, kNAME, 0, nullptr, 0); + if (nameSize != GDI_ERROR && nameSize > 0 && + nameData.SetLength(nameSize, fallible)) { + ::GetFontData(hdc, kNAME, 0, nameData.Elements(), nameSize); + + // face names + if (famData->mFontInfo.mLoadFaceNames) { + gfxFontUtils::ReadCanonicalName((const char*)(nameData.Elements()), + nameSize, gfxFontUtils::NAME_ID_FULL, + fontData.mFullName); + gfxFontUtils::ReadCanonicalName( + (const char*)(nameData.Elements()), nameSize, + gfxFontUtils::NAME_ID_POSTSCRIPT, fontData.mPostscriptName); + nameDataLoaded = true; + famData->mFontInfo.mLoadStats.facenames++; + } + + // other family names + if (famData->mFontInfo.mLoadOtherNames) { + gfxFontUtils::ReadOtherFamilyNamesForFace( + famData->mFamilyName, (const char*)(nameData.Elements()), nameSize, + famData->mOtherFamilyNames, false); + } + } + } + + // read cmap + bool cmapLoaded = false; + gfxWindowsFontType feType = + GDIFontEntry::DetermineFontType(metrics, fontType); + if (famData->mFontInfo.mLoadCmaps && (feType == GFX_FONT_TYPE_PS_OPENTYPE || + feType == GFX_FONT_TYPE_TT_OPENTYPE || + feType == GFX_FONT_TYPE_TRUETYPE)) { + uint32_t kCMAP = + NativeEndian::swapToBigEndian(TRUETYPE_TAG('c', 'm', 'a', 'p')); + uint32_t cmapSize; + AutoTArray<uint8_t, 1024> cmapData; + + cmapSize = ::GetFontData(hdc, kCMAP, 0, nullptr, 0); + if (cmapSize != GDI_ERROR && cmapSize > 0 && + cmapData.SetLength(cmapSize, fallible)) { + ::GetFontData(hdc, kCMAP, 0, cmapData.Elements(), cmapSize); + RefPtr<gfxCharacterMap> charmap = new gfxCharacterMap(); + uint32_t offset; + + if (NS_SUCCEEDED(gfxFontUtils::ReadCMAP(cmapData.Elements(), cmapSize, + *charmap, offset))) { + fontData.mCharacterMap = charmap; + fontData.mUVSOffset = offset; + cmapLoaded = true; + famData->mFontInfo.mLoadStats.cmaps++; + } + } + } + + if (cmapLoaded || nameDataLoaded) { + famData->mFontInfo.mFontFaceData.InsertOrUpdate(fontName, fontData); + } + + return famData->mFontInfo.mCanceled ? 0 : 1; +} + +void GDIFontInfo::LoadFontFamilyData(const nsACString& aFamilyName) { + // iterate over the family + LOGFONTW logFont; + memset(&logFont, 0, sizeof(LOGFONTW)); + logFont.lfCharSet = DEFAULT_CHARSET; + logFont.lfPitchAndFamily = 0; + NS_ConvertUTF8toUTF16 name(aFamilyName); + uint32_t l = std::min<uint32_t>(name.Length(), LF_FACESIZE - 1); + memcpy(logFont.lfFaceName, name.BeginReading(), l * sizeof(char16_t)); + + EnumerateFontsForFamilyData data(aFamilyName, *this); + + EnumFontFamiliesExW(mHdc, &logFont, + (FONTENUMPROCW)GDIFontInfo::EnumerateFontsForFamily, + (LPARAM)(&data), 0); + + // if found other names, insert them + if (data.mOtherFamilyNames.Length() != 0) { + mOtherFamilyNames.InsertOrUpdate(aFamilyName, data.mOtherFamilyNames); + mLoadStats.othernames += data.mOtherFamilyNames.Length(); + } +} + +already_AddRefed<FontInfoData> gfxGDIFontList::CreateFontInfoData() { + bool loadCmaps = !UsesSystemFallback() || + gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback(); + + RefPtr<GDIFontInfo> fi = + new GDIFontInfo(true, NeedFullnamePostscriptNames(), loadCmaps); + + return fi.forget(); +} + +gfxFontFamily* gfxGDIFontList::CreateFontFamily( + const nsACString& aName, FontVisibility aVisibility) const { + return new GDIFontFamily(aName, aVisibility); +} + +#ifdef MOZ_BUNDLED_FONTS + +void gfxGDIFontList::ActivateBundledFonts() { + nsCOMPtr<nsIFile> localDir; + nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(localDir)); + if (NS_FAILED(rv)) { + return; + } + if (NS_FAILED(localDir->Append(u"fonts"_ns))) { + return; + } + bool isDir; + if (NS_FAILED(localDir->IsDirectory(&isDir)) || !isDir) { + return; + } + + nsCOMPtr<nsIDirectoryEnumerator> e; + rv = localDir->GetDirectoryEntries(getter_AddRefs(e)); + if (NS_FAILED(rv)) { + return; + } + + nsCOMPtr<nsIFile> file; + while (NS_SUCCEEDED(e->GetNextFile(getter_AddRefs(file))) && file) { + nsAutoString path; + if (NS_FAILED(file->GetPath(path))) { + continue; + } + AddFontResourceExW(path.get(), FR_PRIVATE, nullptr); + } +} + +#endif diff --git a/gfx/thebes/gfxGDIFontList.h b/gfx/thebes/gfxGDIFontList.h new file mode 100644 index 0000000000..708f7fc114 --- /dev/null +++ b/gfx/thebes/gfxGDIFontList.h @@ -0,0 +1,356 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_GDIFONTLIST_H +#define GFX_GDIFONTLIST_H + +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/MemoryReporting.h" +#include "gfxWindowsPlatform.h" +#include "gfxPlatformFontList.h" +#include "nsGkAtoms.h" +#include "mozilla/gfx/UnscaledFontGDI.h" + +#include <windows.h> + +class AutoDC // get the global device context, and auto-release it on + // destruction +{ + public: + AutoDC() { mDC = ::GetDC(nullptr); } + + ~AutoDC() { ::ReleaseDC(nullptr, mDC); } + + HDC GetDC() { return mDC; } + + private: + HDC mDC; +}; + +class AutoSelectFont // select a font into the given DC, and auto-restore +{ + public: + AutoSelectFont(HDC aDC, LOGFONTW* aLogFont) : mOwnsFont(false) { + mFont = ::CreateFontIndirectW(aLogFont); + if (mFont) { + mOwnsFont = true; + mDC = aDC; + mOldFont = (HFONT)::SelectObject(aDC, mFont); + } else { + mOldFont = nullptr; + } + } + + AutoSelectFont(HDC aDC, HFONT aFont) : mOwnsFont(false) { + mDC = aDC; + mFont = aFont; + mOldFont = (HFONT)::SelectObject(aDC, aFont); + } + + ~AutoSelectFont() { + if (mOldFont) { + ::SelectObject(mDC, mOldFont); + if (mOwnsFont) { + ::DeleteObject(mFont); + } + } + } + + bool IsValid() const { return mFont != nullptr; } + + HFONT GetFont() const { return mFont; } + + private: + HDC mDC; + HFONT mFont; + HFONT mOldFont; + bool mOwnsFont; +}; + +/** + * List of different types of fonts we support on Windows. + * These can generally be lumped in to 3 categories where we have to + * do special things: Really old fonts bitmap and vector fonts (device + * and raster), Type 1 fonts, and TrueType/OpenType fonts. + * + * This list is sorted in order from least prefered to most prefered. + * We prefer Type1 fonts over OpenType fonts to avoid falling back to + * things like Arial (opentype) when you ask for Helvetica (type1) + **/ +enum gfxWindowsFontType { + GFX_FONT_TYPE_UNKNOWN = 0, + GFX_FONT_TYPE_DEVICE, + GFX_FONT_TYPE_RASTER, + GFX_FONT_TYPE_TRUETYPE, + GFX_FONT_TYPE_PS_OPENTYPE, + GFX_FONT_TYPE_TT_OPENTYPE, + GFX_FONT_TYPE_TYPE1 +}; + +// A single member of a font family (i.e. a single face, such as Times Italic) +// represented as a LOGFONT that will resolve to the correct face. +// This replaces FontEntry from gfxWindowsFonts.h/cpp. +class GDIFontEntry final : public gfxFontEntry { + public: + LPLOGFONTW GetLogFont() { return &mLogFont; } + + nsresult ReadCMAP(FontInfoData* aFontInfoData = nullptr) override; + + void FillLogFont(LOGFONTW* aLogFont, LONG aWeight, gfxFloat aSize); + + static gfxWindowsFontType DetermineFontType(const NEWTEXTMETRICW& metrics, + DWORD fontType) { + gfxWindowsFontType feType; + if (metrics.ntmFlags & NTM_TYPE1) + feType = GFX_FONT_TYPE_TYPE1; + else if (metrics.ntmFlags & NTM_PS_OPENTYPE) + feType = GFX_FONT_TYPE_PS_OPENTYPE; + else if (metrics.ntmFlags & NTM_TT_OPENTYPE) + feType = GFX_FONT_TYPE_TT_OPENTYPE; + else if (fontType == TRUETYPE_FONTTYPE) + feType = GFX_FONT_TYPE_TRUETYPE; + else if (fontType == RASTER_FONTTYPE) + feType = GFX_FONT_TYPE_RASTER; + else if (fontType == DEVICE_FONTTYPE) + feType = GFX_FONT_TYPE_DEVICE; + else + feType = GFX_FONT_TYPE_UNKNOWN; + + return feType; + } + + bool IsType1() const { return (mFontType == GFX_FONT_TYPE_TYPE1); } + + bool IsTrueType() const { + return (mFontType == GFX_FONT_TYPE_TRUETYPE || + mFontType == GFX_FONT_TYPE_PS_OPENTYPE || + mFontType == GFX_FONT_TYPE_TT_OPENTYPE); + } + + bool SkipDuringSystemFallback() override { + return !HasCmapTable(); // explicitly skip non-SFNT fonts + } + + bool TestCharacterMap(uint32_t aCh) override; + + void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const override; + + gfxFontEntry* Clone() const override; + + // GDI backend doesn't support font variations: + bool HasVariations() override { return false; } + void GetVariationAxes(nsTArray<gfxFontVariationAxis>&) override {} + void GetVariationInstances(nsTArray<gfxFontVariationInstance>&) override {} + + // create a font entry for a font with a given name + static GDIFontEntry* CreateFontEntry(const nsACString& aName, + gfxWindowsFontType aFontType, + SlantStyleRange aStyle, + WeightRange aWeight, + StretchRange aStretch, + gfxUserFontData* aUserFontData); + + gfxWindowsFontType mFontType; + bool mForceGDI; + + protected: + friend class gfxGDIFont; + + GDIFontEntry(const nsACString& aFaceName, gfxWindowsFontType aFontType, + SlantStyleRange aStyle, WeightRange aWeight, + StretchRange aStretch, gfxUserFontData* aUserFontData); + + void InitLogFont(const nsACString& aName, gfxWindowsFontType aFontType); + + gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle) override; + + virtual nsresult CopyFontTable(uint32_t aTableTag, + nsTArray<uint8_t>& aBuffer) override; + + already_AddRefed<mozilla::gfx::UnscaledFontGDI> LookupUnscaledFont( + HFONT aFont); + + LOGFONTW mLogFont; + + mozilla::ThreadSafeWeakPtr<mozilla::gfx::UnscaledFontGDI> mUnscaledFont; +}; + +// a single font family, referencing one or more faces +class GDIFontFamily final : public gfxFontFamily { + public: + GDIFontFamily(const nsACString& aName, FontVisibility aVisibility) + : gfxFontFamily(aName, aVisibility), + mWindowsFamily(0), + mWindowsPitch(0), + mCharset() {} + + void FindStyleVariationsLocked(FontInfoData* aFontInfoData = nullptr) + MOZ_REQUIRES(mLock) override; + + bool FilterForFontList(nsAtom* aLangGroup, + const nsACString& aGeneric) const final { + return !IsSymbolFontFamily() && SupportsLangGroup(aLangGroup) && + MatchesGenericFamily(aGeneric); + } + + protected: + friend class gfxGDIFontList; + + // helpers for FilterForFontList + bool IsSymbolFontFamily() const { return mCharset.test(SYMBOL_CHARSET); } + + bool MatchesGenericFamily(const nsACString& aGeneric) const { + if (aGeneric.IsEmpty()) { + return true; + } + + // Japanese 'Mincho' fonts do not belong to FF_MODERN even if + // they are fixed pitch because they have variable stroke width. + if (mWindowsFamily == FF_ROMAN && mWindowsPitch & FIXED_PITCH) { + return aGeneric.EqualsLiteral("monospace"); + } + + // Japanese 'Gothic' fonts do not belong to FF_SWISS even if + // they are variable pitch because they have constant stroke width. + if (mWindowsFamily == FF_MODERN && mWindowsPitch & VARIABLE_PITCH) { + return aGeneric.EqualsLiteral("sans-serif"); + } + + // All other fonts will be grouped correctly using family... + switch (mWindowsFamily) { + case FF_DONTCARE: + return false; + case FF_ROMAN: + return aGeneric.EqualsLiteral("serif"); + case FF_SWISS: + return aGeneric.EqualsLiteral("sans-serif"); + case FF_MODERN: + return aGeneric.EqualsLiteral("monospace"); + case FF_SCRIPT: + return aGeneric.EqualsLiteral("cursive"); + case FF_DECORATIVE: + return aGeneric.EqualsLiteral("fantasy"); + } + + return false; + } + + bool SupportsLangGroup(nsAtom* aLangGroup) const { + if (!aLangGroup || aLangGroup == nsGkAtoms::Unicode) { + return true; + } + + int16_t bit = -1; + + /* map our langgroup names in to Windows charset bits */ + if (aLangGroup == nsGkAtoms::x_western) { + bit = ANSI_CHARSET; + } else if (aLangGroup == nsGkAtoms::Japanese) { + bit = SHIFTJIS_CHARSET; + } else if (aLangGroup == nsGkAtoms::ko) { + bit = HANGEUL_CHARSET; + } else if (aLangGroup == nsGkAtoms::zh_cn) { + bit = GB2312_CHARSET; + } else if (aLangGroup == nsGkAtoms::zh_tw) { + bit = CHINESEBIG5_CHARSET; + } else if (aLangGroup == nsGkAtoms::el) { + bit = GREEK_CHARSET; + } else if (aLangGroup == nsGkAtoms::he) { + bit = HEBREW_CHARSET; + } else if (aLangGroup == nsGkAtoms::ar) { + bit = ARABIC_CHARSET; + } else if (aLangGroup == nsGkAtoms::x_cyrillic) { + bit = RUSSIAN_CHARSET; + } else if (aLangGroup == nsGkAtoms::th) { + bit = THAI_CHARSET; + } + + if (bit != -1) { + return mCharset.test(bit); + } + + return false; + } + + uint8_t mWindowsFamily; + uint8_t mWindowsPitch; + + gfxSparseBitSet mCharset; + + private: + static int CALLBACK FamilyAddStylesProc(const ENUMLOGFONTEXW* lpelfe, + const NEWTEXTMETRICEXW* nmetrics, + DWORD fontType, LPARAM data); +}; + +class gfxGDIFontList final : public gfxPlatformFontList { + public: + static gfxGDIFontList* PlatformFontList() { + return static_cast<gfxGDIFontList*>( + gfxPlatformFontList::PlatformFontList()); + } + + virtual ~gfxGDIFontList() { AutoLock lock(mLock); } + + // initialize font lists + nsresult InitFontListForPlatform() MOZ_REQUIRES(mLock) override; + + gfxFontFamily* CreateFontFamily(const nsACString& aName, + FontVisibility aVisibility) const override; + + bool FindAndAddFamiliesLocked( + nsPresContext* aPresContext, mozilla::StyleGenericFontFamily aGeneric, + const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput, + FindFamiliesFlags aFlags, gfxFontStyle* aStyle = nullptr, + nsAtom* aLanguage = nullptr, gfxFloat aDevToCssSize = 1.0) + MOZ_REQUIRES(mLock) override; + + gfxFontEntry* LookupLocalFont(nsPresContext* aPresContext, + const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) override; + + gfxFontEntry* MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, + uint32_t aLength) override; + + void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const override; + void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const override; + + protected: + FontFamily GetDefaultFontForPlatform(nsPresContext* aPresContext, + const gfxFontStyle* aStyle, + nsAtom* aLanguage = nullptr) + MOZ_REQUIRES(mLock) override; + + private: + friend class gfxWindowsPlatform; + + gfxGDIFontList(); + + nsresult GetFontSubstitutes() MOZ_REQUIRES(mLock); + + static int CALLBACK EnumFontFamExProc(ENUMLOGFONTEXW* lpelfe, + NEWTEXTMETRICEXW* lpntme, + DWORD fontType, LPARAM lParam); + + already_AddRefed<FontInfoData> CreateFontInfoData() override; + +#ifdef MOZ_BUNDLED_FONTS + void ActivateBundledFonts(); +#endif + + FontFamilyTable mFontSubstitutes; + nsTArray<nsCString> mNonExistingFonts; +}; + +#endif /* GFX_GDIFONTLIST_H */ diff --git a/gfx/thebes/gfxGlyphExtents.cpp b/gfx/thebes/gfxGlyphExtents.cpp new file mode 100644 index 0000000000..9c0792481d --- /dev/null +++ b/gfx/thebes/gfxGlyphExtents.cpp @@ -0,0 +1,150 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxGlyphExtents.h" +#include "gfxTextRun.h" + +using namespace mozilla; + +#ifdef DEBUG_roc +# define DEBUG_TEXT_RUN_STORAGE_METRICS +#endif + +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS +extern uint32_t gTextRunStorageHighWaterMark; +extern uint32_t gTextRunStorage; +extern uint32_t gFontCount; +extern uint32_t gGlyphExtentsCount; +extern uint32_t gGlyphExtentsWidthsTotalSize; +extern uint32_t gGlyphExtentsSetupEagerSimple; +extern uint32_t gGlyphExtentsSetupEagerTight; +extern uint32_t gGlyphExtentsSetupLazyTight; +extern uint32_t gGlyphExtentsSetupFallBackToTight; +#endif + +gfxGlyphExtents::~gfxGlyphExtents() { +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + gGlyphExtentsWidthsTotalSize += + mContainedGlyphWidths.SizeOfExcludingThis(&FontCacheMallocSizeOf); + gGlyphExtentsCount++; +#endif + MOZ_COUNT_DTOR(gfxGlyphExtents); +} + +bool gfxGlyphExtents::GetTightGlyphExtentsAppUnitsLocked( + gfxFont* aFont, DrawTarget* aDrawTarget, uint32_t aGlyphID, + gfxRect* aExtents) { + HashEntry* entry = mTightGlyphExtents.GetEntry(aGlyphID); + if (!entry) { + // Some functions higher up in the call chain deliberately pass in a + // nullptr DrawTarget, e.g. GetBaselinePosition() passes nullptr to + // gfxTextRun::MeasureText() and that nullptr reaches here. + if (!aDrawTarget) { + NS_WARNING("Could not get glyph extents (no aDrawTarget)"); + return false; + } + +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + ++gGlyphExtentsSetupLazyTight; +#endif + // We need to temporarily release the read lock, as SetupGlyphExtents will + // take a write lock internally when it wants to set the new entry. + MOZ_PUSH_IGNORE_THREAD_SAFETY + mLock.ReadUnlock(); + aFont->SetupGlyphExtents(aDrawTarget, aGlyphID, true, this); + mLock.ReadLock(); + MOZ_POP_THREAD_SAFETY + + entry = mTightGlyphExtents.GetEntry(aGlyphID); + if (!entry) { + NS_WARNING("Could not get glyph extents"); + return false; + } + } + + *aExtents = gfxRect(entry->x, entry->y, entry->width, entry->height); + return true; +} + +gfxGlyphExtents::GlyphWidths::~GlyphWidths() { + uint32_t i, count = mBlocks.Length(); + for (i = 0; i < count; ++i) { + uintptr_t bits = mBlocks[i]; + if (bits && !(bits & 0x1)) { + delete[] reinterpret_cast<uint16_t*>(bits); + } + } +} + +uint32_t gfxGlyphExtents::GlyphWidths::SizeOfExcludingThis( + MallocSizeOf aMallocSizeOf) const { + uint32_t i; + uint32_t size = mBlocks.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (i = 0; i < mBlocks.Length(); ++i) { + uintptr_t bits = mBlocks[i]; + if (bits && !(bits & 0x1)) { + size += aMallocSizeOf(reinterpret_cast<void*>(bits)); + } + } + return size; +} + +void gfxGlyphExtents::GlyphWidths::Set(uint32_t aGlyphID, uint16_t aWidth) { + uint32_t block = aGlyphID >> BLOCK_SIZE_BITS; + uint32_t len = mBlocks.Length(); + if (block >= len) { + uintptr_t* elems = mBlocks.AppendElements(block + 1 - len); + if (!elems) return; + memset(elems, 0, sizeof(uintptr_t) * (block + 1 - len)); + } + + uintptr_t bits = mBlocks[block]; + uint32_t glyphOffset = aGlyphID & (BLOCK_SIZE - 1); + if (!bits) { + mBlocks[block] = MakeSingle(glyphOffset, aWidth); + return; + } + + uint16_t* newBlock; + if (bits & 0x1) { + // Expand the block to a real block. We could avoid this by checking + // glyphOffset == GetGlyphOffset(bits), but that never happens so don't + // bother + newBlock = new uint16_t[BLOCK_SIZE]; + if (!newBlock) return; + uint32_t i; + for (i = 0; i < BLOCK_SIZE; ++i) { + newBlock[i] = INVALID_WIDTH; + } + newBlock[GetGlyphOffset(bits)] = GetWidth(bits); + mBlocks[block] = reinterpret_cast<uintptr_t>(newBlock); + } else { + newBlock = reinterpret_cast<uint16_t*>(bits); + } + newBlock[glyphOffset] = aWidth; +} + +void gfxGlyphExtents::SetTightGlyphExtents(uint32_t aGlyphID, + const gfxRect& aExtentsAppUnits) { + AutoWriteLock lock(mLock); + HashEntry* entry = mTightGlyphExtents.PutEntry(aGlyphID); + if (!entry) { + return; + } + entry->x = aExtentsAppUnits.X(); + entry->y = aExtentsAppUnits.Y(); + entry->width = aExtentsAppUnits.Width(); + entry->height = aExtentsAppUnits.Height(); +} + +size_t gfxGlyphExtents::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + AutoReadLock lock(mLock); + return mContainedGlyphWidths.SizeOfExcludingThis(aMallocSizeOf) + + mTightGlyphExtents.ShallowSizeOfExcludingThis(aMallocSizeOf); +} + +size_t gfxGlyphExtents::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} diff --git a/gfx/thebes/gfxGlyphExtents.h b/gfx/thebes/gfxGlyphExtents.h new file mode 100644 index 0000000000..fb41112413 --- /dev/null +++ b/gfx/thebes/gfxGlyphExtents.h @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_GLYPHEXTENTS_H +#define GFX_GLYPHEXTENTS_H + +#include "gfxFont.h" +#include "gfxRect.h" +#include "nsTHashtable.h" +#include "nsHashKeys.h" +#include "nsTArray.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RWLock.h" + +class gfxContext; + +namespace mozilla { +namespace gfx { +class DrawTarget; +} // namespace gfx +} // namespace mozilla + +/** + * This stores glyph bounds information for a particular gfxFont, at + * a particular appunits-per-dev-pixel ratio (because the compressed glyph + * width array is stored in appunits). + * + * We store a hashtable from glyph IDs to float bounding rects. For the + * common case where the glyph has no horizontal left bearing, and no + * y overflow above the font ascent or below the font descent, and tight + * bounding boxes are not required, we avoid storing the glyph ID in the + * hashtable and instead consult an array of 16-bit glyph XMost values (in + * appunits). This array always has an entry for the font's space glyph --- the + * width is assumed to be zero. + */ +class gfxGlyphExtents { + typedef mozilla::gfx::DrawTarget DrawTarget; + + public: + explicit gfxGlyphExtents(int32_t aAppUnitsPerDevUnit) + : mAppUnitsPerDevUnit(aAppUnitsPerDevUnit), + mLock("gfxGlyphExtents lock") { + MOZ_COUNT_CTOR(gfxGlyphExtents); + } + ~gfxGlyphExtents(); + + enum { INVALID_WIDTH = 0xFFFF }; + + void NotifyGlyphsChanged() { + mozilla::AutoWriteLock lock(mLock); + mTightGlyphExtents.Clear(); + } + + // returns INVALID_WIDTH => not a contained glyph + // Otherwise the glyph has no before-bearing or vertical bearings, + // and the result is its width measured from the baseline origin, in + // appunits. + uint16_t GetContainedGlyphWidthAppUnitsLocked(uint32_t aGlyphID) const + MOZ_REQUIRES_SHARED(mLock) { + return mContainedGlyphWidths.Get(aGlyphID); + } + + bool IsGlyphKnownLocked(uint32_t aGlyphID) const MOZ_REQUIRES_SHARED(mLock) { + return mContainedGlyphWidths.Get(aGlyphID) != INVALID_WIDTH || + mTightGlyphExtents.GetEntry(aGlyphID) != nullptr; + } + + bool IsGlyphKnownWithTightExtentsLocked(uint32_t aGlyphID) const + MOZ_REQUIRES_SHARED(mLock) { + return mTightGlyphExtents.GetEntry(aGlyphID) != nullptr; + } + + // Get glyph extents; a rectangle relative to the left baseline origin + // Returns true on success. Can fail on OOM or when aContext is null + // and extents were not (successfully) prefetched. + bool GetTightGlyphExtentsAppUnitsLocked(gfxFont* aFont, + DrawTarget* aDrawTarget, + uint32_t aGlyphID, gfxRect* aExtents) + MOZ_REQUIRES_SHARED(mLock); + bool GetTightGlyphExtentsAppUnits(gfxFont* aFont, DrawTarget* aDrawTarget, + uint32_t aGlyphID, gfxRect* aExtents) { + mozilla::AutoReadLock lock(mLock); + return GetTightGlyphExtentsAppUnitsLocked(aFont, aDrawTarget, aGlyphID, + aExtents); + } + + void SetContainedGlyphWidthAppUnits(uint32_t aGlyphID, uint16_t aWidth) { + mozilla::AutoWriteLock lock(mLock); + mContainedGlyphWidths.Set(aGlyphID, aWidth); + } + void SetTightGlyphExtents(uint32_t aGlyphID, const gfxRect& aExtentsAppUnits); + + int32_t GetAppUnitsPerDevUnit() { return mAppUnitsPerDevUnit; } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + private: + class HashEntry : public nsUint32HashKey { + public: + // When constructing a new entry in the hashtable, we'll leave this + // blank. The caller of Put() will fill this in. + explicit HashEntry(KeyTypePointer aPtr) + : nsUint32HashKey(aPtr), x(0.0), y(0.0), width(0.0), height(0.0) {} + HashEntry(HashEntry&& aOther) + : nsUint32HashKey(std::move(aOther)), + x(aOther.x), + y(aOther.y), + width(aOther.width), + height(aOther.height) {} + + float x, y, width, height; + }; + + enum { + BLOCK_SIZE_BITS = 7, + BLOCK_SIZE = 1 << BLOCK_SIZE_BITS + }; // 128-glyph blocks + + class GlyphWidths { + public: + void Set(uint32_t aIndex, uint16_t aValue); + uint16_t Get(uint32_t aIndex) const { + uint32_t block = aIndex >> BLOCK_SIZE_BITS; + if (block >= mBlocks.Length()) return INVALID_WIDTH; + uintptr_t bits = mBlocks[block]; + if (!bits) return INVALID_WIDTH; + uint32_t indexInBlock = aIndex & (BLOCK_SIZE - 1); + if (bits & 0x1) { + if (GetGlyphOffset(bits) != indexInBlock) return INVALID_WIDTH; + return GetWidth(bits); + } + uint16_t* widths = reinterpret_cast<uint16_t*>(bits); + return widths[indexInBlock]; + } + + uint32_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + ~GlyphWidths(); + + private: + static uint32_t GetGlyphOffset(uintptr_t aBits) { + NS_ASSERTION(aBits & 0x1, "This is really a pointer..."); + return (aBits >> 1) & ((1 << BLOCK_SIZE_BITS) - 1); + } + static uint32_t GetWidth(uintptr_t aBits) { + NS_ASSERTION(aBits & 0x1, "This is really a pointer..."); + return aBits >> (1 + BLOCK_SIZE_BITS); + } + static uintptr_t MakeSingle(uint32_t aGlyphOffset, uint16_t aWidth) { + return (aWidth << (1 + BLOCK_SIZE_BITS)) + (aGlyphOffset << 1) + 1; + } + + nsTArray<uintptr_t> mBlocks; + }; + + GlyphWidths mContainedGlyphWidths MOZ_GUARDED_BY(mLock); + nsTHashtable<HashEntry> mTightGlyphExtents MOZ_GUARDED_BY(mLock); + const int32_t mAppUnitsPerDevUnit; + + public: + mutable mozilla::RWLock mLock; + + private: + // not implemented: + gfxGlyphExtents(const gfxGlyphExtents& aOther) = delete; + gfxGlyphExtents& operator=(const gfxGlyphExtents& aOther) = delete; +}; + +#endif diff --git a/gfx/thebes/gfxGradientCache.cpp b/gfx/thebes/gfxGradientCache.cpp new file mode 100644 index 0000000000..c2a1682a7d --- /dev/null +++ b/gfx/thebes/gfxGradientCache.cpp @@ -0,0 +1,285 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxGradientCache.h" + +#include "MainThreadUtils.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/DataMutex.h" +#include "nsTArray.h" +#include "PLDHashTable.h" +#include "nsExpirationTracker.h" +#include "nsClassHashtable.h" +#include <time.h> + +namespace mozilla { +namespace gfx { + +using namespace mozilla; + +struct GradientCacheKey : public PLDHashEntryHdr { + typedef const GradientCacheKey& KeyType; + typedef const GradientCacheKey* KeyTypePointer; + enum { ALLOW_MEMMOVE = true }; + const CopyableTArray<GradientStop> mStops; + ExtendMode mExtend; + BackendType mBackendType; + + GradientCacheKey(const nsTArray<GradientStop>& aStops, ExtendMode aExtend, + BackendType aBackendType) + : mStops(aStops), mExtend(aExtend), mBackendType(aBackendType) {} + + explicit GradientCacheKey(const GradientCacheKey* aOther) + : mStops(aOther->mStops), + mExtend(aOther->mExtend), + mBackendType(aOther->mBackendType) {} + + GradientCacheKey(GradientCacheKey&& aOther) = default; + + union FloatUint32 { + float f; + uint32_t u; + }; + + static PLDHashNumber HashKey(const KeyTypePointer aKey) { + PLDHashNumber hash = 0; + FloatUint32 convert; + hash = AddToHash(hash, int(aKey->mBackendType)); + hash = AddToHash(hash, int(aKey->mExtend)); + for (uint32_t i = 0; i < aKey->mStops.Length(); i++) { + hash = AddToHash(hash, aKey->mStops[i].color.ToABGR()); + // Use the float bits as hash, except for the cases of 0.0 and -0.0 which + // both map to 0 + convert.f = aKey->mStops[i].offset; + hash = AddToHash(hash, convert.f ? convert.u : 0); + } + return hash; + } + + bool KeyEquals(KeyTypePointer aKey) const { + bool sameStops = true; + if (aKey->mStops.Length() != mStops.Length()) { + sameStops = false; + } else { + for (uint32_t i = 0; i < mStops.Length(); i++) { + if (mStops[i].color.ToABGR() != aKey->mStops[i].color.ToABGR() || + mStops[i].offset != aKey->mStops[i].offset) { + sameStops = false; + break; + } + } + } + + return sameStops && (aKey->mBackendType == mBackendType) && + (aKey->mExtend == mExtend); + } + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } +}; + +/** + * This class is what is cached. It need to be allocated in an object separated + * to the cache entry to be able to be tracked by the nsExpirationTracker. + * */ +struct GradientCacheData { + GradientCacheData(GradientStops* aStops, GradientCacheKey&& aKey) + : mStops(aStops), mKey(std::move(aKey)) {} + + GradientCacheData(GradientCacheData&& aOther) = default; + + nsExpirationState* GetExpirationState() { return &mExpirationState; } + + nsExpirationState mExpirationState; + const RefPtr<GradientStops> mStops; + GradientCacheKey mKey; +}; + +/** + * This class implements a cache, that retains the GradientStops used to draw + * the gradients. + * + * An entry stays in the cache as long as it is used often and we don't exceed + * the maximum, in which case the most recently used will be kept. + */ +class GradientCache; +using GradientCacheMutex = StaticDataMutex<UniquePtr<GradientCache>>; +class MOZ_RAII LockedInstance { + public: + explicit LockedInstance(GradientCacheMutex& aDataMutex) + : mAutoLock(aDataMutex.Lock()) {} + UniquePtr<GradientCache>& operator->() const& { return mAutoLock.ref(); } + UniquePtr<GradientCache>& operator->() const&& = delete; + UniquePtr<GradientCache>& operator*() const& { return mAutoLock.ref(); } + UniquePtr<GradientCache>& operator*() const&& = delete; + explicit operator bool() const { return !!mAutoLock.ref(); } + + private: + GradientCacheMutex::AutoLock mAutoLock; +}; + +class GradientCache final + : public ExpirationTrackerImpl<GradientCacheData, 4, GradientCacheMutex, + LockedInstance> { + public: + GradientCache() + : ExpirationTrackerImpl<GradientCacheData, 4, GradientCacheMutex, + LockedInstance>(MAX_GENERATION_MS, + "GradientCache") {} + static bool EnsureInstance() { + LockedInstance lockedInstance(sInstanceMutex); + return EnsureInstanceLocked(lockedInstance); + } + + static void DestroyInstance() { + LockedInstance lockedInstance(sInstanceMutex); + if (lockedInstance) { + *lockedInstance = nullptr; + } + } + + static void AgeAllGenerations() { + LockedInstance lockedInstance(sInstanceMutex); + if (!lockedInstance) { + return; + } + lockedInstance->AgeAllGenerationsLocked(lockedInstance); + lockedInstance->NotifyHandlerEndLocked(lockedInstance); + } + + template <typename CreateFunc> + static already_AddRefed<GradientStops> LookupOrInsert( + const GradientCacheKey& aKey, CreateFunc aCreateFunc) { + uint32_t numberOfEntries; + RefPtr<GradientStops> stops; + { + LockedInstance lockedInstance(sInstanceMutex); + if (!EnsureInstanceLocked(lockedInstance)) { + return aCreateFunc(); + } + + GradientCacheData* gradientData = lockedInstance->mHashEntries.Get(aKey); + if (gradientData) { + if (gradientData->mStops && gradientData->mStops->IsValid()) { + lockedInstance->MarkUsedLocked(gradientData, lockedInstance); + return do_AddRef(gradientData->mStops); + } + + lockedInstance->NotifyExpiredLocked(gradientData, lockedInstance); + lockedInstance->NotifyHandlerEndLocked(lockedInstance); + } + + stops = aCreateFunc(); + if (!stops) { + return nullptr; + } + + auto data = MakeUnique<GradientCacheData>(stops, GradientCacheKey(&aKey)); + nsresult rv = lockedInstance->AddObjectLocked(data.get(), lockedInstance); + if (NS_FAILED(rv)) { + // We are OOM, and we cannot track this object. We don't want to store + // entries in the hash table (since the expiration tracker is + // responsible for removing the cache entries), so we avoid putting that + // entry in the table, which is a good thing considering we are short on + // memory anyway, we probably don't want to retain things. + return stops.forget(); + } + lockedInstance->mHashEntries.InsertOrUpdate(aKey, std::move(data)); + numberOfEntries = lockedInstance->mHashEntries.Count(); + } + + if (numberOfEntries > MAX_ENTRIES) { + // We have too many entries force the cache to age a generation. + NS_DispatchToMainThread( + NS_NewRunnableFunction("GradientCache::OnMaxEntriesBreached", [] { + LockedInstance lockedInstance(sInstanceMutex); + if (!lockedInstance) { + return; + } + lockedInstance->AgeOneGenerationLocked(lockedInstance); + lockedInstance->NotifyHandlerEndLocked(lockedInstance); + })); + } + + return stops.forget(); + } + + GradientCacheMutex& GetMutex() final { return sInstanceMutex; } + + void NotifyExpiredLocked(GradientCacheData* aObject, + const LockedInstance& aLockedInstance) final { + // Remove the gradient from the tracker. + RemoveObjectLocked(aObject, aLockedInstance); + + // If entry exists move the data to mRemovedGradientData because we want to + // drop it outside of the lock. + Maybe<UniquePtr<GradientCacheData>> gradientData = + mHashEntries.Extract(aObject->mKey); + if (gradientData.isSome()) { + mRemovedGradientData.AppendElement(std::move(*gradientData)); + } + } + + void NotifyHandlerEndLocked(const LockedInstance&) final { + NS_DispatchToMainThread( + NS_NewRunnableFunction("GradientCache::DestroyRemovedGradientStops", + [stops = std::move(mRemovedGradientData)] {})); + } + + private: + static const uint32_t MAX_GENERATION_MS = 10000; + + // On Windows some of the Direct2D objects associated with the gradient stops + // can be quite large, so we limit the number of cache entries. + static const uint32_t MAX_ENTRIES = 4000; + static GradientCacheMutex sInstanceMutex; + + [[nodiscard]] static bool EnsureInstanceLocked( + LockedInstance& aLockedInstance) { + if (!aLockedInstance) { + // GradientCache must be created on the main thread. + if (!NS_IsMainThread()) { + // This should only happen at shutdown, we fall back to not caching. + return false; + } + *aLockedInstance = MakeUnique<GradientCache>(); + } + return true; + } + + /** + * FIXME use nsTHashtable to avoid duplicating the GradientCacheKey. + * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47 + */ + nsClassHashtable<GradientCacheKey, GradientCacheData> mHashEntries; + nsTArray<UniquePtr<GradientCacheData>> mRemovedGradientData; +}; + +GradientCacheMutex GradientCache::sInstanceMutex("GradientCache"); + +void gfxGradientCache::Init() { + MOZ_RELEASE_ASSERT(GradientCache::EnsureInstance(), + "First call must be on main thread."); +} + +already_AddRefed<GradientStops> gfxGradientCache::GetOrCreateGradientStops( + const DrawTarget* aDT, nsTArray<GradientStop>& aStops, ExtendMode aExtend) { + if (aDT->IsRecording()) { + return aDT->CreateGradientStops(aStops.Elements(), aStops.Length(), + aExtend); + } + + return GradientCache::LookupOrInsert( + GradientCacheKey(aStops, aExtend, aDT->GetBackendType()), + [&]() -> already_AddRefed<GradientStops> { + return aDT->CreateGradientStops(aStops.Elements(), aStops.Length(), + aExtend); + }); +} + +void gfxGradientCache::PurgeAllCaches() { GradientCache::AgeAllGenerations(); } + +void gfxGradientCache::Shutdown() { GradientCache::DestroyInstance(); } + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/thebes/gfxGradientCache.h b/gfx/thebes/gfxGradientCache.h new file mode 100644 index 0000000000..98f2aa9c35 --- /dev/null +++ b/gfx/thebes/gfxGradientCache.h @@ -0,0 +1,32 @@ + +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_GRADIENT_CACHE_H +#define GFX_GRADIENT_CACHE_H + +#include "nsTArray.h" +#include "gfxPattern.h" +#include "mozilla/gfx/2D.h" + +namespace mozilla { +namespace gfx { + +class gfxGradientCache { + public: + static void Init(); + + static already_AddRefed<gfx::GradientStops> GetOrCreateGradientStops( + const gfx::DrawTarget* aDT, nsTArray<gfx::GradientStop>& aStops, + gfx::ExtendMode aExtend); + + static void PurgeAllCaches(); + static void Shutdown(); +}; + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/thebes/gfxGraphiteShaper.cpp b/gfx/thebes/gfxGraphiteShaper.cpp new file mode 100644 index 0000000000..7021ffb362 --- /dev/null +++ b/gfx/thebes/gfxGraphiteShaper.cpp @@ -0,0 +1,513 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxGraphiteShaper.h" +#include "nsString.h" +#include "gfxContext.h" +#include "gfxFontConstants.h" +#include "gfxTextRun.h" + +#include "graphite2/Font.h" +#include "graphite2/GraphiteExtra.h" +#include "graphite2/Segment.h" + +#include "harfbuzz/hb.h" + +#include "mozilla/ScopeExit.h" + +#include "ThebesRLBox.h" + +#define FloatToFixed(f) (65536 * (f)) +#define FixedToFloat(f) ((f) * (1.0 / 65536.0)) +// Right shifts of negative (signed) integers are undefined, as are overflows +// when converting unsigned to negative signed integers. +// (If speed were an issue we could make some 2's complement assumptions.) +#define FixedToIntRound(f) \ + ((f) > 0 ? ((32768 + (f)) >> 16) : -((32767 - (f)) >> 16)) + +#define CopyAndVerifyOrFail(t, cond, failed) \ + (t).copy_and_verify([&](auto val) { \ + if (!(cond)) { \ + *(failed) = true; \ + } \ + return val; \ + }) + +using namespace mozilla; // for AutoSwap_* types + +/* + * Creation and destruction; on deletion, release any font tables we're holding + */ + +gfxGraphiteShaper::gfxGraphiteShaper(gfxFont* aFont) + : gfxFontShaper(aFont), + mGrFace(mFont->GetFontEntry()->GetGrFace()), + mSandbox(mFont->GetFontEntry()->GetGrSandbox()), + mCallback(mFont->GetFontEntry()->GetGrSandboxAdvanceCallbackHandle()), + mFallbackToSmallCaps(false) { + mCallbackData.mFont = aFont; +} + +gfxGraphiteShaper::~gfxGraphiteShaper() { + auto t_mGrFont = rlbox::from_opaque(mGrFont); + if (t_mGrFont) { + sandbox_invoke(*mSandbox, gr_font_destroy, t_mGrFont); + } + mFont->GetFontEntry()->ReleaseGrFace(mGrFace); +} + +/*static*/ +thread_local gfxGraphiteShaper::CallbackData* + gfxGraphiteShaper::tl_GrGetAdvanceData = nullptr; + +/*static*/ +tainted_opaque_gr<float> gfxGraphiteShaper::GrGetAdvance( + rlbox_sandbox_gr& sandbox, + tainted_opaque_gr<const void*> /* appFontHandle */, + tainted_opaque_gr<uint16_t> t_glyphid) { + CallbackData* cb = tl_GrGetAdvanceData; + if (!cb) { + // GrGetAdvance callback called unexpectedly. Just return safe value. + tainted_gr<float> ret = 0; + return ret.to_opaque(); + } + auto glyphid = rlbox::from_opaque(t_glyphid).unverified_safe_because( + "Here the only use of a glyphid is for lookup to get a width. " + "Implementations of GetGlyphWidth in this code base use a hashtable " + "which is robust to unknown keys. So no validation is required."); + tainted_gr<float> ret = FixedToFloat(cb->mFont->GetGlyphWidth(glyphid)); + return ret.to_opaque(); +} + +static inline uint32_t MakeGraphiteLangTag(uint32_t aTag) { + uint32_t grLangTag = aTag; + // replace trailing space-padding with NULs for graphite + uint32_t mask = 0x000000FF; + while ((grLangTag & mask) == ' ') { + grLangTag &= ~mask; + mask <<= 8; + } + return grLangTag; +} + +struct GrFontFeatures { + tainted_gr<gr_face*> mFace; + tainted_gr<gr_feature_val*> mFeatures; + rlbox_sandbox_gr* mSandbox; +}; + +static void AddFeature(uint32_t aTag, uint32_t aValue, void* aUserArg) { + GrFontFeatures* f = static_cast<GrFontFeatures*>(aUserArg); + + tainted_gr<const gr_feature_ref*> fref = + sandbox_invoke(*(f->mSandbox), gr_face_find_fref, f->mFace, aTag); + if (fref) { + sandbox_invoke(*(f->mSandbox), gr_fref_set_feature_value, fref, aValue, + f->mFeatures); + } +} + +// Count the number of Unicode characters in a UTF-16 string (i.e. surrogate +// pairs are counted as 1, although they are 2 code units). +// (Any isolated surrogates will count 1 each, because in decoding they would +// be replaced by individual U+FFFD REPLACEMENT CHARACTERs.) +static inline size_t CountUnicodes(const char16_t* aText, uint32_t aLength) { + size_t total = 0; + const char16_t* end = aText + aLength; + while (aText < end) { + if (NS_IS_HIGH_SURROGATE(*aText) && aText + 1 < end && + NS_IS_LOW_SURROGATE(*(aText + 1))) { + aText += 2; + } else { + aText++; + } + total++; + } + return total; +} + +bool gfxGraphiteShaper::ShapeText(DrawTarget* aDrawTarget, + const char16_t* aText, uint32_t aOffset, + uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, + RoundingFlags aRounding, + gfxShapedText* aShapedText) { + const gfxFontStyle* style = mFont->GetStyle(); + auto t_mGrFace = rlbox::from_opaque(mGrFace); + auto t_mGrFont = rlbox::from_opaque(mGrFont); + + if (!t_mGrFont) { + if (!t_mGrFace) { + return false; + } + + if (mFont->ProvidesGlyphWidths()) { + auto p_ops = mSandbox->malloc_in_sandbox<gr_font_ops>(); + if (!p_ops) { + return false; + } + auto clean_ops = MakeScopeExit([&] { mSandbox->free_in_sandbox(p_ops); }); + p_ops->size = sizeof(*p_ops); + p_ops->glyph_advance_x = *mCallback; + p_ops->glyph_advance_y = nullptr; // vertical text not yet implemented + t_mGrFont = sandbox_invoke( + *mSandbox, gr_make_font_with_ops, mFont->GetAdjustedSize(), + // For security, we do not pass the callback data to this arg, and use + // a TLS var instead. However, gr_make_font_with_ops expects this to + // be a non null ptr, and changes its behavior if it isn't. Therefore, + // we should pass some dummy non null pointer which will be passed to + // the GrGetAdvance callback, but never used. Let's just pass p_ops + // again, as this is a non-null tainted pointer. + p_ops /* mCallbackData */, p_ops, t_mGrFace); + } else { + t_mGrFont = sandbox_invoke(*mSandbox, gr_make_font, + mFont->GetAdjustedSize(), t_mGrFace); + } + mGrFont = t_mGrFont.to_opaque(); + + if (!t_mGrFont) { + return false; + } + + // determine whether petite-caps falls back to small-caps + if (style->variantCaps != NS_FONT_VARIANT_CAPS_NORMAL) { + switch (style->variantCaps) { + case NS_FONT_VARIANT_CAPS_ALLPETITE: + case NS_FONT_VARIANT_CAPS_PETITECAPS: + bool synLower, synUpper; + mFont->SupportsVariantCaps(aScript, style->variantCaps, + mFallbackToSmallCaps, synLower, synUpper); + break; + default: + break; + } + } + } + + gfxFontEntry* entry = mFont->GetFontEntry(); + uint32_t grLang = 0; + if (style->languageOverride) { + grLang = MakeGraphiteLangTag(style->languageOverride); + } else if (entry->mLanguageOverride) { + grLang = MakeGraphiteLangTag(entry->mLanguageOverride); + } else if (aLanguage) { + nsAutoCString langString; + aLanguage->ToUTF8String(langString); + grLang = GetGraphiteTagForLang(langString); + } + tainted_gr<gr_feature_val*> grFeatures = + sandbox_invoke(*mSandbox, gr_face_featureval_for_lang, t_mGrFace, grLang); + + // insert any merged features into Graphite feature list + GrFontFeatures f = {t_mGrFace, grFeatures, mSandbox}; + MergeFontFeatures(style, mFont->GetFontEntry()->mFeatureSettings, + aShapedText->DisableLigatures(), + mFont->GetFontEntry()->FamilyName(), mFallbackToSmallCaps, + AddFeature, &f); + + // Graphite shaping doesn't map U+00a0 (nbsp) to space if it is missing + // from the font, so check for that possibility. (Most fonts double-map + // the space glyph to both 0x20 and 0xA0, so this won't often be needed; + // so we don't copy the text until we know it's required.) + nsAutoString transformed; + const char16_t NO_BREAK_SPACE = 0x00a0; + if (!entry->HasCharacter(NO_BREAK_SPACE)) { + nsDependentSubstring src(aText, aLength); + if (src.FindChar(NO_BREAK_SPACE) != kNotFound) { + transformed = src; + transformed.ReplaceChar(NO_BREAK_SPACE, ' '); + aText = transformed.BeginReading(); + } + } + + size_t numChars = CountUnicodes(aText, aLength); + gr_bidirtl grBidi = gr_bidirtl( + aShapedText->IsRightToLeft() ? (gr_rtl | gr_nobidi) : gr_nobidi); + + tainted_gr<char16_t*> t_aText = + mSandbox->malloc_in_sandbox<char16_t>(aLength); + if (!t_aText) { + return false; + } + auto clean_txt = MakeScopeExit([&] { mSandbox->free_in_sandbox(t_aText); }); + + rlbox::memcpy(*mSandbox, t_aText, aText, aLength * sizeof(char16_t)); + + tl_GrGetAdvanceData = &mCallbackData; + auto clean_adv_data = MakeScopeExit([&] { tl_GrGetAdvanceData = nullptr; }); + + tainted_gr<gr_segment*> seg = + sandbox_invoke(*mSandbox, gr_make_seg, mGrFont, t_mGrFace, 0, grFeatures, + gr_utf16, t_aText, numChars, grBidi); + + sandbox_invoke(*mSandbox, gr_featureval_destroy, grFeatures); + + if (!seg) { + return false; + } + + nsresult rv = + SetGlyphsFromSegment(aShapedText, aOffset, aLength, aText, + t_aText.to_opaque(), seg.to_opaque(), aRounding); + + sandbox_invoke(*mSandbox, gr_seg_destroy, seg); + + return NS_SUCCEEDED(rv); +} + +nsresult gfxGraphiteShaper::SetGlyphsFromSegment( + gfxShapedText* aShapedText, uint32_t aOffset, uint32_t aLength, + const char16_t* aText, tainted_opaque_gr<char16_t*> t_aText, + tainted_opaque_gr<gr_segment*> aSegment, RoundingFlags aRounding) { + typedef gfxShapedText::CompressedGlyph CompressedGlyph; + + int32_t dev2appUnits = aShapedText->GetAppUnitsPerDevUnit(); + bool rtl = aShapedText->IsRightToLeft(); + + // identify clusters; graphite may have reordered/expanded/ligated glyphs. + tainted_gr<gr_glyph_to_char_association*> data = + sandbox_invoke(*mSandbox, gr_get_glyph_to_char_association, aSegment, + aLength, rlbox::from_opaque(t_aText)); + + if (!data) { + return NS_ERROR_FAILURE; + } + + tainted_gr<gr_glyph_to_char_cluster*> clusters = data->clusters; + tainted_gr<uint16_t*> gids = data->gids; + tainted_gr<float*> xLocs = data->xLocs; + tainted_gr<float*> yLocs = data->yLocs; + + CompressedGlyph* charGlyphs = aShapedText->GetCharacterGlyphs() + aOffset; + + bool roundX = bool(aRounding & RoundingFlags::kRoundX); + bool roundY = bool(aRounding & RoundingFlags::kRoundY); + + bool failedVerify = false; + + // cIndex is primarily used to index into the clusters array which has size + // aLength below. As cIndex is not changing anymore, let's just verify it + // and remove the tainted wrapper. + uint32_t cIndex = + CopyAndVerifyOrFail(data->cIndex, val < aLength, &failedVerify); + if (failedVerify) { + return NS_ERROR_ILLEGAL_VALUE; + } + // now put glyphs into the textrun, one cluster at a time + for (uint32_t i = 0; i <= cIndex; ++i) { + // We makes a local copy of "clusters[i]" which is of type + // tainted_gr<gr_glyph_to_char_cluster> below. We do this intentionally + // rather than taking a reference. Taking a reference with the code + // + // tainted_volatile_gr<gr_glyph_to_char_cluster>& c = clusters[i]; + // + // produces a tainted_volatile which means the value can change at any + // moment allowing for possible time-of-check-time-of-use vuln. We thus + // make a local copy to simplify the verification. + tainted_gr<gr_glyph_to_char_cluster> c = clusters[i]; + + tainted_gr<float> t_adv; // total advance of the cluster + if (rtl) { + if (i == 0) { + t_adv = sandbox_invoke(*mSandbox, gr_seg_advance_X, aSegment) - + xLocs[c.baseGlyph]; + } else { + t_adv = xLocs[clusters[i - 1].baseGlyph] - xLocs[c.baseGlyph]; + } + } else { + if (i == cIndex) { + t_adv = sandbox_invoke(*mSandbox, gr_seg_advance_X, aSegment) - + xLocs[c.baseGlyph]; + } else { + t_adv = xLocs[clusters[i + 1].baseGlyph] - xLocs[c.baseGlyph]; + } + } + + float adv = t_adv.unverified_safe_because( + "Per Bug 1569464 - this is the advance width of a glyph or cluster of " + "glyphs. There are no a-priori limits on what that might be. Incorrect " + "values will tend to result in bad layout or missing text, or bad " + "nscoord values. But, these will not result in safety issues."); + + // check unexpected offset - offs used to index into aText + uint32_t offs = + CopyAndVerifyOrFail(c.baseChar, val < aLength, &failedVerify); + if (failedVerify) { + return NS_ERROR_ILLEGAL_VALUE; + } + + // Check for default-ignorable char that didn't get filtered, combined, + // etc by the shaping process, and skip it. + auto one_glyph = c.nGlyphs == static_cast<uint32_t>(1); + auto one_char = c.nChars == static_cast<uint32_t>(1); + + if ((one_glyph && one_char) + .unverified_safe_because( + "using this boolean check to decide whether to ignore a " + "character or not. The worst that can happen is a bad " + "rendering.")) { + if (aShapedText->FilterIfIgnorable(aOffset + offs, aText[offs])) { + continue; + } + } + + uint32_t appAdvance = roundX ? NSToIntRound(adv) * dev2appUnits + : NSToIntRound(adv * dev2appUnits); + + const char gid_simple_value[] = + "Per Bug 1569464 - these are glyph IDs that can range from 0 to the " + "maximum glyph ID supported by the font. However, out-of-range values " + "here should not lead to safety issues; they would simply result in " + "blank rendering, although this depends on the platform back-end."; + + // gids[c.baseGlyph] is checked and used below. Since this is a + // tainted_volatile, which can change at any moment, we make a local copy + // first to prevent a time-of-check-time-of-use vuln. + uint16_t gid_of_base_glyph = + gids[c.baseGlyph].unverified_safe_because(gid_simple_value); + + const char fast_path[] = + "Even if the number of glyphs set is an incorrect value, the else " + "branch is a more general purpose algorithm which can handle other " + "values of nGlyphs"; + + if (one_glyph.unverified_safe_because(fast_path) && + CompressedGlyph::IsSimpleGlyphID(gid_of_base_glyph) && + CompressedGlyph::IsSimpleAdvance(appAdvance) && + charGlyphs[offs].IsClusterStart() && + (yLocs[c.baseGlyph] == 0).unverified_safe_because(fast_path)) { + charGlyphs[offs].SetSimpleGlyph(appAdvance, gid_of_base_glyph); + + } else { + // not a one-to-one mapping with simple metrics: use DetailedGlyph + AutoTArray<gfxShapedText::DetailedGlyph, 8> details; + float clusterLoc; + + uint32_t glyph_end = + (c.baseGlyph + c.nGlyphs) + .unverified_safe_because( + "This only controls the total number of glyphs set for this " + "particular text. Worst that can happen is a bad rendering"); + + // check overflow - ensure loop start is before the end + uint32_t glyph_start = + CopyAndVerifyOrFail(c.baseGlyph, val <= glyph_end, &failedVerify); + if (failedVerify) { + return NS_ERROR_ILLEGAL_VALUE; + } + + for (uint32_t j = glyph_start; j < glyph_end; ++j) { + gfxShapedText::DetailedGlyph* d = details.AppendElement(); + d->mGlyphID = gids[j].unverified_safe_because(gid_simple_value); + + const char safe_coordinates[] = + "There are no limits on coordinates. Worst case, bad values would " + "force rendering off-screen, but there are no memory safety " + "issues."; + + float yLocs_j = yLocs[j].unverified_safe_because(safe_coordinates); + float xLocs_j = xLocs[j].unverified_safe_because(safe_coordinates); + + d->mOffset.y = roundY ? NSToIntRound(-yLocs_j) * dev2appUnits + : -yLocs_j * dev2appUnits; + if (j == glyph_start) { + d->mAdvance = appAdvance; + clusterLoc = xLocs_j; + } else { + float dx = + rtl ? (xLocs_j - clusterLoc) : (xLocs_j - clusterLoc - adv); + d->mOffset.x = + roundX ? NSToIntRound(dx) * dev2appUnits : dx * dev2appUnits; + d->mAdvance = 0; + } + } + aShapedText->SetDetailedGlyphs(aOffset + offs, details.Length(), + details.Elements()); + } + + // check unexpected offset + uint32_t char_end = CopyAndVerifyOrFail(c.baseChar + c.nChars, + val <= aLength, &failedVerify); + // check overflow - ensure loop start is before the end + uint32_t char_start = + CopyAndVerifyOrFail(c.baseChar + 1, val <= char_end, &failedVerify); + if (failedVerify) { + return NS_ERROR_ILLEGAL_VALUE; + } + + for (uint32_t j = char_start; j < char_end; ++j) { + CompressedGlyph& g = charGlyphs[j]; + NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph"); + g.SetComplex(g.IsClusterStart(), false); + } + } + + sandbox_invoke(*mSandbox, gr_free_char_association, data); + return NS_OK; +} + +// for language tag validation - include list of tags from the IANA registry +#include "gfxLanguageTagList.cpp" + +nsTHashSet<uint32_t>* gfxGraphiteShaper::sLanguageTags; + +/*static*/ +uint32_t gfxGraphiteShaper::GetGraphiteTagForLang(const nsCString& aLang) { + int len = aLang.Length(); + if (len < 2) { + return 0; + } + + // convert primary language subtag to a left-packed, NUL-padded integer + // for the Graphite API + uint32_t grLang = 0; + for (int i = 0; i < 4; ++i) { + grLang <<= 8; + if (i < len) { + uint8_t ch = aLang[i]; + if (ch == '-') { + // found end of primary language subtag, truncate here + len = i; + continue; + } + if (ch < 'a' || ch > 'z') { + // invalid character in tag, so ignore it completely + return 0; + } + grLang += ch; + } + } + + // valid tags must have length = 2 or 3 + if (len < 2 || len > 3) { + return 0; + } + + if (!sLanguageTags) { + // store the registered IANA tags in a hash for convenient validation + sLanguageTags = new nsTHashSet<uint32_t>(ArrayLength(sLanguageTagList)); + for (const uint32_t* tag = sLanguageTagList; *tag != 0; ++tag) { + sLanguageTags->Insert(*tag); + } + } + + // only accept tags known in the IANA registry + if (sLanguageTags->Contains(grLang)) { + return grLang; + } + + return 0; +} + +/*static*/ +void gfxGraphiteShaper::Shutdown() { +#ifdef NS_FREE_PERMANENT_DATA + if (sLanguageTags) { + sLanguageTags->Clear(); + delete sLanguageTags; + sLanguageTags = nullptr; + } +#endif +} diff --git a/gfx/thebes/gfxGraphiteShaper.h b/gfx/thebes/gfxGraphiteShaper.h new file mode 100644 index 0000000000..ca2ae8fcb1 --- /dev/null +++ b/gfx/thebes/gfxGraphiteShaper.h @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_GRAPHITESHAPER_H +#define GFX_GRAPHITESHAPER_H + +#include "gfxFont.h" + +#include "mozilla/gfx/2D.h" +#include "nsTHashSet.h" + +#include "ThebesRLBoxTypes.h" + +struct gr_face; +struct gr_font; +struct gr_segment; + +class gfxGraphiteShaper : public gfxFontShaper { + public: + explicit gfxGraphiteShaper(gfxFont* aFont); + virtual ~gfxGraphiteShaper(); + + bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, RoundingFlags aRounding, + gfxShapedText* aShapedText) override; + + static void Shutdown(); + + protected: + nsresult SetGlyphsFromSegment(gfxShapedText* aShapedText, uint32_t aOffset, + uint32_t aLength, const char16_t* aText, + tainted_opaque_gr<char16_t*> t_aText, + tainted_opaque_gr<gr_segment*> aSegment, + RoundingFlags aRounding); + + // Graphite is run in a rlbox sandbox. Callback GrGetAdvance must be + // explicitly permitted. Since the sandbox is owned in gfxFontEntry class, + // gfxFontEntry needs access to the protected callback. + friend class gfxFontEntryCallbacks; + static tainted_opaque_gr<float> GrGetAdvance( + rlbox_sandbox_gr& sandbox, tainted_opaque_gr<const void*> appFontHandle, + tainted_opaque_gr<uint16_t> glyphid); + + tainted_opaque_gr<gr_face*> + mGrFace; // owned by the font entry; shaper must call + // gfxFontEntry::ReleaseGrFace when finished with it + tainted_opaque_gr<gr_font*> mGrFont; // owned by the shaper itself + + // All libGraphite functionality is sandboxed. This is the sandbox instance. + rlbox_sandbox_gr* mSandbox; + + // Holds the handle to the permitted callback into Firefox for the sandboxed + // libGraphite + sandbox_callback_gr<float (*)(const void*, uint16_t)>* mCallback; + + struct CallbackData { + // mFont is a pointer to the font that owns this shaper, so it will + // remain valid throughout our lifetime + gfxFont* MOZ_NON_OWNING_REF mFont; + }; + + CallbackData mCallbackData; + static thread_local CallbackData* tl_GrGetAdvanceData; + + bool mFallbackToSmallCaps; // special fallback for the petite-caps case + + // Convert HTML 'lang' (BCP47) to Graphite language code + static uint32_t GetGraphiteTagForLang(const nsCString& aLang); + static nsTHashSet<uint32_t>* sLanguageTags; +}; + +#endif /* GFX_GRAPHITESHAPER_H */ diff --git a/gfx/thebes/gfxHarfBuzzShaper.cpp b/gfx/thebes/gfxHarfBuzzShaper.cpp new file mode 100644 index 0000000000..8dbba29cf5 --- /dev/null +++ b/gfx/thebes/gfxHarfBuzzShaper.cpp @@ -0,0 +1,1864 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsString.h" +#include "gfxContext.h" +#include "gfxFontConstants.h" +#include "gfxHarfBuzzShaper.h" +#include "gfxFontUtils.h" +#include "gfxTextRun.h" +#include "mozilla/Sprintf.h" +#include "mozilla/intl/String.h" +#include "mozilla/intl/UnicodeProperties.h" +#include "mozilla/intl/UnicodeScriptCodes.h" +#include "nsUnicodeProperties.h" + +#include "harfbuzz/hb.h" +#include "harfbuzz/hb-ot.h" + +#include <algorithm> + +#define FloatToFixed(f) (65536 * (f)) +#define FixedToFloat(f) ((f) * (1.0 / 65536.0)) +// Right shifts of negative (signed) integers are undefined, as are overflows +// when converting unsigned to negative signed integers. +// (If speed were an issue we could make some 2's complement assumptions.) +#define FixedToIntRound(f) \ + ((f) > 0 ? ((32768 + (f)) >> 16) : -((32767 - (f)) >> 16)) + +using namespace mozilla; // for AutoSwap_* types +using namespace mozilla::unicode; // for Unicode property lookup + +/* + * Creation and destruction; on deletion, release any font tables we're holding + */ + +gfxHarfBuzzShaper::gfxHarfBuzzShaper(gfxFont* aFont) + : gfxFontShaper(aFont), + mHBFont(nullptr), + mBuffer(nullptr), + mCallbackData(), + mKernTable(nullptr), + mHmtxTable(nullptr), + mVmtxTable(nullptr), + mVORGTable(nullptr), + mLocaTable(nullptr), + mGlyfTable(nullptr), + mCmapTable(nullptr), + mCmapFormat(-1), + mSubtableOffset(0), + mUVSTableOffset(0), + mNumLongHMetrics(0), + mNumLongVMetrics(0), + mDefaultVOrg(-1.0), + mUseFontGetGlyph(aFont->ProvidesGetGlyph()), + mIsSymbolFont(false), + mUseFontGlyphWidths(aFont->ProvidesGlyphWidths()), + mInitialized(false), + mVerticalInitialized(false), + mUseVerticalPresentationForms(false), + mLoadedLocaGlyf(false), + mLocaLongOffsets(false) {} + +gfxHarfBuzzShaper::~gfxHarfBuzzShaper() { + // hb_*_destroy functions are safe to call on nullptr + hb_blob_destroy(mCmapTable); + hb_blob_destroy(mHmtxTable); + hb_blob_destroy(mKernTable); + hb_blob_destroy(mVmtxTable); + hb_blob_destroy(mVORGTable); + hb_blob_destroy(mLocaTable); + hb_blob_destroy(mGlyfTable); + hb_font_destroy(mHBFont); + hb_buffer_destroy(mBuffer); +} + +#define UNICODE_BMP_LIMIT 0x10000 + +hb_codepoint_t gfxHarfBuzzShaper::GetGlyphUncached( + hb_codepoint_t unicode) const { + hb_codepoint_t gid = 0; + + if (mUseFontGetGlyph) { + MutexAutoUnlock unlock(mCacheLock); + gid = mFont->GetGlyph(unicode, 0); + } else { + // we only instantiate a harfbuzz shaper if there's a cmap available + NS_ASSERTION(mCmapTable && (mCmapFormat > 0) && (mSubtableOffset > 0), + "cmap data not correctly set up, expect disaster"); + + uint32_t length; + const uint8_t* data = (const uint8_t*)hb_blob_get_data(mCmapTable, &length); + + switch (mCmapFormat) { + case 4: + gid = + unicode < UNICODE_BMP_LIMIT + ? gfxFontUtils::MapCharToGlyphFormat4( + data + mSubtableOffset, length - mSubtableOffset, unicode) + : 0; + break; + case 10: + gid = gfxFontUtils::MapCharToGlyphFormat10(data + mSubtableOffset, + unicode); + break; + case 12: + case 13: + gid = gfxFontUtils::MapCharToGlyphFormat12or13(data + mSubtableOffset, + unicode); + break; + default: + NS_WARNING("unsupported cmap format, glyphs will be missing"); + break; + } + } + + if (!gid) { + if (mIsSymbolFont) { + // For legacy MS Symbol fonts, we try mapping the given character code + // to the PUA range used by these fonts' cmaps. + if (auto pua = gfxFontUtils::MapLegacySymbolFontCharToPUA(unicode)) { + gid = GetGlyphUncached(pua); + } + if (gid) { + return 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 = GetGlyphUncached('-'); + break; + } + } + } + + return gid; +} + +hb_codepoint_t gfxHarfBuzzShaper::GetNominalGlyph( + hb_codepoint_t unicode) const { + MutexAutoLock lock(mCacheLock); + auto cached = mCmapCache->Lookup(unicode); + if (cached) { + return cached.Data().mGlyphId; + } + + // This call can temporarily unlock the cache if mUseFontGetGlyph is true. + hb_codepoint_t gid = GetGlyphUncached(unicode); + + if (mUseFontGetGlyph) { + // GetGlyphUncached may have invalidated our earlier cache lookup! + mCmapCache->Put(unicode, CmapCacheData{unicode, gid}); + } else { + cached.Set(CmapCacheData{unicode, gid}); + } + + return gid; +} + +unsigned int gfxHarfBuzzShaper::GetNominalGlyphs( + unsigned int count, const hb_codepoint_t* first_unicode, + unsigned int unicode_stride, hb_codepoint_t* first_glyph, + unsigned int glyph_stride) { + MutexAutoLock lock(mCacheLock); + unsigned int result = 0; + while (result < count) { + hb_codepoint_t usv = *first_unicode; + auto cached = mCmapCache->Lookup(usv); + if (cached) { + // Cache hit :) + *first_glyph = cached.Data().mGlyphId; + } else { + // Cache miss: call GetGlyphUncached (which handles things like symbol- + // encoding fallback) and fill in the cache entry with the result. + hb_codepoint_t gid = GetGlyphUncached(usv); + if (mUseFontGetGlyph) { + mCmapCache->Put(usv, CmapCacheData{usv, gid}); + } else { + cached.Set(CmapCacheData{usv, gid}); + } + *first_glyph = gid; + } + first_unicode = reinterpret_cast<const hb_codepoint_t*>( + reinterpret_cast<const char*>(first_unicode) + unicode_stride); + first_glyph = reinterpret_cast<hb_codepoint_t*>( + reinterpret_cast<char*>(first_glyph) + glyph_stride); + result++; + } + return result; +} + +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 unsigned int HBGetNominalGlyphs( + hb_font_t* font, void* font_data, unsigned int count, + const hb_codepoint_t* first_unicode, unsigned int unicode_stride, + hb_codepoint_t* first_glyph, unsigned int glyph_stride, void* user_data) { + const gfxHarfBuzzShaper::FontCallbackData* fcd = + static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data); + if (fcd->mShaper->UseVerticalPresentationForms()) { + return 0; + } + + return fcd->mShaper->GetNominalGlyphs(count, first_unicode, unicode_stride, + first_glyph, glyph_stride); +} + +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::GetGlyphHAdvanceUncached( + hb_codepoint_t glyph) const { + if (mUseFontGlyphWidths) { + return GetFont()->GetGlyphWidth(glyph); + } + + // 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::GetGlyphHAdvance(hb_codepoint_t glyph) const { + if (mUseFontGlyphWidths) { + MutexAutoLock lock(mCacheLock); + if (auto cached = mWidthCache->Lookup(glyph)) { + return cached.Data().mAdvance; + } + mCacheLock.Unlock(); + hb_position_t advance = GetFont()->GetGlyphWidth(glyph); + mCacheLock.Lock(); + mWidthCache->Put(glyph, WidthCacheData{glyph, advance}); + return advance; + } + + return GetGlyphHAdvanceUncached(glyph); +} + +void gfxHarfBuzzShaper::GetGlyphHAdvances(unsigned int count, + const hb_codepoint_t* first_glyph, + unsigned int glyph_stride, + hb_position_t* first_advance, + unsigned int advance_stride) const { + if (mUseFontGlyphWidths) { + // Take the cache lock here, hoping we'll be able to retrieve a bunch of + // widths from the cache for the cost of a single locking operation. + MutexAutoLock lock(mCacheLock); + for (unsigned int i = 0; i < count; ++i) { + hb_codepoint_t gid = *first_glyph; + if (auto cached = mWidthCache->Lookup(gid)) { + *first_advance = cached.Data().mAdvance; + } else { + // Unlock to avoid deadlock if the font needs internal locking. + mCacheLock.Unlock(); + hb_position_t advance = GetFont()->GetGlyphWidth(gid); + mCacheLock.Lock(); + mWidthCache->Put(gid, WidthCacheData{gid, advance}); + *first_advance = advance; + } + first_glyph = reinterpret_cast<const hb_codepoint_t*>( + reinterpret_cast<const char*>(first_glyph) + glyph_stride); + first_advance = reinterpret_cast<hb_position_t*>( + reinterpret_cast<char*>(first_advance) + advance_stride); + } + return; + } + + for (unsigned int i = 0; i < count; ++i) { + *first_advance = GetGlyphHAdvanceUncached(*first_glyph); + first_glyph = reinterpret_cast<const hb_codepoint_t*>( + reinterpret_cast<const char*>(first_glyph) + glyph_stride); + first_advance = reinterpret_cast<hb_position_t*>( + reinterpret_cast<char*>(first_advance) + advance_stride); + } +} + +hb_position_t gfxHarfBuzzShaper::GetGlyphVAdvance(hb_codepoint_t glyph) { + InitializeVertical(); + + if (!mVmtxTable) { + // Must be a "vertical" font that doesn't actually have vertical metrics. + // Return an invalid (negative) value to tell the caller to fall back to + // something else. + return -1; + } + + NS_ASSERTION(mNumLongVMetrics > 0, + "font is lacking metrics, we shouldn't be here"); + + if (glyph >= uint32_t(mNumLongVMetrics)) { + glyph = mNumLongVMetrics - 1; + } + + // glyph must be valid now, because we checked during initialization + // that mNumLongVMetrics is > 0, and that the metrics table is large enough + // to contain mNumLongVMetrics records + const ::GlyphMetrics* metrics = reinterpret_cast<const ::GlyphMetrics*>( + hb_blob_get_data(mVmtxTable, nullptr)); + return FloatToFixed(mFont->FUnitsToDevUnitsFactor() * + uint16_t(metrics->metrics[glyph].advanceWidth)); +} + +static hb_position_t 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); + return fcd->mShaper->GetGlyphHAdvance(glyph); +} + +static void HBGetGlyphHAdvances(hb_font_t* font, void* font_data, + unsigned int count, + const hb_codepoint_t* first_glyph, + unsigned int glyph_stride, + hb_position_t* first_advance, + unsigned int advance_stride, void* user_data) { + const gfxHarfBuzzShaper::FontCallbackData* fcd = + static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data); + fcd->mShaper->GetGlyphHAdvances(count, first_glyph, glyph_stride, + first_advance, advance_stride); +} + +static hb_position_t HBGetGlyphVAdvance(hb_font_t* font, void* font_data, + hb_codepoint_t glyph, void* user_data) { + const gfxHarfBuzzShaper::FontCallbackData* fcd = + static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data); + // Currently, we don't offer gfxFont subclasses a method to override this + // and provide hinted platform-specific vertical advances (analogous to the + // GetGlyphWidth method for horizontal advances). If that proves necessary, + // we'll add a new gfxFont method and call it from here. + hb_position_t advance = fcd->mShaper->GetGlyphVAdvance(glyph); + if (advance < 0) { + // Not available (e.g. broken metrics in the font); use a fallback value. + advance = FloatToFixed(fcd->mShaper->GetFont() + ->GetMetrics(nsFontMetrics::eVertical) + .aveCharWidth); + } + // We negate the value from GetGlyphVAdvance here because harfbuzz shapes + // with a coordinate system where positive is upwards, whereas the inline + // direction in which glyphs advance is downwards. + return -advance; +} + +struct VORG { + AutoSwap_PRUint16 majorVersion; + AutoSwap_PRUint16 minorVersion; + AutoSwap_PRInt16 defaultVertOriginY; + AutoSwap_PRUint16 numVertOriginYMetrics; +}; + +struct VORGrec { + AutoSwap_PRUint16 glyphIndex; + AutoSwap_PRInt16 vertOriginY; +}; + +static hb_bool_t HBGetGlyphVOrigin(hb_font_t* font, void* font_data, + hb_codepoint_t glyph, hb_position_t* x, + hb_position_t* y, void* user_data) { + const gfxHarfBuzzShaper::FontCallbackData* fcd = + static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data); + fcd->mShaper->GetGlyphVOrigin(glyph, x, y); + return true; +} + +void gfxHarfBuzzShaper::GetGlyphVOrigin(hb_codepoint_t aGlyph, + hb_position_t* aX, + hb_position_t* aY) const { + *aX = 0.5 * GetGlyphHAdvance(aGlyph); + + if (mVORGTable) { + // We checked in Initialize() that the VORG table is safely readable, + // so no length/bounds-check needed here. + const VORG* vorg = + reinterpret_cast<const VORG*>(hb_blob_get_data(mVORGTable, nullptr)); + + const VORGrec* lo = reinterpret_cast<const VORGrec*>(vorg + 1); + const VORGrec* hi = lo + uint16_t(vorg->numVertOriginYMetrics); + const VORGrec* limit = hi; + while (lo < hi) { + const VORGrec* mid = lo + (hi - lo) / 2; + if (uint16_t(mid->glyphIndex) < aGlyph) { + lo = mid + 1; + } else { + hi = mid; + } + } + + if (lo < limit && uint16_t(lo->glyphIndex) == aGlyph) { + *aY = FloatToFixed(GetFont()->FUnitsToDevUnitsFactor() * + int16_t(lo->vertOriginY)); + } else { + *aY = FloatToFixed(GetFont()->FUnitsToDevUnitsFactor() * + int16_t(vorg->defaultVertOriginY)); + } + return; + } + + if (mVmtxTable) { + bool emptyGlyf; + const Glyf* glyf = FindGlyf(aGlyph, &emptyGlyf); + if (glyf) { + if (emptyGlyf) { + *aY = 0; + return; + } + + const ::GlyphMetrics* metrics = reinterpret_cast<const ::GlyphMetrics*>( + hb_blob_get_data(mVmtxTable, nullptr)); + int16_t lsb; + if (aGlyph < hb_codepoint_t(mNumLongVMetrics)) { + // Glyph is covered by the first (advance & sidebearing) array + lsb = int16_t(metrics->metrics[aGlyph].lsb); + } else { + // Glyph is covered by the second (sidebearing-only) array + const AutoSwap_PRInt16* sidebearings = + reinterpret_cast<const AutoSwap_PRInt16*>( + &metrics->metrics[mNumLongVMetrics]); + lsb = int16_t(sidebearings[aGlyph - mNumLongVMetrics]); + } + *aY = FloatToFixed(mFont->FUnitsToDevUnitsFactor() * + (lsb + int16_t(glyf->yMax))); + return; + } else { + // XXX TODO: not a truetype font; need to get glyph extents + // via some other API? + // For now, fall through to default code below. + } + } + + if (mDefaultVOrg < 0.0) { + // XXX should we consider using OS/2 sTypo* metrics if available? + + gfxFontEntry::AutoTable hheaTable(GetFont()->GetFontEntry(), + TRUETYPE_TAG('h', 'h', 'e', 'a')); + if (hheaTable) { + uint32_t len; + const MetricsHeader* hhea = reinterpret_cast<const MetricsHeader*>( + hb_blob_get_data(hheaTable, &len)); + if (len >= sizeof(MetricsHeader)) { + // divide up the default advance we're using (1em) in proportion + // to ascender:descender from the hhea table + int16_t a = int16_t(hhea->ascender); + int16_t d = int16_t(hhea->descender); + mDefaultVOrg = FloatToFixed(GetFont()->GetAdjustedSize() * a / (a - d)); + } + } + + if (mDefaultVOrg < 0.0) { + // Last resort, for non-sfnt fonts: get the horizontal metrics and + // compute a default VOrg from their ascent and descent. + const gfxFont::Metrics& mtx = mFont->GetHorizontalMetrics(); + gfxFloat advance = + mFont->GetMetrics(nsFontMetrics::eVertical).aveCharWidth; + gfxFloat ascent = mtx.emAscent; + gfxFloat height = ascent + mtx.emDescent; + // vOrigin that will place the glyph so that its origin is shifted + // down most of the way within overall (vertical) advance, in + // proportion to the font ascent as a part of the overall font + // height. + mDefaultVOrg = FloatToFixed(advance * ascent / height); + } + } + + *aY = mDefaultVOrg; +} + +static hb_bool_t HBGetGlyphExtents(hb_font_t* font, void* font_data, + hb_codepoint_t glyph, + hb_glyph_extents_t* extents, + void* user_data) { + const gfxHarfBuzzShaper::FontCallbackData* fcd = + static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data); + return fcd->mShaper->GetGlyphExtents(glyph, extents); +} + +// Find the data for glyph ID |aGlyph| in the 'glyf' table, if present. +// Returns null if not found, otherwise pointer to the beginning of the +// glyph's data. Sets aEmptyGlyf true if there is no actual data; +// otherwise, it's guaranteed that we can read at least the bounding box. +const gfxHarfBuzzShaper::Glyf* gfxHarfBuzzShaper::FindGlyf( + hb_codepoint_t aGlyph, bool* aEmptyGlyf) const { + if (!mLoadedLocaGlyf) { + mLoadedLocaGlyf = true; // only try this once; if it fails, this + // isn't a truetype font + gfxFontEntry* entry = mFont->GetFontEntry(); + uint32_t len; + gfxFontEntry::AutoTable headTable(entry, TRUETYPE_TAG('h', 'e', 'a', 'd')); + if (!headTable) { + return nullptr; + } + const HeadTable* head = + reinterpret_cast<const HeadTable*>(hb_blob_get_data(headTable, &len)); + if (len < sizeof(HeadTable)) { + return nullptr; + } + mLocaLongOffsets = int16_t(head->indexToLocFormat) > 0; + mLocaTable = entry->GetFontTable(TRUETYPE_TAG('l', 'o', 'c', 'a')); + mGlyfTable = entry->GetFontTable(TRUETYPE_TAG('g', 'l', 'y', 'f')); + } + + if (!mLocaTable || !mGlyfTable) { + // it's not a truetype font + return nullptr; + } + + uint32_t offset; // offset of glyph record in the 'glyf' table + uint32_t len; + const char* data = hb_blob_get_data(mLocaTable, &len); + if (mLocaLongOffsets) { + if ((aGlyph + 1) * sizeof(AutoSwap_PRUint32) > len) { + return nullptr; + } + const AutoSwap_PRUint32* offsets = + reinterpret_cast<const AutoSwap_PRUint32*>(data); + offset = offsets[aGlyph]; + *aEmptyGlyf = (offset == uint16_t(offsets[aGlyph + 1])); + } else { + if ((aGlyph + 1) * sizeof(AutoSwap_PRUint16) > len) { + return nullptr; + } + const AutoSwap_PRUint16* offsets = + reinterpret_cast<const AutoSwap_PRUint16*>(data); + offset = uint16_t(offsets[aGlyph]); + *aEmptyGlyf = (offset == uint16_t(offsets[aGlyph + 1])); + offset *= 2; + } + + data = hb_blob_get_data(mGlyfTable, &len); + if (offset + sizeof(Glyf) > len) { + return nullptr; + } + + return reinterpret_cast<const Glyf*>(data + offset); +} + +hb_bool_t gfxHarfBuzzShaper::GetGlyphExtents( + hb_codepoint_t aGlyph, hb_glyph_extents_t* aExtents) const { + bool emptyGlyf; + const Glyf* glyf = FindGlyf(aGlyph, &emptyGlyf); + if (!glyf) { + // TODO: for non-truetype fonts, get extents some other way? + return false; + } + + if (emptyGlyf) { + aExtents->x_bearing = 0; + aExtents->y_bearing = 0; + aExtents->width = 0; + aExtents->height = 0; + return true; + } + + double f = mFont->FUnitsToDevUnitsFactor(); + aExtents->x_bearing = FloatToFixed(int16_t(glyf->xMin) * f); + aExtents->width = + FloatToFixed((int16_t(glyf->xMax) - int16_t(glyf->xMin)) * f); + + // Our y-coordinates are positive-downwards, whereas harfbuzz assumes + // positive-upwards; hence the apparently-reversed subtractions here. + aExtents->y_bearing = FloatToFixed(int16_t(glyf->yMax) * f - + mFont->GetHorizontalMetrics().emAscent); + aExtents->height = + FloatToFixed((int16_t(glyf->yMin) - int16_t(glyf->yMax)) * f); + + return true; +} + +static hb_bool_t HBGetContourPoint(hb_font_t* font, void* font_data, + unsigned int point_index, + hb_codepoint_t glyph, hb_position_t* x, + hb_position_t* y, void* user_data) { + /* not yet implemented - no support for used of hinted contour points + to fine-tune anchor positions in GPOS AnchorFormat2 */ + return false; +} + +struct KernHeaderFmt0 { + AutoSwap_PRUint16 nPairs; + AutoSwap_PRUint16 searchRange; + AutoSwap_PRUint16 entrySelector; + AutoSwap_PRUint16 rangeShift; +}; + +struct KernPair { + AutoSwap_PRUint16 left; + AutoSwap_PRUint16 right; + AutoSwap_PRInt16 value; +}; + +// Find a kern pair in a Format 0 subtable. +// The aSubtable parameter points to the subtable itself, NOT its header, +// as the header structure differs between Windows and Mac (v0 and v1.0) +// versions of the 'kern' table. +// aSubtableLen is the length of the subtable EXCLUDING its header. +// If the pair <aFirstGlyph,aSecondGlyph> is found, the kerning value is +// added to aValue, so that multiple subtables can accumulate a total +// kerning value for a given pair. +static void GetKernValueFmt0(const void* aSubtable, uint32_t aSubtableLen, + uint16_t aFirstGlyph, uint16_t aSecondGlyph, + int32_t& aValue, bool aIsOverride = false, + bool aIsMinimum = false) { + const KernHeaderFmt0* hdr = + reinterpret_cast<const KernHeaderFmt0*>(aSubtable); + + const KernPair* lo = reinterpret_cast<const KernPair*>(hdr + 1); + const KernPair* hi = lo + uint16_t(hdr->nPairs); + const KernPair* limit = hi; + + if (reinterpret_cast<const char*>(aSubtable) + aSubtableLen < + reinterpret_cast<const char*>(hi)) { + // subtable is not large enough to contain the claimed number + // of kern pairs, so just ignore it + return; + } + +#define KERN_PAIR_KEY(l, r) (uint32_t((uint16_t(l) << 16) + uint16_t(r))) + + uint32_t key = KERN_PAIR_KEY(aFirstGlyph, aSecondGlyph); + while (lo < hi) { + const KernPair* mid = lo + (hi - lo) / 2; + if (KERN_PAIR_KEY(mid->left, mid->right) < key) { + lo = mid + 1; + } else { + hi = mid; + } + } + + if (lo < limit && KERN_PAIR_KEY(lo->left, lo->right) == key) { + if (aIsOverride) { + aValue = int16_t(lo->value); + } else if (aIsMinimum) { + aValue = std::max(aValue, int32_t(lo->value)); + } else { + aValue += int16_t(lo->value); + } + } +} + +// Get kerning value from Apple (version 1.0) kern table, +// subtable format 2 (simple N x M array of kerning values) + +// See http://developer.apple.com/fonts/TTRefMan/RM06/Chap6kern.html +// for details of version 1.0 format 2 subtable. + +struct KernHeaderVersion1Fmt2 { + KernTableSubtableHeaderVersion1 header; + AutoSwap_PRUint16 rowWidth; + AutoSwap_PRUint16 leftOffsetTable; + AutoSwap_PRUint16 rightOffsetTable; + AutoSwap_PRUint16 array; +}; + +struct KernClassTableHdr { + AutoSwap_PRUint16 firstGlyph; + AutoSwap_PRUint16 nGlyphs; + AutoSwap_PRUint16 offsets[1]; // actually an array of nGlyphs entries +}; + +static int16_t GetKernValueVersion1Fmt2(const void* aSubtable, + uint32_t aSubtableLen, + uint16_t aFirstGlyph, + uint16_t aSecondGlyph) { + if (aSubtableLen < sizeof(KernHeaderVersion1Fmt2)) { + return 0; + } + + const char* base = reinterpret_cast<const char*>(aSubtable); + const char* subtableEnd = base + aSubtableLen; + + const KernHeaderVersion1Fmt2* h = + reinterpret_cast<const KernHeaderVersion1Fmt2*>(aSubtable); + uint32_t offset = h->array; + + const KernClassTableHdr* leftClassTable = + reinterpret_cast<const KernClassTableHdr*>(base + + uint16_t(h->leftOffsetTable)); + if (reinterpret_cast<const char*>(leftClassTable) + + sizeof(KernClassTableHdr) > + subtableEnd) { + return 0; + } + if (aFirstGlyph >= uint16_t(leftClassTable->firstGlyph)) { + aFirstGlyph -= uint16_t(leftClassTable->firstGlyph); + if (aFirstGlyph < uint16_t(leftClassTable->nGlyphs)) { + if (reinterpret_cast<const char*>(leftClassTable) + + sizeof(KernClassTableHdr) + aFirstGlyph * sizeof(uint16_t) >= + subtableEnd) { + return 0; + } + offset = uint16_t(leftClassTable->offsets[aFirstGlyph]); + } + } + + const KernClassTableHdr* rightClassTable = + reinterpret_cast<const KernClassTableHdr*>(base + + uint16_t(h->rightOffsetTable)); + if (reinterpret_cast<const char*>(rightClassTable) + + sizeof(KernClassTableHdr) > + subtableEnd) { + return 0; + } + if (aSecondGlyph >= uint16_t(rightClassTable->firstGlyph)) { + aSecondGlyph -= uint16_t(rightClassTable->firstGlyph); + if (aSecondGlyph < uint16_t(rightClassTable->nGlyphs)) { + if (reinterpret_cast<const char*>(rightClassTable) + + sizeof(KernClassTableHdr) + aSecondGlyph * sizeof(uint16_t) >= + subtableEnd) { + return 0; + } + offset += uint16_t(rightClassTable->offsets[aSecondGlyph]); + } + } + + const AutoSwap_PRInt16* pval = + reinterpret_cast<const AutoSwap_PRInt16*>(base + offset); + if (reinterpret_cast<const char*>(pval + 1) >= subtableEnd) { + return 0; + } + return *pval; +} + +// Get kerning value from Apple (version 1.0) kern table, +// subtable format 3 (simple N x M array of kerning values) + +// See http://developer.apple.com/fonts/TTRefMan/RM06/Chap6kern.html +// for details of version 1.0 format 3 subtable. + +struct KernHeaderVersion1Fmt3 { + KernTableSubtableHeaderVersion1 header; + AutoSwap_PRUint16 glyphCount; + uint8_t kernValueCount; + uint8_t leftClassCount; + uint8_t rightClassCount; + uint8_t flags; +}; + +static int16_t GetKernValueVersion1Fmt3(const void* aSubtable, + uint32_t aSubtableLen, + uint16_t aFirstGlyph, + uint16_t aSecondGlyph) { + // check that we can safely read the header fields + if (aSubtableLen < sizeof(KernHeaderVersion1Fmt3)) { + return 0; + } + + const KernHeaderVersion1Fmt3* hdr = + reinterpret_cast<const KernHeaderVersion1Fmt3*>(aSubtable); + if (hdr->flags != 0) { + return 0; + } + + uint16_t glyphCount = hdr->glyphCount; + + // check that table is large enough for the arrays + if (sizeof(KernHeaderVersion1Fmt3) + hdr->kernValueCount * sizeof(int16_t) + + glyphCount + glyphCount + hdr->leftClassCount * hdr->rightClassCount > + aSubtableLen) { + return 0; + } + + if (aFirstGlyph >= glyphCount || aSecondGlyph >= glyphCount) { + // glyphs are out of range for the class tables + return 0; + } + + // get pointers to the four arrays within the subtable + const AutoSwap_PRInt16* kernValue = + reinterpret_cast<const AutoSwap_PRInt16*>(hdr + 1); + const uint8_t* leftClass = + reinterpret_cast<const uint8_t*>(kernValue + hdr->kernValueCount); + const uint8_t* rightClass = leftClass + glyphCount; + const uint8_t* kernIndex = rightClass + glyphCount; + + uint8_t lc = leftClass[aFirstGlyph]; + uint8_t rc = rightClass[aSecondGlyph]; + if (lc >= hdr->leftClassCount || rc >= hdr->rightClassCount) { + return 0; + } + + uint8_t ki = kernIndex[leftClass[aFirstGlyph] * hdr->rightClassCount + + rightClass[aSecondGlyph]]; + if (ki >= hdr->kernValueCount) { + return 0; + } + + return kernValue[ki]; +} + +#define KERN0_COVERAGE_HORIZONTAL 0x0001 +#define KERN0_COVERAGE_MINIMUM 0x0002 +#define KERN0_COVERAGE_CROSS_STREAM 0x0004 +#define KERN0_COVERAGE_OVERRIDE 0x0008 +#define KERN0_COVERAGE_RESERVED 0x00F0 + +#define KERN1_COVERAGE_VERTICAL 0x8000 +#define KERN1_COVERAGE_CROSS_STREAM 0x4000 +#define KERN1_COVERAGE_VARIATION 0x2000 +#define KERN1_COVERAGE_RESERVED 0x1F00 + +hb_position_t gfxHarfBuzzShaper::GetHKerning(uint16_t aFirstGlyph, + uint16_t aSecondGlyph) const { + // We want to ignore any kern pairs involving <space>, because we are + // handling words in isolation, the only space characters seen here are + // the ones artificially added by the textRun code. + uint32_t spaceGlyph = mFont->GetSpaceGlyph(); + if (aFirstGlyph == spaceGlyph || aSecondGlyph == spaceGlyph) { + return 0; + } + + if (!mKernTable) { + mKernTable = + mFont->GetFontEntry()->GetFontTable(TRUETYPE_TAG('k', 'e', 'r', 'n')); + if (!mKernTable) { + mKernTable = hb_blob_get_empty(); + } + } + + uint32_t len; + const char* base = hb_blob_get_data(mKernTable, &len); + if (len < sizeof(KernTableVersion0)) { + return 0; + } + int32_t value = 0; + + // First try to interpret as "version 0" kern table + // (see http://www.microsoft.com/typography/otspec/kern.htm) + const KernTableVersion0* kern0 = + reinterpret_cast<const KernTableVersion0*>(base); + if (uint16_t(kern0->version) == 0) { + uint16_t nTables = kern0->nTables; + uint32_t offs = sizeof(KernTableVersion0); + for (uint16_t i = 0; i < nTables; ++i) { + if (offs + sizeof(KernTableSubtableHeaderVersion0) > len) { + break; + } + const KernTableSubtableHeaderVersion0* st0 = + reinterpret_cast<const KernTableSubtableHeaderVersion0*>(base + offs); + uint16_t subtableLen = uint16_t(st0->length); + if (offs + subtableLen > len) { + break; + } + offs += subtableLen; + uint16_t coverage = st0->coverage; + if (!(coverage & KERN0_COVERAGE_HORIZONTAL)) { + // we only care about horizontal kerning (for now) + continue; + } + if (coverage & (KERN0_COVERAGE_CROSS_STREAM | KERN0_COVERAGE_RESERVED)) { + // we don't support cross-stream kerning, and + // reserved bits should be zero; + // ignore the subtable if not + continue; + } + uint8_t format = (coverage >> 8); + switch (format) { + case 0: + GetKernValueFmt0(st0 + 1, subtableLen - sizeof(*st0), aFirstGlyph, + aSecondGlyph, value, + (coverage & KERN0_COVERAGE_OVERRIDE) != 0, + (coverage & KERN0_COVERAGE_MINIMUM) != 0); + break; + default: + // TODO: implement support for other formats, + // if they're ever used in practice +#if DEBUG + { + char buf[1024]; + SprintfLiteral(buf, + "unknown kern subtable in %s: " + "ver 0 format %d\n", + mFont->GetName().get(), format); + NS_WARNING(buf); + } +#endif + break; + } + } + } else { + // It wasn't a "version 0" table; check if it is Apple version 1.0 + // (see http://developer.apple.com/fonts/TTRefMan/RM06/Chap6kern.html) + const KernTableVersion1* kern1 = + reinterpret_cast<const KernTableVersion1*>(base); + if (uint32_t(kern1->version) == 0x00010000) { + uint32_t nTables = kern1->nTables; + uint32_t offs = sizeof(KernTableVersion1); + for (uint32_t i = 0; i < nTables; ++i) { + if (offs + sizeof(KernTableSubtableHeaderVersion1) > len) { + break; + } + const KernTableSubtableHeaderVersion1* st1 = + reinterpret_cast<const KernTableSubtableHeaderVersion1*>(base + + offs); + uint32_t subtableLen = uint32_t(st1->length); + offs += subtableLen; + uint16_t coverage = st1->coverage; + if (coverage & (KERN1_COVERAGE_VERTICAL | KERN1_COVERAGE_CROSS_STREAM | + KERN1_COVERAGE_VARIATION | KERN1_COVERAGE_RESERVED)) { + // we only care about horizontal kerning (for now), + // we don't support cross-stream kerning, + // we don't support variations, + // reserved bits should be zero; + // ignore the subtable if not + continue; + } + uint8_t format = (coverage & 0xff); + switch (format) { + case 0: + GetKernValueFmt0(st1 + 1, subtableLen - sizeof(*st1), aFirstGlyph, + aSecondGlyph, value); + break; + case 2: + value = GetKernValueVersion1Fmt2(st1, subtableLen, aFirstGlyph, + aSecondGlyph); + break; + case 3: + value = GetKernValueVersion1Fmt3(st1, subtableLen, aFirstGlyph, + aSecondGlyph); + break; + default: + // TODO: implement support for other formats. + // Note that format 1 cannot be supported here, + // as it requires the full glyph array to run the FSM, + // not just the current glyph pair. +#if DEBUG + { + char buf[1024]; + SprintfLiteral(buf, + "unknown kern subtable in %s: " + "ver 0 format %d\n", + mFont->GetName().get(), format); + NS_WARNING(buf); + } +#endif + break; + } + } + } + } + + if (value != 0) { + return FloatToFixed(mFont->FUnitsToDevUnitsFactor() * value); + } + return 0; +} + +static hb_position_t HBGetHKerning(hb_font_t* font, void* font_data, + hb_codepoint_t first_glyph, + hb_codepoint_t second_glyph, + void* user_data) { + const gfxHarfBuzzShaper::FontCallbackData* fcd = + static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data); + return fcd->mShaper->GetHKerning(first_glyph, second_glyph); +} + +/* + * HarfBuzz unicode property callbacks + */ + +static hb_codepoint_t HBGetMirroring(hb_unicode_funcs_t* ufuncs, + hb_codepoint_t aCh, void* user_data) { + return intl::UnicodeProperties::CharMirror(aCh); +} + +static hb_unicode_general_category_t HBGetGeneralCategory( + hb_unicode_funcs_t* ufuncs, hb_codepoint_t aCh, void* user_data) { + return hb_unicode_general_category_t(GetGeneralCategory(aCh)); +} + +static hb_script_t HBGetScript(hb_unicode_funcs_t* ufuncs, hb_codepoint_t aCh, + void* user_data) { + return hb_script_t( + GetScriptTagForCode(intl::UnicodeProperties::GetScriptCode(aCh))); +} + +static hb_unicode_combining_class_t HBGetCombiningClass( + hb_unicode_funcs_t* ufuncs, hb_codepoint_t aCh, void* user_data) { + return hb_unicode_combining_class_t( + intl::UnicodeProperties::GetCombiningClass(aCh)); +} + +static hb_bool_t HBUnicodeCompose(hb_unicode_funcs_t* ufuncs, hb_codepoint_t a, + hb_codepoint_t b, hb_codepoint_t* ab, + void* user_data) { + char32_t ch = intl::String::ComposePairNFC(a, b); + if (ch > 0) { + *ab = ch; + return true; + } + + return false; +} + +static hb_bool_t HBUnicodeDecompose(hb_unicode_funcs_t* ufuncs, + hb_codepoint_t ab, hb_codepoint_t* a, + hb_codepoint_t* b, void* user_data) { +#ifdef MOZ_WIDGET_ANDROID + // Hack for the SamsungDevanagari font, bug 1012365: + // support U+0972 by decomposing it. + if (ab == 0x0972) { + *a = 0x0905; + *b = 0x0945; + return true; + } +#endif + + char32_t decomp[2] = {0}; + if (intl::String::DecomposeRawNFD(ab, decomp)) { + if (decomp[1] || decomp[0] != ab) { + *a = decomp[0]; + *b = decomp[1]; + return true; + } + } + + return false; +} + +static void AddOpenTypeFeature(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_nominal_glyphs_func(sHBFontFuncs, HBGetNominalGlyphs, + 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_h_advances_func(sHBFontFuncs, HBGetGlyphHAdvances, + nullptr, nullptr); + hb_font_funcs_set_glyph_v_advance_func(sHBFontFuncs, HBGetGlyphVAdvance, + nullptr, nullptr); + hb_font_funcs_set_glyph_v_origin_func(sHBFontFuncs, HBGetGlyphVOrigin, + nullptr, nullptr); + hb_font_funcs_set_glyph_extents_func(sHBFontFuncs, HBGetGlyphExtents, + nullptr, nullptr); + hb_font_funcs_set_glyph_contour_point_func(sHBFontFuncs, HBGetContourPoint, + nullptr, nullptr); + hb_font_funcs_set_glyph_h_kerning_func(sHBFontFuncs, HBGetHKerning, nullptr, + nullptr); + hb_font_funcs_make_immutable(sHBFontFuncs); + + sNominalGlyphFunc = hb_font_funcs_create(); + hb_font_funcs_set_nominal_glyph_func(sNominalGlyphFunc, HBGetNominalGlyph, + nullptr, nullptr); + hb_font_funcs_make_immutable(sNominalGlyphFunc); + + sHBUnicodeFuncs = hb_unicode_funcs_create(hb_unicode_funcs_get_empty()); + hb_unicode_funcs_set_mirroring_func(sHBUnicodeFuncs, HBGetMirroring, + nullptr, nullptr); + hb_unicode_funcs_set_script_func(sHBUnicodeFuncs, HBGetScript, nullptr, + nullptr); + hb_unicode_funcs_set_general_category_func( + sHBUnicodeFuncs, HBGetGeneralCategory, nullptr, nullptr); + hb_unicode_funcs_set_combining_class_func( + sHBUnicodeFuncs, HBGetCombiningClass, nullptr, nullptr); + hb_unicode_funcs_set_compose_func(sHBUnicodeFuncs, HBUnicodeCompose, + nullptr, nullptr); + hb_unicode_funcs_set_decompose_func(sHBUnicodeFuncs, HBUnicodeDecompose, + nullptr, nullptr); + hb_unicode_funcs_make_immutable(sHBUnicodeFuncs); + } + + gfxFontEntry* entry = mFont->GetFontEntry(); + if (!mUseFontGetGlyph) { + // get the cmap table and find offset to our subtable + mCmapTable = entry->GetFontTable(TRUETYPE_TAG('c', 'm', 'a', 'p')); + if (!mCmapTable) { + NS_WARNING("failed to load cmap, glyphs will be missing"); + return false; + } + uint32_t len; + const uint8_t* data = (const uint8_t*)hb_blob_get_data(mCmapTable, &len); + mCmapFormat = gfxFontUtils::FindPreferredSubtable( + data, len, &mSubtableOffset, &mUVSTableOffset, &mIsSymbolFont); + if (mCmapFormat <= 0) { + return false; + } + } + + // We don't need to take the cache lock here, as we're just initializing the + // shaper and no other thread can yet be using it. + MOZ_PUSH_IGNORE_THREAD_SAFETY + mCmapCache = MakeUnique<CmapCache>(); + + if (mUseFontGlyphWidths) { + mWidthCache = MakeUnique<WidthCache>(); + } else { + // 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; + } + } + MOZ_POP_THREAD_SAFETY + + mBuffer = hb_buffer_create(); + hb_buffer_set_unicode_funcs(mBuffer, sHBUnicodeFuncs); + hb_buffer_set_cluster_level(mBuffer, + HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); + + auto* funcs = + mFont->GetFontEntry()->HasFontTable(TRUETYPE_TAG('C', 'F', 'F', ' ')) + ? sNominalGlyphFunc + : sHBFontFuncs; + mHBFont = CreateHBFont(mFont, funcs, &mCallbackData); + + return true; +} + +hb_font_t* gfxHarfBuzzShaper::CreateHBFont(gfxFont* aFont, + hb_font_funcs_t* aFontFuncs, + FontCallbackData* aCallbackData) { + auto face(aFont->GetFontEntry()->GetHBFace()); + hb_font_t* result = hb_font_create(face); + + if (aFontFuncs && aCallbackData) { + if (aFontFuncs == sNominalGlyphFunc) { + hb_font_t* subfont = hb_font_create_sub_font(result); + hb_font_destroy(result); + result = subfont; + } + hb_font_set_funcs(result, aFontFuncs, aCallbackData, nullptr); + } + hb_font_set_ppem(result, aFont->GetAdjustedSize(), aFont->GetAdjustedSize()); + uint32_t scale = FloatToFixed(aFont->GetAdjustedSize()); // 16.16 fixed-point + hb_font_set_scale(result, scale, scale); + + AutoTArray<gfxFontVariation, 8> vars; + aFont->GetFontEntry()->GetVariationsForStyle(vars, *aFont->GetStyle()); + if (vars.Length() > 0) { + // Fortunately, the hb_variation_t struct is compatible with our + // gfxFontVariation, so we can simply cast here. + static_assert( + sizeof(gfxFontVariation) == sizeof(hb_variation_t) && + offsetof(gfxFontVariation, mTag) == offsetof(hb_variation_t, tag) && + offsetof(gfxFontVariation, mValue) == + offsetof(hb_variation_t, value), + "Gecko vs HarfBuzz struct mismatch!"); + auto hbVars = reinterpret_cast<const hb_variation_t*>(vars.Elements()); + hb_font_set_variations(result, hbVars, vars.Length()); + } + + return result; +} + +bool gfxHarfBuzzShaper::LoadHmtxTable() { + // Read mNumLongHMetrics from metrics-head table without caching its + // blob, and preload/cache the metrics table. + gfxFontEntry* entry = mFont->GetFontEntry(); + gfxFontEntry::AutoTable hheaTable(entry, TRUETYPE_TAG('h', 'h', 'e', 'a')); + if (hheaTable) { + uint32_t len; + const MetricsHeader* hhea = reinterpret_cast<const MetricsHeader*>( + hb_blob_get_data(hheaTable, &len)); + if (len >= sizeof(MetricsHeader)) { + mNumLongHMetrics = hhea->numOfLongMetrics; + if (mNumLongHMetrics > 0 && int16_t(hhea->metricDataFormat) == 0) { + // no point reading metrics if number of entries is zero! + // in that case, we won't be able to use this font + // (this method will return FALSE below if mHmtxTable + // is null) + mHmtxTable = entry->GetFontTable(TRUETYPE_TAG('h', 'm', 't', 'x')); + if (mHmtxTable && hb_blob_get_length(mHmtxTable) < + mNumLongHMetrics * sizeof(LongMetric)) { + // metrics table is not large enough for the claimed + // number of entries: invalid, do not use. + hb_blob_destroy(mHmtxTable); + mHmtxTable = nullptr; + } + } + } + } + if (!mHmtxTable) { + return false; + } + return true; +} + +void gfxHarfBuzzShaper::InitializeVertical() { + // We only do this once. If we don't have a mHmtxTable after that, + // we'll be making up fallback metrics. + if (mVerticalInitialized) { + return; + } + mVerticalInitialized = true; + + if (!mHmtxTable) { + if (!LoadHmtxTable()) { + return; + } + } + + // Load vertical metrics if present in the font; if not, we'll synthesize + // vertical glyph advances based on (horizontal) ascent/descent metrics. + gfxFontEntry* entry = mFont->GetFontEntry(); + gfxFontEntry::AutoTable vheaTable(entry, TRUETYPE_TAG('v', 'h', 'e', 'a')); + if (vheaTable) { + uint32_t len; + const MetricsHeader* vhea = reinterpret_cast<const MetricsHeader*>( + hb_blob_get_data(vheaTable, &len)); + if (len >= sizeof(MetricsHeader)) { + mNumLongVMetrics = vhea->numOfLongMetrics; + gfxFontEntry::AutoTable maxpTable(entry, + TRUETYPE_TAG('m', 'a', 'x', 'p')); + int numGlyphs = -1; // invalid if we fail to read 'maxp' + if (maxpTable && + hb_blob_get_length(maxpTable) >= sizeof(MaxpTableHeader)) { + const MaxpTableHeader* maxp = reinterpret_cast<const MaxpTableHeader*>( + hb_blob_get_data(maxpTable, nullptr)); + numGlyphs = uint16_t(maxp->numGlyphs); + } + if (mNumLongVMetrics > 0 && mNumLongVMetrics <= numGlyphs && + int16_t(vhea->metricDataFormat) == 0) { + mVmtxTable = entry->GetFontTable(TRUETYPE_TAG('v', 'm', 't', 'x')); + if (mVmtxTable && + hb_blob_get_length(mVmtxTable) < + mNumLongVMetrics * sizeof(LongMetric) + + (numGlyphs - mNumLongVMetrics) * sizeof(int16_t)) { + // metrics table is not large enough for the claimed + // number of entries: invalid, do not use. + hb_blob_destroy(mVmtxTable); + mVmtxTable = nullptr; + } + } + } + } + + // For CFF fonts only, load a VORG table if present. + if (entry->HasFontTable(TRUETYPE_TAG('C', 'F', 'F', ' '))) { + mVORGTable = entry->GetFontTable(TRUETYPE_TAG('V', 'O', 'R', 'G')); + if (mVORGTable) { + uint32_t len; + const VORG* vorg = + reinterpret_cast<const VORG*>(hb_blob_get_data(mVORGTable, &len)); + if (len < sizeof(VORG) || uint16_t(vorg->majorVersion) != 1 || + uint16_t(vorg->minorVersion) != 0 || + len < sizeof(VORG) + + uint16_t(vorg->numVertOriginYMetrics) * sizeof(VORGrec)) { + // VORG table is an unknown version, or not large enough + // to be valid -- discard it. + NS_WARNING("discarding invalid VORG table"); + hb_blob_destroy(mVORGTable); + mVORGTable = nullptr; + } + } + } +} + +bool gfxHarfBuzzShaper::ShapeText(DrawTarget* aDrawTarget, + const char16_t* aText, uint32_t aOffset, + uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, + RoundingFlags aRounding, + gfxShapedText* aShapedText) { + mUseVerticalPresentationForms = false; + + if (!Initialize()) { + return false; + } + + if (aVertical) { + InitializeVertical(); + if (!mFont->GetFontEntry()->SupportsOpenTypeFeature( + aScript, HB_TAG('v', 'e', 'r', 't'))) { + mUseVerticalPresentationForms = true; + } + } + + const gfxFontStyle* style = mFont->GetStyle(); + + // determine whether petite-caps falls back to small-caps + bool addSmallCaps = false; + if (style->variantCaps != NS_FONT_VARIANT_CAPS_NORMAL) { + switch (style->variantCaps) { + case NS_FONT_VARIANT_CAPS_ALLPETITE: + case NS_FONT_VARIANT_CAPS_PETITECAPS: + bool synLower, synUpper; + mFont->SupportsVariantCaps(aScript, style->variantCaps, addSmallCaps, + synLower, synUpper); + break; + default: + break; + } + } + + gfxFontEntry* entry = mFont->GetFontEntry(); + + // insert any merged features into hb_feature array + AutoTArray<hb_feature_t, 20> features; + MergeFontFeatures(style, entry->mFeatureSettings, + aShapedText->DisableLigatures(), entry->FamilyName(), + addSmallCaps, AddOpenTypeFeature, &features); + + // For CJK script, match kerning and proportional-alternates (palt) features + // (and their vertical counterparts) as per spec: + // https://learn.microsoft.com/en-us/typography/opentype/spec/features_pt#tag-palt + // and disable kerning by default (for font-kerning:auto). + if (gfxTextRun::IsCJKScript(aScript)) { + hb_tag_t kern = + aVertical ? HB_TAG('v', 'k', 'r', 'n') : HB_TAG('k', 'e', 'r', 'n'); + hb_tag_t alt = + aVertical ? HB_TAG('v', 'p', 'a', 'l') : HB_TAG('p', 'a', 'l', 't'); + struct Cmp { + bool Equals(const hb_feature_t& a, const hb_tag_t& b) const { + return a.tag == b; + } + }; + constexpr auto NoIndex = nsTArray<hb_feature_t>::NoIndex; + nsTArray<hb_feature_t>::index_type i = features.IndexOf(kern, 0, Cmp()); + if (i == NoIndex) { + // Kerning was not explicitly set; override harfbuzz's default to disable + // it. + features.AppendElement(hb_feature_t{kern, 0, HB_FEATURE_GLOBAL_START, + HB_FEATURE_GLOBAL_END}); + } else if (features[i].value) { + // If kerning was explicitly enabled), we also turn on proportional + // alternates, as per the OpenType feature registry. + // Bug 1798297: for the Yu Gothic UI font, we don't do this, because its + // 'palt' feature produces badly-spaced (overcrowded) kana glyphs. + if (!entry->FamilyName().EqualsLiteral("Yu Gothic UI")) { + if (features.IndexOf(alt, 0, Cmp()) == NoIndex) { + features.AppendElement(hb_feature_t{alt, 1, HB_FEATURE_GLOBAL_START, + HB_FEATURE_GLOBAL_END}); + } + } + } + } + + bool isRightToLeft = aShapedText->IsRightToLeft(); + + hb_buffer_set_direction( + mBuffer, aVertical + ? HB_DIRECTION_TTB + : (isRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR)); + hb_script_t scriptTag; + if (aShapedText->GetFlags() & gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT) { + scriptTag = sMathScript; + } else { + scriptTag = GetHBScriptUsedForShaping(aScript); + } + hb_buffer_set_script(mBuffer, scriptTag); + + hb_language_t language; + if (style->languageOverride) { + language = hb_ot_tag_to_language(style->languageOverride); + } else if (entry->mLanguageOverride) { + language = hb_ot_tag_to_language(entry->mLanguageOverride); + } else if (aLanguage) { + nsCString langString; + aLanguage->ToUTF8String(langString); + language = hb_language_from_string(langString.get(), langString.Length()); + } else { + language = hb_ot_tag_to_language(HB_OT_TAG_DEFAULT_LANGUAGE); + } + hb_buffer_set_language(mBuffer, language); + + uint32_t length = aLength; + hb_buffer_add_utf16(mBuffer, reinterpret_cast<const uint16_t*>(aText), length, + 0, length); + + hb_shape(mHBFont, mBuffer, features.Elements(), features.Length()); + + if (isRightToLeft) { + hb_buffer_reverse(mBuffer); + } + + nsresult rv = SetGlyphsFromRun(aShapedText, aOffset, aLength, aText, + aVertical, aRounding); + + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "failed to store glyphs into gfxShapedWord"); + hb_buffer_clear_contents(mBuffer); + + return NS_SUCCEEDED(rv); +} + +#define SMALL_GLYPH_RUN \ + 128 // some testing indicates that 90%+ of text runs + // will fit without requiring separate allocation + // for charToGlyphArray + +nsresult gfxHarfBuzzShaper::SetGlyphsFromRun(gfxShapedText* aShapedText, + uint32_t aOffset, uint32_t aLength, + const char16_t* aText, + bool aVertical, + RoundingFlags aRounding) { + typedef gfxShapedText::CompressedGlyph CompressedGlyph; + + uint32_t numGlyphs; + const hb_glyph_info_t* ginfo = hb_buffer_get_glyph_infos(mBuffer, &numGlyphs); + if (numGlyphs == 0) { + return NS_OK; + } + + AutoTArray<gfxTextRun::DetailedGlyph, 1> detailedGlyphs; + + uint32_t wordLength = aLength; + static const int32_t NO_GLYPH = -1; + AutoTArray<int32_t, SMALL_GLYPH_RUN> charToGlyphArray; + if (!charToGlyphArray.SetLength(wordLength, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + int32_t* charToGlyph = charToGlyphArray.Elements(); + for (uint32_t offset = 0; offset < wordLength; ++offset) { + charToGlyph[offset] = NO_GLYPH; + } + + for (uint32_t i = 0; i < numGlyphs; ++i) { + uint32_t loc = ginfo[i].cluster; + if (loc < wordLength) { + charToGlyph[loc] = i; + } + } + + int32_t glyphStart = 0; // looking for a clump that starts at this glyph + int32_t charStart = 0; // and this char index within the range of the run + + bool roundI, roundB; + if (aVertical) { + roundI = bool(aRounding & RoundingFlags::kRoundY); + roundB = bool(aRounding & RoundingFlags::kRoundX); + } else { + roundI = bool(aRounding & RoundingFlags::kRoundX); + roundB = bool(aRounding & RoundingFlags::kRoundY); + } + + int32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit(); + CompressedGlyph* charGlyphs = aShapedText->GetCharacterGlyphs() + aOffset; + + // factor to convert 16.16 fixed-point pixels to app units + // (only used if not rounding) + double hb2appUnits = FixedToFloat(aShapedText->GetAppUnitsPerDevUnit()); + + // Residual from rounding of previous advance, for use in rounding the + // subsequent offset or advance appropriately. 16.16 fixed-point + // + // When rounding, the goal is to make the distance between glyphs and + // their base glyph equal to the integral number of pixels closest to that + // suggested by that shaper. + // i.e. posInfo[n].x_advance - posInfo[n].x_offset + posInfo[n+1].x_offset + // + // The value of the residual is the part of the desired distance that has + // not been included in integer offsets. + hb_position_t residual = 0; + + // keep track of y-position to set glyph offsets if needed + nscoord bPos = 0; + + const hb_glyph_position_t* posInfo = + hb_buffer_get_glyph_positions(mBuffer, nullptr); + if (!posInfo) { + // Some kind of unexpected failure inside harfbuzz? + return NS_ERROR_UNEXPECTED; + } + + 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..4e4e899660 --- /dev/null +++ b/gfx/thebes/gfxHarfBuzzShaper.h @@ -0,0 +1,240 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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" +#include "mozilla/MruCache.h" +#include "mozilla/Mutex.h" + +class gfxHarfBuzzShaper : public gfxFontShaper { + public: + explicit gfxHarfBuzzShaper(gfxFont* aFont); + virtual ~gfxHarfBuzzShaper(); + + /* + * For HarfBuzz font callback functions, font_data is a ptr to a + * FontCallbackData struct + */ + struct FontCallbackData { + gfxHarfBuzzShaper* mShaper; + }; + + // Initializes the shaper and returns whether this was successful. + bool Initialize(); + + // Returns whether the shaper has been successfully initialized. + bool IsInitialized() const { return mHBFont != nullptr; } + + bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, RoundingFlags aRounding, + gfxShapedText* aShapedText) override; + + // get a given font table in harfbuzz blob form + hb_blob_t* GetFontTable(hb_tag_t aTag) const; + + // map unicode character to glyph ID + hb_codepoint_t GetNominalGlyph(hb_codepoint_t unicode) const; + unsigned int GetNominalGlyphs(unsigned int count, + const hb_codepoint_t* first_unicode, + unsigned int unicode_stride, + hb_codepoint_t* first_glyph, + unsigned int glyph_stride); + 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; + void GetGlyphHAdvances(unsigned int count, const hb_codepoint_t* first_glyph, + unsigned int glyph_stride, + hb_position_t* first_advance, + unsigned int advance_stride) const; + + // Get vertical glyph advance, or -1 if not available; caller should check + // for a negative result and provide a fallback or fail, as appropriate. + hb_position_t GetGlyphVAdvance(hb_codepoint_t glyph); + + void GetGlyphVOrigin(hb_codepoint_t aGlyph, hb_position_t* aX, + hb_position_t* aY) const; + + hb_position_t GetHKerning(uint16_t aFirstGlyph, uint16_t aSecondGlyph) const; + + hb_bool_t GetGlyphExtents(hb_codepoint_t aGlyph, + hb_glyph_extents_t* aExtents) const; + + bool UseVerticalPresentationForms() const { + return mUseVerticalPresentationForms; + } + + static hb_script_t GetHBScriptUsedForShaping(Script aScript) { + // Decide what harfbuzz script code will be used for shaping + hb_script_t hbScript; + if (aScript <= Script::INHERITED) { + // For unresolved "common" or "inherited" runs, + // default to Latin for now. + hbScript = HB_SCRIPT_LATIN; + } else { + hbScript = hb_script_t(mozilla::unicode::GetScriptTagForCode(aScript)); + } + return hbScript; + } + + static hb_codepoint_t GetVerticalPresentationForm(hb_codepoint_t aUnicode); + + // Create an hb_font corresponding to the given gfxFont instance, with size + // and variations set appropriately. If aFontFuncs and aCallbackData are + // provided, they may be used as harfbuzz font callbacks for advances, glyph + // bounds, etc; if not, the built-in hb_ot font functions will be used. + static hb_font_t* CreateHBFont(gfxFont* aFont, + hb_font_funcs_t* aFontFuncs = nullptr, + FontCallbackData* aCallbackData = nullptr); + + hb_font_t* GetHBFont() const { return mHBFont; } + hb_face_t* GetHBFace() const { return hb_font_get_face(mHBFont); } + + protected: + // This is called with the cache locked, but if mUseFontGetGlyph is true, it + // may unlock it temporarily. So in this case, it may invalidate an earlier + // cache entry reference. + hb_codepoint_t GetGlyphUncached(hb_codepoint_t unicode) const + MOZ_REQUIRES(mCacheLock); + + hb_position_t GetGlyphHAdvanceUncached(hb_codepoint_t gid) const; + + 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; + + mutable mozilla::Mutex mCacheLock = mozilla::Mutex("shaperCacheMutex"); + + struct CmapCacheData { + uint32_t mCodepoint; + uint32_t mGlyphId; + }; + + struct CmapCache + : public mozilla::MruCache<uint32_t, CmapCacheData, CmapCache, 251> { + static mozilla::HashNumber Hash(const uint32_t& aKey) { return aKey; } + static bool Match(const uint32_t& aKey, const CmapCacheData& aData) { + return aKey == aData.mCodepoint; + } + }; + + mutable mozilla::UniquePtr<CmapCache> mCmapCache MOZ_GUARDED_BY(mCacheLock); + + struct WidthCacheData { + hb_codepoint_t mGlyphId; + hb_position_t mAdvance; + }; + + struct WidthCache + : public mozilla::MruCache<uint32_t, WidthCacheData, WidthCache, 251> { + static mozilla::HashNumber Hash(const hb_codepoint_t& aKey) { return aKey; } + static bool Match(const uint32_t& aKey, const WidthCacheData& aData) { + return aKey == aData.mGlyphId; + } + }; + + mutable mozilla::UniquePtr<WidthCache> mWidthCache MOZ_GUARDED_BY(mCacheLock); + + FontCallbackData mCallbackData; + + // Following table references etc are declared "mutable" because the + // harfbuzz callback functions take a const ptr to the shaper, but + // wish to cache tables here to avoid repeatedly looking them up + // in the font. + + // Old-style TrueType kern table, if we're not doing GPOS kerning + mutable hb_blob_t* mKernTable; + + // Cached copy of the hmtx table. + mutable hb_blob_t* mHmtxTable; + + // For vertical fonts, cached vmtx and VORG table, if present. + mutable hb_blob_t* mVmtxTable; + mutable hb_blob_t* mVORGTable; + // And for vertical TrueType (not CFF) fonts that have vmtx, + // we also use loca and glyf to get glyph bounding boxes. + mutable hb_blob_t* mLocaTable; + mutable hb_blob_t* mGlyfTable; + + // Cached pointer to cmap subtable to be used for char-to-glyph mapping. + // This comes from GetFontTablePtr; if it is non-null, our destructor + // must call ReleaseFontTablePtr to avoid permanently caching the table. + mutable hb_blob_t* mCmapTable; + mutable int32_t mCmapFormat; + mutable uint32_t mSubtableOffset; + mutable uint32_t mUVSTableOffset; + + // Cached copy of numLongMetrics field from the hhea table, + // for use when looking up glyph metrics; initialized to 0 by the + // constructor so we can tell it hasn't been set yet. + // This is a signed value so that we can use -1 to indicate + // an error (if the hhea table was not available). + mutable int32_t mNumLongHMetrics; + // Similarly for vhea if it's a vertical font. + mutable int32_t mNumLongVMetrics; + + // Default y-coordinate for glyph vertical origin, used if the font + // does not actually have vertical-layout metrics. + mutable gfxFloat mDefaultVOrg; + + // Whether the font implements GetGlyph, or we should read tables + // directly + bool mUseFontGetGlyph; + + // Whether the font is an MS Symbol-encoded font, in which case we will + // try remapping U+0020..00FF to U+F020..F0FF for characters in the U+00xx + // range that are otherwise unsupported. + bool mIsSymbolFont; + + // Whether the font implements GetGlyphWidth, or we should read tables + // directly to get ideal widths + bool mUseFontGlyphWidths; + + bool mInitialized; + bool mVerticalInitialized; + + // Whether to use vertical presentation forms for CJK characters + // when available (only set if the 'vert' feature is not available). + bool mUseVerticalPresentationForms; + + // these are set from the FindGlyf callback on first use of the glyf data + mutable bool mLoadedLocaGlyf; + mutable bool mLocaLongOffsets; +}; + +#endif /* GFX_HARFBUZZSHAPER_H */ diff --git a/gfx/thebes/gfxImageSurface.cpp b/gfx/thebes/gfxImageSurface.cpp new file mode 100644 index 0000000000..3cd285dac7 --- /dev/null +++ b/gfx/thebes/gfxImageSurface.cpp @@ -0,0 +1,326 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/MemoryReporting.h" +#if defined(HAVE_POSIX_MEMALIGN) +# include "gfxAlphaRecovery.h" +#endif +#include "gfxImageSurface.h" + +#include "cairo.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/HelpersCairo.h" +#include "gfx2DGlue.h" +#include <algorithm> + +using namespace mozilla; +using namespace mozilla::gfx; + +gfxImageSurface::gfxImageSurface() + : mSize(0, 0), + mOwnsData(false), + mData(nullptr), + mFormat(SurfaceFormat::UNKNOWN), + mStride(0) {} + +void gfxImageSurface::InitFromSurface(cairo_surface_t* csurf) { + if (!csurf || cairo_surface_status(csurf)) { + MakeInvalid(); + return; + } + + mSize.width = cairo_image_surface_get_width(csurf); + mSize.height = cairo_image_surface_get_height(csurf); + mData = cairo_image_surface_get_data(csurf); + mFormat = CairoFormatToGfxFormat(cairo_image_surface_get_format(csurf)); + mOwnsData = false; + mStride = cairo_image_surface_get_stride(csurf); + + Init(csurf, true); +} + +gfxImageSurface::gfxImageSurface(unsigned char* aData, const IntSize& aSize, + long aStride, gfxImageFormat aFormat) { + InitWithData(aData, aSize, aStride, aFormat); +} + +void gfxImageSurface::MakeInvalid() { + mSize = IntSize(-1, -1); + mData = nullptr; + mStride = 0; +} + +void gfxImageSurface::InitWithData(unsigned char* aData, const IntSize& aSize, + long aStride, gfxImageFormat aFormat) { + mSize = aSize; + mOwnsData = false; + mData = aData; + mFormat = aFormat; + mStride = aStride; + + if (!Factory::CheckSurfaceSize(aSize)) MakeInvalid(); + + cairo_format_t cformat = GfxFormatToCairoFormat(mFormat); + cairo_surface_t* surface = cairo_image_surface_create_for_data( + (unsigned char*)mData, cformat, mSize.width, mSize.height, mStride); + + // cairo_image_surface_create_for_data can return a 'null' surface + // in out of memory conditions. The gfxASurface::Init call checks + // the surface it receives to see if there is an error with the + // surface and handles it appropriately. That is why there is + // no check here. + Init(surface); +} + +static void* TryAllocAlignedBytes(size_t aSize) { + // Use fallible allocators here +#if defined(HAVE_POSIX_MEMALIGN) + void* ptr; + // Try to align for fast alpha recovery. This should only help + // cairo too, can't hurt. + return posix_memalign(&ptr, 1 << gfxAlphaRecovery::GoodAlignmentLog2(), aSize) + ? nullptr + : ptr; +#else + // Oh well, hope that luck is with us in the allocator + return malloc(aSize); +#endif +} + +gfxImageSurface::gfxImageSurface(const IntSize& size, gfxImageFormat format, + bool aClear) + : mSize(size), mData(nullptr), mFormat(format) { + AllocateAndInit(0, 0, aClear); +} + +void gfxImageSurface::AllocateAndInit(long aStride, int32_t aMinimalAllocation, + bool aClear) { + // The callers should set mSize and mFormat. + MOZ_ASSERT(!mData); + mData = nullptr; + mOwnsData = false; + + mStride = aStride > 0 ? aStride : ComputeStride(); + if (aMinimalAllocation < mSize.height * mStride) + aMinimalAllocation = mSize.height * mStride; + + if (!Factory::CheckSurfaceSize(mSize)) MakeInvalid(); + + // if we have a zero-sized surface, just leave mData nullptr + if (mSize.height * mStride > 0) { + // This can fail to allocate memory aligned as we requested, + // or it can fail to allocate any memory at all. + mData = (unsigned char*)TryAllocAlignedBytes(aMinimalAllocation); + if (!mData) return; + if (aClear) memset(mData, 0, aMinimalAllocation); + } + + mOwnsData = true; + + cairo_format_t cformat = GfxFormatToCairoFormat(mFormat); + cairo_surface_t* surface = cairo_image_surface_create_for_data( + (unsigned char*)mData, cformat, mSize.width, mSize.height, mStride); + + Init(surface); + + if (mSurfaceValid) { + RecordMemoryUsed(mSize.height * ComputeStride() + sizeof(gfxImageSurface)); + } +} + +gfxImageSurface::gfxImageSurface(const IntSize& size, gfxImageFormat format, + long aStride, int32_t aExtraBytes, bool aClear) + : mSize(size), mData(nullptr), mFormat(format) { + AllocateAndInit(aStride, aExtraBytes, aClear); +} + +gfxImageSurface::gfxImageSurface(cairo_surface_t* csurf) { + mSize.width = cairo_image_surface_get_width(csurf); + mSize.height = cairo_image_surface_get_height(csurf); + mData = cairo_image_surface_get_data(csurf); + mFormat = CairoFormatToGfxFormat(cairo_image_surface_get_format(csurf)); + mOwnsData = false; + mStride = cairo_image_surface_get_stride(csurf); + + Init(csurf, true); +} + +gfxImageSurface::~gfxImageSurface() { + if (mOwnsData) free(mData); +} + +/*static*/ +long gfxImageSurface::ComputeStride(const IntSize& aSize, + gfxImageFormat aFormat) { + long stride; + + if (aFormat == SurfaceFormat::A8R8G8B8_UINT32) + stride = aSize.width * 4; + else if (aFormat == SurfaceFormat::X8R8G8B8_UINT32) + stride = aSize.width * 4; + else if (aFormat == SurfaceFormat::R5G6B5_UINT16) + stride = aSize.width * 2; + else if (aFormat == SurfaceFormat::A8) + stride = aSize.width; + else { + NS_WARNING("Unknown format specified to gfxImageSurface!"); + stride = aSize.width * 4; + } + + stride = ((stride + 3) / 4) * 4; + + return stride; +} + +size_t gfxImageSurface::SizeOfExcludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = gfxASurface::SizeOfExcludingThis(aMallocSizeOf); + if (mOwnsData) { + n += aMallocSizeOf(mData); + } + return n; +} + +size_t gfxImageSurface::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +bool gfxImageSurface::SizeOfIsMeasured() const { return true; } + +// helper function for the CopyFrom methods +static void CopyForStride(unsigned char* aDest, unsigned char* aSrc, + const IntSize& aSize, long aDestStride, + long aSrcStride) { + if (aDestStride == aSrcStride) { + memcpy(aDest, aSrc, aSrcStride * aSize.height); + } else { + int lineSize = std::min(aDestStride, aSrcStride); + for (int i = 0; i < aSize.height; i++) { + unsigned char* src = aSrc + aSrcStride * i; + unsigned char* dst = aDest + aDestStride * i; + + memcpy(dst, src, lineSize); + } + } +} + +// helper function for the CopyFrom methods +static bool FormatsAreCompatible(gfxImageFormat a1, gfxImageFormat a2) { + if (a1 != a2 && + !(a1 == SurfaceFormat::A8R8G8B8_UINT32 && + a2 == SurfaceFormat::X8R8G8B8_UINT32) && + !(a1 == SurfaceFormat::X8R8G8B8_UINT32 && + a2 == SurfaceFormat::A8R8G8B8_UINT32)) { + return false; + } + + return true; +} + +bool gfxImageSurface::CopyFrom(SourceSurface* aSurface) { + RefPtr<DataSourceSurface> data = aSurface->GetDataSurface(); + + if (!data) { + return false; + } + + IntSize size(data->GetSize().width, data->GetSize().height); + if (size != mSize) { + return false; + } + + if (!FormatsAreCompatible(SurfaceFormatToImageFormat(aSurface->GetFormat()), + mFormat)) { + return false; + } + + DataSourceSurface::ScopedMap map(data, DataSourceSurface::READ); + CopyForStride(mData, map.GetData(), size, mStride, map.GetStride()); + + return true; +} + +bool gfxImageSurface::CopyFrom(gfxImageSurface* other) { + if (other->mSize != mSize) { + return false; + } + + if (!FormatsAreCompatible(other->mFormat, mFormat)) { + return false; + } + + CopyForStride(mData, other->mData, mSize, mStride, other->mStride); + + return true; +} + +bool gfxImageSurface::CopyTo(SourceSurface* aSurface) { + RefPtr<DataSourceSurface> data = aSurface->GetDataSurface(); + + if (!data) { + return false; + } + + IntSize size(data->GetSize().width, data->GetSize().height); + if (size != mSize) { + return false; + } + + if (!FormatsAreCompatible(SurfaceFormatToImageFormat(aSurface->GetFormat()), + mFormat)) { + return false; + } + + DataSourceSurface::ScopedMap map(data, DataSourceSurface::READ_WRITE); + CopyForStride(map.GetData(), mData, size, map.GetStride(), mStride); + + return true; +} + +already_AddRefed<DataSourceSurface> +gfxImageSurface::CopyToB8G8R8A8DataSourceSurface() { + RefPtr<DataSourceSurface> dataSurface = Factory::CreateDataSourceSurface( + IntSize(GetSize().width, GetSize().height), SurfaceFormat::B8G8R8A8); + if (dataSurface) { + CopyTo(dataSurface); + } + return dataSurface.forget(); +} + +already_AddRefed<gfxSubimageSurface> gfxImageSurface::GetSubimage( + const gfxRect& aRect) { + gfxRect r(aRect); + r.Round(); + MOZ_ASSERT(gfxRect(0, 0, mSize.width, mSize.height).Contains(r)); + + gfxImageFormat format = Format(); + + unsigned char* subData = + Data() + (Stride() * (int)r.Y()) + + (int)r.X() * gfxASurface::BytePerPixelFromFormat(Format()); + + if (format == SurfaceFormat::A8R8G8B8_UINT32 && + GetOpaqueRect().Contains(aRect)) { + format = SurfaceFormat::X8R8G8B8_UINT32; + } + + RefPtr<gfxSubimageSurface> image = new gfxSubimageSurface( + this, subData, IntSize((int)r.Width(), (int)r.Height()), format); + + return image.forget(); +} + +gfxSubimageSurface::gfxSubimageSurface(gfxImageSurface* aParent, + unsigned char* aData, + const IntSize& aSize, + gfxImageFormat aFormat) + : gfxImageSurface(aData, aSize, aParent->Stride(), aFormat), + mParent(aParent) {} + +already_AddRefed<gfxImageSurface> gfxImageSurface::GetAsImageSurface() { + RefPtr<gfxImageSurface> surface = this; + return surface.forget(); +} diff --git a/gfx/thebes/gfxImageSurface.h b/gfx/thebes/gfxImageSurface.h new file mode 100644 index 0000000000..eefc233738 --- /dev/null +++ b/gfx/thebes/gfxImageSurface.h @@ -0,0 +1,194 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_IMAGESURFACE_H +#define GFX_IMAGESURFACE_H + +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefPtr.h" +#include "gfxASurface.h" +#include "nsSize.h" + +// ARGB -- raw buffer.. wont be changed.. good for storing data. + +class gfxSubimageSurface; + +namespace mozilla { +namespace gfx { +class DataSourceSurface; +class SourceSurface; +} // namespace gfx +} // namespace mozilla + +/** + * A raw image buffer. The format can be set in the constructor. Its main + * purpose is for storing read-only images and using it as a source surface, + * but it can also be drawn to. + */ +class gfxImageSurface : public gfxASurface { + public: + /** + * Construct an image surface around an existing buffer of image data. + * @param aData A buffer containing the image data + * @param aSize The size of the buffer + * @param aStride The stride of the buffer + * @param format Format of the data + * + * @see gfxImageFormat + */ + gfxImageSurface(unsigned char* aData, const mozilla::gfx::IntSize& aSize, + long aStride, gfxImageFormat aFormat); + + /** + * Construct an image surface. + * @param aSize The size of the buffer + * @param format Format of the data + * + * @see gfxImageFormat + */ + gfxImageSurface(const mozilla::gfx::IntSize& size, gfxImageFormat format, + bool aClear = true); + + /** + * Construct an image surface, with a specified stride and allowing the + * allocation of more memory than required for the storage of the surface + * itself. When aStride and aMinimalAllocation are <=0, this constructor + * is the equivalent of the preceeding one. + * + * @param format Format of the data + * @param aSize The size of the buffer + * @param aStride The stride of the buffer - if <=0, use ComputeStride() + * @param aMinimalAllocation Allocate at least this many bytes. If smaller + * than width * stride, or width*stride <=0, this value is ignored. + * @param aClear + * + * @see gfxImageFormat + */ + gfxImageSurface(const mozilla::gfx::IntSize& aSize, gfxImageFormat aFormat, + long aStride, int32_t aMinimalAllocation, bool aClear); + + explicit gfxImageSurface(cairo_surface_t* csurf); + + virtual ~gfxImageSurface(); + + // ImageSurface methods + gfxImageFormat Format() const { return mFormat; } + + virtual const mozilla::gfx::IntSize GetSize() const override { return mSize; } + int32_t Width() const { + if (mSize.width < 0) { + return 0; + } + return mSize.width; + } + int32_t Height() const { + if (mSize.height < 0) { + return 0; + } + return mSize.height; + } + + /** + * Distance in bytes between the start of a line and the start of the + * next line. + */ + int32_t Stride() const { return mStride; } + /** + * Returns a pointer for the image data. Users of this function can + * write to it, but must not attempt to free the buffer. + */ + unsigned char* Data() const { + return mData; + } // delete this data under us and die. + /** + * Returns the total size of the image data. + */ + int32_t GetDataSize() const { + if (mStride < 0 || mSize.height < 0) { + return 0; + } + return mStride * mSize.height; + } + + /* Fast copy from another image surface; returns TRUE if successful, FALSE + * otherwise */ + bool CopyFrom(gfxImageSurface* other); + + /** + * Fast copy from a source surface; returns TRUE if successful, FALSE + * otherwise Assumes that the format of this surface is compatable with + * aSurface + */ + bool CopyFrom(mozilla::gfx::SourceSurface* aSurface); + + /** + * Fast copy to a source surface; returns TRUE if successful, FALSE otherwise + * Assumes that the format of this surface is compatible with aSurface + */ + bool CopyTo(mozilla::gfx::SourceSurface* aSurface); + + /** + * Copy to a Moz2D DataSourceSurface. + * Marked as virtual so that browsercomps can access this method. + */ + virtual already_AddRefed<mozilla::gfx::DataSourceSurface> + CopyToB8G8R8A8DataSourceSurface(); + + /* return new Subimage with pointing to original image starting from aRect.pos + * and size of aRect.size. New subimage keeping current image reference + */ + already_AddRefed<gfxSubimageSurface> GetSubimage(const gfxRect& aRect); + + virtual already_AddRefed<gfxImageSurface> GetAsImageSurface() override; + + /** See gfxASurface.h. */ + static long ComputeStride(const mozilla::gfx::IntSize&, gfxImageFormat); + + virtual size_t SizeOfExcludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const override; + virtual size_t SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const override; + virtual bool SizeOfIsMeasured() const override; + + protected: + gfxImageSurface(); + void InitWithData(unsigned char* aData, const mozilla::gfx::IntSize& aSize, + long aStride, gfxImageFormat aFormat); + /** + * See the parameters to the matching constructor. This should only + * be called once, in the constructor, which has already set mSize + * and mFormat. + */ + void AllocateAndInit(long aStride, int32_t aMinimalAllocation, bool aClear); + void InitFromSurface(cairo_surface_t* csurf); + + long ComputeStride() const { + if (mSize.height < 0 || mSize.width < 0) { + return 0; + } + return ComputeStride(mSize, mFormat); + } + + void MakeInvalid(); + + mozilla::gfx::IntSize mSize; + bool mOwnsData; + unsigned char* mData; + gfxImageFormat mFormat; + long mStride; +}; + +class gfxSubimageSurface : public gfxImageSurface { + protected: + friend class gfxImageSurface; + gfxSubimageSurface(gfxImageSurface* aParent, unsigned char* aData, + const mozilla::gfx::IntSize& aSize, + gfxImageFormat aFormat); + + private: + RefPtr<gfxImageSurface> mParent; +}; + +#endif /* GFX_IMAGESURFACE_H */ diff --git a/gfx/thebes/gfxLanguageTagList.cpp b/gfx/thebes/gfxLanguageTagList.cpp new file mode 100644 index 0000000000..0994fc1a17 --- /dev/null +++ b/gfx/thebes/gfxLanguageTagList.cpp @@ -0,0 +1,7901 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Derived from the IANA language subtag registry by genLanguageTagList.pl. + * + * Created on Mon Nov 7 14:52:44 2011. + * + * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * * + */ + +// Based on IANA registry dated 2011-08-25 + +static const uint32_t sLanguageTagList[] = { + TRUETYPE_TAG('a', 'a', 0, 0), // aa = Afar + TRUETYPE_TAG('a', 'b', 0, 0), // ab = Abkhazian + TRUETYPE_TAG('a', 'e', 0, 0), // ae = Avestan + TRUETYPE_TAG('a', 'f', 0, 0), // af = Afrikaans + TRUETYPE_TAG('a', 'k', 0, 0), // ak = Akan + TRUETYPE_TAG('a', 'm', 0, 0), // am = Amharic + TRUETYPE_TAG('a', 'n', 0, 0), // an = Aragonese + TRUETYPE_TAG('a', 'r', 0, 0), // ar = Arabic + TRUETYPE_TAG('a', 's', 0, 0), // as = Assamese + TRUETYPE_TAG('a', 'v', 0, 0), // av = Avaric + TRUETYPE_TAG('a', 'y', 0, 0), // ay = Aymara + TRUETYPE_TAG('a', 'z', 0, 0), // az = Azerbaijani + TRUETYPE_TAG('b', 'a', 0, 0), // ba = Bashkir + TRUETYPE_TAG('b', 'e', 0, 0), // be = Belarusian + TRUETYPE_TAG('b', 'g', 0, 0), // bg = Bulgarian + TRUETYPE_TAG('b', 'h', 0, 0), // bh = Bihari languages + TRUETYPE_TAG('b', 'i', 0, 0), // bi = Bislama + TRUETYPE_TAG('b', 'm', 0, 0), // bm = Bambara + TRUETYPE_TAG('b', 'n', 0, 0), // bn = Bengali + TRUETYPE_TAG('b', 'o', 0, 0), // bo = Tibetan + TRUETYPE_TAG('b', 'r', 0, 0), // br = Breton + TRUETYPE_TAG('b', 's', 0, 0), // bs = Bosnian + TRUETYPE_TAG('c', 'a', 0, 0), // ca = Catalan + TRUETYPE_TAG('c', 'e', 0, 0), // ce = Chechen + TRUETYPE_TAG('c', 'h', 0, 0), // ch = Chamorro + TRUETYPE_TAG('c', 'o', 0, 0), // co = Corsican + TRUETYPE_TAG('c', 'r', 0, 0), // cr = Cree + TRUETYPE_TAG('c', 's', 0, 0), // cs = Czech + TRUETYPE_TAG('c', 'u', 0, 0), // cu = Church Slavic + TRUETYPE_TAG('c', 'v', 0, 0), // cv = Chuvash + TRUETYPE_TAG('c', 'y', 0, 0), // cy = Welsh + TRUETYPE_TAG('d', 'a', 0, 0), // da = Danish + TRUETYPE_TAG('d', 'e', 0, 0), // de = German + TRUETYPE_TAG('d', 'v', 0, 0), // dv = Dhivehi + TRUETYPE_TAG('d', 'z', 0, 0), // dz = Dzongkha + TRUETYPE_TAG('e', 'e', 0, 0), // ee = Ewe + TRUETYPE_TAG('e', 'l', 0, 0), // el = Modern Greek (1453-) + TRUETYPE_TAG('e', 'n', 0, 0), // en = English + TRUETYPE_TAG('e', 'o', 0, 0), // eo = Esperanto + TRUETYPE_TAG('e', 's', 0, 0), // es = Spanish + TRUETYPE_TAG('e', 't', 0, 0), // et = Estonian + TRUETYPE_TAG('e', 'u', 0, 0), // eu = Basque + TRUETYPE_TAG('f', 'a', 0, 0), // fa = Persian + TRUETYPE_TAG('f', 'f', 0, 0), // ff = Fulah + TRUETYPE_TAG('f', 'i', 0, 0), // fi = Finnish + TRUETYPE_TAG('f', 'j', 0, 0), // fj = Fijian + TRUETYPE_TAG('f', 'o', 0, 0), // fo = Faroese + TRUETYPE_TAG('f', 'r', 0, 0), // fr = French + TRUETYPE_TAG('f', 'y', 0, 0), // fy = Western Frisian + TRUETYPE_TAG('g', 'a', 0, 0), // ga = Irish + TRUETYPE_TAG('g', 'd', 0, 0), // gd = Scottish Gaelic + TRUETYPE_TAG('g', 'l', 0, 0), // gl = Galician + TRUETYPE_TAG('g', 'n', 0, 0), // gn = Guarani + TRUETYPE_TAG('g', 'u', 0, 0), // gu = Gujarati + TRUETYPE_TAG('g', 'v', 0, 0), // gv = Manx + TRUETYPE_TAG('h', 'a', 0, 0), // ha = Hausa + TRUETYPE_TAG('h', 'e', 0, 0), // he = Hebrew + TRUETYPE_TAG('h', 'i', 0, 0), // hi = Hindi + TRUETYPE_TAG('h', 'o', 0, 0), // ho = Hiri Motu + TRUETYPE_TAG('h', 'r', 0, 0), // hr = Croatian + TRUETYPE_TAG('h', 't', 0, 0), // ht = Haitian + TRUETYPE_TAG('h', 'u', 0, 0), // hu = Hungarian + TRUETYPE_TAG('h', 'y', 0, 0), // hy = Armenian + TRUETYPE_TAG('h', 'z', 0, 0), // hz = Herero + TRUETYPE_TAG('i', 'a', 0, + 0), // ia = Interlingua (International Auxiliary Language + TRUETYPE_TAG('i', 'd', 0, 0), // id = Indonesian + TRUETYPE_TAG('i', 'e', 0, 0), // ie = Interlingue + TRUETYPE_TAG('i', 'g', 0, 0), // ig = Igbo + TRUETYPE_TAG('i', 'i', 0, 0), // ii = Sichuan Yi + TRUETYPE_TAG('i', 'k', 0, 0), // ik = Inupiaq + TRUETYPE_TAG('i', 'n', 0, 0), // in = Indonesian + TRUETYPE_TAG('i', 'o', 0, 0), // io = Ido + TRUETYPE_TAG('i', 's', 0, 0), // is = Icelandic + TRUETYPE_TAG('i', 't', 0, 0), // it = Italian + TRUETYPE_TAG('i', 'u', 0, 0), // iu = Inuktitut + TRUETYPE_TAG('i', 'w', 0, 0), // iw = Hebrew + TRUETYPE_TAG('j', 'a', 0, 0), // ja = Japanese + TRUETYPE_TAG('j', 'i', 0, 0), // ji = Yiddish + TRUETYPE_TAG('j', 'v', 0, 0), // jv = Javanese + TRUETYPE_TAG('j', 'w', 0, 0), // jw = Javanese + TRUETYPE_TAG('k', 'a', 0, 0), // ka = Georgian + TRUETYPE_TAG('k', 'g', 0, 0), // kg = Kongo + TRUETYPE_TAG('k', 'i', 0, 0), // ki = Kikuyu + TRUETYPE_TAG('k', 'j', 0, 0), // kj = Kuanyama + TRUETYPE_TAG('k', 'k', 0, 0), // kk = Kazakh + TRUETYPE_TAG('k', 'l', 0, 0), // kl = Kalaallisut + TRUETYPE_TAG('k', 'm', 0, 0), // km = Central Khmer + TRUETYPE_TAG('k', 'n', 0, 0), // kn = Kannada + TRUETYPE_TAG('k', 'o', 0, 0), // ko = Korean + TRUETYPE_TAG('k', 'r', 0, 0), // kr = Kanuri + TRUETYPE_TAG('k', 's', 0, 0), // ks = Kashmiri + TRUETYPE_TAG('k', 'u', 0, 0), // ku = Kurdish + TRUETYPE_TAG('k', 'v', 0, 0), // kv = Komi + TRUETYPE_TAG('k', 'w', 0, 0), // kw = Cornish + TRUETYPE_TAG('k', 'y', 0, 0), // ky = Kirghiz + TRUETYPE_TAG('l', 'a', 0, 0), // la = Latin + TRUETYPE_TAG('l', 'b', 0, 0), // lb = Luxembourgish + TRUETYPE_TAG('l', 'g', 0, 0), // lg = Ganda + TRUETYPE_TAG('l', 'i', 0, 0), // li = Limburgan + TRUETYPE_TAG('l', 'n', 0, 0), // ln = Lingala + TRUETYPE_TAG('l', 'o', 0, 0), // lo = Lao + TRUETYPE_TAG('l', 't', 0, 0), // lt = Lithuanian + TRUETYPE_TAG('l', 'u', 0, 0), // lu = Luba-Katanga + TRUETYPE_TAG('l', 'v', 0, 0), // lv = Latvian + TRUETYPE_TAG('m', 'g', 0, 0), // mg = Malagasy + TRUETYPE_TAG('m', 'h', 0, 0), // mh = Marshallese + TRUETYPE_TAG('m', 'i', 0, 0), // mi = Maori + TRUETYPE_TAG('m', 'k', 0, 0), // mk = Macedonian + TRUETYPE_TAG('m', 'l', 0, 0), // ml = Malayalam + TRUETYPE_TAG('m', 'n', 0, 0), // mn = Mongolian + TRUETYPE_TAG('m', 'o', 0, 0), // mo = Moldavian + TRUETYPE_TAG('m', 'r', 0, 0), // mr = Marathi + TRUETYPE_TAG('m', 's', 0, 0), // ms = Malay (macrolanguage) + TRUETYPE_TAG('m', 't', 0, 0), // mt = Maltese + TRUETYPE_TAG('m', 'y', 0, 0), // my = Burmese + TRUETYPE_TAG('n', 'a', 0, 0), // na = Nauru + TRUETYPE_TAG('n', 'b', 0, 0), // nb = Norwegian Bokmål + TRUETYPE_TAG('n', 'd', 0, 0), // nd = North Ndebele + TRUETYPE_TAG('n', 'e', 0, 0), // ne = Nepali + TRUETYPE_TAG('n', 'g', 0, 0), // ng = Ndonga + TRUETYPE_TAG('n', 'l', 0, 0), // nl = Dutch + TRUETYPE_TAG('n', 'n', 0, 0), // nn = Norwegian Nynorsk + TRUETYPE_TAG('n', 'o', 0, 0), // no = Norwegian + TRUETYPE_TAG('n', 'r', 0, 0), // nr = South Ndebele + TRUETYPE_TAG('n', 'v', 0, 0), // nv = Navajo + TRUETYPE_TAG('n', 'y', 0, 0), // ny = Nyanja + TRUETYPE_TAG('o', 'c', 0, 0), // oc = Occitan (post 1500) + TRUETYPE_TAG('o', 'j', 0, 0), // oj = Ojibwa + TRUETYPE_TAG('o', 'm', 0, 0), // om = Oromo + TRUETYPE_TAG('o', 'r', 0, 0), // or = Oriya + TRUETYPE_TAG('o', 's', 0, 0), // os = Ossetian + TRUETYPE_TAG('p', 'a', 0, 0), // pa = Panjabi + TRUETYPE_TAG('p', 'i', 0, 0), // pi = Pali + TRUETYPE_TAG('p', 'l', 0, 0), // pl = Polish + TRUETYPE_TAG('p', 's', 0, 0), // ps = Pushto + TRUETYPE_TAG('p', 't', 0, 0), // pt = Portuguese + TRUETYPE_TAG('q', 'u', 0, 0), // qu = Quechua + TRUETYPE_TAG('r', 'm', 0, 0), // rm = Romansh + TRUETYPE_TAG('r', 'n', 0, 0), // rn = Rundi + TRUETYPE_TAG('r', 'o', 0, 0), // ro = Romanian + TRUETYPE_TAG('r', 'u', 0, 0), // ru = Russian + TRUETYPE_TAG('r', 'w', 0, 0), // rw = Kinyarwanda + TRUETYPE_TAG('s', 'a', 0, 0), // sa = Sanskrit + TRUETYPE_TAG('s', 'c', 0, 0), // sc = Sardinian + TRUETYPE_TAG('s', 'd', 0, 0), // sd = Sindhi + TRUETYPE_TAG('s', 'e', 0, 0), // se = Northern Sami + TRUETYPE_TAG('s', 'g', 0, 0), // sg = Sango + TRUETYPE_TAG('s', 'h', 0, 0), // sh = Serbo-Croatian + TRUETYPE_TAG('s', 'i', 0, 0), // si = Sinhala + TRUETYPE_TAG('s', 'k', 0, 0), // sk = Slovak + TRUETYPE_TAG('s', 'l', 0, 0), // sl = Slovenian + TRUETYPE_TAG('s', 'm', 0, 0), // sm = Samoan + TRUETYPE_TAG('s', 'n', 0, 0), // sn = Shona + TRUETYPE_TAG('s', 'o', 0, 0), // so = Somali + TRUETYPE_TAG('s', 'q', 0, 0), // sq = Albanian + TRUETYPE_TAG('s', 'r', 0, 0), // sr = Serbian + TRUETYPE_TAG('s', 's', 0, 0), // ss = Swati + TRUETYPE_TAG('s', 't', 0, 0), // st = Southern Sotho + TRUETYPE_TAG('s', 'u', 0, 0), // su = Sundanese + TRUETYPE_TAG('s', 'v', 0, 0), // sv = Swedish + TRUETYPE_TAG('s', 'w', 0, 0), // sw = Swahili (macrolanguage) + TRUETYPE_TAG('t', 'a', 0, 0), // ta = Tamil + TRUETYPE_TAG('t', 'e', 0, 0), // te = Telugu + TRUETYPE_TAG('t', 'g', 0, 0), // tg = Tajik + TRUETYPE_TAG('t', 'h', 0, 0), // th = Thai + TRUETYPE_TAG('t', 'i', 0, 0), // ti = Tigrinya + TRUETYPE_TAG('t', 'k', 0, 0), // tk = Turkmen + TRUETYPE_TAG('t', 'l', 0, 0), // tl = Tagalog + TRUETYPE_TAG('t', 'n', 0, 0), // tn = Tswana + TRUETYPE_TAG('t', 'o', 0, 0), // to = Tonga (Tonga Islands) + TRUETYPE_TAG('t', 'r', 0, 0), // tr = Turkish + TRUETYPE_TAG('t', 's', 0, 0), // ts = Tsonga + TRUETYPE_TAG('t', 't', 0, 0), // tt = Tatar + TRUETYPE_TAG('t', 'w', 0, 0), // tw = Twi + TRUETYPE_TAG('t', 'y', 0, 0), // ty = Tahitian + TRUETYPE_TAG('u', 'g', 0, 0), // ug = Uighur + TRUETYPE_TAG('u', 'k', 0, 0), // uk = Ukrainian + TRUETYPE_TAG('u', 'r', 0, 0), // ur = Urdu + TRUETYPE_TAG('u', 'z', 0, 0), // uz = Uzbek + TRUETYPE_TAG('v', 'e', 0, 0), // ve = Venda + TRUETYPE_TAG('v', 'i', 0, 0), // vi = Vietnamese + TRUETYPE_TAG('v', 'o', 0, 0), // vo = Volapük + TRUETYPE_TAG('w', 'a', 0, 0), // wa = Walloon + TRUETYPE_TAG('w', 'o', 0, 0), // wo = Wolof + TRUETYPE_TAG('x', 'h', 0, 0), // xh = Xhosa + TRUETYPE_TAG('y', 'i', 0, 0), // yi = Yiddish + TRUETYPE_TAG('y', 'o', 0, 0), // yo = Yoruba + TRUETYPE_TAG('z', 'a', 0, 0), // za = Zhuang + TRUETYPE_TAG('z', 'h', 0, 0), // zh = Chinese + TRUETYPE_TAG('z', 'u', 0, 0), // zu = Zulu + TRUETYPE_TAG('a', 'a', 'a', 0), // aaa = Ghotuo + TRUETYPE_TAG('a', 'a', 'b', 0), // aab = Alumu-Tesu + TRUETYPE_TAG('a', 'a', 'c', 0), // aac = Ari + TRUETYPE_TAG('a', 'a', 'd', 0), // aad = Amal + TRUETYPE_TAG('a', 'a', 'e', 0), // aae = Arbëreshë Albanian + TRUETYPE_TAG('a', 'a', 'f', 0), // aaf = Aranadan + TRUETYPE_TAG('a', 'a', 'g', 0), // aag = Ambrak + TRUETYPE_TAG('a', 'a', 'h', 0), // aah = Abu' Arapesh + TRUETYPE_TAG('a', 'a', 'i', 0), // aai = Arifama-Miniafia + TRUETYPE_TAG('a', 'a', 'k', 0), // aak = Ankave + TRUETYPE_TAG('a', 'a', 'l', 0), // aal = Afade + TRUETYPE_TAG('a', 'a', 'm', 0), // aam = Aramanik + TRUETYPE_TAG('a', 'a', 'n', 0), // aan = Anambé + TRUETYPE_TAG('a', 'a', 'o', 0), // aao = Algerian Saharan Arabic + TRUETYPE_TAG('a', 'a', 'p', 0), // aap = Pará Arára + TRUETYPE_TAG('a', 'a', 'q', 0), // aaq = Eastern Abnaki + TRUETYPE_TAG('a', 'a', 's', 0), // aas = Aasáx + TRUETYPE_TAG('a', 'a', 't', 0), // aat = Arvanitika Albanian + TRUETYPE_TAG('a', 'a', 'u', 0), // aau = Abau + TRUETYPE_TAG('a', 'a', 'v', 0), // aav = Austro-Asiatic languages + TRUETYPE_TAG('a', 'a', 'w', 0), // aaw = Solong + TRUETYPE_TAG('a', 'a', 'x', 0), // aax = Mandobo Atas + TRUETYPE_TAG('a', 'a', 'z', 0), // aaz = Amarasi + TRUETYPE_TAG('a', 'b', 'a', 0), // aba = Abé + TRUETYPE_TAG('a', 'b', 'b', 0), // abb = Bankon + TRUETYPE_TAG('a', 'b', 'c', 0), // abc = Ambala Ayta + TRUETYPE_TAG('a', 'b', 'd', 0), // abd = Manide + TRUETYPE_TAG('a', 'b', 'e', 0), // abe = Western Abnaki + TRUETYPE_TAG('a', 'b', 'f', 0), // abf = Abai Sungai + TRUETYPE_TAG('a', 'b', 'g', 0), // abg = Abaga + TRUETYPE_TAG('a', 'b', 'h', 0), // abh = Tajiki Arabic + TRUETYPE_TAG('a', 'b', 'i', 0), // abi = Abidji + TRUETYPE_TAG('a', 'b', 'j', 0), // abj = Aka-Bea + TRUETYPE_TAG('a', 'b', 'l', 0), // abl = Lampung Nyo + TRUETYPE_TAG('a', 'b', 'm', 0), // abm = Abanyom + TRUETYPE_TAG('a', 'b', 'n', 0), // abn = Abua + TRUETYPE_TAG('a', 'b', 'o', 0), // abo = Abon + TRUETYPE_TAG('a', 'b', 'p', 0), // abp = Abellen Ayta + TRUETYPE_TAG('a', 'b', 'q', 0), // abq = Abaza + TRUETYPE_TAG('a', 'b', 'r', 0), // abr = Abron + TRUETYPE_TAG('a', 'b', 's', 0), // abs = Ambonese Malay + TRUETYPE_TAG('a', 'b', 't', 0), // abt = Ambulas + TRUETYPE_TAG('a', 'b', 'u', 0), // abu = Abure + TRUETYPE_TAG('a', 'b', 'v', 0), // abv = Baharna Arabic + TRUETYPE_TAG('a', 'b', 'w', 0), // abw = Pal + TRUETYPE_TAG('a', 'b', 'x', 0), // abx = Inabaknon + TRUETYPE_TAG('a', 'b', 'y', 0), // aby = Aneme Wake + TRUETYPE_TAG('a', 'b', 'z', 0), // abz = Abui + TRUETYPE_TAG('a', 'c', 'a', 0), // aca = Achagua + TRUETYPE_TAG('a', 'c', 'b', 0), // acb = Áncá + TRUETYPE_TAG('a', 'c', 'd', 0), // acd = Gikyode + TRUETYPE_TAG('a', 'c', 'e', 0), // ace = Achinese + TRUETYPE_TAG('a', 'c', 'f', 0), // acf = Saint Lucian Creole French + TRUETYPE_TAG('a', 'c', 'h', 0), // ach = Acoli + TRUETYPE_TAG('a', 'c', 'i', 0), // aci = Aka-Cari + TRUETYPE_TAG('a', 'c', 'k', 0), // ack = Aka-Kora + TRUETYPE_TAG('a', 'c', 'l', 0), // acl = Akar-Bale + TRUETYPE_TAG('a', 'c', 'm', 0), // acm = Mesopotamian Arabic + TRUETYPE_TAG('a', 'c', 'n', 0), // acn = Achang + TRUETYPE_TAG('a', 'c', 'p', 0), // acp = Eastern Acipa + TRUETYPE_TAG('a', 'c', 'q', 0), // acq = Ta'izzi-Adeni Arabic + TRUETYPE_TAG('a', 'c', 'r', 0), // acr = Achi + TRUETYPE_TAG('a', 'c', 's', 0), // acs = Acroá + TRUETYPE_TAG('a', 'c', 't', 0), // act = Achterhoeks + TRUETYPE_TAG('a', 'c', 'u', 0), // acu = Achuar-Shiwiar + TRUETYPE_TAG('a', 'c', 'v', 0), // acv = Achumawi + TRUETYPE_TAG('a', 'c', 'w', 0), // acw = Hijazi Arabic + TRUETYPE_TAG('a', 'c', 'x', 0), // acx = Omani Arabic + TRUETYPE_TAG('a', 'c', 'y', 0), // acy = Cypriot Arabic + TRUETYPE_TAG('a', 'c', 'z', 0), // acz = Acheron + TRUETYPE_TAG('a', 'd', 'a', 0), // ada = Adangme + TRUETYPE_TAG('a', 'd', 'b', 0), // adb = Adabe + TRUETYPE_TAG('a', 'd', 'd', 0), // add = Dzodinka + TRUETYPE_TAG('a', 'd', 'e', 0), // ade = Adele + TRUETYPE_TAG('a', 'd', 'f', 0), // adf = Dhofari Arabic + TRUETYPE_TAG('a', 'd', 'g', 0), // adg = Andegerebinha + TRUETYPE_TAG('a', 'd', 'h', 0), // adh = Adhola + TRUETYPE_TAG('a', 'd', 'i', 0), // adi = Adi + TRUETYPE_TAG('a', 'd', 'j', 0), // adj = Adioukrou + TRUETYPE_TAG('a', 'd', 'l', 0), // adl = Galo + TRUETYPE_TAG('a', 'd', 'n', 0), // adn = Adang + TRUETYPE_TAG('a', 'd', 'o', 0), // ado = Abu + TRUETYPE_TAG('a', 'd', 'p', 0), // adp = Adap + TRUETYPE_TAG('a', 'd', 'q', 0), // adq = Adangbe + TRUETYPE_TAG('a', 'd', 'r', 0), // adr = Adonara + TRUETYPE_TAG('a', 'd', 's', 0), // ads = Adamorobe Sign Language + TRUETYPE_TAG('a', 'd', 't', 0), // adt = Adnyamathanha + TRUETYPE_TAG('a', 'd', 'u', 0), // adu = Aduge + TRUETYPE_TAG('a', 'd', 'w', 0), // adw = Amundava + TRUETYPE_TAG('a', 'd', 'x', 0), // adx = Amdo Tibetan + TRUETYPE_TAG('a', 'd', 'y', 0), // ady = Adyghe + TRUETYPE_TAG('a', 'd', 'z', 0), // adz = Adzera + TRUETYPE_TAG('a', 'e', 'a', 0), // aea = Areba + TRUETYPE_TAG('a', 'e', 'b', 0), // aeb = Tunisian Arabic + TRUETYPE_TAG('a', 'e', 'c', 0), // aec = Saidi Arabic + TRUETYPE_TAG('a', 'e', 'd', 0), // aed = Argentine Sign Language + TRUETYPE_TAG('a', 'e', 'e', 0), // aee = Northeast Pashayi + TRUETYPE_TAG('a', 'e', 'k', 0), // aek = Haeke + TRUETYPE_TAG('a', 'e', 'l', 0), // ael = Ambele + TRUETYPE_TAG('a', 'e', 'm', 0), // aem = Arem + TRUETYPE_TAG('a', 'e', 'n', 0), // aen = Armenian Sign Language + TRUETYPE_TAG('a', 'e', 'q', 0), // aeq = Aer + TRUETYPE_TAG('a', 'e', 'r', 0), // aer = Eastern Arrernte + TRUETYPE_TAG('a', 'e', 's', 0), // aes = Alsea + TRUETYPE_TAG('a', 'e', 'u', 0), // aeu = Akeu + TRUETYPE_TAG('a', 'e', 'w', 0), // aew = Ambakich + TRUETYPE_TAG('a', 'e', 'y', 0), // aey = Amele + TRUETYPE_TAG('a', 'e', 'z', 0), // aez = Aeka + TRUETYPE_TAG('a', 'f', 'a', 0), // afa = Afro-Asiatic languages + TRUETYPE_TAG('a', 'f', 'b', 0), // afb = Gulf Arabic + TRUETYPE_TAG('a', 'f', 'd', 0), // afd = Andai + TRUETYPE_TAG('a', 'f', 'e', 0), // afe = Putukwam + TRUETYPE_TAG('a', 'f', 'g', 0), // afg = Afghan Sign Language + TRUETYPE_TAG('a', 'f', 'h', 0), // afh = Afrihili + TRUETYPE_TAG('a', 'f', 'i', 0), // afi = Akrukay + TRUETYPE_TAG('a', 'f', 'k', 0), // afk = Nanubae + TRUETYPE_TAG('a', 'f', 'n', 0), // afn = Defaka + TRUETYPE_TAG('a', 'f', 'o', 0), // afo = Eloyi + TRUETYPE_TAG('a', 'f', 'p', 0), // afp = Tapei + TRUETYPE_TAG('a', 'f', 's', 0), // afs = Afro-Seminole Creole + TRUETYPE_TAG('a', 'f', 't', 0), // aft = Afitti + TRUETYPE_TAG('a', 'f', 'u', 0), // afu = Awutu + TRUETYPE_TAG('a', 'f', 'z', 0), // afz = Obokuitai + TRUETYPE_TAG('a', 'g', 'a', 0), // aga = Aguano + TRUETYPE_TAG('a', 'g', 'b', 0), // agb = Legbo + TRUETYPE_TAG('a', 'g', 'c', 0), // agc = Agatu + TRUETYPE_TAG('a', 'g', 'd', 0), // agd = Agarabi + TRUETYPE_TAG('a', 'g', 'e', 0), // age = Angal + TRUETYPE_TAG('a', 'g', 'f', 0), // agf = Arguni + TRUETYPE_TAG('a', 'g', 'g', 0), // agg = Angor + TRUETYPE_TAG('a', 'g', 'h', 0), // agh = Ngelima + TRUETYPE_TAG('a', 'g', 'i', 0), // agi = Agariya + TRUETYPE_TAG('a', 'g', 'j', 0), // agj = Argobba + TRUETYPE_TAG('a', 'g', 'k', 0), // agk = Isarog Agta + TRUETYPE_TAG('a', 'g', 'l', 0), // agl = Fembe + TRUETYPE_TAG('a', 'g', 'm', 0), // agm = Angaataha + TRUETYPE_TAG('a', 'g', 'n', 0), // agn = Agutaynen + TRUETYPE_TAG('a', 'g', 'o', 0), // ago = Tainae + TRUETYPE_TAG('a', 'g', 'p', 0), // agp = Paranan + TRUETYPE_TAG('a', 'g', 'q', 0), // agq = Aghem + TRUETYPE_TAG('a', 'g', 'r', 0), // agr = Aguaruna + TRUETYPE_TAG('a', 'g', 's', 0), // ags = Esimbi + TRUETYPE_TAG('a', 'g', 't', 0), // agt = Central Cagayan Agta + TRUETYPE_TAG('a', 'g', 'u', 0), // agu = Aguacateco + TRUETYPE_TAG('a', 'g', 'v', 0), // agv = Remontado Dumagat + TRUETYPE_TAG('a', 'g', 'w', 0), // agw = Kahua + TRUETYPE_TAG('a', 'g', 'x', 0), // agx = Aghul + TRUETYPE_TAG('a', 'g', 'y', 0), // agy = Southern Alta + TRUETYPE_TAG('a', 'g', 'z', 0), // agz = Mt. Iriga Agta + TRUETYPE_TAG('a', 'h', 'a', 0), // aha = Ahanta + TRUETYPE_TAG('a', 'h', 'b', 0), // ahb = Axamb + TRUETYPE_TAG('a', 'h', 'g', 0), // ahg = Qimant + TRUETYPE_TAG('a', 'h', 'h', 0), // ahh = Aghu + TRUETYPE_TAG('a', 'h', 'i', 0), // ahi = Tiagbamrin Aizi + TRUETYPE_TAG('a', 'h', 'k', 0), // ahk = Akha + TRUETYPE_TAG('a', 'h', 'l', 0), // ahl = Igo + TRUETYPE_TAG('a', 'h', 'm', 0), // ahm = Mobumrin Aizi + TRUETYPE_TAG('a', 'h', 'n', 0), // ahn = Àhàn + TRUETYPE_TAG('a', 'h', 'o', 0), // aho = Ahom + TRUETYPE_TAG('a', 'h', 'p', 0), // ahp = Aproumu Aizi + TRUETYPE_TAG('a', 'h', 'r', 0), // ahr = Ahirani + TRUETYPE_TAG('a', 'h', 's', 0), // ahs = Ashe + TRUETYPE_TAG('a', 'h', 't', 0), // aht = Ahtena + TRUETYPE_TAG('a', 'i', 'a', 0), // aia = Arosi + TRUETYPE_TAG('a', 'i', 'b', 0), // aib = Ainu (China) + TRUETYPE_TAG('a', 'i', 'c', 0), // aic = Ainbai + TRUETYPE_TAG('a', 'i', 'd', 0), // aid = Alngith + TRUETYPE_TAG('a', 'i', 'e', 0), // aie = Amara + TRUETYPE_TAG('a', 'i', 'f', 0), // aif = Agi + TRUETYPE_TAG('a', 'i', 'g', 0), // aig = Antigua and Barbuda Creole English + TRUETYPE_TAG('a', 'i', 'h', 0), // aih = Ai-Cham + TRUETYPE_TAG('a', 'i', 'i', 0), // aii = Assyrian Neo-Aramaic + TRUETYPE_TAG('a', 'i', 'j', 0), // aij = Lishanid Noshan + TRUETYPE_TAG('a', 'i', 'k', 0), // aik = Ake + TRUETYPE_TAG('a', 'i', 'l', 0), // ail = Aimele + TRUETYPE_TAG('a', 'i', 'm', 0), // aim = Aimol + TRUETYPE_TAG('a', 'i', 'n', 0), // ain = Ainu (Japan) + TRUETYPE_TAG('a', 'i', 'o', 0), // aio = Aiton + TRUETYPE_TAG('a', 'i', 'p', 0), // aip = Burumakok + TRUETYPE_TAG('a', 'i', 'q', 0), // aiq = Aimaq + TRUETYPE_TAG('a', 'i', 'r', 0), // air = Airoran + TRUETYPE_TAG('a', 'i', 's', 0), // ais = Nataoran Amis + TRUETYPE_TAG('a', 'i', 't', 0), // ait = Arikem + TRUETYPE_TAG('a', 'i', 'w', 0), // aiw = Aari + TRUETYPE_TAG('a', 'i', 'x', 0), // aix = Aighon + TRUETYPE_TAG('a', 'i', 'y', 0), // aiy = Ali + TRUETYPE_TAG('a', 'j', 'a', 0), // aja = Aja (Sudan) + TRUETYPE_TAG('a', 'j', 'g', 0), // ajg = Aja (Benin) + TRUETYPE_TAG('a', 'j', 'i', 0), // aji = Ajië + TRUETYPE_TAG('a', 'j', 'p', 0), // ajp = South Levantine Arabic + TRUETYPE_TAG('a', 'j', 't', 0), // ajt = Judeo-Tunisian Arabic + TRUETYPE_TAG('a', 'j', 'u', 0), // aju = Judeo-Moroccan Arabic + TRUETYPE_TAG('a', 'j', 'w', 0), // ajw = Ajawa + TRUETYPE_TAG('a', 'j', 'z', 0), // ajz = Amri Karbi + TRUETYPE_TAG('a', 'k', 'b', 0), // akb = Batak Angkola + TRUETYPE_TAG('a', 'k', 'c', 0), // akc = Mpur + TRUETYPE_TAG('a', 'k', 'd', 0), // akd = Ukpet-Ehom + TRUETYPE_TAG('a', 'k', 'e', 0), // ake = Akawaio + TRUETYPE_TAG('a', 'k', 'f', 0), // akf = Akpa + TRUETYPE_TAG('a', 'k', 'g', 0), // akg = Anakalangu + TRUETYPE_TAG('a', 'k', 'h', 0), // akh = Angal Heneng + TRUETYPE_TAG('a', 'k', 'i', 0), // aki = Aiome + TRUETYPE_TAG('a', 'k', 'j', 0), // akj = Aka-Jeru + TRUETYPE_TAG('a', 'k', 'k', 0), // akk = Akkadian + TRUETYPE_TAG('a', 'k', 'l', 0), // akl = Aklanon + TRUETYPE_TAG('a', 'k', 'm', 0), // akm = Aka-Bo + TRUETYPE_TAG('a', 'k', 'o', 0), // ako = Akurio + TRUETYPE_TAG('a', 'k', 'p', 0), // akp = Siwu + TRUETYPE_TAG('a', 'k', 'q', 0), // akq = Ak + TRUETYPE_TAG('a', 'k', 'r', 0), // akr = Araki + TRUETYPE_TAG('a', 'k', 's', 0), // aks = Akaselem + TRUETYPE_TAG('a', 'k', 't', 0), // akt = Akolet + TRUETYPE_TAG('a', 'k', 'u', 0), // aku = Akum + TRUETYPE_TAG('a', 'k', 'v', 0), // akv = Akhvakh + TRUETYPE_TAG('a', 'k', 'w', 0), // akw = Akwa + TRUETYPE_TAG('a', 'k', 'x', 0), // akx = Aka-Kede + TRUETYPE_TAG('a', 'k', 'y', 0), // aky = Aka-Kol + TRUETYPE_TAG('a', 'k', 'z', 0), // akz = Alabama + TRUETYPE_TAG('a', 'l', 'a', 0), // ala = Alago + TRUETYPE_TAG('a', 'l', 'c', 0), // alc = Qawasqar + TRUETYPE_TAG('a', 'l', 'd', 0), // ald = Alladian + TRUETYPE_TAG('a', 'l', 'e', 0), // ale = Aleut + TRUETYPE_TAG('a', 'l', 'f', 0), // alf = Alege + TRUETYPE_TAG('a', 'l', 'g', 0), // alg = Algonquian languages + TRUETYPE_TAG('a', 'l', 'h', 0), // alh = Alawa + TRUETYPE_TAG('a', 'l', 'i', 0), // ali = Amaimon + TRUETYPE_TAG('a', 'l', 'j', 0), // alj = Alangan + TRUETYPE_TAG('a', 'l', 'k', 0), // alk = Alak + TRUETYPE_TAG('a', 'l', 'l', 0), // all = Allar + TRUETYPE_TAG('a', 'l', 'm', 0), // alm = Amblong + TRUETYPE_TAG('a', 'l', 'n', 0), // aln = Gheg Albanian + TRUETYPE_TAG('a', 'l', 'o', 0), // alo = Larike-Wakasihu + TRUETYPE_TAG('a', 'l', 'p', 0), // alp = Alune + TRUETYPE_TAG('a', 'l', 'q', 0), // alq = Algonquin + TRUETYPE_TAG('a', 'l', 'r', 0), // alr = Alutor + TRUETYPE_TAG('a', 'l', 's', 0), // als = Tosk Albanian + TRUETYPE_TAG('a', 'l', 't', 0), // alt = Southern Altai + TRUETYPE_TAG('a', 'l', 'u', 0), // alu = 'Are'are + TRUETYPE_TAG('a', 'l', 'v', 0), // alv = Atlantic-Congo languages + TRUETYPE_TAG('a', 'l', 'w', 0), // alw = Alaba-K’abeena + TRUETYPE_TAG('a', 'l', 'x', 0), // alx = Amol + TRUETYPE_TAG('a', 'l', 'y', 0), // aly = Alyawarr + TRUETYPE_TAG('a', 'l', 'z', 0), // alz = Alur + TRUETYPE_TAG('a', 'm', 'a', 0), // ama = Amanayé + TRUETYPE_TAG('a', 'm', 'b', 0), // amb = Ambo + TRUETYPE_TAG('a', 'm', 'c', 0), // amc = Amahuaca + TRUETYPE_TAG('a', 'm', 'e', 0), // ame = Yanesha' + TRUETYPE_TAG('a', 'm', 'f', 0), // amf = Hamer-Banna + TRUETYPE_TAG('a', 'm', 'g', 0), // amg = Amarag + TRUETYPE_TAG('a', 'm', 'i', 0), // ami = Amis + TRUETYPE_TAG('a', 'm', 'j', 0), // amj = Amdang + TRUETYPE_TAG('a', 'm', 'k', 0), // amk = Ambai + TRUETYPE_TAG('a', 'm', 'l', 0), // aml = War-Jaintia + TRUETYPE_TAG('a', 'm', 'm', 0), // amm = Ama (Papua New Guinea) + TRUETYPE_TAG('a', 'm', 'n', 0), // amn = Amanab + TRUETYPE_TAG('a', 'm', 'o', 0), // amo = Amo + TRUETYPE_TAG('a', 'm', 'p', 0), // amp = Alamblak + TRUETYPE_TAG('a', 'm', 'q', 0), // amq = Amahai + TRUETYPE_TAG('a', 'm', 'r', 0), // amr = Amarakaeri + TRUETYPE_TAG('a', 'm', 's', 0), // ams = Southern Amami-Oshima + TRUETYPE_TAG('a', 'm', 't', 0), // amt = Amto + TRUETYPE_TAG('a', 'm', 'u', 0), // amu = Guerrero Amuzgo + TRUETYPE_TAG('a', 'm', 'v', 0), // amv = Ambelau + TRUETYPE_TAG('a', 'm', 'w', 0), // amw = Western Neo-Aramaic + TRUETYPE_TAG('a', 'm', 'x', 0), // amx = Anmatyerre + TRUETYPE_TAG('a', 'm', 'y', 0), // amy = Ami + TRUETYPE_TAG('a', 'm', 'z', 0), // amz = Atampaya + TRUETYPE_TAG('a', 'n', 'a', 0), // ana = Andaqui + TRUETYPE_TAG('a', 'n', 'b', 0), // anb = Andoa + TRUETYPE_TAG('a', 'n', 'c', 0), // anc = Ngas + TRUETYPE_TAG('a', 'n', 'd', 0), // and = Ansus + TRUETYPE_TAG('a', 'n', 'e', 0), // ane = Xârâcùù + TRUETYPE_TAG('a', 'n', 'f', 0), // anf = Animere + TRUETYPE_TAG('a', 'n', 'g', 0), // ang = Old English (ca. 450-1100) + TRUETYPE_TAG('a', 'n', 'h', 0), // anh = Nend + TRUETYPE_TAG('a', 'n', 'i', 0), // ani = Andi + TRUETYPE_TAG('a', 'n', 'j', 0), // anj = Anor + TRUETYPE_TAG('a', 'n', 'k', 0), // ank = Goemai + TRUETYPE_TAG('a', 'n', 'l', 0), // anl = Anu + TRUETYPE_TAG('a', 'n', 'm', 0), // anm = Anal + TRUETYPE_TAG('a', 'n', 'n', 0), // ann = Obolo + TRUETYPE_TAG('a', 'n', 'o', 0), // ano = Andoque + TRUETYPE_TAG('a', 'n', 'p', 0), // anp = Angika + TRUETYPE_TAG('a', 'n', 'q', 0), // anq = Jarawa (India) + TRUETYPE_TAG('a', 'n', 'r', 0), // anr = Andh + TRUETYPE_TAG('a', 'n', 's', 0), // ans = Anserma + TRUETYPE_TAG('a', 'n', 't', 0), // ant = Antakarinya + TRUETYPE_TAG('a', 'n', 'u', 0), // anu = Anuak + TRUETYPE_TAG('a', 'n', 'v', 0), // anv = Denya + TRUETYPE_TAG('a', 'n', 'w', 0), // anw = Anaang + TRUETYPE_TAG('a', 'n', 'x', 0), // anx = Andra-Hus + TRUETYPE_TAG('a', 'n', 'y', 0), // any = Anyin + TRUETYPE_TAG('a', 'n', 'z', 0), // anz = Anem + TRUETYPE_TAG('a', 'o', 'a', 0), // aoa = Angolar + TRUETYPE_TAG('a', 'o', 'b', 0), // aob = Abom + TRUETYPE_TAG('a', 'o', 'c', 0), // aoc = Pemon + TRUETYPE_TAG('a', 'o', 'd', 0), // aod = Andarum + TRUETYPE_TAG('a', 'o', 'e', 0), // aoe = Angal Enen + TRUETYPE_TAG('a', 'o', 'f', 0), // aof = Bragat + TRUETYPE_TAG('a', 'o', 'g', 0), // aog = Angoram + TRUETYPE_TAG('a', 'o', 'h', 0), // aoh = Arma + TRUETYPE_TAG('a', 'o', 'i', 0), // aoi = Anindilyakwa + TRUETYPE_TAG('a', 'o', 'j', 0), // aoj = Mufian + TRUETYPE_TAG('a', 'o', 'k', 0), // aok = Arhö + TRUETYPE_TAG('a', 'o', 'l', 0), // aol = Alor + TRUETYPE_TAG('a', 'o', 'm', 0), // aom = Ömie + TRUETYPE_TAG('a', 'o', 'n', 0), // aon = Bumbita Arapesh + TRUETYPE_TAG('a', 'o', 'r', 0), // aor = Aore + TRUETYPE_TAG('a', 'o', 's', 0), // aos = Taikat + TRUETYPE_TAG('a', 'o', 't', 0), // aot = A'tong + TRUETYPE_TAG('a', 'o', 'x', 0), // aox = Atorada + TRUETYPE_TAG('a', 'o', 'z', 0), // aoz = Uab Meto + TRUETYPE_TAG('a', 'p', 'a', 0), // apa = Apache languages + TRUETYPE_TAG('a', 'p', 'b', 0), // apb = Sa'a + TRUETYPE_TAG('a', 'p', 'c', 0), // apc = North Levantine Arabic + TRUETYPE_TAG('a', 'p', 'd', 0), // apd = Sudanese Arabic + TRUETYPE_TAG('a', 'p', 'e', 0), // ape = Bukiyip + TRUETYPE_TAG('a', 'p', 'f', 0), // apf = Pahanan Agta + TRUETYPE_TAG('a', 'p', 'g', 0), // apg = Ampanang + TRUETYPE_TAG('a', 'p', 'h', 0), // aph = Athpariya + TRUETYPE_TAG('a', 'p', 'i', 0), // api = Apiaká + TRUETYPE_TAG('a', 'p', 'j', 0), // apj = Jicarilla Apache + TRUETYPE_TAG('a', 'p', 'k', 0), // apk = Kiowa Apache + TRUETYPE_TAG('a', 'p', 'l', 0), // apl = Lipan Apache + TRUETYPE_TAG('a', 'p', 'm', 0), // apm = Mescalero-Chiricahua Apache + TRUETYPE_TAG('a', 'p', 'n', 0), // apn = Apinayé + TRUETYPE_TAG('a', 'p', 'o', 0), // apo = Ambul + TRUETYPE_TAG('a', 'p', 'p', 0), // app = Apma + TRUETYPE_TAG('a', 'p', 'q', 0), // apq = A-Pucikwar + TRUETYPE_TAG('a', 'p', 'r', 0), // apr = Arop-Lokep + TRUETYPE_TAG('a', 'p', 's', 0), // aps = Arop-Sissano + TRUETYPE_TAG('a', 'p', 't', 0), // apt = Apatani + TRUETYPE_TAG('a', 'p', 'u', 0), // apu = Apurinã + TRUETYPE_TAG('a', 'p', 'v', 0), // apv = Alapmunte + TRUETYPE_TAG('a', 'p', 'w', 0), // apw = Western Apache + TRUETYPE_TAG('a', 'p', 'x', 0), // apx = Aputai + TRUETYPE_TAG('a', 'p', 'y', 0), // apy = Apalaí + TRUETYPE_TAG('a', 'p', 'z', 0), // apz = Safeyoka + TRUETYPE_TAG('a', 'q', 'a', 0), // aqa = Alacalufan languages + TRUETYPE_TAG('a', 'q', 'c', 0), // aqc = Archi + TRUETYPE_TAG('a', 'q', 'd', 0), // aqd = Ampari Dogon + TRUETYPE_TAG('a', 'q', 'g', 0), // aqg = Arigidi + TRUETYPE_TAG('a', 'q', 'l', 0), // aql = Algic languages + TRUETYPE_TAG('a', 'q', 'm', 0), // aqm = Atohwaim + TRUETYPE_TAG('a', 'q', 'n', 0), // aqn = Northern Alta + TRUETYPE_TAG('a', 'q', 'p', 0), // aqp = Atakapa + TRUETYPE_TAG('a', 'q', 'r', 0), // aqr = Arhâ + TRUETYPE_TAG('a', 'q', 'z', 0), // aqz = Akuntsu + TRUETYPE_TAG('a', 'r', 'b', 0), // arb = Standard Arabic + TRUETYPE_TAG('a', 'r', 'c', 0), // arc = Official Aramaic (700-300 BCE) + TRUETYPE_TAG('a', 'r', 'd', 0), // ard = Arabana + TRUETYPE_TAG('a', 'r', 'e', 0), // are = Western Arrarnta + TRUETYPE_TAG('a', 'r', 'h', 0), // arh = Arhuaco + TRUETYPE_TAG('a', 'r', 'i', 0), // ari = Arikara + TRUETYPE_TAG('a', 'r', 'j', 0), // arj = Arapaso + TRUETYPE_TAG('a', 'r', 'k', 0), // ark = Arikapú + TRUETYPE_TAG('a', 'r', 'l', 0), // arl = Arabela + TRUETYPE_TAG('a', 'r', 'n', 0), // arn = Mapudungun + TRUETYPE_TAG('a', 'r', 'o', 0), // aro = Araona + TRUETYPE_TAG('a', 'r', 'p', 0), // arp = Arapaho + TRUETYPE_TAG('a', 'r', 'q', 0), // arq = Algerian Arabic + TRUETYPE_TAG('a', 'r', 'r', 0), // arr = Karo (Brazil) + TRUETYPE_TAG('a', 'r', 's', 0), // ars = Najdi Arabic + TRUETYPE_TAG('a', 'r', 't', 0), // art = Artificial languages + TRUETYPE_TAG('a', 'r', 'u', 0), // aru = Aruá (Amazonas State) + TRUETYPE_TAG('a', 'r', 'v', 0), // arv = Arbore + TRUETYPE_TAG('a', 'r', 'w', 0), // arw = Arawak + TRUETYPE_TAG('a', 'r', 'x', 0), // arx = Aruá (Rodonia State) + TRUETYPE_TAG('a', 'r', 'y', 0), // ary = Moroccan Arabic + TRUETYPE_TAG('a', 'r', 'z', 0), // arz = Egyptian Arabic + TRUETYPE_TAG('a', 's', 'a', 0), // asa = Asu (Tanzania) + TRUETYPE_TAG('a', 's', 'b', 0), // asb = Assiniboine + TRUETYPE_TAG('a', 's', 'c', 0), // asc = Casuarina Coast Asmat + TRUETYPE_TAG('a', 's', 'd', 0), // asd = Asas + TRUETYPE_TAG('a', 's', 'e', 0), // ase = American Sign Language + TRUETYPE_TAG('a', 's', 'f', 0), // asf = Australian Sign Language + TRUETYPE_TAG('a', 's', 'g', 0), // asg = Cishingini + TRUETYPE_TAG('a', 's', 'h', 0), // ash = Abishira + TRUETYPE_TAG('a', 's', 'i', 0), // asi = Buruwai + TRUETYPE_TAG('a', 's', 'j', 0), // asj = Nsari + TRUETYPE_TAG('a', 's', 'k', 0), // ask = Ashkun + TRUETYPE_TAG('a', 's', 'l', 0), // asl = Asilulu + TRUETYPE_TAG('a', 's', 'n', 0), // asn = Xingú Asuriní + TRUETYPE_TAG('a', 's', 'o', 0), // aso = Dano + TRUETYPE_TAG('a', 's', 'p', 0), // asp = Algerian Sign Language + TRUETYPE_TAG('a', 's', 'q', 0), // asq = Austrian Sign Language + TRUETYPE_TAG('a', 's', 'r', 0), // asr = Asuri + TRUETYPE_TAG('a', 's', 's', 0), // ass = Ipulo + TRUETYPE_TAG('a', 's', 't', 0), // ast = Asturian + TRUETYPE_TAG('a', 's', 'u', 0), // asu = Tocantins Asurini + TRUETYPE_TAG('a', 's', 'v', 0), // asv = Asoa + TRUETYPE_TAG('a', 's', 'w', + 0), // asw = Australian Aborigines Sign Language + TRUETYPE_TAG('a', 's', 'x', 0), // asx = Muratayak + TRUETYPE_TAG('a', 's', 'y', 0), // asy = Yaosakor Asmat + TRUETYPE_TAG('a', 's', 'z', 0), // asz = As + TRUETYPE_TAG('a', 't', 'a', 0), // ata = Pele-Ata + TRUETYPE_TAG('a', 't', 'b', 0), // atb = Zaiwa + TRUETYPE_TAG('a', 't', 'c', 0), // atc = Atsahuaca + TRUETYPE_TAG('a', 't', 'd', 0), // atd = Ata Manobo + TRUETYPE_TAG('a', 't', 'e', 0), // ate = Atemble + TRUETYPE_TAG('a', 't', 'g', 0), // atg = Ivbie North-Okpela-Arhe + TRUETYPE_TAG('a', 't', 'h', 0), // ath = Athapascan languages + TRUETYPE_TAG('a', 't', 'i', 0), // ati = Attié + TRUETYPE_TAG('a', 't', 'j', 0), // atj = Atikamekw + TRUETYPE_TAG('a', 't', 'k', 0), // atk = Ati + TRUETYPE_TAG('a', 't', 'l', 0), // atl = Mt. Iraya Agta + TRUETYPE_TAG('a', 't', 'm', 0), // atm = Ata + TRUETYPE_TAG('a', 't', 'n', 0), // atn = Ashtiani + TRUETYPE_TAG('a', 't', 'o', 0), // ato = Atong + TRUETYPE_TAG('a', 't', 'p', 0), // atp = Pudtol Atta + TRUETYPE_TAG('a', 't', 'q', 0), // atq = Aralle-Tabulahan + TRUETYPE_TAG('a', 't', 'r', 0), // atr = Waimiri-Atroari + TRUETYPE_TAG('a', 't', 's', 0), // ats = Gros Ventre + TRUETYPE_TAG('a', 't', 't', 0), // att = Pamplona Atta + TRUETYPE_TAG('a', 't', 'u', 0), // atu = Reel + TRUETYPE_TAG('a', 't', 'v', 0), // atv = Northern Altai + TRUETYPE_TAG('a', 't', 'w', 0), // atw = Atsugewi + TRUETYPE_TAG('a', 't', 'x', 0), // atx = Arutani + TRUETYPE_TAG('a', 't', 'y', 0), // aty = Aneityum + TRUETYPE_TAG('a', 't', 'z', 0), // atz = Arta + TRUETYPE_TAG('a', 'u', 'a', 0), // aua = Asumboa + TRUETYPE_TAG('a', 'u', 'b', 0), // aub = Alugu + TRUETYPE_TAG('a', 'u', 'c', 0), // auc = Waorani + TRUETYPE_TAG('a', 'u', 'd', 0), // aud = Anuta + TRUETYPE_TAG('a', 'u', 'e', 0), // aue = =/Kx'au//'ein + TRUETYPE_TAG('a', 'u', 'f', 0), // auf = Arauan languages + TRUETYPE_TAG('a', 'u', 'g', 0), // aug = Aguna + TRUETYPE_TAG('a', 'u', 'h', 0), // auh = Aushi + TRUETYPE_TAG('a', 'u', 'i', 0), // aui = Anuki + TRUETYPE_TAG('a', 'u', 'j', 0), // auj = Awjilah + TRUETYPE_TAG('a', 'u', 'k', 0), // auk = Heyo + TRUETYPE_TAG('a', 'u', 'l', 0), // aul = Aulua + TRUETYPE_TAG('a', 'u', 'm', 0), // aum = Asu (Nigeria) + TRUETYPE_TAG('a', 'u', 'n', 0), // aun = Molmo One + TRUETYPE_TAG('a', 'u', 'o', 0), // auo = Auyokawa + TRUETYPE_TAG('a', 'u', 'p', 0), // aup = Makayam + TRUETYPE_TAG('a', 'u', 'q', 0), // auq = Anus + TRUETYPE_TAG('a', 'u', 'r', 0), // aur = Aruek + TRUETYPE_TAG('a', 'u', 's', 0), // aus = Australian languages + TRUETYPE_TAG('a', 'u', 't', 0), // aut = Austral + TRUETYPE_TAG('a', 'u', 'u', 0), // auu = Auye + TRUETYPE_TAG('a', 'u', 'w', 0), // auw = Awyi + TRUETYPE_TAG('a', 'u', 'x', 0), // aux = Aurá + TRUETYPE_TAG('a', 'u', 'y', 0), // auy = Awiyaana + TRUETYPE_TAG('a', 'u', 'z', 0), // auz = Uzbeki Arabic + TRUETYPE_TAG('a', 'v', 'b', 0), // avb = Avau + TRUETYPE_TAG('a', 'v', 'd', 0), // avd = Alviri-Vidari + TRUETYPE_TAG('a', 'v', 'i', 0), // avi = Avikam + TRUETYPE_TAG('a', 'v', 'k', 0), // avk = Kotava + TRUETYPE_TAG('a', 'v', 'l', 0), // avl = Eastern Egyptian Bedawi Arabic + TRUETYPE_TAG('a', 'v', 'n', 0), // avn = Avatime + TRUETYPE_TAG('a', 'v', 'o', 0), // avo = Agavotaguerra + TRUETYPE_TAG('a', 'v', 's', 0), // avs = Aushiri + TRUETYPE_TAG('a', 'v', 't', 0), // avt = Au + TRUETYPE_TAG('a', 'v', 'u', 0), // avu = Avokaya + TRUETYPE_TAG('a', 'v', 'v', 0), // avv = Avá-Canoeiro + TRUETYPE_TAG('a', 'w', 'a', 0), // awa = Awadhi + TRUETYPE_TAG('a', 'w', 'b', 0), // awb = Awa (Papua New Guinea) + TRUETYPE_TAG('a', 'w', 'c', 0), // awc = Cicipu + TRUETYPE_TAG('a', 'w', 'd', 0), // awd = Arawakan languages + TRUETYPE_TAG('a', 'w', 'e', 0), // awe = Awetí + TRUETYPE_TAG('a', 'w', 'h', 0), // awh = Awbono + TRUETYPE_TAG('a', 'w', 'i', 0), // awi = Aekyom + TRUETYPE_TAG('a', 'w', 'k', 0), // awk = Awabakal + TRUETYPE_TAG('a', 'w', 'm', 0), // awm = Arawum + TRUETYPE_TAG('a', 'w', 'n', 0), // awn = Awngi + TRUETYPE_TAG('a', 'w', 'o', 0), // awo = Awak + TRUETYPE_TAG('a', 'w', 'r', 0), // awr = Awera + TRUETYPE_TAG('a', 'w', 's', 0), // aws = South Awyu + TRUETYPE_TAG('a', 'w', 't', 0), // awt = Araweté + TRUETYPE_TAG('a', 'w', 'u', 0), // awu = Central Awyu + TRUETYPE_TAG('a', 'w', 'v', 0), // awv = Jair Awyu + TRUETYPE_TAG('a', 'w', 'w', 0), // aww = Awun + TRUETYPE_TAG('a', 'w', 'x', 0), // awx = Awara + TRUETYPE_TAG('a', 'w', 'y', 0), // awy = Edera Awyu + TRUETYPE_TAG('a', 'x', 'b', 0), // axb = Abipon + TRUETYPE_TAG('a', 'x', 'g', 0), // axg = Mato Grosso Arára + TRUETYPE_TAG('a', 'x', 'k', 0), // axk = Yaka (Central African Republic) + TRUETYPE_TAG('a', 'x', 'm', 0), // axm = Middle Armenian + TRUETYPE_TAG('a', 'x', 'x', 0), // axx = Xaragure + TRUETYPE_TAG('a', 'y', 'a', 0), // aya = Awar + TRUETYPE_TAG('a', 'y', 'b', 0), // ayb = Ayizo Gbe + TRUETYPE_TAG('a', 'y', 'c', 0), // ayc = Southern Aymara + TRUETYPE_TAG('a', 'y', 'd', 0), // ayd = Ayabadhu + TRUETYPE_TAG('a', 'y', 'e', 0), // aye = Ayere + TRUETYPE_TAG('a', 'y', 'g', 0), // ayg = Ginyanga + TRUETYPE_TAG('a', 'y', 'h', 0), // ayh = Hadrami Arabic + TRUETYPE_TAG('a', 'y', 'i', 0), // ayi = Leyigha + TRUETYPE_TAG('a', 'y', 'k', 0), // ayk = Akuku + TRUETYPE_TAG('a', 'y', 'l', 0), // ayl = Libyan Arabic + TRUETYPE_TAG('a', 'y', 'n', 0), // ayn = Sanaani Arabic + TRUETYPE_TAG('a', 'y', 'o', 0), // ayo = Ayoreo + TRUETYPE_TAG('a', 'y', 'p', 0), // ayp = North Mesopotamian Arabic + TRUETYPE_TAG('a', 'y', 'q', 0), // ayq = Ayi (Papua New Guinea) + TRUETYPE_TAG('a', 'y', 'r', 0), // ayr = Central Aymara + TRUETYPE_TAG('a', 'y', 's', 0), // ays = Sorsogon Ayta + TRUETYPE_TAG('a', 'y', 't', 0), // ayt = Magbukun Ayta + TRUETYPE_TAG('a', 'y', 'u', 0), // ayu = Ayu + TRUETYPE_TAG('a', 'y', 'x', 0), // ayx = Ayi (China) + TRUETYPE_TAG('a', 'y', 'y', 0), // ayy = Tayabas Ayta + TRUETYPE_TAG('a', 'y', 'z', 0), // ayz = Mai Brat + TRUETYPE_TAG('a', 'z', 'a', 0), // aza = Azha + TRUETYPE_TAG('a', 'z', 'b', 0), // azb = South Azerbaijani + TRUETYPE_TAG('a', 'z', 'c', 0), // azc = Uto-Aztecan languages + TRUETYPE_TAG('a', 'z', 'g', 0), // azg = San Pedro Amuzgos Amuzgo + TRUETYPE_TAG('a', 'z', 'j', 0), // azj = North Azerbaijani + TRUETYPE_TAG('a', 'z', 'm', 0), // azm = Ipalapa Amuzgo + TRUETYPE_TAG('a', 'z', 'o', 0), // azo = Awing + TRUETYPE_TAG('a', 'z', 't', 0), // azt = Faire Atta + TRUETYPE_TAG('a', 'z', 'z', 0), // azz = Highland Puebla Nahuatl + TRUETYPE_TAG('b', 'a', 'a', 0), // baa = Babatana + TRUETYPE_TAG('b', 'a', 'b', 0), // bab = Bainouk-Gunyuño + TRUETYPE_TAG('b', 'a', 'c', 0), // bac = Badui + TRUETYPE_TAG('b', 'a', 'd', 0), // bad = Banda languages + TRUETYPE_TAG('b', 'a', 'e', 0), // bae = Baré + TRUETYPE_TAG('b', 'a', 'f', 0), // baf = Nubaca + TRUETYPE_TAG('b', 'a', 'g', 0), // bag = Tuki + TRUETYPE_TAG('b', 'a', 'h', 0), // bah = Bahamas Creole English + TRUETYPE_TAG('b', 'a', 'i', 0), // bai = Bamileke languages + TRUETYPE_TAG('b', 'a', 'j', 0), // baj = Barakai + TRUETYPE_TAG('b', 'a', 'l', 0), // bal = Baluchi + TRUETYPE_TAG('b', 'a', 'n', 0), // ban = Balinese + TRUETYPE_TAG('b', 'a', 'o', 0), // bao = Waimaha + TRUETYPE_TAG('b', 'a', 'p', 0), // bap = Bantawa + TRUETYPE_TAG('b', 'a', 'r', 0), // bar = Bavarian + TRUETYPE_TAG('b', 'a', 's', 0), // bas = Basa (Cameroon) + TRUETYPE_TAG('b', 'a', 't', 0), // bat = Baltic languages + TRUETYPE_TAG('b', 'a', 'u', 0), // bau = Bada (Nigeria) + TRUETYPE_TAG('b', 'a', 'v', 0), // bav = Vengo + TRUETYPE_TAG('b', 'a', 'w', 0), // baw = Bambili-Bambui + TRUETYPE_TAG('b', 'a', 'x', 0), // bax = Bamun + TRUETYPE_TAG('b', 'a', 'y', 0), // bay = Batuley + TRUETYPE_TAG('b', 'a', 'z', 0), // baz = Tunen + TRUETYPE_TAG('b', 'b', 'a', 0), // bba = Baatonum + TRUETYPE_TAG('b', 'b', 'b', 0), // bbb = Barai + TRUETYPE_TAG('b', 'b', 'c', 0), // bbc = Batak Toba + TRUETYPE_TAG('b', 'b', 'd', 0), // bbd = Bau + TRUETYPE_TAG('b', 'b', 'e', 0), // bbe = Bangba + TRUETYPE_TAG('b', 'b', 'f', 0), // bbf = Baibai + TRUETYPE_TAG('b', 'b', 'g', 0), // bbg = Barama + TRUETYPE_TAG('b', 'b', 'h', 0), // bbh = Bugan + TRUETYPE_TAG('b', 'b', 'i', 0), // bbi = Barombi + TRUETYPE_TAG('b', 'b', 'j', 0), // bbj = Ghomálá' + TRUETYPE_TAG('b', 'b', 'k', 0), // bbk = Babanki + TRUETYPE_TAG('b', 'b', 'l', 0), // bbl = Bats + TRUETYPE_TAG('b', 'b', 'm', 0), // bbm = Babango + TRUETYPE_TAG('b', 'b', 'n', 0), // bbn = Uneapa + TRUETYPE_TAG('b', 'b', 'o', 0), // bbo = Northern Bobo Madaré + TRUETYPE_TAG('b', 'b', 'p', 0), // bbp = West Central Banda + TRUETYPE_TAG('b', 'b', 'q', 0), // bbq = Bamali + TRUETYPE_TAG('b', 'b', 'r', 0), // bbr = Girawa + TRUETYPE_TAG('b', 'b', 's', 0), // bbs = Bakpinka + TRUETYPE_TAG('b', 'b', 't', 0), // bbt = Mburku + TRUETYPE_TAG('b', 'b', 'u', 0), // bbu = Kulung (Nigeria) + TRUETYPE_TAG('b', 'b', 'v', 0), // bbv = Karnai + TRUETYPE_TAG('b', 'b', 'w', 0), // bbw = Baba + TRUETYPE_TAG('b', 'b', 'x', 0), // bbx = Bubia + TRUETYPE_TAG('b', 'b', 'y', 0), // bby = Befang + TRUETYPE_TAG('b', 'b', 'z', 0), // bbz = Babalia Creole Arabic + TRUETYPE_TAG('b', 'c', 'a', 0), // bca = Central Bai + TRUETYPE_TAG('b', 'c', 'b', 0), // bcb = Bainouk-Samik + TRUETYPE_TAG('b', 'c', 'c', 0), // bcc = Southern Balochi + TRUETYPE_TAG('b', 'c', 'd', 0), // bcd = North Babar + TRUETYPE_TAG('b', 'c', 'e', 0), // bce = Bamenyam + TRUETYPE_TAG('b', 'c', 'f', 0), // bcf = Bamu + TRUETYPE_TAG('b', 'c', 'g', 0), // bcg = Baga Binari + TRUETYPE_TAG('b', 'c', 'h', 0), // bch = Bariai + TRUETYPE_TAG('b', 'c', 'i', 0), // bci = Baoulé + TRUETYPE_TAG('b', 'c', 'j', 0), // bcj = Bardi + TRUETYPE_TAG('b', 'c', 'k', 0), // bck = Bunaba + TRUETYPE_TAG('b', 'c', 'l', 0), // bcl = Central Bicolano + TRUETYPE_TAG('b', 'c', 'm', 0), // bcm = Bannoni + TRUETYPE_TAG('b', 'c', 'n', 0), // bcn = Bali (Nigeria) + TRUETYPE_TAG('b', 'c', 'o', 0), // bco = Kaluli + TRUETYPE_TAG('b', 'c', 'p', + 0), // bcp = Bali (Democratic Republic of Congo) + TRUETYPE_TAG('b', 'c', 'q', 0), // bcq = Bench + TRUETYPE_TAG('b', 'c', 'r', 0), // bcr = Babine + TRUETYPE_TAG('b', 'c', 's', 0), // bcs = Kohumono + TRUETYPE_TAG('b', 'c', 't', 0), // bct = Bendi + TRUETYPE_TAG('b', 'c', 'u', 0), // bcu = Awad Bing + TRUETYPE_TAG('b', 'c', 'v', 0), // bcv = Shoo-Minda-Nye + TRUETYPE_TAG('b', 'c', 'w', 0), // bcw = Bana + TRUETYPE_TAG('b', 'c', 'y', 0), // bcy = Bacama + TRUETYPE_TAG('b', 'c', 'z', 0), // bcz = Bainouk-Gunyaamolo + TRUETYPE_TAG('b', 'd', 'a', 0), // bda = Bayot + TRUETYPE_TAG('b', 'd', 'b', 0), // bdb = Basap + TRUETYPE_TAG('b', 'd', 'c', 0), // bdc = Emberá-Baudó + TRUETYPE_TAG('b', 'd', 'd', 0), // bdd = Bunama + TRUETYPE_TAG('b', 'd', 'e', 0), // bde = Bade + TRUETYPE_TAG('b', 'd', 'f', 0), // bdf = Biage + TRUETYPE_TAG('b', 'd', 'g', 0), // bdg = Bonggi + TRUETYPE_TAG('b', 'd', 'h', 0), // bdh = Baka (Sudan) + TRUETYPE_TAG('b', 'd', 'i', 0), // bdi = Burun + TRUETYPE_TAG('b', 'd', 'j', 0), // bdj = Bai + TRUETYPE_TAG('b', 'd', 'k', 0), // bdk = Budukh + TRUETYPE_TAG('b', 'd', 'l', 0), // bdl = Indonesian Bajau + TRUETYPE_TAG('b', 'd', 'm', 0), // bdm = Buduma + TRUETYPE_TAG('b', 'd', 'n', 0), // bdn = Baldemu + TRUETYPE_TAG('b', 'd', 'o', 0), // bdo = Morom + TRUETYPE_TAG('b', 'd', 'p', 0), // bdp = Bende + TRUETYPE_TAG('b', 'd', 'q', 0), // bdq = Bahnar + TRUETYPE_TAG('b', 'd', 'r', 0), // bdr = West Coast Bajau + TRUETYPE_TAG('b', 'd', 's', 0), // bds = Burunge + TRUETYPE_TAG('b', 'd', 't', 0), // bdt = Bokoto + TRUETYPE_TAG('b', 'd', 'u', 0), // bdu = Oroko + TRUETYPE_TAG('b', 'd', 'v', 0), // bdv = Bodo Parja + TRUETYPE_TAG('b', 'd', 'w', 0), // bdw = Baham + TRUETYPE_TAG('b', 'd', 'x', 0), // bdx = Budong-Budong + TRUETYPE_TAG('b', 'd', 'y', 0), // bdy = Bandjalang + TRUETYPE_TAG('b', 'd', 'z', 0), // bdz = Badeshi + TRUETYPE_TAG('b', 'e', 'a', 0), // bea = Beaver + TRUETYPE_TAG('b', 'e', 'b', 0), // beb = Bebele + TRUETYPE_TAG('b', 'e', 'c', 0), // bec = Iceve-Maci + TRUETYPE_TAG('b', 'e', 'd', 0), // bed = Bedoanas + TRUETYPE_TAG('b', 'e', 'e', 0), // bee = Byangsi + TRUETYPE_TAG('b', 'e', 'f', 0), // bef = Benabena + TRUETYPE_TAG('b', 'e', 'g', 0), // beg = Belait + TRUETYPE_TAG('b', 'e', 'h', 0), // beh = Biali + TRUETYPE_TAG('b', 'e', 'i', 0), // bei = Bekati' + TRUETYPE_TAG('b', 'e', 'j', 0), // bej = Beja + TRUETYPE_TAG('b', 'e', 'k', 0), // bek = Bebeli + TRUETYPE_TAG('b', 'e', 'm', 0), // bem = Bemba (Zambia) + TRUETYPE_TAG('b', 'e', 'o', 0), // beo = Beami + TRUETYPE_TAG('b', 'e', 'p', 0), // bep = Besoa + TRUETYPE_TAG('b', 'e', 'q', 0), // beq = Beembe + TRUETYPE_TAG('b', 'e', 'r', 0), // ber = Berber languages + TRUETYPE_TAG('b', 'e', 's', 0), // bes = Besme + TRUETYPE_TAG('b', 'e', 't', 0), // bet = Guiberoua Béte + TRUETYPE_TAG('b', 'e', 'u', 0), // beu = Blagar + TRUETYPE_TAG('b', 'e', 'v', 0), // bev = Daloa Bété + TRUETYPE_TAG('b', 'e', 'w', 0), // bew = Betawi + TRUETYPE_TAG('b', 'e', 'x', 0), // bex = Jur Modo + TRUETYPE_TAG('b', 'e', 'y', 0), // bey = Beli (Papua New Guinea) + TRUETYPE_TAG('b', 'e', 'z', 0), // bez = Bena (Tanzania) + TRUETYPE_TAG('b', 'f', 'a', 0), // bfa = Bari + TRUETYPE_TAG('b', 'f', 'b', 0), // bfb = Pauri Bareli + TRUETYPE_TAG('b', 'f', 'c', 0), // bfc = Northern Bai + TRUETYPE_TAG('b', 'f', 'd', 0), // bfd = Bafut + TRUETYPE_TAG('b', 'f', 'e', 0), // bfe = Betaf + TRUETYPE_TAG('b', 'f', 'f', 0), // bff = Bofi + TRUETYPE_TAG('b', 'f', 'g', 0), // bfg = Busang Kayan + TRUETYPE_TAG('b', 'f', 'h', 0), // bfh = Blafe + TRUETYPE_TAG('b', 'f', 'i', 0), // bfi = British Sign Language + TRUETYPE_TAG('b', 'f', 'j', 0), // bfj = Bafanji + TRUETYPE_TAG('b', 'f', 'k', 0), // bfk = Ban Khor Sign Language + TRUETYPE_TAG('b', 'f', 'l', 0), // bfl = Banda-Ndélé + TRUETYPE_TAG('b', 'f', 'm', 0), // bfm = Mmen + TRUETYPE_TAG('b', 'f', 'n', 0), // bfn = Bunak + TRUETYPE_TAG('b', 'f', 'o', 0), // bfo = Malba Birifor + TRUETYPE_TAG('b', 'f', 'p', 0), // bfp = Beba + TRUETYPE_TAG('b', 'f', 'q', 0), // bfq = Badaga + TRUETYPE_TAG('b', 'f', 'r', 0), // bfr = Bazigar + TRUETYPE_TAG('b', 'f', 's', 0), // bfs = Southern Bai + TRUETYPE_TAG('b', 'f', 't', 0), // bft = Balti + TRUETYPE_TAG('b', 'f', 'u', 0), // bfu = Gahri + TRUETYPE_TAG('b', 'f', 'w', 0), // bfw = Bondo + TRUETYPE_TAG('b', 'f', 'x', 0), // bfx = Bantayanon + TRUETYPE_TAG('b', 'f', 'y', 0), // bfy = Bagheli + TRUETYPE_TAG('b', 'f', 'z', 0), // bfz = Mahasu Pahari + TRUETYPE_TAG('b', 'g', 'a', 0), // bga = Gwamhi-Wuri + TRUETYPE_TAG('b', 'g', 'b', 0), // bgb = Bobongko + TRUETYPE_TAG('b', 'g', 'c', 0), // bgc = Haryanvi + TRUETYPE_TAG('b', 'g', 'd', 0), // bgd = Rathwi Bareli + TRUETYPE_TAG('b', 'g', 'e', 0), // bge = Bauria + TRUETYPE_TAG('b', 'g', 'f', 0), // bgf = Bangandu + TRUETYPE_TAG('b', 'g', 'g', 0), // bgg = Bugun + TRUETYPE_TAG('b', 'g', 'i', 0), // bgi = Giangan + TRUETYPE_TAG('b', 'g', 'j', 0), // bgj = Bangolan + TRUETYPE_TAG('b', 'g', 'k', 0), // bgk = Bit + TRUETYPE_TAG('b', 'g', 'l', 0), // bgl = Bo (Laos) + TRUETYPE_TAG('b', 'g', 'm', 0), // bgm = Baga Mboteni + TRUETYPE_TAG('b', 'g', 'n', 0), // bgn = Western Balochi + TRUETYPE_TAG('b', 'g', 'o', 0), // bgo = Baga Koga + TRUETYPE_TAG('b', 'g', 'p', 0), // bgp = Eastern Balochi + TRUETYPE_TAG('b', 'g', 'q', 0), // bgq = Bagri + TRUETYPE_TAG('b', 'g', 'r', 0), // bgr = Bawm Chin + TRUETYPE_TAG('b', 'g', 's', 0), // bgs = Tagabawa + TRUETYPE_TAG('b', 'g', 't', 0), // bgt = Bughotu + TRUETYPE_TAG('b', 'g', 'u', 0), // bgu = Mbongno + TRUETYPE_TAG('b', 'g', 'v', 0), // bgv = Warkay-Bipim + TRUETYPE_TAG('b', 'g', 'w', 0), // bgw = Bhatri + TRUETYPE_TAG('b', 'g', 'x', 0), // bgx = Balkan Gagauz Turkish + TRUETYPE_TAG('b', 'g', 'y', 0), // bgy = Benggoi + TRUETYPE_TAG('b', 'g', 'z', 0), // bgz = Banggai + TRUETYPE_TAG('b', 'h', 'a', 0), // bha = Bharia + TRUETYPE_TAG('b', 'h', 'b', 0), // bhb = Bhili + TRUETYPE_TAG('b', 'h', 'c', 0), // bhc = Biga + TRUETYPE_TAG('b', 'h', 'd', 0), // bhd = Bhadrawahi + TRUETYPE_TAG('b', 'h', 'e', 0), // bhe = Bhaya + TRUETYPE_TAG('b', 'h', 'f', 0), // bhf = Odiai + TRUETYPE_TAG('b', 'h', 'g', 0), // bhg = Binandere + TRUETYPE_TAG('b', 'h', 'h', 0), // bhh = Bukharic + TRUETYPE_TAG('b', 'h', 'i', 0), // bhi = Bhilali + TRUETYPE_TAG('b', 'h', 'j', 0), // bhj = Bahing + TRUETYPE_TAG('b', 'h', 'k', 0), // bhk = Albay Bicolano + TRUETYPE_TAG('b', 'h', 'l', 0), // bhl = Bimin + TRUETYPE_TAG('b', 'h', 'm', 0), // bhm = Bathari + TRUETYPE_TAG('b', 'h', 'n', 0), // bhn = Bohtan Neo-Aramaic + TRUETYPE_TAG('b', 'h', 'o', 0), // bho = Bhojpuri + TRUETYPE_TAG('b', 'h', 'p', 0), // bhp = Bima + TRUETYPE_TAG('b', 'h', 'q', 0), // bhq = Tukang Besi South + TRUETYPE_TAG('b', 'h', 'r', 0), // bhr = Bara Malagasy + TRUETYPE_TAG('b', 'h', 's', 0), // bhs = Buwal + TRUETYPE_TAG('b', 'h', 't', 0), // bht = Bhattiyali + TRUETYPE_TAG('b', 'h', 'u', 0), // bhu = Bhunjia + TRUETYPE_TAG('b', 'h', 'v', 0), // bhv = Bahau + TRUETYPE_TAG('b', 'h', 'w', 0), // bhw = Biak + TRUETYPE_TAG('b', 'h', 'x', 0), // bhx = Bhalay + TRUETYPE_TAG('b', 'h', 'y', 0), // bhy = Bhele + TRUETYPE_TAG('b', 'h', 'z', 0), // bhz = Bada (Indonesia) + TRUETYPE_TAG('b', 'i', 'a', 0), // bia = Badimaya + TRUETYPE_TAG('b', 'i', 'b', 0), // bib = Bissa + TRUETYPE_TAG('b', 'i', 'c', 0), // bic = Bikaru + TRUETYPE_TAG('b', 'i', 'd', 0), // bid = Bidiyo + TRUETYPE_TAG('b', 'i', 'e', 0), // bie = Bepour + TRUETYPE_TAG('b', 'i', 'f', 0), // bif = Biafada + TRUETYPE_TAG('b', 'i', 'g', 0), // big = Biangai + TRUETYPE_TAG('b', 'i', 'j', 0), // bij = Vaghat-Ya-Bijim-Legeri + TRUETYPE_TAG('b', 'i', 'k', 0), // bik = Bikol + TRUETYPE_TAG('b', 'i', 'l', 0), // bil = Bile + TRUETYPE_TAG('b', 'i', 'm', 0), // bim = Bimoba + TRUETYPE_TAG('b', 'i', 'n', 0), // bin = Bini + TRUETYPE_TAG('b', 'i', 'o', 0), // bio = Nai + TRUETYPE_TAG('b', 'i', 'p', 0), // bip = Bila + TRUETYPE_TAG('b', 'i', 'q', 0), // biq = Bipi + TRUETYPE_TAG('b', 'i', 'r', 0), // bir = Bisorio + TRUETYPE_TAG('b', 'i', 't', 0), // bit = Berinomo + TRUETYPE_TAG('b', 'i', 'u', 0), // biu = Biete + TRUETYPE_TAG('b', 'i', 'v', 0), // biv = Southern Birifor + TRUETYPE_TAG('b', 'i', 'w', 0), // biw = Kol (Cameroon) + TRUETYPE_TAG('b', 'i', 'x', 0), // bix = Bijori + TRUETYPE_TAG('b', 'i', 'y', 0), // biy = Birhor + TRUETYPE_TAG('b', 'i', 'z', 0), // biz = Baloi + TRUETYPE_TAG('b', 'j', 'a', 0), // bja = Budza + TRUETYPE_TAG('b', 'j', 'b', 0), // bjb = Banggarla + TRUETYPE_TAG('b', 'j', 'c', 0), // bjc = Bariji + TRUETYPE_TAG('b', 'j', 'd', 0), // bjd = Bandjigali + TRUETYPE_TAG('b', 'j', 'e', 0), // bje = Biao-Jiao Mien + TRUETYPE_TAG('b', 'j', 'f', 0), // bjf = Barzani Jewish Neo-Aramaic + TRUETYPE_TAG('b', 'j', 'g', 0), // bjg = Bidyogo + TRUETYPE_TAG('b', 'j', 'h', 0), // bjh = Bahinemo + TRUETYPE_TAG('b', 'j', 'i', 0), // bji = Burji + TRUETYPE_TAG('b', 'j', 'j', 0), // bjj = Kanauji + TRUETYPE_TAG('b', 'j', 'k', 0), // bjk = Barok + TRUETYPE_TAG('b', 'j', 'l', 0), // bjl = Bulu (Papua New Guinea) + TRUETYPE_TAG('b', 'j', 'm', 0), // bjm = Bajelani + TRUETYPE_TAG('b', 'j', 'n', 0), // bjn = Banjar + TRUETYPE_TAG('b', 'j', 'o', 0), // bjo = Mid-Southern Banda + TRUETYPE_TAG('b', 'j', 'q', 0), // bjq = Southern Betsimisaraka Malagasy + TRUETYPE_TAG('b', 'j', 'r', 0), // bjr = Binumarien + TRUETYPE_TAG('b', 'j', 's', 0), // bjs = Bajan + TRUETYPE_TAG('b', 'j', 't', 0), // bjt = Balanta-Ganja + TRUETYPE_TAG('b', 'j', 'u', 0), // bju = Busuu + TRUETYPE_TAG('b', 'j', 'v', 0), // bjv = Bedjond + TRUETYPE_TAG('b', 'j', 'w', 0), // bjw = Bakwé + TRUETYPE_TAG('b', 'j', 'x', 0), // bjx = Banao Itneg + TRUETYPE_TAG('b', 'j', 'y', 0), // bjy = Bayali + TRUETYPE_TAG('b', 'j', 'z', 0), // bjz = Baruga + TRUETYPE_TAG('b', 'k', 'a', 0), // bka = Kyak + TRUETYPE_TAG('b', 'k', 'b', 0), // bkb = Finallig + TRUETYPE_TAG('b', 'k', 'c', 0), // bkc = Baka (Cameroon) + TRUETYPE_TAG('b', 'k', 'd', 0), // bkd = Binukid + TRUETYPE_TAG('b', 'k', 'f', 0), // bkf = Beeke + TRUETYPE_TAG('b', 'k', 'g', 0), // bkg = Buraka + TRUETYPE_TAG('b', 'k', 'h', 0), // bkh = Bakoko + TRUETYPE_TAG('b', 'k', 'i', 0), // bki = Baki + TRUETYPE_TAG('b', 'k', 'j', 0), // bkj = Pande + TRUETYPE_TAG('b', 'k', 'k', 0), // bkk = Brokskat + TRUETYPE_TAG('b', 'k', 'l', 0), // bkl = Berik + TRUETYPE_TAG('b', 'k', 'm', 0), // bkm = Kom (Cameroon) + TRUETYPE_TAG('b', 'k', 'n', 0), // bkn = Bukitan + TRUETYPE_TAG('b', 'k', 'o', 0), // bko = Kwa' + TRUETYPE_TAG('b', 'k', 'p', + 0), // bkp = Boko (Democratic Republic of Congo) + TRUETYPE_TAG('b', 'k', 'q', 0), // bkq = Bakairí + TRUETYPE_TAG('b', 'k', 'r', 0), // bkr = Bakumpai + TRUETYPE_TAG('b', 'k', 's', 0), // bks = Northern Sorsoganon + TRUETYPE_TAG('b', 'k', 't', 0), // bkt = Boloki + TRUETYPE_TAG('b', 'k', 'u', 0), // bku = Buhid + TRUETYPE_TAG('b', 'k', 'v', 0), // bkv = Bekwarra + TRUETYPE_TAG('b', 'k', 'w', 0), // bkw = Bekwel + TRUETYPE_TAG('b', 'k', 'x', 0), // bkx = Baikeno + TRUETYPE_TAG('b', 'k', 'y', 0), // bky = Bokyi + TRUETYPE_TAG('b', 'k', 'z', 0), // bkz = Bungku + TRUETYPE_TAG('b', 'l', 'a', 0), // bla = Siksika + TRUETYPE_TAG('b', 'l', 'b', 0), // blb = Bilua + TRUETYPE_TAG('b', 'l', 'c', 0), // blc = Bella Coola + TRUETYPE_TAG('b', 'l', 'd', 0), // bld = Bolango + TRUETYPE_TAG('b', 'l', 'e', 0), // ble = Balanta-Kentohe + TRUETYPE_TAG('b', 'l', 'f', 0), // blf = Buol + TRUETYPE_TAG('b', 'l', 'g', 0), // blg = Balau + TRUETYPE_TAG('b', 'l', 'h', 0), // blh = Kuwaa + TRUETYPE_TAG('b', 'l', 'i', 0), // bli = Bolia + TRUETYPE_TAG('b', 'l', 'j', 0), // blj = Bolongan + TRUETYPE_TAG('b', 'l', 'k', 0), // blk = Pa'o Karen + TRUETYPE_TAG('b', 'l', 'l', 0), // bll = Biloxi + TRUETYPE_TAG('b', 'l', 'm', 0), // blm = Beli (Sudan) + TRUETYPE_TAG('b', 'l', 'n', 0), // bln = Southern Catanduanes Bicolano + TRUETYPE_TAG('b', 'l', 'o', 0), // blo = Anii + TRUETYPE_TAG('b', 'l', 'p', 0), // blp = Blablanga + TRUETYPE_TAG('b', 'l', 'q', 0), // blq = Baluan-Pam + TRUETYPE_TAG('b', 'l', 'r', 0), // blr = Blang + TRUETYPE_TAG('b', 'l', 's', 0), // bls = Balaesang + TRUETYPE_TAG('b', 'l', 't', 0), // blt = Tai Dam + TRUETYPE_TAG('b', 'l', 'v', 0), // blv = Bolo + TRUETYPE_TAG('b', 'l', 'w', 0), // blw = Balangao + TRUETYPE_TAG('b', 'l', 'x', 0), // blx = Mag-Indi Ayta + TRUETYPE_TAG('b', 'l', 'y', 0), // bly = Notre + TRUETYPE_TAG('b', 'l', 'z', 0), // blz = Balantak + TRUETYPE_TAG('b', 'm', 'a', 0), // bma = Lame + TRUETYPE_TAG('b', 'm', 'b', 0), // bmb = Bembe + TRUETYPE_TAG('b', 'm', 'c', 0), // bmc = Biem + TRUETYPE_TAG('b', 'm', 'd', 0), // bmd = Baga Manduri + TRUETYPE_TAG('b', 'm', 'e', 0), // bme = Limassa + TRUETYPE_TAG('b', 'm', 'f', 0), // bmf = Bom + TRUETYPE_TAG('b', 'm', 'g', 0), // bmg = Bamwe + TRUETYPE_TAG('b', 'm', 'h', 0), // bmh = Kein + TRUETYPE_TAG('b', 'm', 'i', 0), // bmi = Bagirmi + TRUETYPE_TAG('b', 'm', 'j', 0), // bmj = Bote-Majhi + TRUETYPE_TAG('b', 'm', 'k', 0), // bmk = Ghayavi + TRUETYPE_TAG('b', 'm', 'l', 0), // bml = Bomboli + TRUETYPE_TAG('b', 'm', 'm', 0), // bmm = Northern Betsimisaraka Malagasy + TRUETYPE_TAG('b', 'm', 'n', 0), // bmn = Bina (Papua New Guinea) + TRUETYPE_TAG('b', 'm', 'o', 0), // bmo = Bambalang + TRUETYPE_TAG('b', 'm', 'p', 0), // bmp = Bulgebi + TRUETYPE_TAG('b', 'm', 'q', 0), // bmq = Bomu + TRUETYPE_TAG('b', 'm', 'r', 0), // bmr = Muinane + TRUETYPE_TAG('b', 'm', 's', 0), // bms = Bilma Kanuri + TRUETYPE_TAG('b', 'm', 't', 0), // bmt = Biao Mon + TRUETYPE_TAG('b', 'm', 'u', 0), // bmu = Somba-Siawari + TRUETYPE_TAG('b', 'm', 'v', 0), // bmv = Bum + TRUETYPE_TAG('b', 'm', 'w', 0), // bmw = Bomwali + TRUETYPE_TAG('b', 'm', 'x', 0), // bmx = Baimak + TRUETYPE_TAG('b', 'm', 'y', + 0), // bmy = Bemba (Democratic Republic of Congo) + TRUETYPE_TAG('b', 'm', 'z', 0), // bmz = Baramu + TRUETYPE_TAG('b', 'n', 'a', 0), // bna = Bonerate + TRUETYPE_TAG('b', 'n', 'b', 0), // bnb = Bookan + TRUETYPE_TAG('b', 'n', 'c', 0), // bnc = Bontok + TRUETYPE_TAG('b', 'n', 'd', 0), // bnd = Banda (Indonesia) + TRUETYPE_TAG('b', 'n', 'e', 0), // bne = Bintauna + TRUETYPE_TAG('b', 'n', 'f', 0), // bnf = Masiwang + TRUETYPE_TAG('b', 'n', 'g', 0), // bng = Benga + TRUETYPE_TAG('b', 'n', 'i', 0), // bni = Bangi + TRUETYPE_TAG('b', 'n', 'j', 0), // bnj = Eastern Tawbuid + TRUETYPE_TAG('b', 'n', 'k', 0), // bnk = Bierebo + TRUETYPE_TAG('b', 'n', 'l', 0), // bnl = Boon + TRUETYPE_TAG('b', 'n', 'm', 0), // bnm = Batanga + TRUETYPE_TAG('b', 'n', 'n', 0), // bnn = Bunun + TRUETYPE_TAG('b', 'n', 'o', 0), // bno = Bantoanon + TRUETYPE_TAG('b', 'n', 'p', 0), // bnp = Bola + TRUETYPE_TAG('b', 'n', 'q', 0), // bnq = Bantik + TRUETYPE_TAG('b', 'n', 'r', 0), // bnr = Butmas-Tur + TRUETYPE_TAG('b', 'n', 's', 0), // bns = Bundeli + TRUETYPE_TAG('b', 'n', 't', 0), // bnt = Bantu languages + TRUETYPE_TAG('b', 'n', 'u', 0), // bnu = Bentong + TRUETYPE_TAG('b', 'n', 'v', 0), // bnv = Bonerif + TRUETYPE_TAG('b', 'n', 'w', 0), // bnw = Bisis + TRUETYPE_TAG('b', 'n', 'x', 0), // bnx = Bangubangu + TRUETYPE_TAG('b', 'n', 'y', 0), // bny = Bintulu + TRUETYPE_TAG('b', 'n', 'z', 0), // bnz = Beezen + TRUETYPE_TAG('b', 'o', 'a', 0), // boa = Bora + TRUETYPE_TAG('b', 'o', 'b', 0), // bob = Aweer + TRUETYPE_TAG('b', 'o', 'e', 0), // boe = Mundabli + TRUETYPE_TAG('b', 'o', 'f', 0), // bof = Bolon + TRUETYPE_TAG('b', 'o', 'g', 0), // bog = Bamako Sign Language + TRUETYPE_TAG('b', 'o', 'h', 0), // boh = Boma + TRUETYPE_TAG('b', 'o', 'i', 0), // boi = Barbareño + TRUETYPE_TAG('b', 'o', 'j', 0), // boj = Anjam + TRUETYPE_TAG('b', 'o', 'k', 0), // bok = Bonjo + TRUETYPE_TAG('b', 'o', 'l', 0), // bol = Bole + TRUETYPE_TAG('b', 'o', 'm', 0), // bom = Berom + TRUETYPE_TAG('b', 'o', 'n', 0), // bon = Bine + TRUETYPE_TAG('b', 'o', 'o', 0), // boo = Tiemacèwè Bozo + TRUETYPE_TAG('b', 'o', 'p', 0), // bop = Bonkiman + TRUETYPE_TAG('b', 'o', 'q', 0), // boq = Bogaya + TRUETYPE_TAG('b', 'o', 'r', 0), // bor = Borôro + TRUETYPE_TAG('b', 'o', 't', 0), // bot = Bongo + TRUETYPE_TAG('b', 'o', 'u', 0), // bou = Bondei + TRUETYPE_TAG('b', 'o', 'v', 0), // bov = Tuwuli + TRUETYPE_TAG('b', 'o', 'w', 0), // bow = Rema + TRUETYPE_TAG('b', 'o', 'x', 0), // box = Buamu + TRUETYPE_TAG('b', 'o', 'y', 0), // boy = Bodo (Central African Republic) + TRUETYPE_TAG('b', 'o', 'z', 0), // boz = Tiéyaxo Bozo + TRUETYPE_TAG('b', 'p', 'a', 0), // bpa = Dakaka + TRUETYPE_TAG('b', 'p', 'b', 0), // bpb = Barbacoas + TRUETYPE_TAG('b', 'p', 'd', 0), // bpd = Banda-Banda + TRUETYPE_TAG('b', 'p', 'g', 0), // bpg = Bonggo + TRUETYPE_TAG('b', 'p', 'h', 0), // bph = Botlikh + TRUETYPE_TAG('b', 'p', 'i', 0), // bpi = Bagupi + TRUETYPE_TAG('b', 'p', 'j', 0), // bpj = Binji + TRUETYPE_TAG('b', 'p', 'k', 0), // bpk = Orowe + TRUETYPE_TAG('b', 'p', 'l', 0), // bpl = Broome Pearling Lugger Pidgin + TRUETYPE_TAG('b', 'p', 'm', 0), // bpm = Biyom + TRUETYPE_TAG('b', 'p', 'n', 0), // bpn = Dzao Min + TRUETYPE_TAG('b', 'p', 'o', 0), // bpo = Anasi + TRUETYPE_TAG('b', 'p', 'p', 0), // bpp = Kaure + TRUETYPE_TAG('b', 'p', 'q', 0), // bpq = Banda Malay + TRUETYPE_TAG('b', 'p', 'r', 0), // bpr = Koronadal Blaan + TRUETYPE_TAG('b', 'p', 's', 0), // bps = Sarangani Blaan + TRUETYPE_TAG('b', 'p', 't', 0), // bpt = Barrow Point + TRUETYPE_TAG('b', 'p', 'u', 0), // bpu = Bongu + TRUETYPE_TAG('b', 'p', 'v', 0), // bpv = Bian Marind + TRUETYPE_TAG('b', 'p', 'w', 0), // bpw = Bo (Papua New Guinea) + TRUETYPE_TAG('b', 'p', 'x', 0), // bpx = Palya Bareli + TRUETYPE_TAG('b', 'p', 'y', 0), // bpy = Bishnupriya + TRUETYPE_TAG('b', 'p', 'z', 0), // bpz = Bilba + TRUETYPE_TAG('b', 'q', 'a', 0), // bqa = Tchumbuli + TRUETYPE_TAG('b', 'q', 'b', 0), // bqb = Bagusa + TRUETYPE_TAG('b', 'q', 'c', 0), // bqc = Boko (Benin) + TRUETYPE_TAG('b', 'q', 'd', 0), // bqd = Bung + TRUETYPE_TAG('b', 'q', 'f', 0), // bqf = Baga Kaloum + TRUETYPE_TAG('b', 'q', 'g', 0), // bqg = Bago-Kusuntu + TRUETYPE_TAG('b', 'q', 'h', 0), // bqh = Baima + TRUETYPE_TAG('b', 'q', 'i', 0), // bqi = Bakhtiari + TRUETYPE_TAG('b', 'q', 'j', 0), // bqj = Bandial + TRUETYPE_TAG('b', 'q', 'k', 0), // bqk = Banda-Mbrès + TRUETYPE_TAG('b', 'q', 'l', 0), // bql = Bilakura + TRUETYPE_TAG('b', 'q', 'm', 0), // bqm = Wumboko + TRUETYPE_TAG('b', 'q', 'n', 0), // bqn = Bulgarian Sign Language + TRUETYPE_TAG('b', 'q', 'o', 0), // bqo = Balo + TRUETYPE_TAG('b', 'q', 'p', 0), // bqp = Busa + TRUETYPE_TAG('b', 'q', 'q', 0), // bqq = Biritai + TRUETYPE_TAG('b', 'q', 'r', 0), // bqr = Burusu + TRUETYPE_TAG('b', 'q', 's', 0), // bqs = Bosngun + TRUETYPE_TAG('b', 'q', 't', 0), // bqt = Bamukumbit + TRUETYPE_TAG('b', 'q', 'u', 0), // bqu = Boguru + TRUETYPE_TAG('b', 'q', 'v', 0), // bqv = Begbere-Ejar + TRUETYPE_TAG('b', 'q', 'w', 0), // bqw = Buru (Nigeria) + TRUETYPE_TAG('b', 'q', 'x', 0), // bqx = Baangi + TRUETYPE_TAG('b', 'q', 'y', 0), // bqy = Bengkala Sign Language + TRUETYPE_TAG('b', 'q', 'z', 0), // bqz = Bakaka + TRUETYPE_TAG('b', 'r', 'a', 0), // bra = Braj + TRUETYPE_TAG('b', 'r', 'b', 0), // brb = Lave + TRUETYPE_TAG('b', 'r', 'c', 0), // brc = Berbice Creole Dutch + TRUETYPE_TAG('b', 'r', 'd', 0), // brd = Baraamu + TRUETYPE_TAG('b', 'r', 'f', 0), // brf = Bera + TRUETYPE_TAG('b', 'r', 'g', 0), // brg = Baure + TRUETYPE_TAG('b', 'r', 'h', 0), // brh = Brahui + TRUETYPE_TAG('b', 'r', 'i', 0), // bri = Mokpwe + TRUETYPE_TAG('b', 'r', 'j', 0), // brj = Bieria + TRUETYPE_TAG('b', 'r', 'k', 0), // brk = Birked + TRUETYPE_TAG('b', 'r', 'l', 0), // brl = Birwa + TRUETYPE_TAG('b', 'r', 'm', 0), // brm = Barambu + TRUETYPE_TAG('b', 'r', 'n', 0), // brn = Boruca + TRUETYPE_TAG('b', 'r', 'o', 0), // bro = Brokkat + TRUETYPE_TAG('b', 'r', 'p', 0), // brp = Barapasi + TRUETYPE_TAG('b', 'r', 'q', 0), // brq = Breri + TRUETYPE_TAG('b', 'r', 'r', 0), // brr = Birao + TRUETYPE_TAG('b', 'r', 's', 0), // brs = Baras + TRUETYPE_TAG('b', 'r', 't', 0), // brt = Bitare + TRUETYPE_TAG('b', 'r', 'u', 0), // bru = Eastern Bru + TRUETYPE_TAG('b', 'r', 'v', 0), // brv = Western Bru + TRUETYPE_TAG('b', 'r', 'w', 0), // brw = Bellari + TRUETYPE_TAG('b', 'r', 'x', 0), // brx = Bodo (India) + TRUETYPE_TAG('b', 'r', 'y', 0), // bry = Burui + TRUETYPE_TAG('b', 'r', 'z', 0), // brz = Bilbil + TRUETYPE_TAG('b', 's', 'a', 0), // bsa = Abinomn + TRUETYPE_TAG('b', 's', 'b', 0), // bsb = Brunei Bisaya + TRUETYPE_TAG('b', 's', 'c', 0), // bsc = Bassari + TRUETYPE_TAG('b', 's', 'e', 0), // bse = Wushi + TRUETYPE_TAG('b', 's', 'f', 0), // bsf = Bauchi + TRUETYPE_TAG('b', 's', 'g', 0), // bsg = Bashkardi + TRUETYPE_TAG('b', 's', 'h', 0), // bsh = Kati + TRUETYPE_TAG('b', 's', 'i', 0), // bsi = Bassossi + TRUETYPE_TAG('b', 's', 'j', 0), // bsj = Bangwinji + TRUETYPE_TAG('b', 's', 'k', 0), // bsk = Burushaski + TRUETYPE_TAG('b', 's', 'l', 0), // bsl = Basa-Gumna + TRUETYPE_TAG('b', 's', 'm', 0), // bsm = Busami + TRUETYPE_TAG('b', 's', 'n', 0), // bsn = Barasana-Eduria + TRUETYPE_TAG('b', 's', 'o', 0), // bso = Buso + TRUETYPE_TAG('b', 's', 'p', 0), // bsp = Baga Sitemu + TRUETYPE_TAG('b', 's', 'q', 0), // bsq = Bassa + TRUETYPE_TAG('b', 's', 'r', 0), // bsr = Bassa-Kontagora + TRUETYPE_TAG('b', 's', 's', 0), // bss = Akoose + TRUETYPE_TAG('b', 's', 't', 0), // bst = Basketo + TRUETYPE_TAG('b', 's', 'u', 0), // bsu = Bahonsuai + TRUETYPE_TAG('b', 's', 'v', 0), // bsv = Baga Sobané + TRUETYPE_TAG('b', 's', 'w', 0), // bsw = Baiso + TRUETYPE_TAG('b', 's', 'x', 0), // bsx = Yangkam + TRUETYPE_TAG('b', 's', 'y', 0), // bsy = Sabah Bisaya + TRUETYPE_TAG('b', 't', 'a', 0), // bta = Bata + TRUETYPE_TAG('b', 't', 'b', 0), // btb = Beti (Cameroon) + TRUETYPE_TAG('b', 't', 'c', 0), // btc = Bati (Cameroon) + TRUETYPE_TAG('b', 't', 'd', 0), // btd = Batak Dairi + TRUETYPE_TAG('b', 't', 'e', 0), // bte = Gamo-Ningi + TRUETYPE_TAG('b', 't', 'f', 0), // btf = Birgit + TRUETYPE_TAG('b', 't', 'g', 0), // btg = Gagnoa Bété + TRUETYPE_TAG('b', 't', 'h', 0), // bth = Biatah Bidayuh + TRUETYPE_TAG('b', 't', 'i', 0), // bti = Burate + TRUETYPE_TAG('b', 't', 'j', 0), // btj = Bacanese Malay + TRUETYPE_TAG('b', 't', 'k', 0), // btk = Batak languages + TRUETYPE_TAG('b', 't', 'l', 0), // btl = Bhatola + TRUETYPE_TAG('b', 't', 'm', 0), // btm = Batak Mandailing + TRUETYPE_TAG('b', 't', 'n', 0), // btn = Ratagnon + TRUETYPE_TAG('b', 't', 'o', 0), // bto = Rinconada Bikol + TRUETYPE_TAG('b', 't', 'p', 0), // btp = Budibud + TRUETYPE_TAG('b', 't', 'q', 0), // btq = Batek + TRUETYPE_TAG('b', 't', 'r', 0), // btr = Baetora + TRUETYPE_TAG('b', 't', 's', 0), // bts = Batak Simalungun + TRUETYPE_TAG('b', 't', 't', 0), // btt = Bete-Bendi + TRUETYPE_TAG('b', 't', 'u', 0), // btu = Batu + TRUETYPE_TAG('b', 't', 'v', 0), // btv = Bateri + TRUETYPE_TAG('b', 't', 'w', 0), // btw = Butuanon + TRUETYPE_TAG('b', 't', 'x', 0), // btx = Batak Karo + TRUETYPE_TAG('b', 't', 'y', 0), // bty = Bobot + TRUETYPE_TAG('b', 't', 'z', 0), // btz = Batak Alas-Kluet + TRUETYPE_TAG('b', 'u', 'a', 0), // bua = Buriat + TRUETYPE_TAG('b', 'u', 'b', 0), // bub = Bua + TRUETYPE_TAG('b', 'u', 'c', 0), // buc = Bushi + TRUETYPE_TAG('b', 'u', 'd', 0), // bud = Ntcham + TRUETYPE_TAG('b', 'u', 'e', 0), // bue = Beothuk + TRUETYPE_TAG('b', 'u', 'f', 0), // buf = Bushoong + TRUETYPE_TAG('b', 'u', 'g', 0), // bug = Buginese + TRUETYPE_TAG('b', 'u', 'h', 0), // buh = Younuo Bunu + TRUETYPE_TAG('b', 'u', 'i', 0), // bui = Bongili + TRUETYPE_TAG('b', 'u', 'j', 0), // buj = Basa-Gurmana + TRUETYPE_TAG('b', 'u', 'k', 0), // buk = Bugawac + TRUETYPE_TAG('b', 'u', 'm', 0), // bum = Bulu (Cameroon) + TRUETYPE_TAG('b', 'u', 'n', 0), // bun = Sherbro + TRUETYPE_TAG('b', 'u', 'o', 0), // buo = Terei + TRUETYPE_TAG('b', 'u', 'p', 0), // bup = Busoa + TRUETYPE_TAG('b', 'u', 'q', 0), // buq = Brem + TRUETYPE_TAG('b', 'u', 's', 0), // bus = Bokobaru + TRUETYPE_TAG('b', 'u', 't', 0), // but = Bungain + TRUETYPE_TAG('b', 'u', 'u', 0), // buu = Budu + TRUETYPE_TAG('b', 'u', 'v', 0), // buv = Bun + TRUETYPE_TAG('b', 'u', 'w', 0), // buw = Bubi + TRUETYPE_TAG('b', 'u', 'x', 0), // bux = Boghom + TRUETYPE_TAG('b', 'u', 'y', 0), // buy = Bullom So + TRUETYPE_TAG('b', 'u', 'z', 0), // buz = Bukwen + TRUETYPE_TAG('b', 'v', 'a', 0), // bva = Barein + TRUETYPE_TAG('b', 'v', 'b', 0), // bvb = Bube + TRUETYPE_TAG('b', 'v', 'c', 0), // bvc = Baelelea + TRUETYPE_TAG('b', 'v', 'd', 0), // bvd = Baeggu + TRUETYPE_TAG('b', 'v', 'e', 0), // bve = Berau Malay + TRUETYPE_TAG('b', 'v', 'f', 0), // bvf = Boor + TRUETYPE_TAG('b', 'v', 'g', 0), // bvg = Bonkeng + TRUETYPE_TAG('b', 'v', 'h', 0), // bvh = Bure + TRUETYPE_TAG('b', 'v', 'i', 0), // bvi = Belanda Viri + TRUETYPE_TAG('b', 'v', 'j', 0), // bvj = Baan + TRUETYPE_TAG('b', 'v', 'k', 0), // bvk = Bukat + TRUETYPE_TAG('b', 'v', 'l', 0), // bvl = Bolivian Sign Language + TRUETYPE_TAG('b', 'v', 'm', 0), // bvm = Bamunka + TRUETYPE_TAG('b', 'v', 'n', 0), // bvn = Buna + TRUETYPE_TAG('b', 'v', 'o', 0), // bvo = Bolgo + TRUETYPE_TAG('b', 'v', 'q', 0), // bvq = Birri + TRUETYPE_TAG('b', 'v', 'r', 0), // bvr = Burarra + TRUETYPE_TAG('b', 'v', 't', 0), // bvt = Bati (Indonesia) + TRUETYPE_TAG('b', 'v', 'u', 0), // bvu = Bukit Malay + TRUETYPE_TAG('b', 'v', 'v', 0), // bvv = Baniva + TRUETYPE_TAG('b', 'v', 'w', 0), // bvw = Boga + TRUETYPE_TAG('b', 'v', 'x', 0), // bvx = Dibole + TRUETYPE_TAG('b', 'v', 'y', 0), // bvy = Baybayanon + TRUETYPE_TAG('b', 'v', 'z', 0), // bvz = Bauzi + TRUETYPE_TAG('b', 'w', 'a', 0), // bwa = Bwatoo + TRUETYPE_TAG('b', 'w', 'b', 0), // bwb = Namosi-Naitasiri-Serua + TRUETYPE_TAG('b', 'w', 'c', 0), // bwc = Bwile + TRUETYPE_TAG('b', 'w', 'd', 0), // bwd = Bwaidoka + TRUETYPE_TAG('b', 'w', 'e', 0), // bwe = Bwe Karen + TRUETYPE_TAG('b', 'w', 'f', 0), // bwf = Boselewa + TRUETYPE_TAG('b', 'w', 'g', 0), // bwg = Barwe + TRUETYPE_TAG('b', 'w', 'h', 0), // bwh = Bishuo + TRUETYPE_TAG('b', 'w', 'i', 0), // bwi = Baniwa + TRUETYPE_TAG('b', 'w', 'j', 0), // bwj = Láá Láá Bwamu + TRUETYPE_TAG('b', 'w', 'k', 0), // bwk = Bauwaki + TRUETYPE_TAG('b', 'w', 'l', 0), // bwl = Bwela + TRUETYPE_TAG('b', 'w', 'm', 0), // bwm = Biwat + TRUETYPE_TAG('b', 'w', 'n', 0), // bwn = Wunai Bunu + TRUETYPE_TAG('b', 'w', 'o', 0), // bwo = Boro (Ethiopia) + TRUETYPE_TAG('b', 'w', 'p', 0), // bwp = Mandobo Bawah + TRUETYPE_TAG('b', 'w', 'q', 0), // bwq = Southern Bobo Madaré + TRUETYPE_TAG('b', 'w', 'r', 0), // bwr = Bura-Pabir + TRUETYPE_TAG('b', 'w', 's', 0), // bws = Bomboma + TRUETYPE_TAG('b', 'w', 't', 0), // bwt = Bafaw-Balong + TRUETYPE_TAG('b', 'w', 'u', 0), // bwu = Buli (Ghana) + TRUETYPE_TAG('b', 'w', 'w', 0), // bww = Bwa + TRUETYPE_TAG('b', 'w', 'x', 0), // bwx = Bu-Nao Bunu + TRUETYPE_TAG('b', 'w', 'y', 0), // bwy = Cwi Bwamu + TRUETYPE_TAG('b', 'w', 'z', 0), // bwz = Bwisi + TRUETYPE_TAG('b', 'x', 'a', 0), // bxa = Bauro + TRUETYPE_TAG('b', 'x', 'b', 0), // bxb = Belanda Bor + TRUETYPE_TAG('b', 'x', 'c', 0), // bxc = Molengue + TRUETYPE_TAG('b', 'x', 'd', 0), // bxd = Pela + TRUETYPE_TAG('b', 'x', 'e', 0), // bxe = Birale + TRUETYPE_TAG('b', 'x', 'f', 0), // bxf = Bilur + TRUETYPE_TAG('b', 'x', 'g', 0), // bxg = Bangala + TRUETYPE_TAG('b', 'x', 'h', 0), // bxh = Buhutu + TRUETYPE_TAG('b', 'x', 'i', 0), // bxi = Pirlatapa + TRUETYPE_TAG('b', 'x', 'j', 0), // bxj = Bayungu + TRUETYPE_TAG('b', 'x', 'k', 0), // bxk = Bukusu + TRUETYPE_TAG('b', 'x', 'l', 0), // bxl = Jalkunan + TRUETYPE_TAG('b', 'x', 'm', 0), // bxm = Mongolia Buriat + TRUETYPE_TAG('b', 'x', 'n', 0), // bxn = Burduna + TRUETYPE_TAG('b', 'x', 'o', 0), // bxo = Barikanchi + TRUETYPE_TAG('b', 'x', 'p', 0), // bxp = Bebil + TRUETYPE_TAG('b', 'x', 'q', 0), // bxq = Beele + TRUETYPE_TAG('b', 'x', 'r', 0), // bxr = Russia Buriat + TRUETYPE_TAG('b', 'x', 's', 0), // bxs = Busam + TRUETYPE_TAG('b', 'x', 'u', 0), // bxu = China Buriat + TRUETYPE_TAG('b', 'x', 'v', 0), // bxv = Berakou + TRUETYPE_TAG('b', 'x', 'w', 0), // bxw = Bankagooma + TRUETYPE_TAG('b', 'x', 'x', + 0), // bxx = Borna (Democratic Republic of Congo) + TRUETYPE_TAG('b', 'x', 'z', 0), // bxz = Binahari + TRUETYPE_TAG('b', 'y', 'a', 0), // bya = Batak + TRUETYPE_TAG('b', 'y', 'b', 0), // byb = Bikya + TRUETYPE_TAG('b', 'y', 'c', 0), // byc = Ubaghara + TRUETYPE_TAG('b', 'y', 'd', 0), // byd = Benyadu' + TRUETYPE_TAG('b', 'y', 'e', 0), // bye = Pouye + TRUETYPE_TAG('b', 'y', 'f', 0), // byf = Bete + TRUETYPE_TAG('b', 'y', 'g', 0), // byg = Baygo + TRUETYPE_TAG('b', 'y', 'h', 0), // byh = Bhujel + TRUETYPE_TAG('b', 'y', 'i', 0), // byi = Buyu + TRUETYPE_TAG('b', 'y', 'j', 0), // byj = Bina (Nigeria) + TRUETYPE_TAG('b', 'y', 'k', 0), // byk = Biao + TRUETYPE_TAG('b', 'y', 'l', 0), // byl = Bayono + TRUETYPE_TAG('b', 'y', 'm', 0), // bym = Bidyara + TRUETYPE_TAG('b', 'y', 'n', 0), // byn = Bilin + TRUETYPE_TAG('b', 'y', 'o', 0), // byo = Biyo + TRUETYPE_TAG('b', 'y', 'p', 0), // byp = Bumaji + TRUETYPE_TAG('b', 'y', 'q', 0), // byq = Basay + TRUETYPE_TAG('b', 'y', 'r', 0), // byr = Baruya + TRUETYPE_TAG('b', 'y', 's', 0), // bys = Burak + TRUETYPE_TAG('b', 'y', 't', 0), // byt = Berti + TRUETYPE_TAG('b', 'y', 'v', 0), // byv = Medumba + TRUETYPE_TAG('b', 'y', 'w', 0), // byw = Belhariya + TRUETYPE_TAG('b', 'y', 'x', 0), // byx = Qaqet + TRUETYPE_TAG('b', 'y', 'y', 0), // byy = Buya + TRUETYPE_TAG('b', 'y', 'z', 0), // byz = Banaro + TRUETYPE_TAG('b', 'z', 'a', 0), // bza = Bandi + TRUETYPE_TAG('b', 'z', 'b', 0), // bzb = Andio + TRUETYPE_TAG('b', 'z', 'c', 0), // bzc = Southern Betsimisaraka Malagasy + TRUETYPE_TAG('b', 'z', 'd', 0), // bzd = Bribri + TRUETYPE_TAG('b', 'z', 'e', 0), // bze = Jenaama Bozo + TRUETYPE_TAG('b', 'z', 'f', 0), // bzf = Boikin + TRUETYPE_TAG('b', 'z', 'g', 0), // bzg = Babuza + TRUETYPE_TAG('b', 'z', 'h', 0), // bzh = Mapos Buang + TRUETYPE_TAG('b', 'z', 'i', 0), // bzi = Bisu + TRUETYPE_TAG('b', 'z', 'j', 0), // bzj = Belize Kriol English + TRUETYPE_TAG('b', 'z', 'k', 0), // bzk = Nicaragua Creole English + TRUETYPE_TAG('b', 'z', 'l', 0), // bzl = Boano (Sulawesi) + TRUETYPE_TAG('b', 'z', 'm', 0), // bzm = Bolondo + TRUETYPE_TAG('b', 'z', 'n', 0), // bzn = Boano (Maluku) + TRUETYPE_TAG('b', 'z', 'o', 0), // bzo = Bozaba + TRUETYPE_TAG('b', 'z', 'p', 0), // bzp = Kemberano + TRUETYPE_TAG('b', 'z', 'q', 0), // bzq = Buli (Indonesia) + TRUETYPE_TAG('b', 'z', 'r', 0), // bzr = Biri + TRUETYPE_TAG('b', 'z', 's', 0), // bzs = Brazilian Sign Language + TRUETYPE_TAG('b', 'z', 't', 0), // bzt = Brithenig + TRUETYPE_TAG('b', 'z', 'u', 0), // bzu = Burmeso + TRUETYPE_TAG('b', 'z', 'v', 0), // bzv = Bebe + TRUETYPE_TAG('b', 'z', 'w', 0), // bzw = Basa (Nigeria) + TRUETYPE_TAG('b', 'z', 'x', 0), // bzx = Kɛlɛngaxo Bozo + TRUETYPE_TAG('b', 'z', 'y', 0), // bzy = Obanliku + TRUETYPE_TAG('b', 'z', 'z', 0), // bzz = Evant + TRUETYPE_TAG('c', 'a', 'a', 0), // caa = Chortí + TRUETYPE_TAG('c', 'a', 'b', 0), // cab = Garifuna + TRUETYPE_TAG('c', 'a', 'c', 0), // cac = Chuj + TRUETYPE_TAG('c', 'a', 'd', 0), // cad = Caddo + TRUETYPE_TAG('c', 'a', 'e', 0), // cae = Lehar + TRUETYPE_TAG('c', 'a', 'f', 0), // caf = Southern Carrier + TRUETYPE_TAG('c', 'a', 'g', 0), // cag = Nivaclé + TRUETYPE_TAG('c', 'a', 'h', 0), // cah = Cahuarano + TRUETYPE_TAG('c', 'a', 'i', 0), // cai = Central American Indian languages + TRUETYPE_TAG('c', 'a', 'j', 0), // caj = Chané + TRUETYPE_TAG('c', 'a', 'k', 0), // cak = Kaqchikel + TRUETYPE_TAG('c', 'a', 'l', 0), // cal = Carolinian + TRUETYPE_TAG('c', 'a', 'm', 0), // cam = Cemuhî + TRUETYPE_TAG('c', 'a', 'n', 0), // can = Chambri + TRUETYPE_TAG('c', 'a', 'o', 0), // cao = Chácobo + TRUETYPE_TAG('c', 'a', 'p', 0), // cap = Chipaya + TRUETYPE_TAG('c', 'a', 'q', 0), // caq = Car Nicobarese + TRUETYPE_TAG('c', 'a', 'r', 0), // car = Galibi Carib + TRUETYPE_TAG('c', 'a', 's', 0), // cas = Tsimané + TRUETYPE_TAG('c', 'a', 'u', 0), // cau = Caucasian languages + TRUETYPE_TAG('c', 'a', 'v', 0), // cav = Cavineña + TRUETYPE_TAG('c', 'a', 'w', 0), // caw = Callawalla + TRUETYPE_TAG('c', 'a', 'x', 0), // cax = Chiquitano + TRUETYPE_TAG('c', 'a', 'y', 0), // cay = Cayuga + TRUETYPE_TAG('c', 'a', 'z', 0), // caz = Canichana + TRUETYPE_TAG('c', 'b', 'a', 0), // cba = Chibchan languages + TRUETYPE_TAG('c', 'b', 'b', 0), // cbb = Cabiyarí + TRUETYPE_TAG('c', 'b', 'c', 0), // cbc = Carapana + TRUETYPE_TAG('c', 'b', 'd', 0), // cbd = Carijona + TRUETYPE_TAG('c', 'b', 'e', 0), // cbe = Chipiajes + TRUETYPE_TAG('c', 'b', 'g', 0), // cbg = Chimila + TRUETYPE_TAG('c', 'b', 'h', 0), // cbh = Cagua + TRUETYPE_TAG('c', 'b', 'i', 0), // cbi = Chachi + TRUETYPE_TAG('c', 'b', 'j', 0), // cbj = Ede Cabe + TRUETYPE_TAG('c', 'b', 'k', 0), // cbk = Chavacano + TRUETYPE_TAG('c', 'b', 'l', 0), // cbl = Bualkhaw Chin + TRUETYPE_TAG('c', 'b', 'n', 0), // cbn = Nyahkur + TRUETYPE_TAG('c', 'b', 'o', 0), // cbo = Izora + TRUETYPE_TAG('c', 'b', 'r', 0), // cbr = Cashibo-Cacataibo + TRUETYPE_TAG('c', 'b', 's', 0), // cbs = Cashinahua + TRUETYPE_TAG('c', 'b', 't', 0), // cbt = Chayahuita + TRUETYPE_TAG('c', 'b', 'u', 0), // cbu = Candoshi-Shapra + TRUETYPE_TAG('c', 'b', 'v', 0), // cbv = Cacua + TRUETYPE_TAG('c', 'b', 'w', 0), // cbw = Kinabalian + TRUETYPE_TAG('c', 'b', 'y', 0), // cby = Carabayo + TRUETYPE_TAG('c', 'c', 'a', 0), // cca = Cauca + TRUETYPE_TAG('c', 'c', 'c', 0), // ccc = Chamicuro + TRUETYPE_TAG('c', 'c', 'd', 0), // ccd = Cafundo Creole + TRUETYPE_TAG('c', 'c', 'e', 0), // cce = Chopi + TRUETYPE_TAG('c', 'c', 'g', 0), // ccg = Samba Daka + TRUETYPE_TAG('c', 'c', 'h', 0), // cch = Atsam + TRUETYPE_TAG('c', 'c', 'j', 0), // ccj = Kasanga + TRUETYPE_TAG('c', 'c', 'l', 0), // ccl = Cutchi-Swahili + TRUETYPE_TAG('c', 'c', 'm', 0), // ccm = Malaccan Creole Malay + TRUETYPE_TAG('c', 'c', 'n', 0), // ccn = North Caucasian languages + TRUETYPE_TAG('c', 'c', 'o', 0), // cco = Comaltepec Chinantec + TRUETYPE_TAG('c', 'c', 'p', 0), // ccp = Chakma + TRUETYPE_TAG('c', 'c', 'q', 0), // ccq = Chaungtha + TRUETYPE_TAG('c', 'c', 'r', 0), // ccr = Cacaopera + TRUETYPE_TAG('c', 'c', 's', 0), // ccs = South Caucasian languages + TRUETYPE_TAG('c', 'd', 'a', 0), // cda = Choni + TRUETYPE_TAG('c', 'd', 'c', 0), // cdc = Chadic languages + TRUETYPE_TAG('c', 'd', 'd', 0), // cdd = Caddoan languages + TRUETYPE_TAG('c', 'd', 'e', 0), // cde = Chenchu + TRUETYPE_TAG('c', 'd', 'f', 0), // cdf = Chiru + TRUETYPE_TAG('c', 'd', 'g', 0), // cdg = Chamari + TRUETYPE_TAG('c', 'd', 'h', 0), // cdh = Chambeali + TRUETYPE_TAG('c', 'd', 'i', 0), // cdi = Chodri + TRUETYPE_TAG('c', 'd', 'j', 0), // cdj = Churahi + TRUETYPE_TAG('c', 'd', 'm', 0), // cdm = Chepang + TRUETYPE_TAG('c', 'd', 'n', 0), // cdn = Chaudangsi + TRUETYPE_TAG('c', 'd', 'o', 0), // cdo = Min Dong Chinese + TRUETYPE_TAG('c', 'd', 'r', 0), // cdr = Cinda-Regi-Tiyal + TRUETYPE_TAG('c', 'd', 's', 0), // cds = Chadian Sign Language + TRUETYPE_TAG('c', 'd', 'y', 0), // cdy = Chadong + TRUETYPE_TAG('c', 'd', 'z', 0), // cdz = Koda + TRUETYPE_TAG('c', 'e', 'a', 0), // cea = Lower Chehalis + TRUETYPE_TAG('c', 'e', 'b', 0), // ceb = Cebuano + TRUETYPE_TAG('c', 'e', 'g', 0), // ceg = Chamacoco + TRUETYPE_TAG('c', 'e', 'l', 0), // cel = Celtic languages + TRUETYPE_TAG('c', 'e', 'n', 0), // cen = Cen + TRUETYPE_TAG('c', 'e', 't', 0), // cet = Centúúm + TRUETYPE_TAG('c', 'f', 'a', 0), // cfa = Dijim-Bwilim + TRUETYPE_TAG('c', 'f', 'd', 0), // cfd = Cara + TRUETYPE_TAG('c', 'f', 'g', 0), // cfg = Como Karim + TRUETYPE_TAG('c', 'f', 'm', 0), // cfm = Falam Chin + TRUETYPE_TAG('c', 'g', 'a', 0), // cga = Changriwa + TRUETYPE_TAG('c', 'g', 'c', 0), // cgc = Kagayanen + TRUETYPE_TAG('c', 'g', 'g', 0), // cgg = Chiga + TRUETYPE_TAG('c', 'g', 'k', 0), // cgk = Chocangacakha + TRUETYPE_TAG('c', 'h', 'b', 0), // chb = Chibcha + TRUETYPE_TAG('c', 'h', 'c', 0), // chc = Catawba + TRUETYPE_TAG('c', 'h', 'd', 0), // chd = Highland Oaxaca Chontal + TRUETYPE_TAG('c', 'h', 'f', 0), // chf = Tabasco Chontal + TRUETYPE_TAG('c', 'h', 'g', 0), // chg = Chagatai + TRUETYPE_TAG('c', 'h', 'h', 0), // chh = Chinook + TRUETYPE_TAG('c', 'h', 'j', 0), // chj = Ojitlán Chinantec + TRUETYPE_TAG('c', 'h', 'k', 0), // chk = Chuukese + TRUETYPE_TAG('c', 'h', 'l', 0), // chl = Cahuilla + TRUETYPE_TAG('c', 'h', 'm', 0), // chm = Mari (Russia) + TRUETYPE_TAG('c', 'h', 'n', 0), // chn = Chinook jargon + TRUETYPE_TAG('c', 'h', 'o', 0), // cho = Choctaw + TRUETYPE_TAG('c', 'h', 'p', 0), // chp = Chipewyan + TRUETYPE_TAG('c', 'h', 'q', 0), // chq = Quiotepec Chinantec + TRUETYPE_TAG('c', 'h', 'r', 0), // chr = Cherokee + TRUETYPE_TAG('c', 'h', 't', 0), // cht = Cholón + TRUETYPE_TAG('c', 'h', 'w', 0), // chw = Chuwabu + TRUETYPE_TAG('c', 'h', 'x', 0), // chx = Chantyal + TRUETYPE_TAG('c', 'h', 'y', 0), // chy = Cheyenne + TRUETYPE_TAG('c', 'h', 'z', 0), // chz = Ozumacín Chinantec + TRUETYPE_TAG('c', 'i', 'a', 0), // cia = Cia-Cia + TRUETYPE_TAG('c', 'i', 'b', 0), // cib = Ci Gbe + TRUETYPE_TAG('c', 'i', 'c', 0), // cic = Chickasaw + TRUETYPE_TAG('c', 'i', 'd', 0), // cid = Chimariko + TRUETYPE_TAG('c', 'i', 'e', 0), // cie = Cineni + TRUETYPE_TAG('c', 'i', 'h', 0), // cih = Chinali + TRUETYPE_TAG('c', 'i', 'k', 0), // cik = Chitkuli Kinnauri + TRUETYPE_TAG('c', 'i', 'm', 0), // cim = Cimbrian + TRUETYPE_TAG('c', 'i', 'n', 0), // cin = Cinta Larga + TRUETYPE_TAG('c', 'i', 'p', 0), // cip = Chiapanec + TRUETYPE_TAG('c', 'i', 'r', 0), // cir = Tiri + TRUETYPE_TAG('c', 'i', 'w', 0), // ciw = Chippewa + TRUETYPE_TAG('c', 'i', 'y', 0), // ciy = Chaima + TRUETYPE_TAG('c', 'j', 'a', 0), // cja = Western Cham + TRUETYPE_TAG('c', 'j', 'e', 0), // cje = Chru + TRUETYPE_TAG('c', 'j', 'h', 0), // cjh = Upper Chehalis + TRUETYPE_TAG('c', 'j', 'i', 0), // cji = Chamalal + TRUETYPE_TAG('c', 'j', 'k', 0), // cjk = Chokwe + TRUETYPE_TAG('c', 'j', 'm', 0), // cjm = Eastern Cham + TRUETYPE_TAG('c', 'j', 'n', 0), // cjn = Chenapian + TRUETYPE_TAG('c', 'j', 'o', 0), // cjo = Ashéninka Pajonal + TRUETYPE_TAG('c', 'j', 'p', 0), // cjp = Cabécar + TRUETYPE_TAG('c', 'j', 'r', 0), // cjr = Chorotega + TRUETYPE_TAG('c', 'j', 's', 0), // cjs = Shor + TRUETYPE_TAG('c', 'j', 'v', 0), // cjv = Chuave + TRUETYPE_TAG('c', 'j', 'y', 0), // cjy = Jinyu Chinese + TRUETYPE_TAG('c', 'k', 'a', 0), // cka = Khumi Awa Chin + TRUETYPE_TAG('c', 'k', 'b', 0), // ckb = Central Kurdish + TRUETYPE_TAG('c', 'k', 'h', 0), // ckh = Chak + TRUETYPE_TAG('c', 'k', 'l', 0), // ckl = Cibak + TRUETYPE_TAG('c', 'k', 'o', 0), // cko = Anufo + TRUETYPE_TAG('c', 'k', 'q', 0), // ckq = Kajakse + TRUETYPE_TAG('c', 'k', 'r', 0), // ckr = Kairak + TRUETYPE_TAG('c', 'k', 's', 0), // cks = Tayo + TRUETYPE_TAG('c', 'k', 't', 0), // ckt = Chukot + TRUETYPE_TAG('c', 'k', 'u', 0), // cku = Koasati + TRUETYPE_TAG('c', 'k', 'v', 0), // ckv = Kavalan + TRUETYPE_TAG('c', 'k', 'x', 0), // ckx = Caka + TRUETYPE_TAG('c', 'k', 'y', 0), // cky = Cakfem-Mushere + TRUETYPE_TAG('c', 'k', 'z', 0), // ckz = Cakchiquel-Quiché Mixed Language + TRUETYPE_TAG('c', 'l', 'a', 0), // cla = Ron + TRUETYPE_TAG('c', 'l', 'c', 0), // clc = Chilcotin + TRUETYPE_TAG('c', 'l', 'd', 0), // cld = Chaldean Neo-Aramaic + TRUETYPE_TAG('c', 'l', 'e', 0), // cle = Lealao Chinantec + TRUETYPE_TAG('c', 'l', 'h', 0), // clh = Chilisso + TRUETYPE_TAG('c', 'l', 'i', 0), // cli = Chakali + TRUETYPE_TAG('c', 'l', 'k', 0), // clk = Idu-Mishmi + TRUETYPE_TAG('c', 'l', 'l', 0), // cll = Chala + TRUETYPE_TAG('c', 'l', 'm', 0), // clm = Clallam + TRUETYPE_TAG('c', 'l', 'o', 0), // clo = Lowland Oaxaca Chontal + TRUETYPE_TAG('c', 'l', 'u', 0), // clu = Caluyanun + TRUETYPE_TAG('c', 'l', 'w', 0), // clw = Chulym + TRUETYPE_TAG('c', 'l', 'y', 0), // cly = Eastern Highland Chatino + TRUETYPE_TAG('c', 'm', 'a', 0), // cma = Maa + TRUETYPE_TAG('c', 'm', 'c', 0), // cmc = Chamic languages + TRUETYPE_TAG('c', 'm', 'e', 0), // cme = Cerma + TRUETYPE_TAG('c', 'm', 'g', 0), // cmg = Classical Mongolian + TRUETYPE_TAG('c', 'm', 'i', 0), // cmi = Emberá-Chamí + TRUETYPE_TAG('c', 'm', 'k', 0), // cmk = Chimakum + TRUETYPE_TAG('c', 'm', 'l', 0), // cml = Campalagian + TRUETYPE_TAG('c', 'm', 'm', 0), // cmm = Michigamea + TRUETYPE_TAG('c', 'm', 'n', 0), // cmn = Mandarin Chinese + TRUETYPE_TAG('c', 'm', 'o', 0), // cmo = Central Mnong + TRUETYPE_TAG('c', 'm', 'r', 0), // cmr = Mro Chin + TRUETYPE_TAG('c', 'm', 's', 0), // cms = Messapic + TRUETYPE_TAG('c', 'm', 't', 0), // cmt = Camtho + TRUETYPE_TAG('c', 'n', 'a', 0), // cna = Changthang + TRUETYPE_TAG('c', 'n', 'b', 0), // cnb = Chinbon Chin + TRUETYPE_TAG('c', 'n', 'c', 0), // cnc = Côông + TRUETYPE_TAG('c', 'n', 'g', 0), // cng = Northern Qiang + TRUETYPE_TAG('c', 'n', 'h', 0), // cnh = Haka Chin + TRUETYPE_TAG('c', 'n', 'i', 0), // cni = Asháninka + TRUETYPE_TAG('c', 'n', 'k', 0), // cnk = Khumi Chin + TRUETYPE_TAG('c', 'n', 'l', 0), // cnl = Lalana Chinantec + TRUETYPE_TAG('c', 'n', 'o', 0), // cno = Con + TRUETYPE_TAG('c', 'n', 's', 0), // cns = Central Asmat + TRUETYPE_TAG('c', 'n', 't', 0), // cnt = Tepetotutla Chinantec + TRUETYPE_TAG('c', 'n', 'u', 0), // cnu = Chenoua + TRUETYPE_TAG('c', 'n', 'w', 0), // cnw = Ngawn Chin + TRUETYPE_TAG('c', 'n', 'x', 0), // cnx = Middle Cornish + TRUETYPE_TAG('c', 'o', 'a', 0), // coa = Cocos Islands Malay + TRUETYPE_TAG('c', 'o', 'b', 0), // cob = Chicomuceltec + TRUETYPE_TAG('c', 'o', 'c', 0), // coc = Cocopa + TRUETYPE_TAG('c', 'o', 'd', 0), // cod = Cocama-Cocamilla + TRUETYPE_TAG('c', 'o', 'e', 0), // coe = Koreguaje + TRUETYPE_TAG('c', 'o', 'f', 0), // cof = Colorado + TRUETYPE_TAG('c', 'o', 'g', 0), // cog = Chong + TRUETYPE_TAG('c', 'o', 'h', 0), // coh = Chonyi-Dzihana-Kauma + TRUETYPE_TAG('c', 'o', 'j', 0), // coj = Cochimi + TRUETYPE_TAG('c', 'o', 'k', 0), // cok = Santa Teresa Cora + TRUETYPE_TAG('c', 'o', 'l', 0), // col = Columbia-Wenatchi + TRUETYPE_TAG('c', 'o', 'm', 0), // com = Comanche + TRUETYPE_TAG('c', 'o', 'n', 0), // con = Cofán + TRUETYPE_TAG('c', 'o', 'o', 0), // coo = Comox + TRUETYPE_TAG('c', 'o', 'p', 0), // cop = Coptic + TRUETYPE_TAG('c', 'o', 'q', 0), // coq = Coquille + TRUETYPE_TAG('c', 'o', 't', 0), // cot = Caquinte + TRUETYPE_TAG('c', 'o', 'u', 0), // cou = Wamey + TRUETYPE_TAG('c', 'o', 'v', 0), // cov = Cao Miao + TRUETYPE_TAG('c', 'o', 'w', 0), // cow = Cowlitz + TRUETYPE_TAG('c', 'o', 'x', 0), // cox = Nanti + TRUETYPE_TAG('c', 'o', 'y', 0), // coy = Coyaima + TRUETYPE_TAG('c', 'o', 'z', 0), // coz = Chochotec + TRUETYPE_TAG('c', 'p', 'a', 0), // cpa = Palantla Chinantec + TRUETYPE_TAG('c', 'p', 'b', 0), // cpb = Ucayali-Yurúa Ashéninka + TRUETYPE_TAG('c', 'p', 'c', 0), // cpc = Ajyíninka Apurucayali + TRUETYPE_TAG('c', 'p', 'e', 0), // cpe = English-based creoles and pidgins + TRUETYPE_TAG('c', 'p', 'f', 0), // cpf = French-based creoles and pidgins + TRUETYPE_TAG('c', 'p', 'g', 0), // cpg = Cappadocian Greek + TRUETYPE_TAG('c', 'p', 'i', 0), // cpi = Chinese Pidgin English + TRUETYPE_TAG('c', 'p', 'n', 0), // cpn = Cherepon + TRUETYPE_TAG('c', 'p', 'p', + 0), // cpp = Portuguese-based creoles and pidgins + TRUETYPE_TAG('c', 'p', 's', 0), // cps = Capiznon + TRUETYPE_TAG('c', 'p', 'u', 0), // cpu = Pichis Ashéninka + TRUETYPE_TAG('c', 'p', 'x', 0), // cpx = Pu-Xian Chinese + TRUETYPE_TAG('c', 'p', 'y', 0), // cpy = South Ucayali Ashéninka + TRUETYPE_TAG('c', 'q', 'd', 0), // cqd = Chuanqiandian Cluster Miao + TRUETYPE_TAG('c', 'q', 'u', 0), // cqu = Chilean Quechua + TRUETYPE_TAG('c', 'r', 'a', 0), // cra = Chara + TRUETYPE_TAG('c', 'r', 'b', 0), // crb = Island Carib + TRUETYPE_TAG('c', 'r', 'c', 0), // crc = Lonwolwol + TRUETYPE_TAG('c', 'r', 'd', 0), // crd = Coeur d'Alene + TRUETYPE_TAG('c', 'r', 'f', 0), // crf = Caramanta + TRUETYPE_TAG('c', 'r', 'g', 0), // crg = Michif + TRUETYPE_TAG('c', 'r', 'h', 0), // crh = Crimean Tatar + TRUETYPE_TAG('c', 'r', 'i', 0), // cri = Sãotomense + TRUETYPE_TAG('c', 'r', 'j', 0), // crj = Southern East Cree + TRUETYPE_TAG('c', 'r', 'k', 0), // crk = Plains Cree + TRUETYPE_TAG('c', 'r', 'l', 0), // crl = Northern East Cree + TRUETYPE_TAG('c', 'r', 'm', 0), // crm = Moose Cree + TRUETYPE_TAG('c', 'r', 'n', 0), // crn = El Nayar Cora + TRUETYPE_TAG('c', 'r', 'o', 0), // cro = Crow + TRUETYPE_TAG('c', 'r', 'p', 0), // crp = Creoles and pidgins + TRUETYPE_TAG('c', 'r', 'q', 0), // crq = Iyo'wujwa Chorote + TRUETYPE_TAG('c', 'r', 'r', 0), // crr = Carolina Algonquian + TRUETYPE_TAG('c', 'r', 's', 0), // crs = Seselwa Creole French + TRUETYPE_TAG('c', 'r', 't', 0), // crt = Iyojwa'ja Chorote + TRUETYPE_TAG('c', 'r', 'v', 0), // crv = Chaura + TRUETYPE_TAG('c', 'r', 'w', 0), // crw = Chrau + TRUETYPE_TAG('c', 'r', 'x', 0), // crx = Carrier + TRUETYPE_TAG('c', 'r', 'y', 0), // cry = Cori + TRUETYPE_TAG('c', 'r', 'z', 0), // crz = Cruzeño + TRUETYPE_TAG('c', 's', 'a', 0), // csa = Chiltepec Chinantec + TRUETYPE_TAG('c', 's', 'b', 0), // csb = Kashubian + TRUETYPE_TAG('c', 's', 'c', 0), // csc = Catalan Sign Language + TRUETYPE_TAG('c', 's', 'd', 0), // csd = Chiangmai Sign Language + TRUETYPE_TAG('c', 's', 'e', 0), // cse = Czech Sign Language + TRUETYPE_TAG('c', 's', 'f', 0), // csf = Cuba Sign Language + TRUETYPE_TAG('c', 's', 'g', 0), // csg = Chilean Sign Language + TRUETYPE_TAG('c', 's', 'h', 0), // csh = Asho Chin + TRUETYPE_TAG('c', 's', 'i', 0), // csi = Coast Miwok + TRUETYPE_TAG('c', 's', 'k', 0), // csk = Jola-Kasa + TRUETYPE_TAG('c', 's', 'l', 0), // csl = Chinese Sign Language + TRUETYPE_TAG('c', 's', 'm', 0), // csm = Central Sierra Miwok + TRUETYPE_TAG('c', 's', 'n', 0), // csn = Colombian Sign Language + TRUETYPE_TAG('c', 's', 'o', 0), // cso = Sochiapam Chinantec + TRUETYPE_TAG('c', 's', 'q', 0), // csq = Croatia Sign Language + TRUETYPE_TAG('c', 's', 'r', 0), // csr = Costa Rican Sign Language + TRUETYPE_TAG('c', 's', 's', 0), // css = Southern Ohlone + TRUETYPE_TAG('c', 's', 't', 0), // cst = Northern Ohlone + TRUETYPE_TAG('c', 's', 'u', 0), // csu = Central Sudanic languages + TRUETYPE_TAG('c', 's', 'w', 0), // csw = Swampy Cree + TRUETYPE_TAG('c', 's', 'y', 0), // csy = Siyin Chin + TRUETYPE_TAG('c', 's', 'z', 0), // csz = Coos + TRUETYPE_TAG('c', 't', 'a', 0), // cta = Tataltepec Chatino + TRUETYPE_TAG('c', 't', 'c', 0), // ctc = Chetco + TRUETYPE_TAG('c', 't', 'd', 0), // ctd = Tedim Chin + TRUETYPE_TAG('c', 't', 'e', 0), // cte = Tepinapa Chinantec + TRUETYPE_TAG('c', 't', 'g', 0), // ctg = Chittagonian + TRUETYPE_TAG('c', 't', 'l', 0), // ctl = Tlacoatzintepec Chinantec + TRUETYPE_TAG('c', 't', 'm', 0), // ctm = Chitimacha + TRUETYPE_TAG('c', 't', 'n', 0), // ctn = Chhintange + TRUETYPE_TAG('c', 't', 'o', 0), // cto = Emberá-Catío + TRUETYPE_TAG('c', 't', 'p', 0), // ctp = Western Highland Chatino + TRUETYPE_TAG('c', 't', 's', 0), // cts = Northern Catanduanes Bicolano + TRUETYPE_TAG('c', 't', 't', 0), // ctt = Wayanad Chetti + TRUETYPE_TAG('c', 't', 'u', 0), // ctu = Chol + TRUETYPE_TAG('c', 't', 'z', 0), // ctz = Zacatepec Chatino + TRUETYPE_TAG('c', 'u', 'a', 0), // cua = Cua + TRUETYPE_TAG('c', 'u', 'b', 0), // cub = Cubeo + TRUETYPE_TAG('c', 'u', 'c', 0), // cuc = Usila Chinantec + TRUETYPE_TAG('c', 'u', 'g', 0), // cug = Cung + TRUETYPE_TAG('c', 'u', 'h', 0), // cuh = Chuka + TRUETYPE_TAG('c', 'u', 'i', 0), // cui = Cuiba + TRUETYPE_TAG('c', 'u', 'j', 0), // cuj = Mashco Piro + TRUETYPE_TAG('c', 'u', 'k', 0), // cuk = San Blas Kuna + TRUETYPE_TAG('c', 'u', 'l', 0), // cul = Culina + TRUETYPE_TAG('c', 'u', 'm', 0), // cum = Cumeral + TRUETYPE_TAG('c', 'u', 'o', 0), // cuo = Cumanagoto + TRUETYPE_TAG('c', 'u', 'p', 0), // cup = Cupeño + TRUETYPE_TAG('c', 'u', 'q', 0), // cuq = Cun + TRUETYPE_TAG('c', 'u', 'r', 0), // cur = Chhulung + TRUETYPE_TAG('c', 'u', 's', 0), // cus = Cushitic languages + TRUETYPE_TAG('c', 'u', 't', 0), // cut = Teutila Cuicatec + TRUETYPE_TAG('c', 'u', 'u', 0), // cuu = Tai Ya + TRUETYPE_TAG('c', 'u', 'v', 0), // cuv = Cuvok + TRUETYPE_TAG('c', 'u', 'w', 0), // cuw = Chukwa + TRUETYPE_TAG('c', 'u', 'x', 0), // cux = Tepeuxila Cuicatec + TRUETYPE_TAG('c', 'v', 'g', 0), // cvg = Chug + TRUETYPE_TAG('c', 'v', 'n', 0), // cvn = Valle Nacional Chinantec + TRUETYPE_TAG('c', 'w', 'a', 0), // cwa = Kabwa + TRUETYPE_TAG('c', 'w', 'b', 0), // cwb = Maindo + TRUETYPE_TAG('c', 'w', 'd', 0), // cwd = Woods Cree + TRUETYPE_TAG('c', 'w', 'e', 0), // cwe = Kwere + TRUETYPE_TAG('c', 'w', 'g', 0), // cwg = Chewong + TRUETYPE_TAG('c', 'w', 't', 0), // cwt = Kuwaataay + TRUETYPE_TAG('c', 'y', 'a', 0), // cya = Nopala Chatino + TRUETYPE_TAG('c', 'y', 'b', 0), // cyb = Cayubaba + TRUETYPE_TAG('c', 'y', 'o', 0), // cyo = Cuyonon + TRUETYPE_TAG('c', 'z', 'h', 0), // czh = Huizhou Chinese + TRUETYPE_TAG('c', 'z', 'k', 0), // czk = Knaanic + TRUETYPE_TAG('c', 'z', 'n', 0), // czn = Zenzontepec Chatino + TRUETYPE_TAG('c', 'z', 'o', 0), // czo = Min Zhong Chinese + TRUETYPE_TAG('c', 'z', 't', 0), // czt = Zotung Chin + TRUETYPE_TAG('d', 'a', 'a', 0), // daa = Dangaléat + TRUETYPE_TAG('d', 'a', 'c', 0), // dac = Dambi + TRUETYPE_TAG('d', 'a', 'd', 0), // dad = Marik + TRUETYPE_TAG('d', 'a', 'e', 0), // dae = Duupa + TRUETYPE_TAG('d', 'a', 'f', 0), // daf = Dan + TRUETYPE_TAG('d', 'a', 'g', 0), // dag = Dagbani + TRUETYPE_TAG('d', 'a', 'h', 0), // dah = Gwahatike + TRUETYPE_TAG('d', 'a', 'i', 0), // dai = Day + TRUETYPE_TAG('d', 'a', 'j', 0), // daj = Dar Fur Daju + TRUETYPE_TAG('d', 'a', 'k', 0), // dak = Dakota + TRUETYPE_TAG('d', 'a', 'l', 0), // dal = Dahalo + TRUETYPE_TAG('d', 'a', 'm', 0), // dam = Damakawa + TRUETYPE_TAG('d', 'a', 'o', 0), // dao = Daai Chin + TRUETYPE_TAG('d', 'a', 'p', 0), // dap = Nisi (India) + TRUETYPE_TAG('d', 'a', 'q', 0), // daq = Dandami Maria + TRUETYPE_TAG('d', 'a', 'r', 0), // dar = Dargwa + TRUETYPE_TAG('d', 'a', 's', 0), // das = Daho-Doo + TRUETYPE_TAG('d', 'a', 'u', 0), // dau = Dar Sila Daju + TRUETYPE_TAG('d', 'a', 'v', 0), // dav = Taita + TRUETYPE_TAG('d', 'a', 'w', 0), // daw = Davawenyo + TRUETYPE_TAG('d', 'a', 'x', 0), // dax = Dayi + TRUETYPE_TAG('d', 'a', 'y', 0), // day = Land Dayak languages + TRUETYPE_TAG('d', 'a', 'z', 0), // daz = Dao + TRUETYPE_TAG('d', 'b', 'a', 0), // dba = Bangi Me + TRUETYPE_TAG('d', 'b', 'b', 0), // dbb = Deno + TRUETYPE_TAG('d', 'b', 'd', 0), // dbd = Dadiya + TRUETYPE_TAG('d', 'b', 'e', 0), // dbe = Dabe + TRUETYPE_TAG('d', 'b', 'f', 0), // dbf = Edopi + TRUETYPE_TAG('d', 'b', 'g', 0), // dbg = Dogul Dom Dogon + TRUETYPE_TAG('d', 'b', 'i', 0), // dbi = Doka + TRUETYPE_TAG('d', 'b', 'j', 0), // dbj = Ida'an + TRUETYPE_TAG('d', 'b', 'l', 0), // dbl = Dyirbal + TRUETYPE_TAG('d', 'b', 'm', 0), // dbm = Duguri + TRUETYPE_TAG('d', 'b', 'n', 0), // dbn = Duriankere + TRUETYPE_TAG('d', 'b', 'o', 0), // dbo = Dulbu + TRUETYPE_TAG('d', 'b', 'p', 0), // dbp = Duwai + TRUETYPE_TAG('d', 'b', 'q', 0), // dbq = Daba + TRUETYPE_TAG('d', 'b', 'r', 0), // dbr = Dabarre + TRUETYPE_TAG('d', 'b', 'u', 0), // dbu = Bondum Dom Dogon + TRUETYPE_TAG('d', 'b', 'v', 0), // dbv = Dungu + TRUETYPE_TAG('d', 'b', 'y', 0), // dby = Dibiyaso + TRUETYPE_TAG('d', 'c', 'c', 0), // dcc = Deccan + TRUETYPE_TAG('d', 'c', 'r', 0), // dcr = Negerhollands + TRUETYPE_TAG('d', 'd', 'd', 0), // ddd = Dongotono + TRUETYPE_TAG('d', 'd', 'e', 0), // dde = Doondo + TRUETYPE_TAG('d', 'd', 'g', 0), // ddg = Fataluku + TRUETYPE_TAG('d', 'd', 'i', 0), // ddi = West Goodenough + TRUETYPE_TAG('d', 'd', 'j', 0), // ddj = Jaru + TRUETYPE_TAG('d', 'd', 'n', 0), // ddn = Dendi (Benin) + TRUETYPE_TAG('d', 'd', 'o', 0), // ddo = Dido + TRUETYPE_TAG('d', 'd', 's', 0), // dds = Donno So Dogon + TRUETYPE_TAG('d', 'd', 'w', 0), // ddw = Dawera-Daweloor + TRUETYPE_TAG('d', 'e', 'c', 0), // dec = Dagik + TRUETYPE_TAG('d', 'e', 'd', 0), // ded = Dedua + TRUETYPE_TAG('d', 'e', 'e', 0), // dee = Dewoin + TRUETYPE_TAG('d', 'e', 'f', 0), // def = Dezfuli + TRUETYPE_TAG('d', 'e', 'g', 0), // deg = Degema + TRUETYPE_TAG('d', 'e', 'h', 0), // deh = Dehwari + TRUETYPE_TAG('d', 'e', 'i', 0), // dei = Demisa + TRUETYPE_TAG('d', 'e', 'k', 0), // dek = Dek + TRUETYPE_TAG('d', 'e', 'l', 0), // del = Delaware + TRUETYPE_TAG('d', 'e', 'm', 0), // dem = Dem + TRUETYPE_TAG('d', 'e', 'n', 0), // den = Slave (Athapascan) + TRUETYPE_TAG('d', 'e', 'p', 0), // dep = Pidgin Delaware + TRUETYPE_TAG('d', 'e', 'q', 0), // deq = Dendi (Central African Republic) + TRUETYPE_TAG('d', 'e', 'r', 0), // der = Deori + TRUETYPE_TAG('d', 'e', 's', 0), // des = Desano + TRUETYPE_TAG('d', 'e', 'v', 0), // dev = Domung + TRUETYPE_TAG('d', 'e', 'z', 0), // dez = Dengese + TRUETYPE_TAG('d', 'g', 'a', 0), // dga = Southern Dagaare + TRUETYPE_TAG('d', 'g', 'b', 0), // dgb = Bunoge Dogon + TRUETYPE_TAG('d', 'g', 'c', 0), // dgc = Casiguran Dumagat Agta + TRUETYPE_TAG('d', 'g', 'd', 0), // dgd = Dagaari Dioula + TRUETYPE_TAG('d', 'g', 'e', 0), // dge = Degenan + TRUETYPE_TAG('d', 'g', 'g', 0), // dgg = Doga + TRUETYPE_TAG('d', 'g', 'h', 0), // dgh = Dghwede + TRUETYPE_TAG('d', 'g', 'i', 0), // dgi = Northern Dagara + TRUETYPE_TAG('d', 'g', 'k', 0), // dgk = Dagba + TRUETYPE_TAG('d', 'g', 'n', 0), // dgn = Dagoman + TRUETYPE_TAG('d', 'g', 'o', 0), // dgo = Dogri (individual language) + TRUETYPE_TAG('d', 'g', 'r', 0), // dgr = Dogrib + TRUETYPE_TAG('d', 'g', 's', 0), // dgs = Dogoso + TRUETYPE_TAG('d', 'g', 'u', 0), // dgu = Degaru + TRUETYPE_TAG('d', 'g', 'x', 0), // dgx = Doghoro + TRUETYPE_TAG('d', 'g', 'z', 0), // dgz = Daga + TRUETYPE_TAG('d', 'h', 'a', 0), // dha = Dhanwar (India) + TRUETYPE_TAG('d', 'h', 'd', 0), // dhd = Dhundari + TRUETYPE_TAG('d', 'h', 'g', 0), // dhg = Dhangu + TRUETYPE_TAG('d', 'h', 'i', 0), // dhi = Dhimal + TRUETYPE_TAG('d', 'h', 'l', 0), // dhl = Dhalandji + TRUETYPE_TAG('d', 'h', 'm', 0), // dhm = Zemba + TRUETYPE_TAG('d', 'h', 'n', 0), // dhn = Dhanki + TRUETYPE_TAG('d', 'h', 'o', 0), // dho = Dhodia + TRUETYPE_TAG('d', 'h', 'r', 0), // dhr = Dhargari + TRUETYPE_TAG('d', 'h', 's', 0), // dhs = Dhaiso + TRUETYPE_TAG('d', 'h', 'u', 0), // dhu = Dhurga + TRUETYPE_TAG('d', 'h', 'v', 0), // dhv = Dehu + TRUETYPE_TAG('d', 'h', 'w', 0), // dhw = Dhanwar (Nepal) + TRUETYPE_TAG('d', 'i', 'a', 0), // dia = Dia + TRUETYPE_TAG('d', 'i', 'b', 0), // dib = South Central Dinka + TRUETYPE_TAG('d', 'i', 'c', 0), // dic = Lakota Dida + TRUETYPE_TAG('d', 'i', 'd', 0), // did = Didinga + TRUETYPE_TAG('d', 'i', 'f', 0), // dif = Dieri + TRUETYPE_TAG('d', 'i', 'g', 0), // dig = Digo + TRUETYPE_TAG('d', 'i', 'h', 0), // dih = Kumiai + TRUETYPE_TAG('d', 'i', 'i', 0), // dii = Dimbong + TRUETYPE_TAG('d', 'i', 'j', 0), // dij = Dai + TRUETYPE_TAG('d', 'i', 'k', 0), // dik = Southwestern Dinka + TRUETYPE_TAG('d', 'i', 'l', 0), // dil = Dilling + TRUETYPE_TAG('d', 'i', 'm', 0), // dim = Dime + TRUETYPE_TAG('d', 'i', 'n', 0), // din = Dinka + TRUETYPE_TAG('d', 'i', 'o', 0), // dio = Dibo + TRUETYPE_TAG('d', 'i', 'p', 0), // dip = Northeastern Dinka + TRUETYPE_TAG('d', 'i', 'q', 0), // diq = Dimli (individual language) + TRUETYPE_TAG('d', 'i', 'r', 0), // dir = Dirim + TRUETYPE_TAG('d', 'i', 's', 0), // dis = Dimasa + TRUETYPE_TAG('d', 'i', 't', 0), // dit = Dirari + TRUETYPE_TAG('d', 'i', 'u', 0), // diu = Diriku + TRUETYPE_TAG('d', 'i', 'w', 0), // diw = Northwestern Dinka + TRUETYPE_TAG('d', 'i', 'x', 0), // dix = Dixon Reef + TRUETYPE_TAG('d', 'i', 'y', 0), // diy = Diuwe + TRUETYPE_TAG('d', 'i', 'z', 0), // diz = Ding + TRUETYPE_TAG('d', 'j', 'b', 0), // djb = Djinba + TRUETYPE_TAG('d', 'j', 'c', 0), // djc = Dar Daju Daju + TRUETYPE_TAG('d', 'j', 'd', 0), // djd = Djamindjung + TRUETYPE_TAG('d', 'j', 'e', 0), // dje = Zarma + TRUETYPE_TAG('d', 'j', 'f', 0), // djf = Djangun + TRUETYPE_TAG('d', 'j', 'i', 0), // dji = Djinang + TRUETYPE_TAG('d', 'j', 'j', 0), // djj = Djeebbana + TRUETYPE_TAG('d', 'j', 'k', 0), // djk = Eastern Maroon Creole + TRUETYPE_TAG('d', 'j', 'l', 0), // djl = Djiwarli + TRUETYPE_TAG('d', 'j', 'm', 0), // djm = Jamsay Dogon + TRUETYPE_TAG('d', 'j', 'n', 0), // djn = Djauan + TRUETYPE_TAG('d', 'j', 'o', 0), // djo = Jangkang + TRUETYPE_TAG('d', 'j', 'r', 0), // djr = Djambarrpuyngu + TRUETYPE_TAG('d', 'j', 'u', 0), // dju = Kapriman + TRUETYPE_TAG('d', 'j', 'w', 0), // djw = Djawi + TRUETYPE_TAG('d', 'k', 'a', 0), // dka = Dakpakha + TRUETYPE_TAG('d', 'k', 'k', 0), // dkk = Dakka + TRUETYPE_TAG('d', 'k', 'l', 0), // dkl = Kolum So Dogon + TRUETYPE_TAG('d', 'k', 'r', 0), // dkr = Kuijau + TRUETYPE_TAG('d', 'k', 's', 0), // dks = Southeastern Dinka + TRUETYPE_TAG('d', 'k', 'x', 0), // dkx = Mazagway + TRUETYPE_TAG('d', 'l', 'g', 0), // dlg = Dolgan + TRUETYPE_TAG('d', 'l', 'm', 0), // dlm = Dalmatian + TRUETYPE_TAG('d', 'l', 'n', 0), // dln = Darlong + TRUETYPE_TAG('d', 'm', 'a', 0), // dma = Duma + TRUETYPE_TAG('d', 'm', 'b', 0), // dmb = Mombo Dogon + TRUETYPE_TAG('d', 'm', 'c', 0), // dmc = Dimir + TRUETYPE_TAG('d', 'm', 'e', 0), // dme = Dugwor + TRUETYPE_TAG('d', 'm', 'g', 0), // dmg = Upper Kinabatangan + TRUETYPE_TAG('d', 'm', 'k', 0), // dmk = Domaaki + TRUETYPE_TAG('d', 'm', 'l', 0), // dml = Dameli + TRUETYPE_TAG('d', 'm', 'm', 0), // dmm = Dama + TRUETYPE_TAG('d', 'm', 'n', 0), // dmn = Mande languages + TRUETYPE_TAG('d', 'm', 'o', 0), // dmo = Kemezung + TRUETYPE_TAG('d', 'm', 'r', 0), // dmr = East Damar + TRUETYPE_TAG('d', 'm', 's', 0), // dms = Dampelas + TRUETYPE_TAG('d', 'm', 'u', 0), // dmu = Dubu + TRUETYPE_TAG('d', 'm', 'v', 0), // dmv = Dumpas + TRUETYPE_TAG('d', 'm', 'x', 0), // dmx = Dema + TRUETYPE_TAG('d', 'm', 'y', 0), // dmy = Demta + TRUETYPE_TAG('d', 'n', 'a', 0), // dna = Upper Grand Valley Dani + TRUETYPE_TAG('d', 'n', 'd', 0), // dnd = Daonda + TRUETYPE_TAG('d', 'n', 'e', 0), // dne = Ndendeule + TRUETYPE_TAG('d', 'n', 'g', 0), // dng = Dungan + TRUETYPE_TAG('d', 'n', 'i', 0), // dni = Lower Grand Valley Dani + TRUETYPE_TAG('d', 'n', 'k', 0), // dnk = Dengka + TRUETYPE_TAG('d', 'n', 'n', 0), // dnn = Dzùùngoo + TRUETYPE_TAG('d', 'n', 'r', 0), // dnr = Danaru + TRUETYPE_TAG('d', 'n', 't', 0), // dnt = Mid Grand Valley Dani + TRUETYPE_TAG('d', 'n', 'u', 0), // dnu = Danau + TRUETYPE_TAG('d', 'n', 'w', 0), // dnw = Western Dani + TRUETYPE_TAG('d', 'n', 'y', 0), // dny = Dení + TRUETYPE_TAG('d', 'o', 'a', 0), // doa = Dom + TRUETYPE_TAG('d', 'o', 'b', 0), // dob = Dobu + TRUETYPE_TAG('d', 'o', 'c', 0), // doc = Northern Dong + TRUETYPE_TAG('d', 'o', 'e', 0), // doe = Doe + TRUETYPE_TAG('d', 'o', 'f', 0), // dof = Domu + TRUETYPE_TAG('d', 'o', 'h', 0), // doh = Dong + TRUETYPE_TAG('d', 'o', 'i', 0), // doi = Dogri (macrolanguage) + TRUETYPE_TAG('d', 'o', 'k', 0), // dok = Dondo + TRUETYPE_TAG('d', 'o', 'l', 0), // dol = Doso + TRUETYPE_TAG('d', 'o', 'n', 0), // don = Toura (Papua New Guinea) + TRUETYPE_TAG('d', 'o', 'o', 0), // doo = Dongo + TRUETYPE_TAG('d', 'o', 'p', 0), // dop = Lukpa + TRUETYPE_TAG('d', 'o', 'q', 0), // doq = Dominican Sign Language + TRUETYPE_TAG('d', 'o', 'r', 0), // dor = Dori'o + TRUETYPE_TAG('d', 'o', 's', 0), // dos = Dogosé + TRUETYPE_TAG('d', 'o', 't', 0), // dot = Dass + TRUETYPE_TAG('d', 'o', 'v', 0), // dov = Dombe + TRUETYPE_TAG('d', 'o', 'w', 0), // dow = Doyayo + TRUETYPE_TAG('d', 'o', 'x', 0), // dox = Bussa + TRUETYPE_TAG('d', 'o', 'y', 0), // doy = Dompo + TRUETYPE_TAG('d', 'o', 'z', 0), // doz = Dorze + TRUETYPE_TAG('d', 'p', 'p', 0), // dpp = Papar + TRUETYPE_TAG('d', 'r', 'a', 0), // dra = Dravidian languages + TRUETYPE_TAG('d', 'r', 'b', 0), // drb = Dair + TRUETYPE_TAG('d', 'r', 'c', 0), // drc = Minderico + TRUETYPE_TAG('d', 'r', 'd', 0), // drd = Darmiya + TRUETYPE_TAG('d', 'r', 'e', 0), // dre = Dolpo + TRUETYPE_TAG('d', 'r', 'g', 0), // drg = Rungus + TRUETYPE_TAG('d', 'r', 'h', 0), // drh = Darkhat + TRUETYPE_TAG('d', 'r', 'i', 0), // dri = C'lela + TRUETYPE_TAG('d', 'r', 'l', 0), // drl = Darling + TRUETYPE_TAG('d', 'r', 'n', 0), // drn = West Damar + TRUETYPE_TAG('d', 'r', 'o', 0), // dro = Daro-Matu Melanau + TRUETYPE_TAG('d', 'r', 'q', 0), // drq = Dura + TRUETYPE_TAG('d', 'r', 'r', 0), // drr = Dororo + TRUETYPE_TAG('d', 'r', 's', 0), // drs = Gedeo + TRUETYPE_TAG('d', 'r', 't', 0), // drt = Drents + TRUETYPE_TAG('d', 'r', 'u', 0), // dru = Rukai + TRUETYPE_TAG('d', 'r', 'w', 0), // drw = Darwazi + TRUETYPE_TAG('d', 'r', 'y', 0), // dry = Darai + TRUETYPE_TAG('d', 's', 'b', 0), // dsb = Lower Sorbian + TRUETYPE_TAG('d', 's', 'e', 0), // dse = Dutch Sign Language + TRUETYPE_TAG('d', 's', 'h', 0), // dsh = Daasanach + TRUETYPE_TAG('d', 's', 'i', 0), // dsi = Disa + TRUETYPE_TAG('d', 's', 'l', 0), // dsl = Danish Sign Language + TRUETYPE_TAG('d', 's', 'n', 0), // dsn = Dusner + TRUETYPE_TAG('d', 's', 'o', 0), // dso = Desiya + TRUETYPE_TAG('d', 's', 'q', 0), // dsq = Tadaksahak + TRUETYPE_TAG('d', 't', 'a', 0), // dta = Daur + TRUETYPE_TAG('d', 't', 'b', 0), // dtb = Labuk-Kinabatangan Kadazan + TRUETYPE_TAG('d', 't', 'd', 0), // dtd = Ditidaht + TRUETYPE_TAG('d', 't', 'i', 0), // dti = Ana Tinga Dogon + TRUETYPE_TAG('d', 't', 'k', 0), // dtk = Tene Kan Dogon + TRUETYPE_TAG('d', 't', 'm', 0), // dtm = Tomo Kan Dogon + TRUETYPE_TAG('d', 't', 'p', 0), // dtp = Central Dusun + TRUETYPE_TAG('d', 't', 'r', 0), // dtr = Lotud + TRUETYPE_TAG('d', 't', 's', 0), // dts = Toro So Dogon + TRUETYPE_TAG('d', 't', 't', 0), // dtt = Toro Tegu Dogon + TRUETYPE_TAG('d', 't', 'u', 0), // dtu = Tebul Ure Dogon + TRUETYPE_TAG('d', 'u', 'a', 0), // dua = Duala + TRUETYPE_TAG('d', 'u', 'b', 0), // dub = Dubli + TRUETYPE_TAG('d', 'u', 'c', 0), // duc = Duna + TRUETYPE_TAG('d', 'u', 'd', 0), // dud = Hun-Saare + TRUETYPE_TAG('d', 'u', 'e', 0), // due = Umiray Dumaget Agta + TRUETYPE_TAG('d', 'u', 'f', 0), // duf = Dumbea + TRUETYPE_TAG('d', 'u', 'g', 0), // dug = Duruma + TRUETYPE_TAG('d', 'u', 'h', 0), // duh = Dungra Bhil + TRUETYPE_TAG('d', 'u', 'i', 0), // dui = Dumun + TRUETYPE_TAG('d', 'u', 'j', 0), // duj = Dhuwal + TRUETYPE_TAG('d', 'u', 'k', 0), // duk = Uyajitaya + TRUETYPE_TAG('d', 'u', 'l', 0), // dul = Alabat Island Agta + TRUETYPE_TAG('d', 'u', 'm', 0), // dum = Middle Dutch (ca. 1050-1350) + TRUETYPE_TAG('d', 'u', 'n', 0), // dun = Dusun Deyah + TRUETYPE_TAG('d', 'u', 'o', 0), // duo = Dupaninan Agta + TRUETYPE_TAG('d', 'u', 'p', 0), // dup = Duano + TRUETYPE_TAG('d', 'u', 'q', 0), // duq = Dusun Malang + TRUETYPE_TAG('d', 'u', 'r', 0), // dur = Dii + TRUETYPE_TAG('d', 'u', 's', 0), // dus = Dumi + TRUETYPE_TAG('d', 'u', 'u', 0), // duu = Drung + TRUETYPE_TAG('d', 'u', 'v', 0), // duv = Duvle + TRUETYPE_TAG('d', 'u', 'w', 0), // duw = Dusun Witu + TRUETYPE_TAG('d', 'u', 'x', 0), // dux = Duungooma + TRUETYPE_TAG('d', 'u', 'y', 0), // duy = Dicamay Agta + TRUETYPE_TAG('d', 'u', 'z', 0), // duz = Duli + TRUETYPE_TAG('d', 'v', 'a', 0), // dva = Duau + TRUETYPE_TAG('d', 'w', 'a', 0), // dwa = Diri + TRUETYPE_TAG('d', 'w', 'l', 0), // dwl = Walo Kumbe Dogon + TRUETYPE_TAG('d', 'w', 'r', 0), // dwr = Dawro + TRUETYPE_TAG('d', 'w', 's', 0), // dws = Dutton World Speedwords + TRUETYPE_TAG('d', 'w', 'w', 0), // dww = Dawawa + TRUETYPE_TAG('d', 'y', 'a', 0), // dya = Dyan + TRUETYPE_TAG('d', 'y', 'b', 0), // dyb = Dyaberdyaber + TRUETYPE_TAG('d', 'y', 'd', 0), // dyd = Dyugun + TRUETYPE_TAG('d', 'y', 'g', 0), // dyg = Villa Viciosa Agta + TRUETYPE_TAG('d', 'y', 'i', 0), // dyi = Djimini Senoufo + TRUETYPE_TAG('d', 'y', 'm', 0), // dym = Yanda Dom Dogon + TRUETYPE_TAG('d', 'y', 'n', 0), // dyn = Dyangadi + TRUETYPE_TAG('d', 'y', 'o', 0), // dyo = Jola-Fonyi + TRUETYPE_TAG('d', 'y', 'u', 0), // dyu = Dyula + TRUETYPE_TAG('d', 'y', 'y', 0), // dyy = Dyaabugay + TRUETYPE_TAG('d', 'z', 'a', 0), // dza = Tunzu + TRUETYPE_TAG('d', 'z', 'd', 0), // dzd = Daza + TRUETYPE_TAG('d', 'z', 'g', 0), // dzg = Dazaga + TRUETYPE_TAG('d', 'z', 'l', 0), // dzl = Dzalakha + TRUETYPE_TAG('d', 'z', 'n', 0), // dzn = Dzando + TRUETYPE_TAG('e', 'b', 'g', 0), // ebg = Ebughu + TRUETYPE_TAG('e', 'b', 'k', 0), // ebk = Eastern Bontok + TRUETYPE_TAG('e', 'b', 'o', 0), // ebo = Teke-Ebo + TRUETYPE_TAG('e', 'b', 'r', 0), // ebr = Ebrié + TRUETYPE_TAG('e', 'b', 'u', 0), // ebu = Embu + TRUETYPE_TAG('e', 'c', 'r', 0), // ecr = Eteocretan + TRUETYPE_TAG('e', 'c', 's', 0), // ecs = Ecuadorian Sign Language + TRUETYPE_TAG('e', 'c', 'y', 0), // ecy = Eteocypriot + TRUETYPE_TAG('e', 'e', 'e', 0), // eee = E + TRUETYPE_TAG('e', 'f', 'a', 0), // efa = Efai + TRUETYPE_TAG('e', 'f', 'e', 0), // efe = Efe + TRUETYPE_TAG('e', 'f', 'i', 0), // efi = Efik + TRUETYPE_TAG('e', 'g', 'a', 0), // ega = Ega + TRUETYPE_TAG('e', 'g', 'l', 0), // egl = Emilian + TRUETYPE_TAG('e', 'g', 'o', 0), // ego = Eggon + TRUETYPE_TAG('e', 'g', 'x', 0), // egx = Egyptian languages + TRUETYPE_TAG('e', 'g', 'y', 0), // egy = Egyptian (Ancient) + TRUETYPE_TAG('e', 'h', 'u', 0), // ehu = Ehueun + TRUETYPE_TAG('e', 'i', 'p', 0), // eip = Eipomek + TRUETYPE_TAG('e', 'i', 't', 0), // eit = Eitiep + TRUETYPE_TAG('e', 'i', 'v', 0), // eiv = Askopan + TRUETYPE_TAG('e', 'j', 'a', 0), // eja = Ejamat + TRUETYPE_TAG('e', 'k', 'a', 0), // eka = Ekajuk + TRUETYPE_TAG('e', 'k', 'e', 0), // eke = Ekit + TRUETYPE_TAG('e', 'k', 'g', 0), // ekg = Ekari + TRUETYPE_TAG('e', 'k', 'i', 0), // eki = Eki + TRUETYPE_TAG('e', 'k', 'k', 0), // ekk = Standard Estonian + TRUETYPE_TAG('e', 'k', 'l', 0), // ekl = Kol + TRUETYPE_TAG('e', 'k', 'm', 0), // ekm = Elip + TRUETYPE_TAG('e', 'k', 'o', 0), // eko = Koti + TRUETYPE_TAG('e', 'k', 'p', 0), // ekp = Ekpeye + TRUETYPE_TAG('e', 'k', 'r', 0), // ekr = Yace + TRUETYPE_TAG('e', 'k', 'y', 0), // eky = Eastern Kayah + TRUETYPE_TAG('e', 'l', 'e', 0), // ele = Elepi + TRUETYPE_TAG('e', 'l', 'h', 0), // elh = El Hugeirat + TRUETYPE_TAG('e', 'l', 'i', 0), // eli = Nding + TRUETYPE_TAG('e', 'l', 'k', 0), // elk = Elkei + TRUETYPE_TAG('e', 'l', 'm', 0), // elm = Eleme + TRUETYPE_TAG('e', 'l', 'o', 0), // elo = El Molo + TRUETYPE_TAG('e', 'l', 'p', 0), // elp = Elpaputih + TRUETYPE_TAG('e', 'l', 'u', 0), // elu = Elu + TRUETYPE_TAG('e', 'l', 'x', 0), // elx = Elamite + TRUETYPE_TAG('e', 'm', 'a', 0), // ema = Emai-Iuleha-Ora + TRUETYPE_TAG('e', 'm', 'b', 0), // emb = Embaloh + TRUETYPE_TAG('e', 'm', 'e', 0), // eme = Emerillon + TRUETYPE_TAG('e', 'm', 'g', 0), // emg = Eastern Meohang + TRUETYPE_TAG('e', 'm', 'i', 0), // emi = Mussau-Emira + TRUETYPE_TAG('e', 'm', 'k', 0), // emk = Eastern Maninkakan + TRUETYPE_TAG('e', 'm', 'm', 0), // emm = Mamulique + TRUETYPE_TAG('e', 'm', 'n', 0), // emn = Eman + TRUETYPE_TAG('e', 'm', 'o', 0), // emo = Emok + TRUETYPE_TAG('e', 'm', 'p', 0), // emp = Northern Emberá + TRUETYPE_TAG('e', 'm', 's', 0), // ems = Pacific Gulf Yupik + TRUETYPE_TAG('e', 'm', 'u', 0), // emu = Eastern Muria + TRUETYPE_TAG('e', 'm', 'w', 0), // emw = Emplawas + TRUETYPE_TAG('e', 'm', 'x', 0), // emx = Erromintxela + TRUETYPE_TAG('e', 'm', 'y', 0), // emy = Epigraphic Mayan + TRUETYPE_TAG('e', 'n', 'a', 0), // ena = Apali + TRUETYPE_TAG('e', 'n', 'b', 0), // enb = Markweeta + TRUETYPE_TAG('e', 'n', 'c', 0), // enc = En + TRUETYPE_TAG('e', 'n', 'd', 0), // end = Ende + TRUETYPE_TAG('e', 'n', 'f', 0), // enf = Forest Enets + TRUETYPE_TAG('e', 'n', 'h', 0), // enh = Tundra Enets + TRUETYPE_TAG('e', 'n', 'm', 0), // enm = Middle English (1100-1500) + TRUETYPE_TAG('e', 'n', 'n', 0), // enn = Engenni + TRUETYPE_TAG('e', 'n', 'o', 0), // eno = Enggano + TRUETYPE_TAG('e', 'n', 'q', 0), // enq = Enga + TRUETYPE_TAG('e', 'n', 'r', 0), // enr = Emumu + TRUETYPE_TAG('e', 'n', 'u', 0), // enu = Enu + TRUETYPE_TAG('e', 'n', 'v', 0), // env = Enwan (Edu State) + TRUETYPE_TAG('e', 'n', 'w', 0), // enw = Enwan (Akwa Ibom State) + TRUETYPE_TAG('e', 'o', 't', 0), // eot = Beti (Côte d'Ivoire) + TRUETYPE_TAG('e', 'p', 'i', 0), // epi = Epie + TRUETYPE_TAG('e', 'r', 'a', 0), // era = Eravallan + TRUETYPE_TAG('e', 'r', 'g', 0), // erg = Sie + TRUETYPE_TAG('e', 'r', 'h', 0), // erh = Eruwa + TRUETYPE_TAG('e', 'r', 'i', 0), // eri = Ogea + TRUETYPE_TAG('e', 'r', 'k', 0), // erk = South Efate + TRUETYPE_TAG('e', 'r', 'o', 0), // ero = Horpa + TRUETYPE_TAG('e', 'r', 'r', 0), // err = Erre + TRUETYPE_TAG('e', 'r', 's', 0), // ers = Ersu + TRUETYPE_TAG('e', 'r', 't', 0), // ert = Eritai + TRUETYPE_TAG('e', 'r', 'w', 0), // erw = Erokwanas + TRUETYPE_TAG('e', 's', 'e', 0), // ese = Ese Ejja + TRUETYPE_TAG('e', 's', 'h', 0), // esh = Eshtehardi + TRUETYPE_TAG('e', 's', 'i', 0), // esi = North Alaskan Inupiatun + TRUETYPE_TAG('e', 's', 'k', 0), // esk = Northwest Alaska Inupiatun + TRUETYPE_TAG('e', 's', 'l', 0), // esl = Egypt Sign Language + TRUETYPE_TAG('e', 's', 'm', 0), // esm = Esuma + TRUETYPE_TAG('e', 's', 'n', 0), // esn = Salvadoran Sign Language + TRUETYPE_TAG('e', 's', 'o', 0), // eso = Estonian Sign Language + TRUETYPE_TAG('e', 's', 'q', 0), // esq = Esselen + TRUETYPE_TAG('e', 's', 's', 0), // ess = Central Siberian Yupik + TRUETYPE_TAG('e', 's', 'u', 0), // esu = Central Yupik + TRUETYPE_TAG('e', 's', 'x', 0), // esx = Eskimo-Aleut languages + TRUETYPE_TAG('e', 't', 'b', 0), // etb = Etebi + TRUETYPE_TAG('e', 't', 'c', 0), // etc = Etchemin + TRUETYPE_TAG('e', 't', 'h', 0), // eth = Ethiopian Sign Language + TRUETYPE_TAG('e', 't', 'n', 0), // etn = Eton (Vanuatu) + TRUETYPE_TAG('e', 't', 'o', 0), // eto = Eton (Cameroon) + TRUETYPE_TAG('e', 't', 'r', 0), // etr = Edolo + TRUETYPE_TAG('e', 't', 's', 0), // ets = Yekhee + TRUETYPE_TAG('e', 't', 't', 0), // ett = Etruscan + TRUETYPE_TAG('e', 't', 'u', 0), // etu = Ejagham + TRUETYPE_TAG('e', 't', 'x', 0), // etx = Eten + TRUETYPE_TAG('e', 't', 'z', 0), // etz = Semimi + TRUETYPE_TAG('e', 'u', 'q', 0), // euq = Basque (family) + TRUETYPE_TAG('e', 'v', 'e', 0), // eve = Even + TRUETYPE_TAG('e', 'v', 'h', 0), // evh = Uvbie + TRUETYPE_TAG('e', 'v', 'n', 0), // evn = Evenki + TRUETYPE_TAG('e', 'w', 'o', 0), // ewo = Ewondo + TRUETYPE_TAG('e', 'x', 't', 0), // ext = Extremaduran + TRUETYPE_TAG('e', 'y', 'a', 0), // eya = Eyak + TRUETYPE_TAG('e', 'y', 'o', 0), // eyo = Keiyo + TRUETYPE_TAG('e', 'z', 'e', 0), // eze = Uzekwe + TRUETYPE_TAG('f', 'a', 'a', 0), // faa = Fasu + TRUETYPE_TAG('f', 'a', 'b', 0), // fab = Fa D'ambu + TRUETYPE_TAG('f', 'a', 'd', 0), // fad = Wagi + TRUETYPE_TAG('f', 'a', 'f', 0), // faf = Fagani + TRUETYPE_TAG('f', 'a', 'g', 0), // fag = Finongan + TRUETYPE_TAG('f', 'a', 'h', 0), // fah = Baissa Fali + TRUETYPE_TAG('f', 'a', 'i', 0), // fai = Faiwol + TRUETYPE_TAG('f', 'a', 'j', 0), // faj = Faita + TRUETYPE_TAG('f', 'a', 'k', 0), // fak = Fang (Cameroon) + TRUETYPE_TAG('f', 'a', 'l', 0), // fal = South Fali + TRUETYPE_TAG('f', 'a', 'm', 0), // fam = Fam + TRUETYPE_TAG('f', 'a', 'n', 0), // fan = Fang (Equatorial Guinea) + TRUETYPE_TAG('f', 'a', 'p', 0), // fap = Palor + TRUETYPE_TAG('f', 'a', 'r', 0), // far = Fataleka + TRUETYPE_TAG('f', 'a', 't', 0), // fat = Fanti + TRUETYPE_TAG('f', 'a', 'u', 0), // fau = Fayu + TRUETYPE_TAG('f', 'a', 'x', 0), // fax = Fala + TRUETYPE_TAG('f', 'a', 'y', 0), // fay = Southwestern Fars + TRUETYPE_TAG('f', 'a', 'z', 0), // faz = Northwestern Fars + TRUETYPE_TAG('f', 'b', 'l', 0), // fbl = West Albay Bikol + TRUETYPE_TAG('f', 'c', 's', 0), // fcs = Quebec Sign Language + TRUETYPE_TAG('f', 'e', 'r', 0), // fer = Feroge + TRUETYPE_TAG('f', 'f', 'i', 0), // ffi = Foia Foia + TRUETYPE_TAG('f', 'f', 'm', 0), // ffm = Maasina Fulfulde + TRUETYPE_TAG('f', 'g', 'r', 0), // fgr = Fongoro + TRUETYPE_TAG('f', 'i', 'a', 0), // fia = Nobiin + TRUETYPE_TAG('f', 'i', 'e', 0), // fie = Fyer + TRUETYPE_TAG('f', 'i', 'l', 0), // fil = Filipino + TRUETYPE_TAG('f', 'i', 'p', 0), // fip = Fipa + TRUETYPE_TAG('f', 'i', 'r', 0), // fir = Firan + TRUETYPE_TAG('f', 'i', 't', 0), // fit = Tornedalen Finnish + TRUETYPE_TAG('f', 'i', 'u', 0), // fiu = Finno-Ugrian languages + TRUETYPE_TAG('f', 'i', 'w', 0), // fiw = Fiwaga + TRUETYPE_TAG('f', 'k', 'v', 0), // fkv = Kven Finnish + TRUETYPE_TAG('f', 'l', 'a', 0), // fla = Kalispel-Pend d'Oreille + TRUETYPE_TAG('f', 'l', 'h', 0), // flh = Foau + TRUETYPE_TAG('f', 'l', 'i', 0), // fli = Fali + TRUETYPE_TAG('f', 'l', 'l', 0), // fll = North Fali + TRUETYPE_TAG('f', 'l', 'n', 0), // fln = Flinders Island + TRUETYPE_TAG('f', 'l', 'r', 0), // flr = Fuliiru + TRUETYPE_TAG('f', 'l', 'y', 0), // fly = Tsotsitaal + TRUETYPE_TAG('f', 'm', 'p', 0), // fmp = Fe'fe' + TRUETYPE_TAG('f', 'm', 'u', 0), // fmu = Far Western Muria + TRUETYPE_TAG('f', 'n', 'g', 0), // fng = Fanagalo + TRUETYPE_TAG('f', 'n', 'i', 0), // fni = Fania + TRUETYPE_TAG('f', 'o', 'd', 0), // fod = Foodo + TRUETYPE_TAG('f', 'o', 'i', 0), // foi = Foi + TRUETYPE_TAG('f', 'o', 'm', 0), // fom = Foma + TRUETYPE_TAG('f', 'o', 'n', 0), // fon = Fon + TRUETYPE_TAG('f', 'o', 'r', 0), // for = Fore + TRUETYPE_TAG('f', 'o', 's', 0), // fos = Siraya + TRUETYPE_TAG('f', 'o', 'x', 0), // fox = Formosan languages + TRUETYPE_TAG('f', 'p', 'e', 0), // fpe = Fernando Po Creole English + TRUETYPE_TAG('f', 'q', 's', 0), // fqs = Fas + TRUETYPE_TAG('f', 'r', 'c', 0), // frc = Cajun French + TRUETYPE_TAG('f', 'r', 'd', 0), // frd = Fordata + TRUETYPE_TAG('f', 'r', 'k', 0), // frk = Frankish + TRUETYPE_TAG('f', 'r', 'm', 0), // frm = Middle French (ca. 1400-1600) + TRUETYPE_TAG('f', 'r', 'o', 0), // fro = Old French (842-ca. 1400) + TRUETYPE_TAG('f', 'r', 'p', 0), // frp = Arpitan + TRUETYPE_TAG('f', 'r', 'q', 0), // frq = Forak + TRUETYPE_TAG('f', 'r', 'r', 0), // frr = Northern Frisian + TRUETYPE_TAG('f', 'r', 's', 0), // frs = Eastern Frisian + TRUETYPE_TAG('f', 'r', 't', 0), // frt = Fortsenal + TRUETYPE_TAG('f', 's', 'e', 0), // fse = Finnish Sign Language + TRUETYPE_TAG('f', 's', 'l', 0), // fsl = French Sign Language + TRUETYPE_TAG('f', 's', 's', 0), // fss = Finland-Swedish Sign Language + TRUETYPE_TAG('f', 'u', 'b', 0), // fub = Adamawa Fulfulde + TRUETYPE_TAG('f', 'u', 'c', 0), // fuc = Pulaar + TRUETYPE_TAG('f', 'u', 'd', 0), // fud = East Futuna + TRUETYPE_TAG('f', 'u', 'e', 0), // fue = Borgu Fulfulde + TRUETYPE_TAG('f', 'u', 'f', 0), // fuf = Pular + TRUETYPE_TAG('f', 'u', 'h', 0), // fuh = Western Niger Fulfulde + TRUETYPE_TAG('f', 'u', 'i', 0), // fui = Bagirmi Fulfulde + TRUETYPE_TAG('f', 'u', 'j', 0), // fuj = Ko + TRUETYPE_TAG('f', 'u', 'm', 0), // fum = Fum + TRUETYPE_TAG('f', 'u', 'n', 0), // fun = Fulniô + TRUETYPE_TAG('f', 'u', 'q', 0), // fuq = Central-Eastern Niger Fulfulde + TRUETYPE_TAG('f', 'u', 'r', 0), // fur = Friulian + TRUETYPE_TAG('f', 'u', 't', 0), // fut = Futuna-Aniwa + TRUETYPE_TAG('f', 'u', 'u', 0), // fuu = Furu + TRUETYPE_TAG('f', 'u', 'v', 0), // fuv = Nigerian Fulfulde + TRUETYPE_TAG('f', 'u', 'y', 0), // fuy = Fuyug + TRUETYPE_TAG('f', 'v', 'r', 0), // fvr = Fur + TRUETYPE_TAG('f', 'w', 'a', 0), // fwa = Fwâi + TRUETYPE_TAG('f', 'w', 'e', 0), // fwe = Fwe + TRUETYPE_TAG('g', 'a', 'a', 0), // gaa = Ga + TRUETYPE_TAG('g', 'a', 'b', 0), // gab = Gabri + TRUETYPE_TAG('g', 'a', 'c', 0), // gac = Mixed Great Andamanese + TRUETYPE_TAG('g', 'a', 'd', 0), // gad = Gaddang + TRUETYPE_TAG('g', 'a', 'e', 0), // gae = Guarequena + TRUETYPE_TAG('g', 'a', 'f', 0), // gaf = Gende + TRUETYPE_TAG('g', 'a', 'g', 0), // gag = Gagauz + TRUETYPE_TAG('g', 'a', 'h', 0), // gah = Alekano + TRUETYPE_TAG('g', 'a', 'i', 0), // gai = Borei + TRUETYPE_TAG('g', 'a', 'j', 0), // gaj = Gadsup + TRUETYPE_TAG('g', 'a', 'k', 0), // gak = Gamkonora + TRUETYPE_TAG('g', 'a', 'l', 0), // gal = Galoli + TRUETYPE_TAG('g', 'a', 'm', 0), // gam = Kandawo + TRUETYPE_TAG('g', 'a', 'n', 0), // gan = Gan Chinese + TRUETYPE_TAG('g', 'a', 'o', 0), // gao = Gants + TRUETYPE_TAG('g', 'a', 'p', 0), // gap = Gal + TRUETYPE_TAG('g', 'a', 'q', 0), // gaq = Gata' + TRUETYPE_TAG('g', 'a', 'r', 0), // gar = Galeya + TRUETYPE_TAG('g', 'a', 's', 0), // gas = Adiwasi Garasia + TRUETYPE_TAG('g', 'a', 't', 0), // gat = Kenati + TRUETYPE_TAG('g', 'a', 'u', 0), // gau = Mudhili Gadaba + TRUETYPE_TAG('g', 'a', 'v', 0), // gav = Gabutamon + TRUETYPE_TAG('g', 'a', 'w', 0), // gaw = Nobonob + TRUETYPE_TAG('g', 'a', 'x', 0), // gax = Borana-Arsi-Guji Oromo + TRUETYPE_TAG('g', 'a', 'y', 0), // gay = Gayo + TRUETYPE_TAG('g', 'a', 'z', 0), // gaz = West Central Oromo + TRUETYPE_TAG('g', 'b', 'a', 0), // gba = Gbaya (Central African Republic) + TRUETYPE_TAG('g', 'b', 'b', 0), // gbb = Kaytetye + TRUETYPE_TAG('g', 'b', 'c', 0), // gbc = Garawa + TRUETYPE_TAG('g', 'b', 'd', 0), // gbd = Karadjeri + TRUETYPE_TAG('g', 'b', 'e', 0), // gbe = Niksek + TRUETYPE_TAG('g', 'b', 'f', 0), // gbf = Gaikundi + TRUETYPE_TAG('g', 'b', 'g', 0), // gbg = Gbanziri + TRUETYPE_TAG('g', 'b', 'h', 0), // gbh = Defi Gbe + TRUETYPE_TAG('g', 'b', 'i', 0), // gbi = Galela + TRUETYPE_TAG('g', 'b', 'j', 0), // gbj = Bodo Gadaba + TRUETYPE_TAG('g', 'b', 'k', 0), // gbk = Gaddi + TRUETYPE_TAG('g', 'b', 'l', 0), // gbl = Gamit + TRUETYPE_TAG('g', 'b', 'm', 0), // gbm = Garhwali + TRUETYPE_TAG('g', 'b', 'n', 0), // gbn = Mo'da + TRUETYPE_TAG('g', 'b', 'o', 0), // gbo = Northern Grebo + TRUETYPE_TAG('g', 'b', 'p', 0), // gbp = Gbaya-Bossangoa + TRUETYPE_TAG('g', 'b', 'q', 0), // gbq = Gbaya-Bozoum + TRUETYPE_TAG('g', 'b', 'r', 0), // gbr = Gbagyi + TRUETYPE_TAG('g', 'b', 's', 0), // gbs = Gbesi Gbe + TRUETYPE_TAG('g', 'b', 'u', 0), // gbu = Gagadu + TRUETYPE_TAG('g', 'b', 'v', 0), // gbv = Gbanu + TRUETYPE_TAG('g', 'b', 'x', 0), // gbx = Eastern Xwla Gbe + TRUETYPE_TAG('g', 'b', 'y', 0), // gby = Gbari + TRUETYPE_TAG('g', 'b', 'z', 0), // gbz = Zoroastrian Dari + TRUETYPE_TAG('g', 'c', 'c', 0), // gcc = Mali + TRUETYPE_TAG('g', 'c', 'd', 0), // gcd = Ganggalida + TRUETYPE_TAG('g', 'c', 'e', 0), // gce = Galice + TRUETYPE_TAG('g', 'c', 'f', 0), // gcf = Guadeloupean Creole French + TRUETYPE_TAG('g', 'c', 'l', 0), // gcl = Grenadian Creole English + TRUETYPE_TAG('g', 'c', 'n', 0), // gcn = Gaina + TRUETYPE_TAG('g', 'c', 'r', 0), // gcr = Guianese Creole French + TRUETYPE_TAG('g', 'c', 't', 0), // gct = Colonia Tovar German + TRUETYPE_TAG('g', 'd', 'a', 0), // gda = Gade Lohar + TRUETYPE_TAG('g', 'd', 'b', 0), // gdb = Pottangi Ollar Gadaba + TRUETYPE_TAG('g', 'd', 'c', 0), // gdc = Gugu Badhun + TRUETYPE_TAG('g', 'd', 'd', 0), // gdd = Gedaged + TRUETYPE_TAG('g', 'd', 'e', 0), // gde = Gude + TRUETYPE_TAG('g', 'd', 'f', 0), // gdf = Guduf-Gava + TRUETYPE_TAG('g', 'd', 'g', 0), // gdg = Ga'dang + TRUETYPE_TAG('g', 'd', 'h', 0), // gdh = Gadjerawang + TRUETYPE_TAG('g', 'd', 'i', 0), // gdi = Gundi + TRUETYPE_TAG('g', 'd', 'j', 0), // gdj = Gurdjar + TRUETYPE_TAG('g', 'd', 'k', 0), // gdk = Gadang + TRUETYPE_TAG('g', 'd', 'l', 0), // gdl = Dirasha + TRUETYPE_TAG('g', 'd', 'm', 0), // gdm = Laal + TRUETYPE_TAG('g', 'd', 'n', 0), // gdn = Umanakaina + TRUETYPE_TAG('g', 'd', 'o', 0), // gdo = Ghodoberi + TRUETYPE_TAG('g', 'd', 'q', 0), // gdq = Mehri + TRUETYPE_TAG('g', 'd', 'r', 0), // gdr = Wipi + TRUETYPE_TAG('g', 'd', 'u', 0), // gdu = Gudu + TRUETYPE_TAG('g', 'd', 'x', 0), // gdx = Godwari + TRUETYPE_TAG('g', 'e', 'a', 0), // gea = Geruma + TRUETYPE_TAG('g', 'e', 'b', 0), // geb = Kire + TRUETYPE_TAG('g', 'e', 'c', 0), // gec = Gboloo Grebo + TRUETYPE_TAG('g', 'e', 'd', 0), // ged = Gade + TRUETYPE_TAG('g', 'e', 'g', 0), // geg = Gengle + TRUETYPE_TAG('g', 'e', 'h', 0), // geh = Hutterite German + TRUETYPE_TAG('g', 'e', 'i', 0), // gei = Gebe + TRUETYPE_TAG('g', 'e', 'j', 0), // gej = Gen + TRUETYPE_TAG('g', 'e', 'k', 0), // gek = Yiwom + TRUETYPE_TAG('g', 'e', 'l', 0), // gel = ut-Ma'in + TRUETYPE_TAG('g', 'e', 'm', 0), // gem = Germanic languages + TRUETYPE_TAG('g', 'e', 'q', 0), // geq = Geme + TRUETYPE_TAG('g', 'e', 's', 0), // ges = Geser-Gorom + TRUETYPE_TAG('g', 'e', 'w', 0), // gew = Gera + TRUETYPE_TAG('g', 'e', 'x', 0), // gex = Garre + TRUETYPE_TAG('g', 'e', 'y', 0), // gey = Enya + TRUETYPE_TAG('g', 'e', 'z', 0), // gez = Geez + TRUETYPE_TAG('g', 'f', 'k', 0), // gfk = Patpatar + TRUETYPE_TAG('g', 'f', 't', 0), // gft = Gafat + TRUETYPE_TAG('g', 'g', 'a', 0), // gga = Gao + TRUETYPE_TAG('g', 'g', 'b', 0), // ggb = Gbii + TRUETYPE_TAG('g', 'g', 'd', 0), // ggd = Gugadj + TRUETYPE_TAG('g', 'g', 'e', 0), // gge = Guragone + TRUETYPE_TAG('g', 'g', 'g', 0), // ggg = Gurgula + TRUETYPE_TAG('g', 'g', 'k', 0), // ggk = Kungarakany + TRUETYPE_TAG('g', 'g', 'l', 0), // ggl = Ganglau + TRUETYPE_TAG('g', 'g', 'n', 0), // ggn = Eastern Gurung + TRUETYPE_TAG('g', 'g', 'o', 0), // ggo = Southern Gondi + TRUETYPE_TAG('g', 'g', 'r', 0), // ggr = Aghu Tharnggalu + TRUETYPE_TAG('g', 'g', 't', 0), // ggt = Gitua + TRUETYPE_TAG('g', 'g', 'u', 0), // ggu = Gagu + TRUETYPE_TAG('g', 'g', 'w', 0), // ggw = Gogodala + TRUETYPE_TAG('g', 'h', 'a', 0), // gha = Ghadamès + TRUETYPE_TAG('g', 'h', 'c', 0), // ghc = Hiberno-Scottish Gaelic + TRUETYPE_TAG('g', 'h', 'e', 0), // ghe = Southern Ghale + TRUETYPE_TAG('g', 'h', 'h', 0), // ghh = Northern Ghale + TRUETYPE_TAG('g', 'h', 'k', 0), // ghk = Geko Karen + TRUETYPE_TAG('g', 'h', 'l', 0), // ghl = Ghulfan + TRUETYPE_TAG('g', 'h', 'n', 0), // ghn = Ghanongga + TRUETYPE_TAG('g', 'h', 'o', 0), // gho = Ghomara + TRUETYPE_TAG('g', 'h', 'r', 0), // ghr = Ghera + TRUETYPE_TAG('g', 'h', 's', 0), // ghs = Guhu-Samane + TRUETYPE_TAG('g', 'h', 't', 0), // ght = Kutang Ghale + TRUETYPE_TAG('g', 'i', 'a', 0), // gia = Kitja + TRUETYPE_TAG('g', 'i', 'b', 0), // gib = Gibanawa + TRUETYPE_TAG('g', 'i', 'c', 0), // gic = Gail + TRUETYPE_TAG('g', 'i', 'd', 0), // gid = Gidar + TRUETYPE_TAG('g', 'i', 'g', 0), // gig = Goaria + TRUETYPE_TAG('g', 'i', 'l', 0), // gil = Gilbertese + TRUETYPE_TAG('g', 'i', 'm', 0), // gim = Gimi (Eastern Highlands) + TRUETYPE_TAG('g', 'i', 'n', 0), // gin = Hinukh + TRUETYPE_TAG('g', 'i', 'o', 0), // gio = Gelao + TRUETYPE_TAG('g', 'i', 'p', 0), // gip = Gimi (West New Britain) + TRUETYPE_TAG('g', 'i', 'q', 0), // giq = Green Gelao + TRUETYPE_TAG('g', 'i', 'r', 0), // gir = Red Gelao + TRUETYPE_TAG('g', 'i', 's', 0), // gis = North Giziga + TRUETYPE_TAG('g', 'i', 't', 0), // git = Gitxsan + TRUETYPE_TAG('g', 'i', 'w', 0), // giw = White Gelao + TRUETYPE_TAG('g', 'i', 'x', 0), // gix = Gilima + TRUETYPE_TAG('g', 'i', 'y', 0), // giy = Giyug + TRUETYPE_TAG('g', 'i', 'z', 0), // giz = South Giziga + TRUETYPE_TAG('g', 'j', 'i', 0), // gji = Geji + TRUETYPE_TAG('g', 'j', 'k', 0), // gjk = Kachi Koli + TRUETYPE_TAG('g', 'j', 'n', 0), // gjn = Gonja + TRUETYPE_TAG('g', 'j', 'u', 0), // gju = Gujari + TRUETYPE_TAG('g', 'k', 'a', 0), // gka = Guya + TRUETYPE_TAG('g', 'k', 'e', 0), // gke = Ndai + TRUETYPE_TAG('g', 'k', 'n', 0), // gkn = Gokana + TRUETYPE_TAG('g', 'k', 'p', 0), // gkp = Guinea Kpelle + TRUETYPE_TAG('g', 'l', 'c', 0), // glc = Bon Gula + TRUETYPE_TAG('g', 'l', 'd', 0), // gld = Nanai + TRUETYPE_TAG('g', 'l', 'h', 0), // glh = Northwest Pashayi + TRUETYPE_TAG('g', 'l', 'i', 0), // gli = Guliguli + TRUETYPE_TAG('g', 'l', 'j', 0), // glj = Gula Iro + TRUETYPE_TAG('g', 'l', 'k', 0), // glk = Gilaki + TRUETYPE_TAG('g', 'l', 'o', 0), // glo = Galambu + TRUETYPE_TAG('g', 'l', 'r', 0), // glr = Glaro-Twabo + TRUETYPE_TAG('g', 'l', 'u', 0), // glu = Gula (Chad) + TRUETYPE_TAG('g', 'l', 'w', 0), // glw = Glavda + TRUETYPE_TAG('g', 'l', 'y', 0), // gly = Gule + TRUETYPE_TAG('g', 'm', 'a', 0), // gma = Gambera + TRUETYPE_TAG('g', 'm', 'b', 0), // gmb = Gula'alaa + TRUETYPE_TAG('g', 'm', 'd', 0), // gmd = Mághdì + TRUETYPE_TAG('g', 'm', 'e', 0), // gme = East Germanic languages + TRUETYPE_TAG('g', 'm', 'h', 0), // gmh = Middle High German (ca. 1050-1500) + TRUETYPE_TAG('g', 'm', 'l', 0), // gml = Middle Low German + TRUETYPE_TAG('g', 'm', 'm', 0), // gmm = Gbaya-Mbodomo + TRUETYPE_TAG('g', 'm', 'n', 0), // gmn = Gimnime + TRUETYPE_TAG('g', 'm', 'q', 0), // gmq = North Germanic languages + TRUETYPE_TAG('g', 'm', 'u', 0), // gmu = Gumalu + TRUETYPE_TAG('g', 'm', 'v', 0), // gmv = Gamo + TRUETYPE_TAG('g', 'm', 'w', 0), // gmw = West Germanic languages + TRUETYPE_TAG('g', 'm', 'x', 0), // gmx = Magoma + TRUETYPE_TAG('g', 'm', 'y', 0), // gmy = Mycenaean Greek + TRUETYPE_TAG('g', 'n', 'a', 0), // gna = Kaansa + TRUETYPE_TAG('g', 'n', 'b', 0), // gnb = Gangte + TRUETYPE_TAG('g', 'n', 'c', 0), // gnc = Guanche + TRUETYPE_TAG('g', 'n', 'd', 0), // gnd = Zulgo-Gemzek + TRUETYPE_TAG('g', 'n', 'e', 0), // gne = Ganang + TRUETYPE_TAG('g', 'n', 'g', 0), // gng = Ngangam + TRUETYPE_TAG('g', 'n', 'h', 0), // gnh = Lere + TRUETYPE_TAG('g', 'n', 'i', 0), // gni = Gooniyandi + TRUETYPE_TAG('g', 'n', 'k', 0), // gnk = //Gana + TRUETYPE_TAG('g', 'n', 'l', 0), // gnl = Gangulu + TRUETYPE_TAG('g', 'n', 'm', 0), // gnm = Ginuman + TRUETYPE_TAG('g', 'n', 'n', 0), // gnn = Gumatj + TRUETYPE_TAG('g', 'n', 'o', 0), // gno = Northern Gondi + TRUETYPE_TAG('g', 'n', 'q', 0), // gnq = Gana + TRUETYPE_TAG('g', 'n', 'r', 0), // gnr = Gureng Gureng + TRUETYPE_TAG('g', 'n', 't', 0), // gnt = Guntai + TRUETYPE_TAG('g', 'n', 'u', 0), // gnu = Gnau + TRUETYPE_TAG('g', 'n', 'w', 0), // gnw = Western Bolivian Guaraní + TRUETYPE_TAG('g', 'n', 'z', 0), // gnz = Ganzi + TRUETYPE_TAG('g', 'o', 'a', 0), // goa = Guro + TRUETYPE_TAG('g', 'o', 'b', 0), // gob = Playero + TRUETYPE_TAG('g', 'o', 'c', 0), // goc = Gorakor + TRUETYPE_TAG('g', 'o', 'd', 0), // god = Godié + TRUETYPE_TAG('g', 'o', 'e', 0), // goe = Gongduk + TRUETYPE_TAG('g', 'o', 'f', 0), // gof = Gofa + TRUETYPE_TAG('g', 'o', 'g', 0), // gog = Gogo + TRUETYPE_TAG('g', 'o', 'h', 0), // goh = Old High German (ca. 750-1050) + TRUETYPE_TAG('g', 'o', 'i', 0), // goi = Gobasi + TRUETYPE_TAG('g', 'o', 'j', 0), // goj = Gowlan + TRUETYPE_TAG('g', 'o', 'k', 0), // gok = Gowli + TRUETYPE_TAG('g', 'o', 'l', 0), // gol = Gola + TRUETYPE_TAG('g', 'o', 'm', 0), // gom = Goan Konkani + TRUETYPE_TAG('g', 'o', 'n', 0), // gon = Gondi + TRUETYPE_TAG('g', 'o', 'o', 0), // goo = Gone Dau + TRUETYPE_TAG('g', 'o', 'p', 0), // gop = Yeretuar + TRUETYPE_TAG('g', 'o', 'q', 0), // goq = Gorap + TRUETYPE_TAG('g', 'o', 'r', 0), // gor = Gorontalo + TRUETYPE_TAG('g', 'o', 's', 0), // gos = Gronings + TRUETYPE_TAG('g', 'o', 't', 0), // got = Gothic + TRUETYPE_TAG('g', 'o', 'u', 0), // gou = Gavar + TRUETYPE_TAG('g', 'o', 'w', 0), // gow = Gorowa + TRUETYPE_TAG('g', 'o', 'x', 0), // gox = Gobu + TRUETYPE_TAG('g', 'o', 'y', 0), // goy = Goundo + TRUETYPE_TAG('g', 'o', 'z', 0), // goz = Gozarkhani + TRUETYPE_TAG('g', 'p', 'a', 0), // gpa = Gupa-Abawa + TRUETYPE_TAG('g', 'p', 'n', 0), // gpn = Taiap + TRUETYPE_TAG('g', 'q', 'a', 0), // gqa = Ga'anda + TRUETYPE_TAG('g', 'q', 'i', 0), // gqi = Guiqiong + TRUETYPE_TAG('g', 'q', 'n', 0), // gqn = Guana (Brazil) + TRUETYPE_TAG('g', 'q', 'r', 0), // gqr = Gor + TRUETYPE_TAG('g', 'r', 'a', 0), // gra = Rajput Garasia + TRUETYPE_TAG('g', 'r', 'b', 0), // grb = Grebo + TRUETYPE_TAG('g', 'r', 'c', 0), // grc = Ancient Greek (to 1453) + TRUETYPE_TAG('g', 'r', 'd', 0), // grd = Guruntum-Mbaaru + TRUETYPE_TAG('g', 'r', 'g', 0), // grg = Madi + TRUETYPE_TAG('g', 'r', 'h', 0), // grh = Gbiri-Niragu + TRUETYPE_TAG('g', 'r', 'i', 0), // gri = Ghari + TRUETYPE_TAG('g', 'r', 'j', 0), // grj = Southern Grebo + TRUETYPE_TAG('g', 'r', 'k', 0), // grk = Greek languages + TRUETYPE_TAG('g', 'r', 'm', 0), // grm = Kota Marudu Talantang + TRUETYPE_TAG('g', 'r', 'o', 0), // gro = Groma + TRUETYPE_TAG('g', 'r', 'q', 0), // grq = Gorovu + TRUETYPE_TAG('g', 'r', 'r', 0), // grr = Taznatit + TRUETYPE_TAG('g', 'r', 's', 0), // grs = Gresi + TRUETYPE_TAG('g', 'r', 't', 0), // grt = Garo + TRUETYPE_TAG('g', 'r', 'u', 0), // gru = Kistane + TRUETYPE_TAG('g', 'r', 'v', 0), // grv = Central Grebo + TRUETYPE_TAG('g', 'r', 'w', 0), // grw = Gweda + TRUETYPE_TAG('g', 'r', 'x', 0), // grx = Guriaso + TRUETYPE_TAG('g', 'r', 'y', 0), // gry = Barclayville Grebo + TRUETYPE_TAG('g', 'r', 'z', 0), // grz = Guramalum + TRUETYPE_TAG('g', 's', 'e', 0), // gse = Ghanaian Sign Language + TRUETYPE_TAG('g', 's', 'g', 0), // gsg = German Sign Language + TRUETYPE_TAG('g', 's', 'l', 0), // gsl = Gusilay + TRUETYPE_TAG('g', 's', 'm', 0), // gsm = Guatemalan Sign Language + TRUETYPE_TAG('g', 's', 'n', 0), // gsn = Gusan + TRUETYPE_TAG('g', 's', 'o', 0), // gso = Southwest Gbaya + TRUETYPE_TAG('g', 's', 'p', 0), // gsp = Wasembo + TRUETYPE_TAG('g', 's', 's', 0), // gss = Greek Sign Language + TRUETYPE_TAG('g', 's', 'w', 0), // gsw = Swiss German + TRUETYPE_TAG('g', 't', 'a', 0), // gta = Guató + TRUETYPE_TAG('g', 't', 'i', 0), // gti = Gbati-ri + TRUETYPE_TAG('g', 'u', 'a', 0), // gua = Shiki + TRUETYPE_TAG('g', 'u', 'b', 0), // gub = Guajajára + TRUETYPE_TAG('g', 'u', 'c', 0), // guc = Wayuu + TRUETYPE_TAG('g', 'u', 'd', 0), // gud = Yocoboué Dida + TRUETYPE_TAG('g', 'u', 'e', 0), // gue = Gurinji + TRUETYPE_TAG('g', 'u', 'f', 0), // guf = Gupapuyngu + TRUETYPE_TAG('g', 'u', 'g', 0), // gug = Paraguayan Guaraní + TRUETYPE_TAG('g', 'u', 'h', 0), // guh = Guahibo + TRUETYPE_TAG('g', 'u', 'i', 0), // gui = Eastern Bolivian Guaraní + TRUETYPE_TAG('g', 'u', 'k', 0), // guk = Gumuz + TRUETYPE_TAG('g', 'u', 'l', 0), // gul = Sea Island Creole English + TRUETYPE_TAG('g', 'u', 'm', 0), // gum = Guambiano + TRUETYPE_TAG('g', 'u', 'n', 0), // gun = Mbyá Guaraní + TRUETYPE_TAG('g', 'u', 'o', 0), // guo = Guayabero + TRUETYPE_TAG('g', 'u', 'p', 0), // gup = Gunwinggu + TRUETYPE_TAG('g', 'u', 'q', 0), // guq = Aché + TRUETYPE_TAG('g', 'u', 'r', 0), // gur = Farefare + TRUETYPE_TAG('g', 'u', 's', 0), // gus = Guinean Sign Language + TRUETYPE_TAG('g', 'u', 't', 0), // gut = Maléku Jaíka + TRUETYPE_TAG('g', 'u', 'u', 0), // guu = Yanomamö + TRUETYPE_TAG('g', 'u', 'v', 0), // guv = Gey + TRUETYPE_TAG('g', 'u', 'w', 0), // guw = Gun + TRUETYPE_TAG('g', 'u', 'x', 0), // gux = Gourmanchéma + TRUETYPE_TAG('g', 'u', 'z', 0), // guz = Gusii + TRUETYPE_TAG('g', 'v', 'a', 0), // gva = Guana (Paraguay) + TRUETYPE_TAG('g', 'v', 'c', 0), // gvc = Guanano + TRUETYPE_TAG('g', 'v', 'e', 0), // gve = Duwet + TRUETYPE_TAG('g', 'v', 'f', 0), // gvf = Golin + TRUETYPE_TAG('g', 'v', 'j', 0), // gvj = Guajá + TRUETYPE_TAG('g', 'v', 'l', 0), // gvl = Gulay + TRUETYPE_TAG('g', 'v', 'm', 0), // gvm = Gurmana + TRUETYPE_TAG('g', 'v', 'n', 0), // gvn = Kuku-Yalanji + TRUETYPE_TAG('g', 'v', 'o', 0), // gvo = Gavião Do Jiparaná + TRUETYPE_TAG('g', 'v', 'p', 0), // gvp = Pará Gavião + TRUETYPE_TAG('g', 'v', 'r', 0), // gvr = Western Gurung + TRUETYPE_TAG('g', 'v', 's', 0), // gvs = Gumawana + TRUETYPE_TAG('g', 'v', 'y', 0), // gvy = Guyani + TRUETYPE_TAG('g', 'w', 'a', 0), // gwa = Mbato + TRUETYPE_TAG('g', 'w', 'b', 0), // gwb = Gwa + TRUETYPE_TAG('g', 'w', 'c', 0), // gwc = Kalami + TRUETYPE_TAG('g', 'w', 'd', 0), // gwd = Gawwada + TRUETYPE_TAG('g', 'w', 'e', 0), // gwe = Gweno + TRUETYPE_TAG('g', 'w', 'f', 0), // gwf = Gowro + TRUETYPE_TAG('g', 'w', 'g', 0), // gwg = Moo + TRUETYPE_TAG('g', 'w', 'i', 0), // gwi = Gwichʼin + TRUETYPE_TAG('g', 'w', 'j', 0), // gwj = /Gwi + TRUETYPE_TAG('g', 'w', 'n', 0), // gwn = Gwandara + TRUETYPE_TAG('g', 'w', 'r', 0), // gwr = Gwere + TRUETYPE_TAG('g', 'w', 't', 0), // gwt = Gawar-Bati + TRUETYPE_TAG('g', 'w', 'u', 0), // gwu = Guwamu + TRUETYPE_TAG('g', 'w', 'w', 0), // gww = Kwini + TRUETYPE_TAG('g', 'w', 'x', 0), // gwx = Gua + TRUETYPE_TAG('g', 'x', 'x', 0), // gxx = Wè Southern + TRUETYPE_TAG('g', 'y', 'a', 0), // gya = Northwest Gbaya + TRUETYPE_TAG('g', 'y', 'b', 0), // gyb = Garus + TRUETYPE_TAG('g', 'y', 'd', 0), // gyd = Kayardild + TRUETYPE_TAG('g', 'y', 'e', 0), // gye = Gyem + TRUETYPE_TAG('g', 'y', 'f', 0), // gyf = Gungabula + TRUETYPE_TAG('g', 'y', 'g', 0), // gyg = Gbayi + TRUETYPE_TAG('g', 'y', 'i', 0), // gyi = Gyele + TRUETYPE_TAG('g', 'y', 'l', 0), // gyl = Gayil + TRUETYPE_TAG('g', 'y', 'm', 0), // gym = Ngäbere + TRUETYPE_TAG('g', 'y', 'n', 0), // gyn = Guyanese Creole English + TRUETYPE_TAG('g', 'y', 'r', 0), // gyr = Guarayu + TRUETYPE_TAG('g', 'y', 'y', 0), // gyy = Gunya + TRUETYPE_TAG('g', 'z', 'a', 0), // gza = Ganza + TRUETYPE_TAG('g', 'z', 'i', 0), // gzi = Gazi + TRUETYPE_TAG('g', 'z', 'n', 0), // gzn = Gane + TRUETYPE_TAG('h', 'a', 'a', 0), // haa = Han + TRUETYPE_TAG('h', 'a', 'b', 0), // hab = Hanoi Sign Language + TRUETYPE_TAG('h', 'a', 'c', 0), // hac = Gurani + TRUETYPE_TAG('h', 'a', 'd', 0), // had = Hatam + TRUETYPE_TAG('h', 'a', 'e', 0), // hae = Eastern Oromo + TRUETYPE_TAG('h', 'a', 'f', 0), // haf = Haiphong Sign Language + TRUETYPE_TAG('h', 'a', 'g', 0), // hag = Hanga + TRUETYPE_TAG('h', 'a', 'h', 0), // hah = Hahon + TRUETYPE_TAG('h', 'a', 'i', 0), // hai = Haida + TRUETYPE_TAG('h', 'a', 'j', 0), // haj = Hajong + TRUETYPE_TAG('h', 'a', 'k', 0), // hak = Hakka Chinese + TRUETYPE_TAG('h', 'a', 'l', 0), // hal = Halang + TRUETYPE_TAG('h', 'a', 'm', 0), // ham = Hewa + TRUETYPE_TAG('h', 'a', 'n', 0), // han = Hangaza + TRUETYPE_TAG('h', 'a', 'o', 0), // hao = Hakö + TRUETYPE_TAG('h', 'a', 'p', 0), // hap = Hupla + TRUETYPE_TAG('h', 'a', 'q', 0), // haq = Ha + TRUETYPE_TAG('h', 'a', 'r', 0), // har = Harari + TRUETYPE_TAG('h', 'a', 's', 0), // has = Haisla + TRUETYPE_TAG('h', 'a', 'v', 0), // hav = Havu + TRUETYPE_TAG('h', 'a', 'w', 0), // haw = Hawaiian + TRUETYPE_TAG('h', 'a', 'x', 0), // hax = Southern Haida + TRUETYPE_TAG('h', 'a', 'y', 0), // hay = Haya + TRUETYPE_TAG('h', 'a', 'z', 0), // haz = Hazaragi + TRUETYPE_TAG('h', 'b', 'a', 0), // hba = Hamba + TRUETYPE_TAG('h', 'b', 'b', 0), // hbb = Huba + TRUETYPE_TAG('h', 'b', 'n', 0), // hbn = Heiban + TRUETYPE_TAG('h', 'b', 'o', 0), // hbo = Ancient Hebrew + TRUETYPE_TAG('h', 'b', 'u', 0), // hbu = Habu + TRUETYPE_TAG('h', 'c', 'a', 0), // hca = Andaman Creole Hindi + TRUETYPE_TAG('h', 'c', 'h', 0), // hch = Huichol + TRUETYPE_TAG('h', 'd', 'n', 0), // hdn = Northern Haida + TRUETYPE_TAG('h', 'd', 's', 0), // hds = Honduras Sign Language + TRUETYPE_TAG('h', 'd', 'y', 0), // hdy = Hadiyya + TRUETYPE_TAG('h', 'e', 'a', 0), // hea = Northern Qiandong Miao + TRUETYPE_TAG('h', 'e', 'd', 0), // hed = Herdé + TRUETYPE_TAG('h', 'e', 'g', 0), // heg = Helong + TRUETYPE_TAG('h', 'e', 'h', 0), // heh = Hehe + TRUETYPE_TAG('h', 'e', 'i', 0), // hei = Heiltsuk + TRUETYPE_TAG('h', 'e', 'm', 0), // hem = Hemba + TRUETYPE_TAG('h', 'g', 'm', 0), // hgm = Hai//om + TRUETYPE_TAG('h', 'g', 'w', 0), // hgw = Haigwai + TRUETYPE_TAG('h', 'h', 'i', 0), // hhi = Hoia Hoia + TRUETYPE_TAG('h', 'h', 'r', 0), // hhr = Kerak + TRUETYPE_TAG('h', 'h', 'y', 0), // hhy = Hoyahoya + TRUETYPE_TAG('h', 'i', 'a', 0), // hia = Lamang + TRUETYPE_TAG('h', 'i', 'b', 0), // hib = Hibito + TRUETYPE_TAG('h', 'i', 'd', 0), // hid = Hidatsa + TRUETYPE_TAG('h', 'i', 'f', 0), // hif = Fiji Hindi + TRUETYPE_TAG('h', 'i', 'g', 0), // hig = Kamwe + TRUETYPE_TAG('h', 'i', 'h', 0), // hih = Pamosu + TRUETYPE_TAG('h', 'i', 'i', 0), // hii = Hinduri + TRUETYPE_TAG('h', 'i', 'j', 0), // hij = Hijuk + TRUETYPE_TAG('h', 'i', 'k', 0), // hik = Seit-Kaitetu + TRUETYPE_TAG('h', 'i', 'l', 0), // hil = Hiligaynon + TRUETYPE_TAG('h', 'i', 'm', 0), // him = Himachali languages + TRUETYPE_TAG('h', 'i', 'o', 0), // hio = Tsoa + TRUETYPE_TAG('h', 'i', 'r', 0), // hir = Himarimã + TRUETYPE_TAG('h', 'i', 't', 0), // hit = Hittite + TRUETYPE_TAG('h', 'i', 'w', 0), // hiw = Hiw + TRUETYPE_TAG('h', 'i', 'x', 0), // hix = Hixkaryána + TRUETYPE_TAG('h', 'j', 'i', 0), // hji = Haji + TRUETYPE_TAG('h', 'k', 'a', 0), // hka = Kahe + TRUETYPE_TAG('h', 'k', 'e', 0), // hke = Hunde + TRUETYPE_TAG('h', 'k', 'k', 0), // hkk = Hunjara-Kaina Ke + TRUETYPE_TAG('h', 'k', 's', 0), // hks = Hong Kong Sign Language + TRUETYPE_TAG('h', 'l', 'a', 0), // hla = Halia + TRUETYPE_TAG('h', 'l', 'b', 0), // hlb = Halbi + TRUETYPE_TAG('h', 'l', 'd', 0), // hld = Halang Doan + TRUETYPE_TAG('h', 'l', 'e', 0), // hle = Hlersu + TRUETYPE_TAG('h', 'l', 't', 0), // hlt = Nga La + TRUETYPE_TAG('h', 'l', 'u', 0), // hlu = Hieroglyphic Luwian + TRUETYPE_TAG('h', 'm', 'a', 0), // hma = Southern Mashan Hmong + TRUETYPE_TAG('h', 'm', 'b', 0), // hmb = Humburi Senni Songhay + TRUETYPE_TAG('h', 'm', 'c', 0), // hmc = Central Huishui Hmong + TRUETYPE_TAG('h', 'm', 'd', 0), // hmd = Large Flowery Miao + TRUETYPE_TAG('h', 'm', 'e', 0), // hme = Eastern Huishui Hmong + TRUETYPE_TAG('h', 'm', 'f', 0), // hmf = Hmong Don + TRUETYPE_TAG('h', 'm', 'g', 0), // hmg = Southwestern Guiyang Hmong + TRUETYPE_TAG('h', 'm', 'h', 0), // hmh = Southwestern Huishui Hmong + TRUETYPE_TAG('h', 'm', 'i', 0), // hmi = Northern Huishui Hmong + TRUETYPE_TAG('h', 'm', 'j', 0), // hmj = Ge + TRUETYPE_TAG('h', 'm', 'k', 0), // hmk = Maek + TRUETYPE_TAG('h', 'm', 'l', 0), // hml = Luopohe Hmong + TRUETYPE_TAG('h', 'm', 'm', 0), // hmm = Central Mashan Hmong + TRUETYPE_TAG('h', 'm', 'n', 0), // hmn = Hmong + TRUETYPE_TAG('h', 'm', 'p', 0), // hmp = Northern Mashan Hmong + TRUETYPE_TAG('h', 'm', 'q', 0), // hmq = Eastern Qiandong Miao + TRUETYPE_TAG('h', 'm', 'r', 0), // hmr = Hmar + TRUETYPE_TAG('h', 'm', 's', 0), // hms = Southern Qiandong Miao + TRUETYPE_TAG('h', 'm', 't', 0), // hmt = Hamtai + TRUETYPE_TAG('h', 'm', 'u', 0), // hmu = Hamap + TRUETYPE_TAG('h', 'm', 'v', 0), // hmv = Hmong Dô + TRUETYPE_TAG('h', 'm', 'w', 0), // hmw = Western Mashan Hmong + TRUETYPE_TAG('h', 'm', 'x', 0), // hmx = Hmong-Mien languages + TRUETYPE_TAG('h', 'm', 'y', 0), // hmy = Southern Guiyang Hmong + TRUETYPE_TAG('h', 'm', 'z', 0), // hmz = Hmong Shua + TRUETYPE_TAG('h', 'n', 'a', 0), // hna = Mina (Cameroon) + TRUETYPE_TAG('h', 'n', 'd', 0), // hnd = Southern Hindko + TRUETYPE_TAG('h', 'n', 'e', 0), // hne = Chhattisgarhi + TRUETYPE_TAG('h', 'n', 'h', 0), // hnh = //Ani + TRUETYPE_TAG('h', 'n', 'i', 0), // hni = Hani + TRUETYPE_TAG('h', 'n', 'j', 0), // hnj = Hmong Njua + TRUETYPE_TAG('h', 'n', 'n', 0), // hnn = Hanunoo + TRUETYPE_TAG('h', 'n', 'o', 0), // hno = Northern Hindko + TRUETYPE_TAG('h', 'n', 's', 0), // hns = Caribbean Hindustani + TRUETYPE_TAG('h', 'n', 'u', 0), // hnu = Hung + TRUETYPE_TAG('h', 'o', 'a', 0), // hoa = Hoava + TRUETYPE_TAG('h', 'o', 'b', 0), // hob = Mari (Madang Province) + TRUETYPE_TAG('h', 'o', 'c', 0), // hoc = Ho + TRUETYPE_TAG('h', 'o', 'd', 0), // hod = Holma + TRUETYPE_TAG('h', 'o', 'e', 0), // hoe = Horom + TRUETYPE_TAG('h', 'o', 'h', 0), // hoh = Hobyót + TRUETYPE_TAG('h', 'o', 'i', 0), // hoi = Holikachuk + TRUETYPE_TAG('h', 'o', 'j', 0), // hoj = Hadothi + TRUETYPE_TAG('h', 'o', 'k', 0), // hok = Hokan languages + TRUETYPE_TAG('h', 'o', 'l', 0), // hol = Holu + TRUETYPE_TAG('h', 'o', 'm', 0), // hom = Homa + TRUETYPE_TAG('h', 'o', 'o', 0), // hoo = Holoholo + TRUETYPE_TAG('h', 'o', 'p', 0), // hop = Hopi + TRUETYPE_TAG('h', 'o', 'r', 0), // hor = Horo + TRUETYPE_TAG('h', 'o', 's', 0), // hos = Ho Chi Minh City Sign Language + TRUETYPE_TAG('h', 'o', 't', 0), // hot = Hote + TRUETYPE_TAG('h', 'o', 'v', 0), // hov = Hovongan + TRUETYPE_TAG('h', 'o', 'w', 0), // how = Honi + TRUETYPE_TAG('h', 'o', 'y', 0), // hoy = Holiya + TRUETYPE_TAG('h', 'o', 'z', 0), // hoz = Hozo + TRUETYPE_TAG('h', 'p', 'o', 0), // hpo = Hpon + TRUETYPE_TAG('h', 'p', 's', 0), // hps = Hawai'i Pidgin Sign Language + TRUETYPE_TAG('h', 'r', 'a', 0), // hra = Hrangkhol + TRUETYPE_TAG('h', 'r', 'e', 0), // hre = Hre + TRUETYPE_TAG('h', 'r', 'k', 0), // hrk = Haruku + TRUETYPE_TAG('h', 'r', 'm', 0), // hrm = Horned Miao + TRUETYPE_TAG('h', 'r', 'o', 0), // hro = Haroi + TRUETYPE_TAG('h', 'r', 'r', 0), // hrr = Horuru + TRUETYPE_TAG('h', 'r', 't', 0), // hrt = Hértevin + TRUETYPE_TAG('h', 'r', 'u', 0), // hru = Hruso + TRUETYPE_TAG('h', 'r', 'x', 0), // hrx = Hunsrik + TRUETYPE_TAG('h', 'r', 'z', 0), // hrz = Harzani + TRUETYPE_TAG('h', 's', 'b', 0), // hsb = Upper Sorbian + TRUETYPE_TAG('h', 's', 'h', 0), // hsh = Hungarian Sign Language + TRUETYPE_TAG('h', 's', 'l', 0), // hsl = Hausa Sign Language + TRUETYPE_TAG('h', 's', 'n', 0), // hsn = Xiang Chinese + TRUETYPE_TAG('h', 's', 's', 0), // hss = Harsusi + TRUETYPE_TAG('h', 't', 'i', 0), // hti = Hoti + TRUETYPE_TAG('h', 't', 'o', 0), // hto = Minica Huitoto + TRUETYPE_TAG('h', 't', 's', 0), // hts = Hadza + TRUETYPE_TAG('h', 't', 'u', 0), // htu = Hitu + TRUETYPE_TAG('h', 't', 'x', 0), // htx = Middle Hittite + TRUETYPE_TAG('h', 'u', 'b', 0), // hub = Huambisa + TRUETYPE_TAG('h', 'u', 'c', 0), // huc = =/Hua + TRUETYPE_TAG('h', 'u', 'd', 0), // hud = Huaulu + TRUETYPE_TAG('h', 'u', 'e', 0), // hue = San Francisco Del Mar Huave + TRUETYPE_TAG('h', 'u', 'f', 0), // huf = Humene + TRUETYPE_TAG('h', 'u', 'g', 0), // hug = Huachipaeri + TRUETYPE_TAG('h', 'u', 'h', 0), // huh = Huilliche + TRUETYPE_TAG('h', 'u', 'i', 0), // hui = Huli + TRUETYPE_TAG('h', 'u', 'j', 0), // huj = Northern Guiyang Hmong + TRUETYPE_TAG('h', 'u', 'k', 0), // huk = Hulung + TRUETYPE_TAG('h', 'u', 'l', 0), // hul = Hula + TRUETYPE_TAG('h', 'u', 'm', 0), // hum = Hungana + TRUETYPE_TAG('h', 'u', 'o', 0), // huo = Hu + TRUETYPE_TAG('h', 'u', 'p', 0), // hup = Hupa + TRUETYPE_TAG('h', 'u', 'q', 0), // huq = Tsat + TRUETYPE_TAG('h', 'u', 'r', 0), // hur = Halkomelem + TRUETYPE_TAG('h', 'u', 's', 0), // hus = Huastec + TRUETYPE_TAG('h', 'u', 't', 0), // hut = Humla + TRUETYPE_TAG('h', 'u', 'u', 0), // huu = Murui Huitoto + TRUETYPE_TAG('h', 'u', 'v', 0), // huv = San Mateo Del Mar Huave + TRUETYPE_TAG('h', 'u', 'w', 0), // huw = Hukumina + TRUETYPE_TAG('h', 'u', 'x', 0), // hux = Nüpode Huitoto + TRUETYPE_TAG('h', 'u', 'y', 0), // huy = Hulaulá + TRUETYPE_TAG('h', 'u', 'z', 0), // huz = Hunzib + TRUETYPE_TAG('h', 'v', 'c', 0), // hvc = Haitian Vodoun Culture Language + TRUETYPE_TAG('h', 'v', 'e', 0), // hve = San Dionisio Del Mar Huave + TRUETYPE_TAG('h', 'v', 'k', 0), // hvk = Haveke + TRUETYPE_TAG('h', 'v', 'n', 0), // hvn = Sabu + TRUETYPE_TAG('h', 'v', 'v', 0), // hvv = Santa María Del Mar Huave + TRUETYPE_TAG('h', 'w', 'a', 0), // hwa = Wané + TRUETYPE_TAG('h', 'w', 'c', 0), // hwc = Hawai'i Creole English + TRUETYPE_TAG('h', 'w', 'o', 0), // hwo = Hwana + TRUETYPE_TAG('h', 'y', 'a', 0), // hya = Hya + TRUETYPE_TAG('h', 'y', 'x', 0), // hyx = Armenian (family) + TRUETYPE_TAG('i', 'a', 'i', 0), // iai = Iaai + TRUETYPE_TAG('i', 'a', 'n', 0), // ian = Iatmul + TRUETYPE_TAG('i', 'a', 'p', 0), // iap = Iapama + TRUETYPE_TAG('i', 'a', 'r', 0), // iar = Purari + TRUETYPE_TAG('i', 'b', 'a', 0), // iba = Iban + TRUETYPE_TAG('i', 'b', 'b', 0), // ibb = Ibibio + TRUETYPE_TAG('i', 'b', 'd', 0), // ibd = Iwaidja + TRUETYPE_TAG('i', 'b', 'e', 0), // ibe = Akpes + TRUETYPE_TAG('i', 'b', 'g', 0), // ibg = Ibanag + TRUETYPE_TAG('i', 'b', 'i', 0), // ibi = Ibilo + TRUETYPE_TAG('i', 'b', 'l', 0), // ibl = Ibaloi + TRUETYPE_TAG('i', 'b', 'm', 0), // ibm = Agoi + TRUETYPE_TAG('i', 'b', 'n', 0), // ibn = Ibino + TRUETYPE_TAG('i', 'b', 'r', 0), // ibr = Ibuoro + TRUETYPE_TAG('i', 'b', 'u', 0), // ibu = Ibu + TRUETYPE_TAG('i', 'b', 'y', 0), // iby = Ibani + TRUETYPE_TAG('i', 'c', 'a', 0), // ica = Ede Ica + TRUETYPE_TAG('i', 'c', 'h', 0), // ich = Etkywan + TRUETYPE_TAG('i', 'c', 'l', 0), // icl = Icelandic Sign Language + TRUETYPE_TAG('i', 'c', 'r', 0), // icr = Islander Creole English + TRUETYPE_TAG('i', 'd', 'a', 0), // ida = Idakho-Isukha-Tiriki + TRUETYPE_TAG('i', 'd', 'b', 0), // idb = Indo-Portuguese + TRUETYPE_TAG('i', 'd', 'c', 0), // idc = Idon + TRUETYPE_TAG('i', 'd', 'd', 0), // idd = Ede Idaca + TRUETYPE_TAG('i', 'd', 'e', 0), // ide = Idere + TRUETYPE_TAG('i', 'd', 'i', 0), // idi = Idi + TRUETYPE_TAG('i', 'd', 'r', 0), // idr = Indri + TRUETYPE_TAG('i', 'd', 's', 0), // ids = Idesa + TRUETYPE_TAG('i', 'd', 't', 0), // idt = Idaté + TRUETYPE_TAG('i', 'd', 'u', 0), // idu = Idoma + TRUETYPE_TAG('i', 'f', 'a', 0), // ifa = Amganad Ifugao + TRUETYPE_TAG('i', 'f', 'b', 0), // ifb = Batad Ifugao + TRUETYPE_TAG('i', 'f', 'e', 0), // ife = Ifè + TRUETYPE_TAG('i', 'f', 'f', 0), // iff = Ifo + TRUETYPE_TAG('i', 'f', 'k', 0), // ifk = Tuwali Ifugao + TRUETYPE_TAG('i', 'f', 'm', 0), // ifm = Teke-Fuumu + TRUETYPE_TAG('i', 'f', 'u', 0), // ifu = Mayoyao Ifugao + TRUETYPE_TAG('i', 'f', 'y', 0), // ify = Keley-I Kallahan + TRUETYPE_TAG('i', 'g', 'b', 0), // igb = Ebira + TRUETYPE_TAG('i', 'g', 'e', 0), // ige = Igede + TRUETYPE_TAG('i', 'g', 'g', 0), // igg = Igana + TRUETYPE_TAG('i', 'g', 'l', 0), // igl = Igala + TRUETYPE_TAG('i', 'g', 'm', 0), // igm = Kanggape + TRUETYPE_TAG('i', 'g', 'n', 0), // ign = Ignaciano + TRUETYPE_TAG('i', 'g', 'o', 0), // igo = Isebe + TRUETYPE_TAG('i', 'g', 's', 0), // igs = Interglossa + TRUETYPE_TAG('i', 'g', 'w', 0), // igw = Igwe + TRUETYPE_TAG('i', 'h', 'b', 0), // ihb = Iha Based Pidgin + TRUETYPE_TAG('i', 'h', 'i', 0), // ihi = Ihievbe + TRUETYPE_TAG('i', 'h', 'p', 0), // ihp = Iha + TRUETYPE_TAG('i', 'i', 'r', 0), // iir = Indo-Iranian languages + TRUETYPE_TAG('i', 'j', 'c', 0), // ijc = Izon + TRUETYPE_TAG('i', 'j', 'e', 0), // ije = Biseni + TRUETYPE_TAG('i', 'j', 'j', 0), // ijj = Ede Ije + TRUETYPE_TAG('i', 'j', 'n', 0), // ijn = Kalabari + TRUETYPE_TAG('i', 'j', 'o', 0), // ijo = Ijo languages + TRUETYPE_TAG('i', 'j', 's', 0), // ijs = Southeast Ijo + TRUETYPE_TAG('i', 'k', 'e', 0), // ike = Eastern Canadian Inuktitut + TRUETYPE_TAG('i', 'k', 'i', 0), // iki = Iko + TRUETYPE_TAG('i', 'k', 'k', 0), // ikk = Ika + TRUETYPE_TAG('i', 'k', 'l', 0), // ikl = Ikulu + TRUETYPE_TAG('i', 'k', 'o', 0), // iko = Olulumo-Ikom + TRUETYPE_TAG('i', 'k', 'p', 0), // ikp = Ikpeshi + TRUETYPE_TAG('i', 'k', 't', 0), // ikt = Western Canadian Inuktitut + TRUETYPE_TAG('i', 'k', 'v', 0), // ikv = Iku-Gora-Ankwa + TRUETYPE_TAG('i', 'k', 'w', 0), // ikw = Ikwere + TRUETYPE_TAG('i', 'k', 'x', 0), // ikx = Ik + TRUETYPE_TAG('i', 'k', 'z', 0), // ikz = Ikizu + TRUETYPE_TAG('i', 'l', 'a', 0), // ila = Ile Ape + TRUETYPE_TAG('i', 'l', 'b', 0), // ilb = Ila + TRUETYPE_TAG('i', 'l', 'g', 0), // ilg = Garig-Ilgar + TRUETYPE_TAG('i', 'l', 'i', 0), // ili = Ili Turki + TRUETYPE_TAG('i', 'l', 'k', 0), // ilk = Ilongot + TRUETYPE_TAG('i', 'l', 'l', 0), // ill = Iranun + TRUETYPE_TAG('i', 'l', 'o', 0), // ilo = Iloko + TRUETYPE_TAG('i', 'l', 's', 0), // ils = International Sign + TRUETYPE_TAG('i', 'l', 'u', 0), // ilu = Ili'uun + TRUETYPE_TAG('i', 'l', 'v', 0), // ilv = Ilue + TRUETYPE_TAG('i', 'l', 'w', 0), // ilw = Talur + TRUETYPE_TAG('i', 'm', 'a', 0), // ima = Mala Malasar + TRUETYPE_TAG('i', 'm', 'e', 0), // ime = Imeraguen + TRUETYPE_TAG('i', 'm', 'i', 0), // imi = Anamgura + TRUETYPE_TAG('i', 'm', 'l', 0), // iml = Miluk + TRUETYPE_TAG('i', 'm', 'n', 0), // imn = Imonda + TRUETYPE_TAG('i', 'm', 'o', 0), // imo = Imbongu + TRUETYPE_TAG('i', 'm', 'r', 0), // imr = Imroing + TRUETYPE_TAG('i', 'm', 's', 0), // ims = Marsian + TRUETYPE_TAG('i', 'm', 'y', 0), // imy = Milyan + TRUETYPE_TAG('i', 'n', 'b', 0), // inb = Inga + TRUETYPE_TAG('i', 'n', 'c', 0), // inc = Indic languages + TRUETYPE_TAG('i', 'n', 'e', 0), // ine = Indo-European languages + TRUETYPE_TAG('i', 'n', 'g', 0), // ing = Degexit'an + TRUETYPE_TAG('i', 'n', 'h', 0), // inh = Ingush + TRUETYPE_TAG('i', 'n', 'j', 0), // inj = Jungle Inga + TRUETYPE_TAG('i', 'n', 'l', 0), // inl = Indonesian Sign Language + TRUETYPE_TAG('i', 'n', 'm', 0), // inm = Minaean + TRUETYPE_TAG('i', 'n', 'n', 0), // inn = Isinai + TRUETYPE_TAG('i', 'n', 'o', 0), // ino = Inoke-Yate + TRUETYPE_TAG('i', 'n', 'p', 0), // inp = Iñapari + TRUETYPE_TAG('i', 'n', 's', 0), // ins = Indian Sign Language + TRUETYPE_TAG('i', 'n', 't', 0), // int = Intha + TRUETYPE_TAG('i', 'n', 'z', 0), // inz = Ineseño + TRUETYPE_TAG('i', 'o', 'r', 0), // ior = Inor + TRUETYPE_TAG('i', 'o', 'u', 0), // iou = Tuma-Irumu + TRUETYPE_TAG('i', 'o', 'w', 0), // iow = Iowa-Oto + TRUETYPE_TAG('i', 'p', 'i', 0), // ipi = Ipili + TRUETYPE_TAG('i', 'p', 'o', 0), // ipo = Ipiko + TRUETYPE_TAG('i', 'q', 'u', 0), // iqu = Iquito + TRUETYPE_TAG('i', 'r', 'a', 0), // ira = Iranian languages + TRUETYPE_TAG('i', 'r', 'e', 0), // ire = Iresim + TRUETYPE_TAG('i', 'r', 'h', 0), // irh = Irarutu + TRUETYPE_TAG('i', 'r', 'i', 0), // iri = Irigwe + TRUETYPE_TAG('i', 'r', 'k', 0), // irk = Iraqw + TRUETYPE_TAG('i', 'r', 'n', 0), // irn = Irántxe + TRUETYPE_TAG('i', 'r', 'o', 0), // iro = Iroquoian languages + TRUETYPE_TAG('i', 'r', 'r', 0), // irr = Ir + TRUETYPE_TAG('i', 'r', 'u', 0), // iru = Irula + TRUETYPE_TAG('i', 'r', 'x', 0), // irx = Kamberau + TRUETYPE_TAG('i', 'r', 'y', 0), // iry = Iraya + TRUETYPE_TAG('i', 's', 'a', 0), // isa = Isabi + TRUETYPE_TAG('i', 's', 'c', 0), // isc = Isconahua + TRUETYPE_TAG('i', 's', 'd', 0), // isd = Isnag + TRUETYPE_TAG('i', 's', 'e', 0), // ise = Italian Sign Language + TRUETYPE_TAG('i', 's', 'g', 0), // isg = Irish Sign Language + TRUETYPE_TAG('i', 's', 'h', 0), // ish = Esan + TRUETYPE_TAG('i', 's', 'i', 0), // isi = Nkem-Nkum + TRUETYPE_TAG('i', 's', 'k', 0), // isk = Ishkashimi + TRUETYPE_TAG('i', 's', 'm', 0), // ism = Masimasi + TRUETYPE_TAG('i', 's', 'n', 0), // isn = Isanzu + TRUETYPE_TAG('i', 's', 'o', 0), // iso = Isoko + TRUETYPE_TAG('i', 's', 'r', 0), // isr = Israeli Sign Language + TRUETYPE_TAG('i', 's', 't', 0), // ist = Istriot + TRUETYPE_TAG('i', 's', 'u', 0), // isu = Isu (Menchum Division) + TRUETYPE_TAG('i', 't', 'b', 0), // itb = Binongan Itneg + TRUETYPE_TAG('i', 't', 'c', 0), // itc = Italic languages + TRUETYPE_TAG('i', 't', 'e', 0), // ite = Itene + TRUETYPE_TAG('i', 't', 'i', 0), // iti = Inlaod Itneg + TRUETYPE_TAG('i', 't', 'k', 0), // itk = Judeo-Italian + TRUETYPE_TAG('i', 't', 'l', 0), // itl = Itelmen + TRUETYPE_TAG('i', 't', 'm', 0), // itm = Itu Mbon Uzo + TRUETYPE_TAG('i', 't', 'o', 0), // ito = Itonama + TRUETYPE_TAG('i', 't', 'r', 0), // itr = Iteri + TRUETYPE_TAG('i', 't', 's', 0), // its = Isekiri + TRUETYPE_TAG('i', 't', 't', 0), // itt = Maeng Itneg + TRUETYPE_TAG('i', 't', 'v', 0), // itv = Itawit + TRUETYPE_TAG('i', 't', 'w', 0), // itw = Ito + TRUETYPE_TAG('i', 't', 'x', 0), // itx = Itik + TRUETYPE_TAG('i', 't', 'y', 0), // ity = Moyadan Itneg + TRUETYPE_TAG('i', 't', 'z', 0), // itz = Itzá + TRUETYPE_TAG('i', 'u', 'm', 0), // ium = Iu Mien + TRUETYPE_TAG('i', 'v', 'b', 0), // ivb = Ibatan + TRUETYPE_TAG('i', 'v', 'v', 0), // ivv = Ivatan + TRUETYPE_TAG('i', 'w', 'k', 0), // iwk = I-Wak + TRUETYPE_TAG('i', 'w', 'm', 0), // iwm = Iwam + TRUETYPE_TAG('i', 'w', 'o', 0), // iwo = Iwur + TRUETYPE_TAG('i', 'w', 's', 0), // iws = Sepik Iwam + TRUETYPE_TAG('i', 'x', 'c', 0), // ixc = Ixcatec + TRUETYPE_TAG('i', 'x', 'l', 0), // ixl = Ixil + TRUETYPE_TAG('i', 'y', 'a', 0), // iya = Iyayu + TRUETYPE_TAG('i', 'y', 'o', 0), // iyo = Mesaka + TRUETYPE_TAG('i', 'y', 'x', 0), // iyx = Yaka (Congo) + TRUETYPE_TAG('i', 'z', 'h', 0), // izh = Ingrian + TRUETYPE_TAG('i', 'z', 'i', 0), // izi = Izi-Ezaa-Ikwo-Mgbo + TRUETYPE_TAG('i', 'z', 'r', 0), // izr = Izere + TRUETYPE_TAG('j', 'a', 'a', 0), // jaa = Jamamadí + TRUETYPE_TAG('j', 'a', 'b', 0), // jab = Hyam + TRUETYPE_TAG('j', 'a', 'c', 0), // jac = Popti' + TRUETYPE_TAG('j', 'a', 'd', 0), // jad = Jahanka + TRUETYPE_TAG('j', 'a', 'e', 0), // jae = Yabem + TRUETYPE_TAG('j', 'a', 'f', 0), // jaf = Jara + TRUETYPE_TAG('j', 'a', 'h', 0), // jah = Jah Hut + TRUETYPE_TAG('j', 'a', 'j', 0), // jaj = Zazao + TRUETYPE_TAG('j', 'a', 'k', 0), // jak = Jakun + TRUETYPE_TAG('j', 'a', 'l', 0), // jal = Yalahatan + TRUETYPE_TAG('j', 'a', 'm', 0), // jam = Jamaican Creole English + TRUETYPE_TAG('j', 'a', 'o', 0), // jao = Yanyuwa + TRUETYPE_TAG('j', 'a', 'q', 0), // jaq = Yaqay + TRUETYPE_TAG('j', 'a', 'r', 0), // jar = Jarawa (Nigeria) + TRUETYPE_TAG('j', 'a', 's', 0), // jas = New Caledonian Javanese + TRUETYPE_TAG('j', 'a', 't', 0), // jat = Jakati + TRUETYPE_TAG('j', 'a', 'u', 0), // jau = Yaur + TRUETYPE_TAG('j', 'a', 'x', 0), // jax = Jambi Malay + TRUETYPE_TAG('j', 'a', 'y', 0), // jay = Yan-nhangu + TRUETYPE_TAG('j', 'a', 'z', 0), // jaz = Jawe + TRUETYPE_TAG('j', 'b', 'e', 0), // jbe = Judeo-Berber + TRUETYPE_TAG('j', 'b', 'j', 0), // jbj = Arandai + TRUETYPE_TAG('j', 'b', 'n', 0), // jbn = Nafusi + TRUETYPE_TAG('j', 'b', 'o', 0), // jbo = Lojban + TRUETYPE_TAG('j', 'b', 'r', 0), // jbr = Jofotek-Bromnya + TRUETYPE_TAG('j', 'b', 't', 0), // jbt = Jabutí + TRUETYPE_TAG('j', 'b', 'u', 0), // jbu = Jukun Takum + TRUETYPE_TAG('j', 'c', 's', 0), // jcs = Jamaican Country Sign Language + TRUETYPE_TAG('j', 'c', 't', 0), // jct = Krymchak + TRUETYPE_TAG('j', 'd', 'a', 0), // jda = Jad + TRUETYPE_TAG('j', 'd', 'g', 0), // jdg = Jadgali + TRUETYPE_TAG('j', 'd', 't', 0), // jdt = Judeo-Tat + TRUETYPE_TAG('j', 'e', 'b', 0), // jeb = Jebero + TRUETYPE_TAG('j', 'e', 'e', 0), // jee = Jerung + TRUETYPE_TAG('j', 'e', 'g', 0), // jeg = Jeng + TRUETYPE_TAG('j', 'e', 'h', 0), // jeh = Jeh + TRUETYPE_TAG('j', 'e', 'i', 0), // jei = Yei + TRUETYPE_TAG('j', 'e', 'k', 0), // jek = Jeri Kuo + TRUETYPE_TAG('j', 'e', 'l', 0), // jel = Yelmek + TRUETYPE_TAG('j', 'e', 'n', 0), // jen = Dza + TRUETYPE_TAG('j', 'e', 'r', 0), // jer = Jere + TRUETYPE_TAG('j', 'e', 't', 0), // jet = Manem + TRUETYPE_TAG('j', 'e', 'u', 0), // jeu = Jonkor Bourmataguil + TRUETYPE_TAG('j', 'g', 'b', 0), // jgb = Ngbee + TRUETYPE_TAG('j', 'g', 'e', 0), // jge = Judeo-Georgian + TRUETYPE_TAG('j', 'g', 'o', 0), // jgo = Ngomba + TRUETYPE_TAG('j', 'h', 'i', 0), // jhi = Jehai + TRUETYPE_TAG('j', 'h', 's', 0), // jhs = Jhankot Sign Language + TRUETYPE_TAG('j', 'i', 'a', 0), // jia = Jina + TRUETYPE_TAG('j', 'i', 'b', 0), // jib = Jibu + TRUETYPE_TAG('j', 'i', 'c', 0), // jic = Tol + TRUETYPE_TAG('j', 'i', 'd', 0), // jid = Bu + TRUETYPE_TAG('j', 'i', 'e', 0), // jie = Jilbe + TRUETYPE_TAG('j', 'i', 'g', 0), // jig = Djingili + TRUETYPE_TAG('j', 'i', 'h', 0), // jih = Shangzhai + TRUETYPE_TAG('j', 'i', 'i', 0), // jii = Jiiddu + TRUETYPE_TAG('j', 'i', 'l', 0), // jil = Jilim + TRUETYPE_TAG('j', 'i', 'm', 0), // jim = Jimi (Cameroon) + TRUETYPE_TAG('j', 'i', 'o', 0), // jio = Jiamao + TRUETYPE_TAG('j', 'i', 'q', 0), // jiq = Guanyinqiao + TRUETYPE_TAG('j', 'i', 't', 0), // jit = Jita + TRUETYPE_TAG('j', 'i', 'u', 0), // jiu = Youle Jinuo + TRUETYPE_TAG('j', 'i', 'v', 0), // jiv = Shuar + TRUETYPE_TAG('j', 'i', 'y', 0), // jiy = Buyuan Jinuo + TRUETYPE_TAG('j', 'k', 'o', 0), // jko = Kubo + TRUETYPE_TAG('j', 'k', 'u', 0), // jku = Labir + TRUETYPE_TAG('j', 'l', 'e', 0), // jle = Ngile + TRUETYPE_TAG('j', 'l', 's', 0), // jls = Jamaican Sign Language + TRUETYPE_TAG('j', 'm', 'a', 0), // jma = Dima + TRUETYPE_TAG('j', 'm', 'b', 0), // jmb = Zumbun + TRUETYPE_TAG('j', 'm', 'c', 0), // jmc = Machame + TRUETYPE_TAG('j', 'm', 'd', 0), // jmd = Yamdena + TRUETYPE_TAG('j', 'm', 'i', 0), // jmi = Jimi (Nigeria) + TRUETYPE_TAG('j', 'm', 'l', 0), // jml = Jumli + TRUETYPE_TAG('j', 'm', 'n', 0), // jmn = Makuri Naga + TRUETYPE_TAG('j', 'm', 'r', 0), // jmr = Kamara + TRUETYPE_TAG('j', 'm', 's', 0), // jms = Mashi (Nigeria) + TRUETYPE_TAG('j', 'm', 'x', 0), // jmx = Western Juxtlahuaca Mixtec + TRUETYPE_TAG('j', 'n', 'a', 0), // jna = Jangshung + TRUETYPE_TAG('j', 'n', 'd', 0), // jnd = Jandavra + TRUETYPE_TAG('j', 'n', 'g', 0), // jng = Yangman + TRUETYPE_TAG('j', 'n', 'i', 0), // jni = Janji + TRUETYPE_TAG('j', 'n', 'j', 0), // jnj = Yemsa + TRUETYPE_TAG('j', 'n', 'l', 0), // jnl = Rawat + TRUETYPE_TAG('j', 'n', 's', 0), // jns = Jaunsari + TRUETYPE_TAG('j', 'o', 'b', 0), // job = Joba + TRUETYPE_TAG('j', 'o', 'd', 0), // jod = Wojenaka + TRUETYPE_TAG('j', 'o', 'r', 0), // jor = Jorá + TRUETYPE_TAG('j', 'o', 's', 0), // jos = Jordanian Sign Language + TRUETYPE_TAG('j', 'o', 'w', 0), // jow = Jowulu + TRUETYPE_TAG('j', 'p', 'a', 0), // jpa = Jewish Palestinian Aramaic + TRUETYPE_TAG('j', 'p', 'r', 0), // jpr = Judeo-Persian + TRUETYPE_TAG('j', 'p', 'x', 0), // jpx = Japanese (family) + TRUETYPE_TAG('j', 'q', 'r', 0), // jqr = Jaqaru + TRUETYPE_TAG('j', 'r', 'a', 0), // jra = Jarai + TRUETYPE_TAG('j', 'r', 'b', 0), // jrb = Judeo-Arabic + TRUETYPE_TAG('j', 'r', 'r', 0), // jrr = Jiru + TRUETYPE_TAG('j', 'r', 't', 0), // jrt = Jorto + TRUETYPE_TAG('j', 'r', 'u', 0), // jru = Japrería + TRUETYPE_TAG('j', 's', 'l', 0), // jsl = Japanese Sign Language + TRUETYPE_TAG('j', 'u', 'a', 0), // jua = Júma + TRUETYPE_TAG('j', 'u', 'b', 0), // jub = Wannu + TRUETYPE_TAG('j', 'u', 'c', 0), // juc = Jurchen + TRUETYPE_TAG('j', 'u', 'd', 0), // jud = Worodougou + TRUETYPE_TAG('j', 'u', 'h', 0), // juh = Hõne + TRUETYPE_TAG('j', 'u', 'k', 0), // juk = Wapan + TRUETYPE_TAG('j', 'u', 'l', 0), // jul = Jirel + TRUETYPE_TAG('j', 'u', 'm', 0), // jum = Jumjum + TRUETYPE_TAG('j', 'u', 'n', 0), // jun = Juang + TRUETYPE_TAG('j', 'u', 'o', 0), // juo = Jiba + TRUETYPE_TAG('j', 'u', 'p', 0), // jup = Hupdë + TRUETYPE_TAG('j', 'u', 'r', 0), // jur = Jurúna + TRUETYPE_TAG('j', 'u', 's', 0), // jus = Jumla Sign Language + TRUETYPE_TAG('j', 'u', 't', 0), // jut = Jutish + TRUETYPE_TAG('j', 'u', 'u', 0), // juu = Ju + TRUETYPE_TAG('j', 'u', 'w', 0), // juw = Wãpha + TRUETYPE_TAG('j', 'u', 'y', 0), // juy = Juray + TRUETYPE_TAG('j', 'v', 'd', 0), // jvd = Javindo + TRUETYPE_TAG('j', 'v', 'n', 0), // jvn = Caribbean Javanese + TRUETYPE_TAG('j', 'w', 'i', 0), // jwi = Jwira-Pepesa + TRUETYPE_TAG('j', 'y', 'a', 0), // jya = Jiarong + TRUETYPE_TAG('j', 'y', 'e', 0), // jye = Judeo-Yemeni Arabic + TRUETYPE_TAG('j', 'y', 'y', 0), // jyy = Jaya + TRUETYPE_TAG('k', 'a', 'a', 0), // kaa = Kara-Kalpak + TRUETYPE_TAG('k', 'a', 'b', 0), // kab = Kabyle + TRUETYPE_TAG('k', 'a', 'c', 0), // kac = Kachin + TRUETYPE_TAG('k', 'a', 'd', 0), // kad = Kadara + TRUETYPE_TAG('k', 'a', 'e', 0), // kae = Ketangalan + TRUETYPE_TAG('k', 'a', 'f', 0), // kaf = Katso + TRUETYPE_TAG('k', 'a', 'g', 0), // kag = Kajaman + TRUETYPE_TAG('k', 'a', 'h', 0), // kah = Kara (Central African Republic) + TRUETYPE_TAG('k', 'a', 'i', 0), // kai = Karekare + TRUETYPE_TAG('k', 'a', 'j', 0), // kaj = Jju + TRUETYPE_TAG('k', 'a', 'k', 0), // kak = Kayapa Kallahan + TRUETYPE_TAG('k', 'a', 'm', 0), // kam = Kamba (Kenya) + TRUETYPE_TAG('k', 'a', 'o', 0), // kao = Xaasongaxango + TRUETYPE_TAG('k', 'a', 'p', 0), // kap = Bezhta + TRUETYPE_TAG('k', 'a', 'q', 0), // kaq = Capanahua + TRUETYPE_TAG('k', 'a', 'r', 0), // kar = Karen languages + TRUETYPE_TAG('k', 'a', 'v', 0), // kav = Katukína + TRUETYPE_TAG('k', 'a', 'w', 0), // kaw = Kawi + TRUETYPE_TAG('k', 'a', 'x', 0), // kax = Kao + TRUETYPE_TAG('k', 'a', 'y', 0), // kay = Kamayurá + TRUETYPE_TAG('k', 'b', 'a', 0), // kba = Kalarko + TRUETYPE_TAG('k', 'b', 'b', 0), // kbb = Kaxuiâna + TRUETYPE_TAG('k', 'b', 'c', 0), // kbc = Kadiwéu + TRUETYPE_TAG('k', 'b', 'd', 0), // kbd = Kabardian + TRUETYPE_TAG('k', 'b', 'e', 0), // kbe = Kanju + TRUETYPE_TAG('k', 'b', 'f', 0), // kbf = Kakauhua + TRUETYPE_TAG('k', 'b', 'g', 0), // kbg = Khamba + TRUETYPE_TAG('k', 'b', 'h', 0), // kbh = Camsá + TRUETYPE_TAG('k', 'b', 'i', 0), // kbi = Kaptiau + TRUETYPE_TAG('k', 'b', 'j', 0), // kbj = Kari + TRUETYPE_TAG('k', 'b', 'k', 0), // kbk = Grass Koiari + TRUETYPE_TAG('k', 'b', 'l', 0), // kbl = Kanembu + TRUETYPE_TAG('k', 'b', 'm', 0), // kbm = Iwal + TRUETYPE_TAG('k', 'b', 'n', 0), // kbn = Kare (Central African Republic) + TRUETYPE_TAG('k', 'b', 'o', 0), // kbo = Keliko + TRUETYPE_TAG('k', 'b', 'p', 0), // kbp = Kabiyè + TRUETYPE_TAG('k', 'b', 'q', 0), // kbq = Kamano + TRUETYPE_TAG('k', 'b', 'r', 0), // kbr = Kafa + TRUETYPE_TAG('k', 'b', 's', 0), // kbs = Kande + TRUETYPE_TAG('k', 'b', 't', 0), // kbt = Abadi + TRUETYPE_TAG('k', 'b', 'u', 0), // kbu = Kabutra + TRUETYPE_TAG('k', 'b', 'v', 0), // kbv = Dera (Indonesia) + TRUETYPE_TAG('k', 'b', 'w', 0), // kbw = Kaiep + TRUETYPE_TAG('k', 'b', 'x', 0), // kbx = Ap Ma + TRUETYPE_TAG('k', 'b', 'y', 0), // kby = Manga Kanuri + TRUETYPE_TAG('k', 'b', 'z', 0), // kbz = Duhwa + TRUETYPE_TAG('k', 'c', 'a', 0), // kca = Khanty + TRUETYPE_TAG('k', 'c', 'b', 0), // kcb = Kawacha + TRUETYPE_TAG('k', 'c', 'c', 0), // kcc = Lubila + TRUETYPE_TAG('k', 'c', 'd', 0), // kcd = Ngkâlmpw Kanum + TRUETYPE_TAG('k', 'c', 'e', 0), // kce = Kaivi + TRUETYPE_TAG('k', 'c', 'f', 0), // kcf = Ukaan + TRUETYPE_TAG('k', 'c', 'g', 0), // kcg = Tyap + TRUETYPE_TAG('k', 'c', 'h', 0), // kch = Vono + TRUETYPE_TAG('k', 'c', 'i', 0), // kci = Kamantan + TRUETYPE_TAG('k', 'c', 'j', 0), // kcj = Kobiana + TRUETYPE_TAG('k', 'c', 'k', 0), // kck = Kalanga + TRUETYPE_TAG('k', 'c', 'l', 0), // kcl = Kela (Papua New Guinea) + TRUETYPE_TAG('k', 'c', 'm', 0), // kcm = Gula (Central African Republic) + TRUETYPE_TAG('k', 'c', 'n', 0), // kcn = Nubi + TRUETYPE_TAG('k', 'c', 'o', 0), // kco = Kinalakna + TRUETYPE_TAG('k', 'c', 'p', 0), // kcp = Kanga + TRUETYPE_TAG('k', 'c', 'q', 0), // kcq = Kamo + TRUETYPE_TAG('k', 'c', 'r', 0), // kcr = Katla + TRUETYPE_TAG('k', 'c', 's', 0), // kcs = Koenoem + TRUETYPE_TAG('k', 'c', 't', 0), // kct = Kaian + TRUETYPE_TAG('k', 'c', 'u', 0), // kcu = Kami (Tanzania) + TRUETYPE_TAG('k', 'c', 'v', 0), // kcv = Kete + TRUETYPE_TAG('k', 'c', 'w', 0), // kcw = Kabwari + TRUETYPE_TAG('k', 'c', 'x', 0), // kcx = Kachama-Ganjule + TRUETYPE_TAG('k', 'c', 'y', 0), // kcy = Korandje + TRUETYPE_TAG('k', 'c', 'z', 0), // kcz = Konongo + TRUETYPE_TAG('k', 'd', 'a', 0), // kda = Worimi + TRUETYPE_TAG('k', 'd', 'c', 0), // kdc = Kutu + TRUETYPE_TAG('k', 'd', 'd', 0), // kdd = Yankunytjatjara + TRUETYPE_TAG('k', 'd', 'e', 0), // kde = Makonde + TRUETYPE_TAG('k', 'd', 'f', 0), // kdf = Mamusi + TRUETYPE_TAG('k', 'd', 'g', 0), // kdg = Seba + TRUETYPE_TAG('k', 'd', 'h', 0), // kdh = Tem + TRUETYPE_TAG('k', 'd', 'i', 0), // kdi = Kumam + TRUETYPE_TAG('k', 'd', 'j', 0), // kdj = Karamojong + TRUETYPE_TAG('k', 'd', 'k', 0), // kdk = Numee + TRUETYPE_TAG('k', 'd', 'l', 0), // kdl = Tsikimba + TRUETYPE_TAG('k', 'd', 'm', 0), // kdm = Kagoma + TRUETYPE_TAG('k', 'd', 'n', 0), // kdn = Kunda + TRUETYPE_TAG('k', 'd', 'o', 0), // kdo = Kordofanian languages + TRUETYPE_TAG('k', 'd', 'p', 0), // kdp = Kaningdon-Nindem + TRUETYPE_TAG('k', 'd', 'q', 0), // kdq = Koch + TRUETYPE_TAG('k', 'd', 'r', 0), // kdr = Karaim + TRUETYPE_TAG('k', 'd', 't', 0), // kdt = Kuy + TRUETYPE_TAG('k', 'd', 'u', 0), // kdu = Kadaru + TRUETYPE_TAG('k', 'd', 'v', 0), // kdv = Kado + TRUETYPE_TAG('k', 'd', 'w', 0), // kdw = Koneraw + TRUETYPE_TAG('k', 'd', 'x', 0), // kdx = Kam + TRUETYPE_TAG('k', 'd', 'y', 0), // kdy = Keder + TRUETYPE_TAG('k', 'd', 'z', 0), // kdz = Kwaja + TRUETYPE_TAG('k', 'e', 'a', 0), // kea = Kabuverdianu + TRUETYPE_TAG('k', 'e', 'b', 0), // keb = Kélé + TRUETYPE_TAG('k', 'e', 'c', 0), // kec = Keiga + TRUETYPE_TAG('k', 'e', 'd', 0), // ked = Kerewe + TRUETYPE_TAG('k', 'e', 'e', 0), // kee = Eastern Keres + TRUETYPE_TAG('k', 'e', 'f', 0), // kef = Kpessi + TRUETYPE_TAG('k', 'e', 'g', 0), // keg = Tese + TRUETYPE_TAG('k', 'e', 'h', 0), // keh = Keak + TRUETYPE_TAG('k', 'e', 'i', 0), // kei = Kei + TRUETYPE_TAG('k', 'e', 'j', 0), // kej = Kadar + TRUETYPE_TAG('k', 'e', 'k', 0), // kek = Kekchí + TRUETYPE_TAG('k', 'e', 'l', + 0), // kel = Kela (Democratic Republic of Congo) + TRUETYPE_TAG('k', 'e', 'm', 0), // kem = Kemak + TRUETYPE_TAG('k', 'e', 'n', 0), // ken = Kenyang + TRUETYPE_TAG('k', 'e', 'o', 0), // keo = Kakwa + TRUETYPE_TAG('k', 'e', 'p', 0), // kep = Kaikadi + TRUETYPE_TAG('k', 'e', 'q', 0), // keq = Kamar + TRUETYPE_TAG('k', 'e', 'r', 0), // ker = Kera + TRUETYPE_TAG('k', 'e', 's', 0), // kes = Kugbo + TRUETYPE_TAG('k', 'e', 't', 0), // ket = Ket + TRUETYPE_TAG('k', 'e', 'u', 0), // keu = Akebu + TRUETYPE_TAG('k', 'e', 'v', 0), // kev = Kanikkaran + TRUETYPE_TAG('k', 'e', 'w', 0), // kew = West Kewa + TRUETYPE_TAG('k', 'e', 'x', 0), // kex = Kukna + TRUETYPE_TAG('k', 'e', 'y', 0), // key = Kupia + TRUETYPE_TAG('k', 'e', 'z', 0), // kez = Kukele + TRUETYPE_TAG('k', 'f', 'a', 0), // kfa = Kodava + TRUETYPE_TAG('k', 'f', 'b', 0), // kfb = Northwestern Kolami + TRUETYPE_TAG('k', 'f', 'c', 0), // kfc = Konda-Dora + TRUETYPE_TAG('k', 'f', 'd', 0), // kfd = Korra Koraga + TRUETYPE_TAG('k', 'f', 'e', 0), // kfe = Kota (India) + TRUETYPE_TAG('k', 'f', 'f', 0), // kff = Koya + TRUETYPE_TAG('k', 'f', 'g', 0), // kfg = Kudiya + TRUETYPE_TAG('k', 'f', 'h', 0), // kfh = Kurichiya + TRUETYPE_TAG('k', 'f', 'i', 0), // kfi = Kannada Kurumba + TRUETYPE_TAG('k', 'f', 'j', 0), // kfj = Kemiehua + TRUETYPE_TAG('k', 'f', 'k', 0), // kfk = Kinnauri + TRUETYPE_TAG('k', 'f', 'l', 0), // kfl = Kung + TRUETYPE_TAG('k', 'f', 'm', 0), // kfm = Khunsari + TRUETYPE_TAG('k', 'f', 'n', 0), // kfn = Kuk + TRUETYPE_TAG('k', 'f', 'o', 0), // kfo = Koro (Côte d'Ivoire) + TRUETYPE_TAG('k', 'f', 'p', 0), // kfp = Korwa + TRUETYPE_TAG('k', 'f', 'q', 0), // kfq = Korku + TRUETYPE_TAG('k', 'f', 'r', 0), // kfr = Kachchi + TRUETYPE_TAG('k', 'f', 's', 0), // kfs = Bilaspuri + TRUETYPE_TAG('k', 'f', 't', 0), // kft = Kanjari + TRUETYPE_TAG('k', 'f', 'u', 0), // kfu = Katkari + TRUETYPE_TAG('k', 'f', 'v', 0), // kfv = Kurmukar + TRUETYPE_TAG('k', 'f', 'w', 0), // kfw = Kharam Naga + TRUETYPE_TAG('k', 'f', 'x', 0), // kfx = Kullu Pahari + TRUETYPE_TAG('k', 'f', 'y', 0), // kfy = Kumaoni + TRUETYPE_TAG('k', 'f', 'z', 0), // kfz = Koromfé + TRUETYPE_TAG('k', 'g', 'a', 0), // kga = Koyaga + TRUETYPE_TAG('k', 'g', 'b', 0), // kgb = Kawe + TRUETYPE_TAG('k', 'g', 'c', 0), // kgc = Kasseng + TRUETYPE_TAG('k', 'g', 'd', 0), // kgd = Kataang + TRUETYPE_TAG('k', 'g', 'e', 0), // kge = Komering + TRUETYPE_TAG('k', 'g', 'f', 0), // kgf = Kube + TRUETYPE_TAG('k', 'g', 'g', 0), // kgg = Kusunda + TRUETYPE_TAG('k', 'g', 'h', 0), // kgh = Upper Tanudan Kalinga + TRUETYPE_TAG('k', 'g', 'i', 0), // kgi = Selangor Sign Language + TRUETYPE_TAG('k', 'g', 'j', 0), // kgj = Gamale Kham + TRUETYPE_TAG('k', 'g', 'k', 0), // kgk = Kaiwá + TRUETYPE_TAG('k', 'g', 'l', 0), // kgl = Kunggari + TRUETYPE_TAG('k', 'g', 'm', 0), // kgm = Karipúna + TRUETYPE_TAG('k', 'g', 'n', 0), // kgn = Karingani + TRUETYPE_TAG('k', 'g', 'o', 0), // kgo = Krongo + TRUETYPE_TAG('k', 'g', 'p', 0), // kgp = Kaingang + TRUETYPE_TAG('k', 'g', 'q', 0), // kgq = Kamoro + TRUETYPE_TAG('k', 'g', 'r', 0), // kgr = Abun + TRUETYPE_TAG('k', 'g', 's', 0), // kgs = Kumbainggar + TRUETYPE_TAG('k', 'g', 't', 0), // kgt = Somyev + TRUETYPE_TAG('k', 'g', 'u', 0), // kgu = Kobol + TRUETYPE_TAG('k', 'g', 'v', 0), // kgv = Karas + TRUETYPE_TAG('k', 'g', 'w', 0), // kgw = Karon Dori + TRUETYPE_TAG('k', 'g', 'x', 0), // kgx = Kamaru + TRUETYPE_TAG('k', 'g', 'y', 0), // kgy = Kyerung + TRUETYPE_TAG('k', 'h', 'a', 0), // kha = Khasi + TRUETYPE_TAG('k', 'h', 'b', 0), // khb = Lü + TRUETYPE_TAG('k', 'h', 'c', 0), // khc = Tukang Besi North + TRUETYPE_TAG('k', 'h', 'd', 0), // khd = Bädi Kanum + TRUETYPE_TAG('k', 'h', 'e', 0), // khe = Korowai + TRUETYPE_TAG('k', 'h', 'f', 0), // khf = Khuen + TRUETYPE_TAG('k', 'h', 'g', 0), // khg = Khams Tibetan + TRUETYPE_TAG('k', 'h', 'h', 0), // khh = Kehu + TRUETYPE_TAG('k', 'h', 'i', 0), // khi = Khoisan languages + TRUETYPE_TAG('k', 'h', 'j', 0), // khj = Kuturmi + TRUETYPE_TAG('k', 'h', 'k', 0), // khk = Halh Mongolian + TRUETYPE_TAG('k', 'h', 'l', 0), // khl = Lusi + TRUETYPE_TAG('k', 'h', 'n', 0), // khn = Khandesi + TRUETYPE_TAG('k', 'h', 'o', 0), // kho = Khotanese + TRUETYPE_TAG('k', 'h', 'p', 0), // khp = Kapori + TRUETYPE_TAG('k', 'h', 'q', 0), // khq = Koyra Chiini Songhay + TRUETYPE_TAG('k', 'h', 'r', 0), // khr = Kharia + TRUETYPE_TAG('k', 'h', 's', 0), // khs = Kasua + TRUETYPE_TAG('k', 'h', 't', 0), // kht = Khamti + TRUETYPE_TAG('k', 'h', 'u', 0), // khu = Nkhumbi + TRUETYPE_TAG('k', 'h', 'v', 0), // khv = Khvarshi + TRUETYPE_TAG('k', 'h', 'w', 0), // khw = Khowar + TRUETYPE_TAG('k', 'h', 'x', 0), // khx = Kanu + TRUETYPE_TAG('k', 'h', 'y', + 0), // khy = Kele (Democratic Republic of Congo) + TRUETYPE_TAG('k', 'h', 'z', 0), // khz = Keapara + TRUETYPE_TAG('k', 'i', 'a', 0), // kia = Kim + TRUETYPE_TAG('k', 'i', 'b', 0), // kib = Koalib + TRUETYPE_TAG('k', 'i', 'c', 0), // kic = Kickapoo + TRUETYPE_TAG('k', 'i', 'd', 0), // kid = Koshin + TRUETYPE_TAG('k', 'i', 'e', 0), // kie = Kibet + TRUETYPE_TAG('k', 'i', 'f', 0), // kif = Eastern Parbate Kham + TRUETYPE_TAG('k', 'i', 'g', 0), // kig = Kimaama + TRUETYPE_TAG('k', 'i', 'h', 0), // kih = Kilmeri + TRUETYPE_TAG('k', 'i', 'i', 0), // kii = Kitsai + TRUETYPE_TAG('k', 'i', 'j', 0), // kij = Kilivila + TRUETYPE_TAG('k', 'i', 'l', 0), // kil = Kariya + TRUETYPE_TAG('k', 'i', 'm', 0), // kim = Karagas + TRUETYPE_TAG('k', 'i', 'o', 0), // kio = Kiowa + TRUETYPE_TAG('k', 'i', 'p', 0), // kip = Sheshi Kham + TRUETYPE_TAG('k', 'i', 'q', 0), // kiq = Kosadle + TRUETYPE_TAG('k', 'i', 's', 0), // kis = Kis + TRUETYPE_TAG('k', 'i', 't', 0), // kit = Agob + TRUETYPE_TAG('k', 'i', 'u', 0), // kiu = Kirmanjki (individual language) + TRUETYPE_TAG('k', 'i', 'v', 0), // kiv = Kimbu + TRUETYPE_TAG('k', 'i', 'w', 0), // kiw = Northeast Kiwai + TRUETYPE_TAG('k', 'i', 'x', 0), // kix = Khiamniungan Naga + TRUETYPE_TAG('k', 'i', 'y', 0), // kiy = Kirikiri + TRUETYPE_TAG('k', 'i', 'z', 0), // kiz = Kisi + TRUETYPE_TAG('k', 'j', 'a', 0), // kja = Mlap + TRUETYPE_TAG('k', 'j', 'b', 0), // kjb = Q'anjob'al + TRUETYPE_TAG('k', 'j', 'c', 0), // kjc = Coastal Konjo + TRUETYPE_TAG('k', 'j', 'd', 0), // kjd = Southern Kiwai + TRUETYPE_TAG('k', 'j', 'e', 0), // kje = Kisar + TRUETYPE_TAG('k', 'j', 'f', 0), // kjf = Khalaj + TRUETYPE_TAG('k', 'j', 'g', 0), // kjg = Khmu + TRUETYPE_TAG('k', 'j', 'h', 0), // kjh = Khakas + TRUETYPE_TAG('k', 'j', 'i', 0), // kji = Zabana + TRUETYPE_TAG('k', 'j', 'j', 0), // kjj = Khinalugh + TRUETYPE_TAG('k', 'j', 'k', 0), // kjk = Highland Konjo + TRUETYPE_TAG('k', 'j', 'l', 0), // kjl = Western Parbate Kham + TRUETYPE_TAG('k', 'j', 'm', 0), // kjm = Kháng + TRUETYPE_TAG('k', 'j', 'n', 0), // kjn = Kunjen + TRUETYPE_TAG('k', 'j', 'o', 0), // kjo = Harijan Kinnauri + TRUETYPE_TAG('k', 'j', 'p', 0), // kjp = Pwo Eastern Karen + TRUETYPE_TAG('k', 'j', 'q', 0), // kjq = Western Keres + TRUETYPE_TAG('k', 'j', 'r', 0), // kjr = Kurudu + TRUETYPE_TAG('k', 'j', 's', 0), // kjs = East Kewa + TRUETYPE_TAG('k', 'j', 't', 0), // kjt = Phrae Pwo Karen + TRUETYPE_TAG('k', 'j', 'u', 0), // kju = Kashaya + TRUETYPE_TAG('k', 'j', 'x', 0), // kjx = Ramopa + TRUETYPE_TAG('k', 'j', 'y', 0), // kjy = Erave + TRUETYPE_TAG('k', 'j', 'z', 0), // kjz = Bumthangkha + TRUETYPE_TAG('k', 'k', 'a', 0), // kka = Kakanda + TRUETYPE_TAG('k', 'k', 'b', 0), // kkb = Kwerisa + TRUETYPE_TAG('k', 'k', 'c', 0), // kkc = Odoodee + TRUETYPE_TAG('k', 'k', 'd', 0), // kkd = Kinuku + TRUETYPE_TAG('k', 'k', 'e', 0), // kke = Kakabe + TRUETYPE_TAG('k', 'k', 'f', 0), // kkf = Kalaktang Monpa + TRUETYPE_TAG('k', 'k', 'g', 0), // kkg = Mabaka Valley Kalinga + TRUETYPE_TAG('k', 'k', 'h', 0), // kkh = Khün + TRUETYPE_TAG('k', 'k', 'i', 0), // kki = Kagulu + TRUETYPE_TAG('k', 'k', 'j', 0), // kkj = Kako + TRUETYPE_TAG('k', 'k', 'k', 0), // kkk = Kokota + TRUETYPE_TAG('k', 'k', 'l', 0), // kkl = Kosarek Yale + TRUETYPE_TAG('k', 'k', 'm', 0), // kkm = Kiong + TRUETYPE_TAG('k', 'k', 'n', 0), // kkn = Kon Keu + TRUETYPE_TAG('k', 'k', 'o', 0), // kko = Karko + TRUETYPE_TAG('k', 'k', 'p', 0), // kkp = Gugubera + TRUETYPE_TAG('k', 'k', 'q', 0), // kkq = Kaiku + TRUETYPE_TAG('k', 'k', 'r', 0), // kkr = Kir-Balar + TRUETYPE_TAG('k', 'k', 's', 0), // kks = Giiwo + TRUETYPE_TAG('k', 'k', 't', 0), // kkt = Koi + TRUETYPE_TAG('k', 'k', 'u', 0), // kku = Tumi + TRUETYPE_TAG('k', 'k', 'v', 0), // kkv = Kangean + TRUETYPE_TAG('k', 'k', 'w', 0), // kkw = Teke-Kukuya + TRUETYPE_TAG('k', 'k', 'x', 0), // kkx = Kohin + TRUETYPE_TAG('k', 'k', 'y', 0), // kky = Guguyimidjir + TRUETYPE_TAG('k', 'k', 'z', 0), // kkz = Kaska + TRUETYPE_TAG('k', 'l', 'a', 0), // kla = Klamath-Modoc + TRUETYPE_TAG('k', 'l', 'b', 0), // klb = Kiliwa + TRUETYPE_TAG('k', 'l', 'c', 0), // klc = Kolbila + TRUETYPE_TAG('k', 'l', 'd', 0), // kld = Gamilaraay + TRUETYPE_TAG('k', 'l', 'e', 0), // kle = Kulung (Nepal) + TRUETYPE_TAG('k', 'l', 'f', 0), // klf = Kendeje + TRUETYPE_TAG('k', 'l', 'g', 0), // klg = Tagakaulo + TRUETYPE_TAG('k', 'l', 'h', 0), // klh = Weliki + TRUETYPE_TAG('k', 'l', 'i', 0), // kli = Kalumpang + TRUETYPE_TAG('k', 'l', 'j', 0), // klj = Turkic Khalaj + TRUETYPE_TAG('k', 'l', 'k', 0), // klk = Kono (Nigeria) + TRUETYPE_TAG('k', 'l', 'l', 0), // kll = Kagan Kalagan + TRUETYPE_TAG('k', 'l', 'm', 0), // klm = Migum + TRUETYPE_TAG('k', 'l', 'n', 0), // kln = Kalenjin + TRUETYPE_TAG('k', 'l', 'o', 0), // klo = Kapya + TRUETYPE_TAG('k', 'l', 'p', 0), // klp = Kamasa + TRUETYPE_TAG('k', 'l', 'q', 0), // klq = Rumu + TRUETYPE_TAG('k', 'l', 'r', 0), // klr = Khaling + TRUETYPE_TAG('k', 'l', 's', 0), // kls = Kalasha + TRUETYPE_TAG('k', 'l', 't', 0), // klt = Nukna + TRUETYPE_TAG('k', 'l', 'u', 0), // klu = Klao + TRUETYPE_TAG('k', 'l', 'v', 0), // klv = Maskelynes + TRUETYPE_TAG('k', 'l', 'w', 0), // klw = Lindu + TRUETYPE_TAG('k', 'l', 'x', 0), // klx = Koluwawa + TRUETYPE_TAG('k', 'l', 'y', 0), // kly = Kalao + TRUETYPE_TAG('k', 'l', 'z', 0), // klz = Kabola + TRUETYPE_TAG('k', 'm', 'a', 0), // kma = Konni + TRUETYPE_TAG('k', 'm', 'b', 0), // kmb = Kimbundu + TRUETYPE_TAG('k', 'm', 'c', 0), // kmc = Southern Dong + TRUETYPE_TAG('k', 'm', 'd', 0), // kmd = Majukayang Kalinga + TRUETYPE_TAG('k', 'm', 'e', 0), // kme = Bakole + TRUETYPE_TAG('k', 'm', 'f', 0), // kmf = Kare (Papua New Guinea) + TRUETYPE_TAG('k', 'm', 'g', 0), // kmg = Kâte + TRUETYPE_TAG('k', 'm', 'h', 0), // kmh = Kalam + TRUETYPE_TAG('k', 'm', 'i', 0), // kmi = Kami (Nigeria) + TRUETYPE_TAG('k', 'm', 'j', 0), // kmj = Kumarbhag Paharia + TRUETYPE_TAG('k', 'm', 'k', 0), // kmk = Limos Kalinga + TRUETYPE_TAG('k', 'm', 'l', 0), // kml = Lower Tanudan Kalinga + TRUETYPE_TAG('k', 'm', 'm', 0), // kmm = Kom (India) + TRUETYPE_TAG('k', 'm', 'n', 0), // kmn = Awtuw + TRUETYPE_TAG('k', 'm', 'o', 0), // kmo = Kwoma + TRUETYPE_TAG('k', 'm', 'p', 0), // kmp = Gimme + TRUETYPE_TAG('k', 'm', 'q', 0), // kmq = Kwama + TRUETYPE_TAG('k', 'm', 'r', 0), // kmr = Northern Kurdish + TRUETYPE_TAG('k', 'm', 's', 0), // kms = Kamasau + TRUETYPE_TAG('k', 'm', 't', 0), // kmt = Kemtuik + TRUETYPE_TAG('k', 'm', 'u', 0), // kmu = Kanite + TRUETYPE_TAG('k', 'm', 'v', 0), // kmv = Karipúna Creole French + TRUETYPE_TAG('k', 'm', 'w', + 0), // kmw = Komo (Democratic Republic of Congo) + TRUETYPE_TAG('k', 'm', 'x', 0), // kmx = Waboda + TRUETYPE_TAG('k', 'm', 'y', 0), // kmy = Koma + TRUETYPE_TAG('k', 'm', 'z', 0), // kmz = Khorasani Turkish + TRUETYPE_TAG('k', 'n', 'a', 0), // kna = Dera (Nigeria) + TRUETYPE_TAG('k', 'n', 'b', 0), // knb = Lubuagan Kalinga + TRUETYPE_TAG('k', 'n', 'c', 0), // knc = Central Kanuri + TRUETYPE_TAG('k', 'n', 'd', 0), // knd = Konda + TRUETYPE_TAG('k', 'n', 'e', 0), // kne = Kankanaey + TRUETYPE_TAG('k', 'n', 'f', 0), // knf = Mankanya + TRUETYPE_TAG('k', 'n', 'g', 0), // kng = Koongo + TRUETYPE_TAG('k', 'n', 'i', 0), // kni = Kanufi + TRUETYPE_TAG('k', 'n', 'j', 0), // knj = Western Kanjobal + TRUETYPE_TAG('k', 'n', 'k', 0), // knk = Kuranko + TRUETYPE_TAG('k', 'n', 'l', 0), // knl = Keninjal + TRUETYPE_TAG('k', 'n', 'm', 0), // knm = Kanamarí + TRUETYPE_TAG('k', 'n', 'n', 0), // knn = Konkani (individual language) + TRUETYPE_TAG('k', 'n', 'o', 0), // kno = Kono (Sierra Leone) + TRUETYPE_TAG('k', 'n', 'p', 0), // knp = Kwanja + TRUETYPE_TAG('k', 'n', 'q', 0), // knq = Kintaq + TRUETYPE_TAG('k', 'n', 'r', 0), // knr = Kaningra + TRUETYPE_TAG('k', 'n', 's', 0), // kns = Kensiu + TRUETYPE_TAG('k', 'n', 't', 0), // knt = Panoan Katukína + TRUETYPE_TAG('k', 'n', 'u', 0), // knu = Kono (Guinea) + TRUETYPE_TAG('k', 'n', 'v', 0), // knv = Tabo + TRUETYPE_TAG('k', 'n', 'w', 0), // knw = Kung-Ekoka + TRUETYPE_TAG('k', 'n', 'x', 0), // knx = Kendayan + TRUETYPE_TAG('k', 'n', 'y', 0), // kny = Kanyok + TRUETYPE_TAG('k', 'n', 'z', 0), // knz = Kalamsé + TRUETYPE_TAG('k', 'o', 'a', 0), // koa = Konomala + TRUETYPE_TAG('k', 'o', 'c', 0), // koc = Kpati + TRUETYPE_TAG('k', 'o', 'd', 0), // kod = Kodi + TRUETYPE_TAG('k', 'o', 'e', 0), // koe = Kacipo-Balesi + TRUETYPE_TAG('k', 'o', 'f', 0), // kof = Kubi + TRUETYPE_TAG('k', 'o', 'g', 0), // kog = Cogui + TRUETYPE_TAG('k', 'o', 'h', 0), // koh = Koyo + TRUETYPE_TAG('k', 'o', 'i', 0), // koi = Komi-Permyak + TRUETYPE_TAG('k', 'o', 'j', 0), // koj = Sara Dunjo + TRUETYPE_TAG('k', 'o', 'k', 0), // kok = Konkani (macrolanguage) + TRUETYPE_TAG('k', 'o', 'l', 0), // kol = Kol (Papua New Guinea) + TRUETYPE_TAG('k', 'o', 'o', 0), // koo = Konzo + TRUETYPE_TAG('k', 'o', 'p', 0), // kop = Waube + TRUETYPE_TAG('k', 'o', 'q', 0), // koq = Kota (Gabon) + TRUETYPE_TAG('k', 'o', 's', 0), // kos = Kosraean + TRUETYPE_TAG('k', 'o', 't', 0), // kot = Lagwan + TRUETYPE_TAG('k', 'o', 'u', 0), // kou = Koke + TRUETYPE_TAG('k', 'o', 'v', 0), // kov = Kudu-Camo + TRUETYPE_TAG('k', 'o', 'w', 0), // kow = Kugama + TRUETYPE_TAG('k', 'o', 'x', 0), // kox = Coxima + TRUETYPE_TAG('k', 'o', 'y', 0), // koy = Koyukon + TRUETYPE_TAG('k', 'o', 'z', 0), // koz = Korak + TRUETYPE_TAG('k', 'p', 'a', 0), // kpa = Kutto + TRUETYPE_TAG('k', 'p', 'b', 0), // kpb = Mullu Kurumba + TRUETYPE_TAG('k', 'p', 'c', 0), // kpc = Curripaco + TRUETYPE_TAG('k', 'p', 'd', 0), // kpd = Koba + TRUETYPE_TAG('k', 'p', 'e', 0), // kpe = Kpelle + TRUETYPE_TAG('k', 'p', 'f', 0), // kpf = Komba + TRUETYPE_TAG('k', 'p', 'g', 0), // kpg = Kapingamarangi + TRUETYPE_TAG('k', 'p', 'h', 0), // kph = Kplang + TRUETYPE_TAG('k', 'p', 'i', 0), // kpi = Kofei + TRUETYPE_TAG('k', 'p', 'j', 0), // kpj = Karajá + TRUETYPE_TAG('k', 'p', 'k', 0), // kpk = Kpan + TRUETYPE_TAG('k', 'p', 'l', 0), // kpl = Kpala + TRUETYPE_TAG('k', 'p', 'm', 0), // kpm = Koho + TRUETYPE_TAG('k', 'p', 'n', 0), // kpn = Kepkiriwát + TRUETYPE_TAG('k', 'p', 'o', 0), // kpo = Ikposo + TRUETYPE_TAG('k', 'p', 'p', 0), // kpp = Paku Karen + TRUETYPE_TAG('k', 'p', 'q', 0), // kpq = Korupun-Sela + TRUETYPE_TAG('k', 'p', 'r', 0), // kpr = Korafe-Yegha + TRUETYPE_TAG('k', 'p', 's', 0), // kps = Tehit + TRUETYPE_TAG('k', 'p', 't', 0), // kpt = Karata + TRUETYPE_TAG('k', 'p', 'u', 0), // kpu = Kafoa + TRUETYPE_TAG('k', 'p', 'v', 0), // kpv = Komi-Zyrian + TRUETYPE_TAG('k', 'p', 'w', 0), // kpw = Kobon + TRUETYPE_TAG('k', 'p', 'x', 0), // kpx = Mountain Koiali + TRUETYPE_TAG('k', 'p', 'y', 0), // kpy = Koryak + TRUETYPE_TAG('k', 'p', 'z', 0), // kpz = Kupsabiny + TRUETYPE_TAG('k', 'q', 'a', 0), // kqa = Mum + TRUETYPE_TAG('k', 'q', 'b', 0), // kqb = Kovai + TRUETYPE_TAG('k', 'q', 'c', 0), // kqc = Doromu-Koki + TRUETYPE_TAG('k', 'q', 'd', 0), // kqd = Koy Sanjaq Surat + TRUETYPE_TAG('k', 'q', 'e', 0), // kqe = Kalagan + TRUETYPE_TAG('k', 'q', 'f', 0), // kqf = Kakabai + TRUETYPE_TAG('k', 'q', 'g', 0), // kqg = Khe + TRUETYPE_TAG('k', 'q', 'h', 0), // kqh = Kisankasa + TRUETYPE_TAG('k', 'q', 'i', 0), // kqi = Koitabu + TRUETYPE_TAG('k', 'q', 'j', 0), // kqj = Koromira + TRUETYPE_TAG('k', 'q', 'k', 0), // kqk = Kotafon Gbe + TRUETYPE_TAG('k', 'q', 'l', 0), // kql = Kyenele + TRUETYPE_TAG('k', 'q', 'm', 0), // kqm = Khisa + TRUETYPE_TAG('k', 'q', 'n', 0), // kqn = Kaonde + TRUETYPE_TAG('k', 'q', 'o', 0), // kqo = Eastern Krahn + TRUETYPE_TAG('k', 'q', 'p', 0), // kqp = Kimré + TRUETYPE_TAG('k', 'q', 'q', 0), // kqq = Krenak + TRUETYPE_TAG('k', 'q', 'r', 0), // kqr = Kimaragang + TRUETYPE_TAG('k', 'q', 's', 0), // kqs = Northern Kissi + TRUETYPE_TAG('k', 'q', 't', 0), // kqt = Klias River Kadazan + TRUETYPE_TAG('k', 'q', 'u', 0), // kqu = Seroa + TRUETYPE_TAG('k', 'q', 'v', 0), // kqv = Okolod + TRUETYPE_TAG('k', 'q', 'w', 0), // kqw = Kandas + TRUETYPE_TAG('k', 'q', 'x', 0), // kqx = Mser + TRUETYPE_TAG('k', 'q', 'y', 0), // kqy = Koorete + TRUETYPE_TAG('k', 'q', 'z', 0), // kqz = Korana + TRUETYPE_TAG('k', 'r', 'a', 0), // kra = Kumhali + TRUETYPE_TAG('k', 'r', 'b', 0), // krb = Karkin + TRUETYPE_TAG('k', 'r', 'c', 0), // krc = Karachay-Balkar + TRUETYPE_TAG('k', 'r', 'd', 0), // krd = Kairui-Midiki + TRUETYPE_TAG('k', 'r', 'e', 0), // kre = Panará + TRUETYPE_TAG('k', 'r', 'f', 0), // krf = Koro (Vanuatu) + TRUETYPE_TAG('k', 'r', 'h', 0), // krh = Kurama + TRUETYPE_TAG('k', 'r', 'i', 0), // kri = Krio + TRUETYPE_TAG('k', 'r', 'j', 0), // krj = Kinaray-A + TRUETYPE_TAG('k', 'r', 'k', 0), // krk = Kerek + TRUETYPE_TAG('k', 'r', 'l', 0), // krl = Karelian + TRUETYPE_TAG('k', 'r', 'm', 0), // krm = Krim + TRUETYPE_TAG('k', 'r', 'n', 0), // krn = Sapo + TRUETYPE_TAG('k', 'r', 'o', 0), // kro = Kru languages + TRUETYPE_TAG('k', 'r', 'p', 0), // krp = Korop + TRUETYPE_TAG('k', 'r', 'r', 0), // krr = Kru'ng 2 + TRUETYPE_TAG('k', 'r', 's', 0), // krs = Gbaya (Sudan) + TRUETYPE_TAG('k', 'r', 't', 0), // krt = Tumari Kanuri + TRUETYPE_TAG('k', 'r', 'u', 0), // kru = Kurukh + TRUETYPE_TAG('k', 'r', 'v', 0), // krv = Kavet + TRUETYPE_TAG('k', 'r', 'w', 0), // krw = Western Krahn + TRUETYPE_TAG('k', 'r', 'x', 0), // krx = Karon + TRUETYPE_TAG('k', 'r', 'y', 0), // kry = Kryts + TRUETYPE_TAG('k', 'r', 'z', 0), // krz = Sota Kanum + TRUETYPE_TAG('k', 's', 'a', 0), // ksa = Shuwa-Zamani + TRUETYPE_TAG('k', 's', 'b', 0), // ksb = Shambala + TRUETYPE_TAG('k', 's', 'c', 0), // ksc = Southern Kalinga + TRUETYPE_TAG('k', 's', 'd', 0), // ksd = Kuanua + TRUETYPE_TAG('k', 's', 'e', 0), // kse = Kuni + TRUETYPE_TAG('k', 's', 'f', 0), // ksf = Bafia + TRUETYPE_TAG('k', 's', 'g', 0), // ksg = Kusaghe + TRUETYPE_TAG('k', 's', 'h', 0), // ksh = Kölsch + TRUETYPE_TAG('k', 's', 'i', 0), // ksi = Krisa + TRUETYPE_TAG('k', 's', 'j', 0), // ksj = Uare + TRUETYPE_TAG('k', 's', 'k', 0), // ksk = Kansa + TRUETYPE_TAG('k', 's', 'l', 0), // ksl = Kumalu + TRUETYPE_TAG('k', 's', 'm', 0), // ksm = Kumba + TRUETYPE_TAG('k', 's', 'n', 0), // ksn = Kasiguranin + TRUETYPE_TAG('k', 's', 'o', 0), // kso = Kofa + TRUETYPE_TAG('k', 's', 'p', 0), // ksp = Kaba + TRUETYPE_TAG('k', 's', 'q', 0), // ksq = Kwaami + TRUETYPE_TAG('k', 's', 'r', 0), // ksr = Borong + TRUETYPE_TAG('k', 's', 's', 0), // kss = Southern Kisi + TRUETYPE_TAG('k', 's', 't', 0), // kst = Winyé + TRUETYPE_TAG('k', 's', 'u', 0), // ksu = Khamyang + TRUETYPE_TAG('k', 's', 'v', 0), // ksv = Kusu + TRUETYPE_TAG('k', 's', 'w', 0), // ksw = S'gaw Karen + TRUETYPE_TAG('k', 's', 'x', 0), // ksx = Kedang + TRUETYPE_TAG('k', 's', 'y', 0), // ksy = Kharia Thar + TRUETYPE_TAG('k', 's', 'z', 0), // ksz = Kodaku + TRUETYPE_TAG('k', 't', 'a', 0), // kta = Katua + TRUETYPE_TAG('k', 't', 'b', 0), // ktb = Kambaata + TRUETYPE_TAG('k', 't', 'c', 0), // ktc = Kholok + TRUETYPE_TAG('k', 't', 'd', 0), // ktd = Kokata + TRUETYPE_TAG('k', 't', 'e', 0), // kte = Nubri + TRUETYPE_TAG('k', 't', 'f', 0), // ktf = Kwami + TRUETYPE_TAG('k', 't', 'g', 0), // ktg = Kalkutung + TRUETYPE_TAG('k', 't', 'h', 0), // kth = Karanga + TRUETYPE_TAG('k', 't', 'i', 0), // kti = North Muyu + TRUETYPE_TAG('k', 't', 'j', 0), // ktj = Plapo Krumen + TRUETYPE_TAG('k', 't', 'k', 0), // ktk = Kaniet + TRUETYPE_TAG('k', 't', 'l', 0), // ktl = Koroshi + TRUETYPE_TAG('k', 't', 'm', 0), // ktm = Kurti + TRUETYPE_TAG('k', 't', 'n', 0), // ktn = Karitiâna + TRUETYPE_TAG('k', 't', 'o', 0), // kto = Kuot + TRUETYPE_TAG('k', 't', 'p', 0), // ktp = Kaduo + TRUETYPE_TAG('k', 't', 'q', 0), // ktq = Katabaga + TRUETYPE_TAG('k', 't', 'r', 0), // ktr = Kota Marudu Tinagas + TRUETYPE_TAG('k', 't', 's', 0), // kts = South Muyu + TRUETYPE_TAG('k', 't', 't', 0), // ktt = Ketum + TRUETYPE_TAG('k', 't', 'u', + 0), // ktu = Kituba (Democratic Republic of Congo) + TRUETYPE_TAG('k', 't', 'v', 0), // ktv = Eastern Katu + TRUETYPE_TAG('k', 't', 'w', 0), // ktw = Kato + TRUETYPE_TAG('k', 't', 'x', 0), // ktx = Kaxararí + TRUETYPE_TAG('k', 't', 'y', 0), // kty = Kango (Bas-Uélé District) + TRUETYPE_TAG('k', 't', 'z', 0), // ktz = Ju/'hoan + TRUETYPE_TAG('k', 'u', 'b', 0), // kub = Kutep + TRUETYPE_TAG('k', 'u', 'c', 0), // kuc = Kwinsu + TRUETYPE_TAG('k', 'u', 'd', 0), // kud = 'Auhelawa + TRUETYPE_TAG('k', 'u', 'e', 0), // kue = Kuman + TRUETYPE_TAG('k', 'u', 'f', 0), // kuf = Western Katu + TRUETYPE_TAG('k', 'u', 'g', 0), // kug = Kupa + TRUETYPE_TAG('k', 'u', 'h', 0), // kuh = Kushi + TRUETYPE_TAG('k', 'u', 'i', 0), // kui = Kuikúro-Kalapálo + TRUETYPE_TAG('k', 'u', 'j', 0), // kuj = Kuria + TRUETYPE_TAG('k', 'u', 'k', 0), // kuk = Kepo' + TRUETYPE_TAG('k', 'u', 'l', 0), // kul = Kulere + TRUETYPE_TAG('k', 'u', 'm', 0), // kum = Kumyk + TRUETYPE_TAG('k', 'u', 'n', 0), // kun = Kunama + TRUETYPE_TAG('k', 'u', 'o', 0), // kuo = Kumukio + TRUETYPE_TAG('k', 'u', 'p', 0), // kup = Kunimaipa + TRUETYPE_TAG('k', 'u', 'q', 0), // kuq = Karipuna + TRUETYPE_TAG('k', 'u', 's', 0), // kus = Kusaal + TRUETYPE_TAG('k', 'u', 't', 0), // kut = Kutenai + TRUETYPE_TAG('k', 'u', 'u', 0), // kuu = Upper Kuskokwim + TRUETYPE_TAG('k', 'u', 'v', 0), // kuv = Kur + TRUETYPE_TAG('k', 'u', 'w', 0), // kuw = Kpagua + TRUETYPE_TAG('k', 'u', 'x', 0), // kux = Kukatja + TRUETYPE_TAG('k', 'u', 'y', 0), // kuy = Kuuku-Ya'u + TRUETYPE_TAG('k', 'u', 'z', 0), // kuz = Kunza + TRUETYPE_TAG('k', 'v', 'a', 0), // kva = Bagvalal + TRUETYPE_TAG('k', 'v', 'b', 0), // kvb = Kubu + TRUETYPE_TAG('k', 'v', 'c', 0), // kvc = Kove + TRUETYPE_TAG('k', 'v', 'd', 0), // kvd = Kui (Indonesia) + TRUETYPE_TAG('k', 'v', 'e', 0), // kve = Kalabakan + TRUETYPE_TAG('k', 'v', 'f', 0), // kvf = Kabalai + TRUETYPE_TAG('k', 'v', 'g', 0), // kvg = Kuni-Boazi + TRUETYPE_TAG('k', 'v', 'h', 0), // kvh = Komodo + TRUETYPE_TAG('k', 'v', 'i', 0), // kvi = Kwang + TRUETYPE_TAG('k', 'v', 'j', 0), // kvj = Psikye + TRUETYPE_TAG('k', 'v', 'k', 0), // kvk = Korean Sign Language + TRUETYPE_TAG('k', 'v', 'l', 0), // kvl = Brek Karen + TRUETYPE_TAG('k', 'v', 'm', 0), // kvm = Kendem + TRUETYPE_TAG('k', 'v', 'n', 0), // kvn = Border Kuna + TRUETYPE_TAG('k', 'v', 'o', 0), // kvo = Dobel + TRUETYPE_TAG('k', 'v', 'p', 0), // kvp = Kompane + TRUETYPE_TAG('k', 'v', 'q', 0), // kvq = Geba Karen + TRUETYPE_TAG('k', 'v', 'r', 0), // kvr = Kerinci + TRUETYPE_TAG('k', 'v', 's', 0), // kvs = Kunggara + TRUETYPE_TAG('k', 'v', 't', 0), // kvt = Lahta Karen + TRUETYPE_TAG('k', 'v', 'u', 0), // kvu = Yinbaw Karen + TRUETYPE_TAG('k', 'v', 'v', 0), // kvv = Kola + TRUETYPE_TAG('k', 'v', 'w', 0), // kvw = Wersing + TRUETYPE_TAG('k', 'v', 'x', 0), // kvx = Parkari Koli + TRUETYPE_TAG('k', 'v', 'y', 0), // kvy = Yintale Karen + TRUETYPE_TAG('k', 'v', 'z', 0), // kvz = Tsakwambo + TRUETYPE_TAG('k', 'w', 'a', 0), // kwa = Dâw + TRUETYPE_TAG('k', 'w', 'b', 0), // kwb = Kwa + TRUETYPE_TAG('k', 'w', 'c', 0), // kwc = Likwala + TRUETYPE_TAG('k', 'w', 'd', 0), // kwd = Kwaio + TRUETYPE_TAG('k', 'w', 'e', 0), // kwe = Kwerba + TRUETYPE_TAG('k', 'w', 'f', 0), // kwf = Kwara'ae + TRUETYPE_TAG('k', 'w', 'g', 0), // kwg = Sara Kaba Deme + TRUETYPE_TAG('k', 'w', 'h', 0), // kwh = Kowiai + TRUETYPE_TAG('k', 'w', 'i', 0), // kwi = Awa-Cuaiquer + TRUETYPE_TAG('k', 'w', 'j', 0), // kwj = Kwanga + TRUETYPE_TAG('k', 'w', 'k', 0), // kwk = Kwakiutl + TRUETYPE_TAG('k', 'w', 'l', 0), // kwl = Kofyar + TRUETYPE_TAG('k', 'w', 'm', 0), // kwm = Kwambi + TRUETYPE_TAG('k', 'w', 'n', 0), // kwn = Kwangali + TRUETYPE_TAG('k', 'w', 'o', 0), // kwo = Kwomtari + TRUETYPE_TAG('k', 'w', 'p', 0), // kwp = Kodia + TRUETYPE_TAG('k', 'w', 'q', 0), // kwq = Kwak + TRUETYPE_TAG('k', 'w', 'r', 0), // kwr = Kwer + TRUETYPE_TAG('k', 'w', 's', 0), // kws = Kwese + TRUETYPE_TAG('k', 'w', 't', 0), // kwt = Kwesten + TRUETYPE_TAG('k', 'w', 'u', 0), // kwu = Kwakum + TRUETYPE_TAG('k', 'w', 'v', 0), // kwv = Sara Kaba Náà + TRUETYPE_TAG('k', 'w', 'w', 0), // kww = Kwinti + TRUETYPE_TAG('k', 'w', 'x', 0), // kwx = Khirwar + TRUETYPE_TAG('k', 'w', 'y', 0), // kwy = San Salvador Kongo + TRUETYPE_TAG('k', 'w', 'z', 0), // kwz = Kwadi + TRUETYPE_TAG('k', 'x', 'a', 0), // kxa = Kairiru + TRUETYPE_TAG('k', 'x', 'b', 0), // kxb = Krobu + TRUETYPE_TAG('k', 'x', 'c', 0), // kxc = Konso + TRUETYPE_TAG('k', 'x', 'd', 0), // kxd = Brunei + TRUETYPE_TAG('k', 'x', 'e', 0), // kxe = Kakihum + TRUETYPE_TAG('k', 'x', 'f', 0), // kxf = Manumanaw Karen + TRUETYPE_TAG('k', 'x', 'h', 0), // kxh = Karo (Ethiopia) + TRUETYPE_TAG('k', 'x', 'i', 0), // kxi = Keningau Murut + TRUETYPE_TAG('k', 'x', 'j', 0), // kxj = Kulfa + TRUETYPE_TAG('k', 'x', 'k', 0), // kxk = Zayein Karen + TRUETYPE_TAG('k', 'x', 'l', 0), // kxl = Nepali Kurux + TRUETYPE_TAG('k', 'x', 'm', 0), // kxm = Northern Khmer + TRUETYPE_TAG('k', 'x', 'n', 0), // kxn = Kanowit-Tanjong Melanau + TRUETYPE_TAG('k', 'x', 'o', 0), // kxo = Kanoé + TRUETYPE_TAG('k', 'x', 'p', 0), // kxp = Wadiyara Koli + TRUETYPE_TAG('k', 'x', 'q', 0), // kxq = Smärky Kanum + TRUETYPE_TAG('k', 'x', 'r', 0), // kxr = Koro (Papua New Guinea) + TRUETYPE_TAG('k', 'x', 's', 0), // kxs = Kangjia + TRUETYPE_TAG('k', 'x', 't', 0), // kxt = Koiwat + TRUETYPE_TAG('k', 'x', 'u', 0), // kxu = Kui (India) + TRUETYPE_TAG('k', 'x', 'v', 0), // kxv = Kuvi + TRUETYPE_TAG('k', 'x', 'w', 0), // kxw = Konai + TRUETYPE_TAG('k', 'x', 'x', 0), // kxx = Likuba + TRUETYPE_TAG('k', 'x', 'y', 0), // kxy = Kayong + TRUETYPE_TAG('k', 'x', 'z', 0), // kxz = Kerewo + TRUETYPE_TAG('k', 'y', 'a', 0), // kya = Kwaya + TRUETYPE_TAG('k', 'y', 'b', 0), // kyb = Butbut Kalinga + TRUETYPE_TAG('k', 'y', 'c', 0), // kyc = Kyaka + TRUETYPE_TAG('k', 'y', 'd', 0), // kyd = Karey + TRUETYPE_TAG('k', 'y', 'e', 0), // kye = Krache + TRUETYPE_TAG('k', 'y', 'f', 0), // kyf = Kouya + TRUETYPE_TAG('k', 'y', 'g', 0), // kyg = Keyagana + TRUETYPE_TAG('k', 'y', 'h', 0), // kyh = Karok + TRUETYPE_TAG('k', 'y', 'i', 0), // kyi = Kiput + TRUETYPE_TAG('k', 'y', 'j', 0), // kyj = Karao + TRUETYPE_TAG('k', 'y', 'k', 0), // kyk = Kamayo + TRUETYPE_TAG('k', 'y', 'l', 0), // kyl = Kalapuya + TRUETYPE_TAG('k', 'y', 'm', 0), // kym = Kpatili + TRUETYPE_TAG('k', 'y', 'n', 0), // kyn = Northern Binukidnon + TRUETYPE_TAG('k', 'y', 'o', 0), // kyo = Kelon + TRUETYPE_TAG('k', 'y', 'p', 0), // kyp = Kang + TRUETYPE_TAG('k', 'y', 'q', 0), // kyq = Kenga + TRUETYPE_TAG('k', 'y', 'r', 0), // kyr = Kuruáya + TRUETYPE_TAG('k', 'y', 's', 0), // kys = Baram Kayan + TRUETYPE_TAG('k', 'y', 't', 0), // kyt = Kayagar + TRUETYPE_TAG('k', 'y', 'u', 0), // kyu = Western Kayah + TRUETYPE_TAG('k', 'y', 'v', 0), // kyv = Kayort + TRUETYPE_TAG('k', 'y', 'w', 0), // kyw = Kudmali + TRUETYPE_TAG('k', 'y', 'x', 0), // kyx = Rapoisi + TRUETYPE_TAG('k', 'y', 'y', 0), // kyy = Kambaira + TRUETYPE_TAG('k', 'y', 'z', 0), // kyz = Kayabí + TRUETYPE_TAG('k', 'z', 'a', 0), // kza = Western Karaboro + TRUETYPE_TAG('k', 'z', 'b', 0), // kzb = Kaibobo + TRUETYPE_TAG('k', 'z', 'c', 0), // kzc = Bondoukou Kulango + TRUETYPE_TAG('k', 'z', 'd', 0), // kzd = Kadai + TRUETYPE_TAG('k', 'z', 'e', 0), // kze = Kosena + TRUETYPE_TAG('k', 'z', 'f', 0), // kzf = Da'a Kaili + TRUETYPE_TAG('k', 'z', 'g', 0), // kzg = Kikai + TRUETYPE_TAG('k', 'z', 'h', 0), // kzh = Kenuzi-Dongola + TRUETYPE_TAG('k', 'z', 'i', 0), // kzi = Kelabit + TRUETYPE_TAG('k', 'z', 'j', 0), // kzj = Coastal Kadazan + TRUETYPE_TAG('k', 'z', 'k', 0), // kzk = Kazukuru + TRUETYPE_TAG('k', 'z', 'l', 0), // kzl = Kayeli + TRUETYPE_TAG('k', 'z', 'm', 0), // kzm = Kais + TRUETYPE_TAG('k', 'z', 'n', 0), // kzn = Kokola + TRUETYPE_TAG('k', 'z', 'o', 0), // kzo = Kaningi + TRUETYPE_TAG('k', 'z', 'p', 0), // kzp = Kaidipang + TRUETYPE_TAG('k', 'z', 'q', 0), // kzq = Kaike + TRUETYPE_TAG('k', 'z', 'r', 0), // kzr = Karang + TRUETYPE_TAG('k', 'z', 's', 0), // kzs = Sugut Dusun + TRUETYPE_TAG('k', 'z', 't', 0), // kzt = Tambunan Dusun + TRUETYPE_TAG('k', 'z', 'u', 0), // kzu = Kayupulau + TRUETYPE_TAG('k', 'z', 'v', 0), // kzv = Komyandaret + TRUETYPE_TAG('k', 'z', 'w', 0), // kzw = Karirí-Xocó + TRUETYPE_TAG('k', 'z', 'x', 0), // kzx = Kamarian + TRUETYPE_TAG('k', 'z', 'y', 0), // kzy = Kango (Tshopo District) + TRUETYPE_TAG('k', 'z', 'z', 0), // kzz = Kalabra + TRUETYPE_TAG('l', 'a', 'a', 0), // laa = Southern Subanen + TRUETYPE_TAG('l', 'a', 'b', 0), // lab = Linear A + TRUETYPE_TAG('l', 'a', 'c', 0), // lac = Lacandon + TRUETYPE_TAG('l', 'a', 'd', 0), // lad = Ladino + TRUETYPE_TAG('l', 'a', 'e', 0), // lae = Pattani + TRUETYPE_TAG('l', 'a', 'f', 0), // laf = Lafofa + TRUETYPE_TAG('l', 'a', 'g', 0), // lag = Langi + TRUETYPE_TAG('l', 'a', 'h', 0), // lah = Lahnda + TRUETYPE_TAG('l', 'a', 'i', 0), // lai = Lambya + TRUETYPE_TAG('l', 'a', 'j', 0), // laj = Lango (Uganda) + TRUETYPE_TAG('l', 'a', 'k', 0), // lak = Laka (Nigeria) + TRUETYPE_TAG('l', 'a', 'l', 0), // lal = Lalia + TRUETYPE_TAG('l', 'a', 'm', 0), // lam = Lamba + TRUETYPE_TAG('l', 'a', 'n', 0), // lan = Laru + TRUETYPE_TAG('l', 'a', 'p', 0), // lap = Laka (Chad) + TRUETYPE_TAG('l', 'a', 'q', 0), // laq = Qabiao + TRUETYPE_TAG('l', 'a', 'r', 0), // lar = Larteh + TRUETYPE_TAG('l', 'a', 's', 0), // las = Lama (Togo) + TRUETYPE_TAG('l', 'a', 'u', 0), // lau = Laba + TRUETYPE_TAG('l', 'a', 'w', 0), // law = Lauje + TRUETYPE_TAG('l', 'a', 'x', 0), // lax = Tiwa + TRUETYPE_TAG('l', 'a', 'y', 0), // lay = Lama (Myanmar) + TRUETYPE_TAG('l', 'a', 'z', 0), // laz = Aribwatsa + TRUETYPE_TAG('l', 'b', 'a', 0), // lba = Lui + TRUETYPE_TAG('l', 'b', 'b', 0), // lbb = Label + TRUETYPE_TAG('l', 'b', 'c', 0), // lbc = Lakkia + TRUETYPE_TAG('l', 'b', 'e', 0), // lbe = Lak + TRUETYPE_TAG('l', 'b', 'f', 0), // lbf = Tinani + TRUETYPE_TAG('l', 'b', 'g', 0), // lbg = Laopang + TRUETYPE_TAG('l', 'b', 'i', 0), // lbi = La'bi + TRUETYPE_TAG('l', 'b', 'j', 0), // lbj = Ladakhi + TRUETYPE_TAG('l', 'b', 'k', 0), // lbk = Central Bontok + TRUETYPE_TAG('l', 'b', 'l', 0), // lbl = Libon Bikol + TRUETYPE_TAG('l', 'b', 'm', 0), // lbm = Lodhi + TRUETYPE_TAG('l', 'b', 'n', 0), // lbn = Lamet + TRUETYPE_TAG('l', 'b', 'o', 0), // lbo = Laven + TRUETYPE_TAG('l', 'b', 'q', 0), // lbq = Wampar + TRUETYPE_TAG('l', 'b', 'r', 0), // lbr = Northern Lorung + TRUETYPE_TAG('l', 'b', 's', 0), // lbs = Libyan Sign Language + TRUETYPE_TAG('l', 'b', 't', 0), // lbt = Lachi + TRUETYPE_TAG('l', 'b', 'u', 0), // lbu = Labu + TRUETYPE_TAG('l', 'b', 'v', 0), // lbv = Lavatbura-Lamusong + TRUETYPE_TAG('l', 'b', 'w', 0), // lbw = Tolaki + TRUETYPE_TAG('l', 'b', 'x', 0), // lbx = Lawangan + TRUETYPE_TAG('l', 'b', 'y', 0), // lby = Lamu-Lamu + TRUETYPE_TAG('l', 'b', 'z', 0), // lbz = Lardil + TRUETYPE_TAG('l', 'c', 'c', 0), // lcc = Legenyem + TRUETYPE_TAG('l', 'c', 'd', 0), // lcd = Lola + TRUETYPE_TAG('l', 'c', 'e', 0), // lce = Loncong + TRUETYPE_TAG('l', 'c', 'f', 0), // lcf = Lubu + TRUETYPE_TAG('l', 'c', 'h', 0), // lch = Luchazi + TRUETYPE_TAG('l', 'c', 'l', 0), // lcl = Lisela + TRUETYPE_TAG('l', 'c', 'm', 0), // lcm = Tungag + TRUETYPE_TAG('l', 'c', 'p', 0), // lcp = Western Lawa + TRUETYPE_TAG('l', 'c', 'q', 0), // lcq = Luhu + TRUETYPE_TAG('l', 'c', 's', 0), // lcs = Lisabata-Nuniali + TRUETYPE_TAG('l', 'd', 'b', 0), // ldb = Idun + TRUETYPE_TAG('l', 'd', 'd', 0), // ldd = Luri + TRUETYPE_TAG('l', 'd', 'g', 0), // ldg = Lenyima + TRUETYPE_TAG('l', 'd', 'h', 0), // ldh = Lamja-Dengsa-Tola + TRUETYPE_TAG('l', 'd', 'i', 0), // ldi = Laari + TRUETYPE_TAG('l', 'd', 'j', 0), // ldj = Lemoro + TRUETYPE_TAG('l', 'd', 'k', 0), // ldk = Leelau + TRUETYPE_TAG('l', 'd', 'l', 0), // ldl = Kaan + TRUETYPE_TAG('l', 'd', 'm', 0), // ldm = Landoma + TRUETYPE_TAG('l', 'd', 'n', 0), // ldn = Láadan + TRUETYPE_TAG('l', 'd', 'o', 0), // ldo = Loo + TRUETYPE_TAG('l', 'd', 'p', 0), // ldp = Tso + TRUETYPE_TAG('l', 'd', 'q', 0), // ldq = Lufu + TRUETYPE_TAG('l', 'e', 'a', 0), // lea = Lega-Shabunda + TRUETYPE_TAG('l', 'e', 'b', 0), // leb = Lala-Bisa + TRUETYPE_TAG('l', 'e', 'c', 0), // lec = Leco + TRUETYPE_TAG('l', 'e', 'd', 0), // led = Lendu + TRUETYPE_TAG('l', 'e', 'e', 0), // lee = Lyélé + TRUETYPE_TAG('l', 'e', 'f', 0), // lef = Lelemi + TRUETYPE_TAG('l', 'e', 'g', 0), // leg = Lengua + TRUETYPE_TAG('l', 'e', 'h', 0), // leh = Lenje + TRUETYPE_TAG('l', 'e', 'i', 0), // lei = Lemio + TRUETYPE_TAG('l', 'e', 'j', 0), // lej = Lengola + TRUETYPE_TAG('l', 'e', 'k', 0), // lek = Leipon + TRUETYPE_TAG('l', 'e', 'l', + 0), // lel = Lele (Democratic Republic of Congo) + TRUETYPE_TAG('l', 'e', 'm', 0), // lem = Nomaande + TRUETYPE_TAG('l', 'e', 'n', 0), // len = Lenca + TRUETYPE_TAG('l', 'e', 'o', 0), // leo = Leti (Cameroon) + TRUETYPE_TAG('l', 'e', 'p', 0), // lep = Lepcha + TRUETYPE_TAG('l', 'e', 'q', 0), // leq = Lembena + TRUETYPE_TAG('l', 'e', 'r', 0), // ler = Lenkau + TRUETYPE_TAG('l', 'e', 's', 0), // les = Lese + TRUETYPE_TAG('l', 'e', 't', 0), // let = Lesing-Gelimi + TRUETYPE_TAG('l', 'e', 'u', 0), // leu = Kara (Papua New Guinea) + TRUETYPE_TAG('l', 'e', 'v', 0), // lev = Lamma + TRUETYPE_TAG('l', 'e', 'w', 0), // lew = Ledo Kaili + TRUETYPE_TAG('l', 'e', 'x', 0), // lex = Luang + TRUETYPE_TAG('l', 'e', 'y', 0), // ley = Lemolang + TRUETYPE_TAG('l', 'e', 'z', 0), // lez = Lezghian + TRUETYPE_TAG('l', 'f', 'a', 0), // lfa = Lefa + TRUETYPE_TAG('l', 'f', 'n', 0), // lfn = Lingua Franca Nova + TRUETYPE_TAG('l', 'g', 'a', 0), // lga = Lungga + TRUETYPE_TAG('l', 'g', 'b', 0), // lgb = Laghu + TRUETYPE_TAG('l', 'g', 'g', 0), // lgg = Lugbara + TRUETYPE_TAG('l', 'g', 'h', 0), // lgh = Laghuu + TRUETYPE_TAG('l', 'g', 'i', 0), // lgi = Lengilu + TRUETYPE_TAG('l', 'g', 'k', 0), // lgk = Lingarak + TRUETYPE_TAG('l', 'g', 'l', 0), // lgl = Wala + TRUETYPE_TAG('l', 'g', 'm', 0), // lgm = Lega-Mwenga + TRUETYPE_TAG('l', 'g', 'n', 0), // lgn = Opuuo + TRUETYPE_TAG('l', 'g', 'q', 0), // lgq = Logba + TRUETYPE_TAG('l', 'g', 'r', 0), // lgr = Lengo + TRUETYPE_TAG('l', 'g', 't', 0), // lgt = Pahi + TRUETYPE_TAG('l', 'g', 'u', 0), // lgu = Longgu + TRUETYPE_TAG('l', 'g', 'z', 0), // lgz = Ligenza + TRUETYPE_TAG('l', 'h', 'a', 0), // lha = Laha (Viet Nam) + TRUETYPE_TAG('l', 'h', 'h', 0), // lhh = Laha (Indonesia) + TRUETYPE_TAG('l', 'h', 'i', 0), // lhi = Lahu Shi + TRUETYPE_TAG('l', 'h', 'l', 0), // lhl = Lahul Lohar + TRUETYPE_TAG('l', 'h', 'm', 0), // lhm = Lhomi + TRUETYPE_TAG('l', 'h', 'n', 0), // lhn = Lahanan + TRUETYPE_TAG('l', 'h', 'p', 0), // lhp = Lhokpu + TRUETYPE_TAG('l', 'h', 's', 0), // lhs = Mlahsö + TRUETYPE_TAG('l', 'h', 't', 0), // lht = Lo-Toga + TRUETYPE_TAG('l', 'h', 'u', 0), // lhu = Lahu + TRUETYPE_TAG('l', 'i', 'a', 0), // lia = West-Central Limba + TRUETYPE_TAG('l', 'i', 'b', 0), // lib = Likum + TRUETYPE_TAG('l', 'i', 'c', 0), // lic = Hlai + TRUETYPE_TAG('l', 'i', 'd', 0), // lid = Nyindrou + TRUETYPE_TAG('l', 'i', 'e', 0), // lie = Likila + TRUETYPE_TAG('l', 'i', 'f', 0), // lif = Limbu + TRUETYPE_TAG('l', 'i', 'g', 0), // lig = Ligbi + TRUETYPE_TAG('l', 'i', 'h', 0), // lih = Lihir + TRUETYPE_TAG('l', 'i', 'i', 0), // lii = Lingkhim + TRUETYPE_TAG('l', 'i', 'j', 0), // lij = Ligurian + TRUETYPE_TAG('l', 'i', 'k', 0), // lik = Lika + TRUETYPE_TAG('l', 'i', 'l', 0), // lil = Lillooet + TRUETYPE_TAG('l', 'i', 'o', 0), // lio = Liki + TRUETYPE_TAG('l', 'i', 'p', 0), // lip = Sekpele + TRUETYPE_TAG('l', 'i', 'q', 0), // liq = Libido + TRUETYPE_TAG('l', 'i', 'r', 0), // lir = Liberian English + TRUETYPE_TAG('l', 'i', 's', 0), // lis = Lisu + TRUETYPE_TAG('l', 'i', 'u', 0), // liu = Logorik + TRUETYPE_TAG('l', 'i', 'v', 0), // liv = Liv + TRUETYPE_TAG('l', 'i', 'w', 0), // liw = Col + TRUETYPE_TAG('l', 'i', 'x', 0), // lix = Liabuku + TRUETYPE_TAG('l', 'i', 'y', 0), // liy = Banda-Bambari + TRUETYPE_TAG('l', 'i', 'z', 0), // liz = Libinza + TRUETYPE_TAG('l', 'j', 'e', 0), // lje = Rampi + TRUETYPE_TAG('l', 'j', 'i', 0), // lji = Laiyolo + TRUETYPE_TAG('l', 'j', 'l', 0), // ljl = Li'o + TRUETYPE_TAG('l', 'j', 'p', 0), // ljp = Lampung Api + TRUETYPE_TAG('l', 'k', 'a', 0), // lka = Lakalei + TRUETYPE_TAG('l', 'k', 'b', 0), // lkb = Kabras + TRUETYPE_TAG('l', 'k', 'c', 0), // lkc = Kucong + TRUETYPE_TAG('l', 'k', 'd', 0), // lkd = Lakondê + TRUETYPE_TAG('l', 'k', 'e', 0), // lke = Kenyi + TRUETYPE_TAG('l', 'k', 'h', 0), // lkh = Lakha + TRUETYPE_TAG('l', 'k', 'i', 0), // lki = Laki + TRUETYPE_TAG('l', 'k', 'j', 0), // lkj = Remun + TRUETYPE_TAG('l', 'k', 'l', 0), // lkl = Laeko-Libuat + TRUETYPE_TAG('l', 'k', 'n', 0), // lkn = Lakon + TRUETYPE_TAG('l', 'k', 'o', 0), // lko = Khayo + TRUETYPE_TAG('l', 'k', 'r', 0), // lkr = Päri + TRUETYPE_TAG('l', 'k', 's', 0), // lks = Kisa + TRUETYPE_TAG('l', 'k', 't', 0), // lkt = Lakota + TRUETYPE_TAG('l', 'k', 'y', 0), // lky = Lokoya + TRUETYPE_TAG('l', 'l', 'a', 0), // lla = Lala-Roba + TRUETYPE_TAG('l', 'l', 'b', 0), // llb = Lolo + TRUETYPE_TAG('l', 'l', 'c', 0), // llc = Lele (Guinea) + TRUETYPE_TAG('l', 'l', 'd', 0), // lld = Ladin + TRUETYPE_TAG('l', 'l', 'e', 0), // lle = Lele (Papua New Guinea) + TRUETYPE_TAG('l', 'l', 'f', 0), // llf = Hermit + TRUETYPE_TAG('l', 'l', 'g', 0), // llg = Lole + TRUETYPE_TAG('l', 'l', 'h', 0), // llh = Lamu + TRUETYPE_TAG('l', 'l', 'i', 0), // lli = Teke-Laali + TRUETYPE_TAG('l', 'l', 'k', 0), // llk = Lelak + TRUETYPE_TAG('l', 'l', 'l', 0), // lll = Lilau + TRUETYPE_TAG('l', 'l', 'm', 0), // llm = Lasalimu + TRUETYPE_TAG('l', 'l', 'n', 0), // lln = Lele (Chad) + TRUETYPE_TAG('l', 'l', 'o', 0), // llo = Khlor + TRUETYPE_TAG('l', 'l', 'p', 0), // llp = North Efate + TRUETYPE_TAG('l', 'l', 'q', 0), // llq = Lolak + TRUETYPE_TAG('l', 'l', 's', 0), // lls = Lithuanian Sign Language + TRUETYPE_TAG('l', 'l', 'u', 0), // llu = Lau + TRUETYPE_TAG('l', 'l', 'x', 0), // llx = Lauan + TRUETYPE_TAG('l', 'm', 'a', 0), // lma = East Limba + TRUETYPE_TAG('l', 'm', 'b', 0), // lmb = Merei + TRUETYPE_TAG('l', 'm', 'c', 0), // lmc = Limilngan + TRUETYPE_TAG('l', 'm', 'd', 0), // lmd = Lumun + TRUETYPE_TAG('l', 'm', 'e', 0), // lme = Pévé + TRUETYPE_TAG('l', 'm', 'f', 0), // lmf = South Lembata + TRUETYPE_TAG('l', 'm', 'g', 0), // lmg = Lamogai + TRUETYPE_TAG('l', 'm', 'h', 0), // lmh = Lambichhong + TRUETYPE_TAG('l', 'm', 'i', 0), // lmi = Lombi + TRUETYPE_TAG('l', 'm', 'j', 0), // lmj = West Lembata + TRUETYPE_TAG('l', 'm', 'k', 0), // lmk = Lamkang + TRUETYPE_TAG('l', 'm', 'l', 0), // lml = Hano + TRUETYPE_TAG('l', 'm', 'm', 0), // lmm = Lamam + TRUETYPE_TAG('l', 'm', 'n', 0), // lmn = Lambadi + TRUETYPE_TAG('l', 'm', 'o', 0), // lmo = Lombard + TRUETYPE_TAG('l', 'm', 'p', 0), // lmp = Limbum + TRUETYPE_TAG('l', 'm', 'q', 0), // lmq = Lamatuka + TRUETYPE_TAG('l', 'm', 'r', 0), // lmr = Lamalera + TRUETYPE_TAG('l', 'm', 'u', 0), // lmu = Lamenu + TRUETYPE_TAG('l', 'm', 'v', 0), // lmv = Lomaiviti + TRUETYPE_TAG('l', 'm', 'w', 0), // lmw = Lake Miwok + TRUETYPE_TAG('l', 'm', 'x', 0), // lmx = Laimbue + TRUETYPE_TAG('l', 'm', 'y', 0), // lmy = Lamboya + TRUETYPE_TAG('l', 'm', 'z', 0), // lmz = Lumbee + TRUETYPE_TAG('l', 'n', 'a', 0), // lna = Langbashe + TRUETYPE_TAG('l', 'n', 'b', 0), // lnb = Mbalanhu + TRUETYPE_TAG('l', 'n', 'd', 0), // lnd = Lundayeh + TRUETYPE_TAG('l', 'n', 'g', 0), // lng = Langobardic + TRUETYPE_TAG('l', 'n', 'h', 0), // lnh = Lanoh + TRUETYPE_TAG('l', 'n', 'i', 0), // lni = Daantanai' + TRUETYPE_TAG('l', 'n', 'j', 0), // lnj = Leningitij + TRUETYPE_TAG('l', 'n', 'l', 0), // lnl = South Central Banda + TRUETYPE_TAG('l', 'n', 'm', 0), // lnm = Langam + TRUETYPE_TAG('l', 'n', 'n', 0), // lnn = Lorediakarkar + TRUETYPE_TAG('l', 'n', 'o', 0), // lno = Lango (Sudan) + TRUETYPE_TAG('l', 'n', 's', 0), // lns = Lamnso' + TRUETYPE_TAG('l', 'n', 'u', 0), // lnu = Longuda + TRUETYPE_TAG('l', 'n', 'z', 0), // lnz = Lonzo + TRUETYPE_TAG('l', 'o', 'a', 0), // loa = Loloda + TRUETYPE_TAG('l', 'o', 'b', 0), // lob = Lobi + TRUETYPE_TAG('l', 'o', 'c', 0), // loc = Inonhan + TRUETYPE_TAG('l', 'o', 'e', 0), // loe = Saluan + TRUETYPE_TAG('l', 'o', 'f', 0), // lof = Logol + TRUETYPE_TAG('l', 'o', 'g', 0), // log = Logo + TRUETYPE_TAG('l', 'o', 'h', 0), // loh = Narim + TRUETYPE_TAG('l', 'o', 'i', 0), // loi = Loma (Côte d'Ivoire) + TRUETYPE_TAG('l', 'o', 'j', 0), // loj = Lou + TRUETYPE_TAG('l', 'o', 'k', 0), // lok = Loko + TRUETYPE_TAG('l', 'o', 'l', 0), // lol = Mongo + TRUETYPE_TAG('l', 'o', 'm', 0), // lom = Loma (Liberia) + TRUETYPE_TAG('l', 'o', 'n', 0), // lon = Malawi Lomwe + TRUETYPE_TAG('l', 'o', 'o', 0), // loo = Lombo + TRUETYPE_TAG('l', 'o', 'p', 0), // lop = Lopa + TRUETYPE_TAG('l', 'o', 'q', 0), // loq = Lobala + TRUETYPE_TAG('l', 'o', 'r', 0), // lor = Téén + TRUETYPE_TAG('l', 'o', 's', 0), // los = Loniu + TRUETYPE_TAG('l', 'o', 't', 0), // lot = Otuho + TRUETYPE_TAG('l', 'o', 'u', 0), // lou = Louisiana Creole French + TRUETYPE_TAG('l', 'o', 'v', 0), // lov = Lopi + TRUETYPE_TAG('l', 'o', 'w', 0), // low = Tampias Lobu + TRUETYPE_TAG('l', 'o', 'x', 0), // lox = Loun + TRUETYPE_TAG('l', 'o', 'y', 0), // loy = Lowa + TRUETYPE_TAG('l', 'o', 'z', 0), // loz = Lozi + TRUETYPE_TAG('l', 'p', 'a', 0), // lpa = Lelepa + TRUETYPE_TAG('l', 'p', 'e', 0), // lpe = Lepki + TRUETYPE_TAG('l', 'p', 'n', 0), // lpn = Long Phuri Naga + TRUETYPE_TAG('l', 'p', 'o', 0), // lpo = Lipo + TRUETYPE_TAG('l', 'p', 'x', 0), // lpx = Lopit + TRUETYPE_TAG('l', 'r', 'a', 0), // lra = Rara Bakati' + TRUETYPE_TAG('l', 'r', 'c', 0), // lrc = Northern Luri + TRUETYPE_TAG('l', 'r', 'e', 0), // lre = Laurentian + TRUETYPE_TAG('l', 'r', 'g', 0), // lrg = Laragia + TRUETYPE_TAG('l', 'r', 'i', 0), // lri = Marachi + TRUETYPE_TAG('l', 'r', 'k', 0), // lrk = Loarki + TRUETYPE_TAG('l', 'r', 'l', 0), // lrl = Lari + TRUETYPE_TAG('l', 'r', 'm', 0), // lrm = Marama + TRUETYPE_TAG('l', 'r', 'n', 0), // lrn = Lorang + TRUETYPE_TAG('l', 'r', 'o', 0), // lro = Laro + TRUETYPE_TAG('l', 'r', 'r', 0), // lrr = Southern Lorung + TRUETYPE_TAG('l', 'r', 't', 0), // lrt = Larantuka Malay + TRUETYPE_TAG('l', 'r', 'v', 0), // lrv = Larevat + TRUETYPE_TAG('l', 'r', 'z', 0), // lrz = Lemerig + TRUETYPE_TAG('l', 's', 'a', 0), // lsa = Lasgerdi + TRUETYPE_TAG('l', 's', 'd', 0), // lsd = Lishana Deni + TRUETYPE_TAG('l', 's', 'e', 0), // lse = Lusengo + TRUETYPE_TAG('l', 's', 'g', 0), // lsg = Lyons Sign Language + TRUETYPE_TAG('l', 's', 'h', 0), // lsh = Lish + TRUETYPE_TAG('l', 's', 'i', 0), // lsi = Lashi + TRUETYPE_TAG('l', 's', 'l', 0), // lsl = Latvian Sign Language + TRUETYPE_TAG('l', 's', 'm', 0), // lsm = Saamia + TRUETYPE_TAG('l', 's', 'o', 0), // lso = Laos Sign Language + TRUETYPE_TAG('l', 's', 'p', 0), // lsp = Panamanian Sign Language + TRUETYPE_TAG('l', 's', 'r', 0), // lsr = Aruop + TRUETYPE_TAG('l', 's', 's', 0), // lss = Lasi + TRUETYPE_TAG('l', 's', 't', 0), // lst = Trinidad and Tobago Sign Language + TRUETYPE_TAG('l', 's', 'y', 0), // lsy = Mauritian Sign Language + TRUETYPE_TAG('l', 't', 'c', 0), // ltc = Late Middle Chinese + TRUETYPE_TAG('l', 't', 'g', 0), // ltg = Latgalian + TRUETYPE_TAG('l', 't', 'i', 0), // lti = Leti (Indonesia) + TRUETYPE_TAG('l', 't', 'n', 0), // ltn = Latundê + TRUETYPE_TAG('l', 't', 'o', 0), // lto = Tsotso + TRUETYPE_TAG('l', 't', 's', 0), // lts = Tachoni + TRUETYPE_TAG('l', 't', 'u', 0), // ltu = Latu + TRUETYPE_TAG('l', 'u', 'a', 0), // lua = Luba-Lulua + TRUETYPE_TAG('l', 'u', 'c', 0), // luc = Aringa + TRUETYPE_TAG('l', 'u', 'd', 0), // lud = Ludian + TRUETYPE_TAG('l', 'u', 'e', 0), // lue = Luvale + TRUETYPE_TAG('l', 'u', 'f', 0), // luf = Laua + TRUETYPE_TAG('l', 'u', 'i', 0), // lui = Luiseno + TRUETYPE_TAG('l', 'u', 'j', 0), // luj = Luna + TRUETYPE_TAG('l', 'u', 'k', 0), // luk = Lunanakha + TRUETYPE_TAG('l', 'u', 'l', 0), // lul = Olu'bo + TRUETYPE_TAG('l', 'u', 'm', 0), // lum = Luimbi + TRUETYPE_TAG('l', 'u', 'n', 0), // lun = Lunda + TRUETYPE_TAG('l', 'u', 'o', 0), // luo = Luo (Kenya and Tanzania) + TRUETYPE_TAG('l', 'u', 'p', 0), // lup = Lumbu + TRUETYPE_TAG('l', 'u', 'q', 0), // luq = Lucumi + TRUETYPE_TAG('l', 'u', 'r', 0), // lur = Laura + TRUETYPE_TAG('l', 'u', 's', 0), // lus = Lushai + TRUETYPE_TAG('l', 'u', 't', 0), // lut = Lushootseed + TRUETYPE_TAG('l', 'u', 'u', 0), // luu = Lumba-Yakkha + TRUETYPE_TAG('l', 'u', 'v', 0), // luv = Luwati + TRUETYPE_TAG('l', 'u', 'w', 0), // luw = Luo (Cameroon) + TRUETYPE_TAG('l', 'u', 'y', 0), // luy = Luyia + TRUETYPE_TAG('l', 'u', 'z', 0), // luz = Southern Luri + TRUETYPE_TAG('l', 'v', 'a', 0), // lva = Maku'a + TRUETYPE_TAG('l', 'v', 'k', 0), // lvk = Lavukaleve + TRUETYPE_TAG('l', 'v', 's', 0), // lvs = Standard Latvian + TRUETYPE_TAG('l', 'v', 'u', 0), // lvu = Levuka + TRUETYPE_TAG('l', 'w', 'a', 0), // lwa = Lwalu + TRUETYPE_TAG('l', 'w', 'e', 0), // lwe = Lewo Eleng + TRUETYPE_TAG('l', 'w', 'g', 0), // lwg = Wanga + TRUETYPE_TAG('l', 'w', 'h', 0), // lwh = White Lachi + TRUETYPE_TAG('l', 'w', 'l', 0), // lwl = Eastern Lawa + TRUETYPE_TAG('l', 'w', 'm', 0), // lwm = Laomian + TRUETYPE_TAG('l', 'w', 'o', 0), // lwo = Luwo + TRUETYPE_TAG('l', 'w', 't', 0), // lwt = Lewotobi + TRUETYPE_TAG('l', 'w', 'w', 0), // lww = Lewo + TRUETYPE_TAG('l', 'y', 'a', 0), // lya = Layakha + TRUETYPE_TAG('l', 'y', 'g', 0), // lyg = Lyngngam + TRUETYPE_TAG('l', 'y', 'n', 0), // lyn = Luyana + TRUETYPE_TAG('l', 'z', 'h', 0), // lzh = Literary Chinese + TRUETYPE_TAG('l', 'z', 'l', 0), // lzl = Litzlitz + TRUETYPE_TAG('l', 'z', 'n', 0), // lzn = Leinong Naga + TRUETYPE_TAG('l', 'z', 'z', 0), // lzz = Laz + TRUETYPE_TAG('m', 'a', 'a', 0), // maa = San Jerónimo Tecóatl Mazatec + TRUETYPE_TAG('m', 'a', 'b', 0), // mab = Yutanduchi Mixtec + TRUETYPE_TAG('m', 'a', 'd', 0), // mad = Madurese + TRUETYPE_TAG('m', 'a', 'e', 0), // mae = Bo-Rukul + TRUETYPE_TAG('m', 'a', 'f', 0), // maf = Mafa + TRUETYPE_TAG('m', 'a', 'g', 0), // mag = Magahi + TRUETYPE_TAG('m', 'a', 'i', 0), // mai = Maithili + TRUETYPE_TAG('m', 'a', 'j', 0), // maj = Jalapa De Díaz Mazatec + TRUETYPE_TAG('m', 'a', 'k', 0), // mak = Makasar + TRUETYPE_TAG('m', 'a', 'm', 0), // mam = Mam + TRUETYPE_TAG('m', 'a', 'n', 0), // man = Mandingo + TRUETYPE_TAG('m', 'a', 'p', 0), // map = Austronesian languages + TRUETYPE_TAG('m', 'a', 'q', 0), // maq = Chiquihuitlán Mazatec + TRUETYPE_TAG('m', 'a', 's', 0), // mas = Masai + TRUETYPE_TAG('m', 'a', 't', 0), // mat = San Francisco Matlatzinca + TRUETYPE_TAG('m', 'a', 'u', 0), // mau = Huautla Mazatec + TRUETYPE_TAG('m', 'a', 'v', 0), // mav = Sateré-Mawé + TRUETYPE_TAG('m', 'a', 'w', 0), // maw = Mampruli + TRUETYPE_TAG('m', 'a', 'x', 0), // max = North Moluccan Malay + TRUETYPE_TAG('m', 'a', 'z', 0), // maz = Central Mazahua + TRUETYPE_TAG('m', 'b', 'a', 0), // mba = Higaonon + TRUETYPE_TAG('m', 'b', 'b', 0), // mbb = Western Bukidnon Manobo + TRUETYPE_TAG('m', 'b', 'c', 0), // mbc = Macushi + TRUETYPE_TAG('m', 'b', 'd', 0), // mbd = Dibabawon Manobo + TRUETYPE_TAG('m', 'b', 'e', 0), // mbe = Molale + TRUETYPE_TAG('m', 'b', 'f', 0), // mbf = Baba Malay + TRUETYPE_TAG('m', 'b', 'h', 0), // mbh = Mangseng + TRUETYPE_TAG('m', 'b', 'i', 0), // mbi = Ilianen Manobo + TRUETYPE_TAG('m', 'b', 'j', 0), // mbj = Nadëb + TRUETYPE_TAG('m', 'b', 'k', 0), // mbk = Malol + TRUETYPE_TAG('m', 'b', 'l', 0), // mbl = Maxakalí + TRUETYPE_TAG('m', 'b', 'm', 0), // mbm = Ombamba + TRUETYPE_TAG('m', 'b', 'n', 0), // mbn = Macaguán + TRUETYPE_TAG('m', 'b', 'o', 0), // mbo = Mbo (Cameroon) + TRUETYPE_TAG('m', 'b', 'p', 0), // mbp = Malayo + TRUETYPE_TAG('m', 'b', 'q', 0), // mbq = Maisin + TRUETYPE_TAG('m', 'b', 'r', 0), // mbr = Nukak Makú + TRUETYPE_TAG('m', 'b', 's', 0), // mbs = Sarangani Manobo + TRUETYPE_TAG('m', 'b', 't', 0), // mbt = Matigsalug Manobo + TRUETYPE_TAG('m', 'b', 'u', 0), // mbu = Mbula-Bwazza + TRUETYPE_TAG('m', 'b', 'v', 0), // mbv = Mbulungish + TRUETYPE_TAG('m', 'b', 'w', 0), // mbw = Maring + TRUETYPE_TAG('m', 'b', 'x', 0), // mbx = Mari (East Sepik Province) + TRUETYPE_TAG('m', 'b', 'y', 0), // mby = Memoni + TRUETYPE_TAG('m', 'b', 'z', 0), // mbz = Amoltepec Mixtec + TRUETYPE_TAG('m', 'c', 'a', 0), // mca = Maca + TRUETYPE_TAG('m', 'c', 'b', 0), // mcb = Machiguenga + TRUETYPE_TAG('m', 'c', 'c', 0), // mcc = Bitur + TRUETYPE_TAG('m', 'c', 'd', 0), // mcd = Sharanahua + TRUETYPE_TAG('m', 'c', 'e', 0), // mce = Itundujia Mixtec + TRUETYPE_TAG('m', 'c', 'f', 0), // mcf = Matsés + TRUETYPE_TAG('m', 'c', 'g', 0), // mcg = Mapoyo + TRUETYPE_TAG('m', 'c', 'h', 0), // mch = Maquiritari + TRUETYPE_TAG('m', 'c', 'i', 0), // mci = Mese + TRUETYPE_TAG('m', 'c', 'j', 0), // mcj = Mvanip + TRUETYPE_TAG('m', 'c', 'k', 0), // mck = Mbunda + TRUETYPE_TAG('m', 'c', 'l', 0), // mcl = Macaguaje + TRUETYPE_TAG('m', 'c', 'm', 0), // mcm = Malaccan Creole Portuguese + TRUETYPE_TAG('m', 'c', 'n', 0), // mcn = Masana + TRUETYPE_TAG('m', 'c', 'o', 0), // mco = Coatlán Mixe + TRUETYPE_TAG('m', 'c', 'p', 0), // mcp = Makaa + TRUETYPE_TAG('m', 'c', 'q', 0), // mcq = Ese + TRUETYPE_TAG('m', 'c', 'r', 0), // mcr = Menya + TRUETYPE_TAG('m', 'c', 's', 0), // mcs = Mambai + TRUETYPE_TAG('m', 'c', 't', 0), // mct = Mengisa + TRUETYPE_TAG('m', 'c', 'u', 0), // mcu = Cameroon Mambila + TRUETYPE_TAG('m', 'c', 'v', 0), // mcv = Minanibai + TRUETYPE_TAG('m', 'c', 'w', 0), // mcw = Mawa (Chad) + TRUETYPE_TAG('m', 'c', 'x', 0), // mcx = Mpiemo + TRUETYPE_TAG('m', 'c', 'y', 0), // mcy = South Watut + TRUETYPE_TAG('m', 'c', 'z', 0), // mcz = Mawan + TRUETYPE_TAG('m', 'd', 'a', 0), // mda = Mada (Nigeria) + TRUETYPE_TAG('m', 'd', 'b', 0), // mdb = Morigi + TRUETYPE_TAG('m', 'd', 'c', 0), // mdc = Male (Papua New Guinea) + TRUETYPE_TAG('m', 'd', 'd', 0), // mdd = Mbum + TRUETYPE_TAG('m', 'd', 'e', 0), // mde = Maba (Chad) + TRUETYPE_TAG('m', 'd', 'f', 0), // mdf = Moksha + TRUETYPE_TAG('m', 'd', 'g', 0), // mdg = Massalat + TRUETYPE_TAG('m', 'd', 'h', 0), // mdh = Maguindanaon + TRUETYPE_TAG('m', 'd', 'i', 0), // mdi = Mamvu + TRUETYPE_TAG('m', 'd', 'j', 0), // mdj = Mangbetu + TRUETYPE_TAG('m', 'd', 'k', 0), // mdk = Mangbutu + TRUETYPE_TAG('m', 'd', 'l', 0), // mdl = Maltese Sign Language + TRUETYPE_TAG('m', 'd', 'm', 0), // mdm = Mayogo + TRUETYPE_TAG('m', 'd', 'n', 0), // mdn = Mbati + TRUETYPE_TAG('m', 'd', 'p', 0), // mdp = Mbala + TRUETYPE_TAG('m', 'd', 'q', 0), // mdq = Mbole + TRUETYPE_TAG('m', 'd', 'r', 0), // mdr = Mandar + TRUETYPE_TAG('m', 'd', 's', 0), // mds = Maria (Papua New Guinea) + TRUETYPE_TAG('m', 'd', 't', 0), // mdt = Mbere + TRUETYPE_TAG('m', 'd', 'u', 0), // mdu = Mboko + TRUETYPE_TAG('m', 'd', 'v', 0), // mdv = Santa Lucía Monteverde Mixtec + TRUETYPE_TAG('m', 'd', 'w', 0), // mdw = Mbosi + TRUETYPE_TAG('m', 'd', 'x', 0), // mdx = Dizin + TRUETYPE_TAG('m', 'd', 'y', 0), // mdy = Male (Ethiopia) + TRUETYPE_TAG('m', 'd', 'z', 0), // mdz = Suruí Do Pará + TRUETYPE_TAG('m', 'e', 'a', 0), // mea = Menka + TRUETYPE_TAG('m', 'e', 'b', 0), // meb = Ikobi-Mena + TRUETYPE_TAG('m', 'e', 'c', 0), // mec = Mara + TRUETYPE_TAG('m', 'e', 'd', 0), // med = Melpa + TRUETYPE_TAG('m', 'e', 'e', 0), // mee = Mengen + TRUETYPE_TAG('m', 'e', 'f', 0), // mef = Megam + TRUETYPE_TAG('m', 'e', 'g', 0), // meg = Mea + TRUETYPE_TAG('m', 'e', 'h', 0), // meh = Southwestern Tlaxiaco Mixtec + TRUETYPE_TAG('m', 'e', 'i', 0), // mei = Midob + TRUETYPE_TAG('m', 'e', 'j', 0), // mej = Meyah + TRUETYPE_TAG('m', 'e', 'k', 0), // mek = Mekeo + TRUETYPE_TAG('m', 'e', 'l', 0), // mel = Central Melanau + TRUETYPE_TAG('m', 'e', 'm', 0), // mem = Mangala + TRUETYPE_TAG('m', 'e', 'n', 0), // men = Mende (Sierra Leone) + TRUETYPE_TAG('m', 'e', 'o', 0), // meo = Kedah Malay + TRUETYPE_TAG('m', 'e', 'p', 0), // mep = Miriwung + TRUETYPE_TAG('m', 'e', 'q', 0), // meq = Merey + TRUETYPE_TAG('m', 'e', 'r', 0), // mer = Meru + TRUETYPE_TAG('m', 'e', 's', 0), // mes = Masmaje + TRUETYPE_TAG('m', 'e', 't', 0), // met = Mato + TRUETYPE_TAG('m', 'e', 'u', 0), // meu = Motu + TRUETYPE_TAG('m', 'e', 'v', 0), // mev = Mann + TRUETYPE_TAG('m', 'e', 'w', 0), // mew = Maaka + TRUETYPE_TAG('m', 'e', 'y', 0), // mey = Hassaniyya + TRUETYPE_TAG('m', 'e', 'z', 0), // mez = Menominee + TRUETYPE_TAG('m', 'f', 'a', 0), // mfa = Pattani Malay + TRUETYPE_TAG('m', 'f', 'b', 0), // mfb = Bangka + TRUETYPE_TAG('m', 'f', 'c', 0), // mfc = Mba + TRUETYPE_TAG('m', 'f', 'd', 0), // mfd = Mendankwe-Nkwen + TRUETYPE_TAG('m', 'f', 'e', 0), // mfe = Morisyen + TRUETYPE_TAG('m', 'f', 'f', 0), // mff = Naki + TRUETYPE_TAG('m', 'f', 'g', 0), // mfg = Mixifore + TRUETYPE_TAG('m', 'f', 'h', 0), // mfh = Matal + TRUETYPE_TAG('m', 'f', 'i', 0), // mfi = Wandala + TRUETYPE_TAG('m', 'f', 'j', 0), // mfj = Mefele + TRUETYPE_TAG('m', 'f', 'k', 0), // mfk = North Mofu + TRUETYPE_TAG('m', 'f', 'l', 0), // mfl = Putai + TRUETYPE_TAG('m', 'f', 'm', 0), // mfm = Marghi South + TRUETYPE_TAG('m', 'f', 'n', 0), // mfn = Cross River Mbembe + TRUETYPE_TAG('m', 'f', 'o', 0), // mfo = Mbe + TRUETYPE_TAG('m', 'f', 'p', 0), // mfp = Makassar Malay + TRUETYPE_TAG('m', 'f', 'q', 0), // mfq = Moba + TRUETYPE_TAG('m', 'f', 'r', 0), // mfr = Marithiel + TRUETYPE_TAG('m', 'f', 's', 0), // mfs = Mexican Sign Language + TRUETYPE_TAG('m', 'f', 't', 0), // mft = Mokerang + TRUETYPE_TAG('m', 'f', 'u', 0), // mfu = Mbwela + TRUETYPE_TAG('m', 'f', 'v', 0), // mfv = Mandjak + TRUETYPE_TAG('m', 'f', 'w', 0), // mfw = Mulaha + TRUETYPE_TAG('m', 'f', 'x', 0), // mfx = Melo + TRUETYPE_TAG('m', 'f', 'y', 0), // mfy = Mayo + TRUETYPE_TAG('m', 'f', 'z', 0), // mfz = Mabaan + TRUETYPE_TAG('m', 'g', 'a', 0), // mga = Middle Irish (900-1200) + TRUETYPE_TAG('m', 'g', 'b', 0), // mgb = Mararit + TRUETYPE_TAG('m', 'g', 'c', 0), // mgc = Morokodo + TRUETYPE_TAG('m', 'g', 'd', 0), // mgd = Moru + TRUETYPE_TAG('m', 'g', 'e', 0), // mge = Mango + TRUETYPE_TAG('m', 'g', 'f', 0), // mgf = Maklew + TRUETYPE_TAG('m', 'g', 'g', 0), // mgg = Mpongmpong + TRUETYPE_TAG('m', 'g', 'h', 0), // mgh = Makhuwa-Meetto + TRUETYPE_TAG('m', 'g', 'i', 0), // mgi = Lijili + TRUETYPE_TAG('m', 'g', 'j', 0), // mgj = Abureni + TRUETYPE_TAG('m', 'g', 'k', 0), // mgk = Mawes + TRUETYPE_TAG('m', 'g', 'l', 0), // mgl = Maleu-Kilenge + TRUETYPE_TAG('m', 'g', 'm', 0), // mgm = Mambae + TRUETYPE_TAG('m', 'g', 'n', 0), // mgn = Mbangi + TRUETYPE_TAG('m', 'g', 'o', 0), // mgo = Meta' + TRUETYPE_TAG('m', 'g', 'p', 0), // mgp = Eastern Magar + TRUETYPE_TAG('m', 'g', 'q', 0), // mgq = Malila + TRUETYPE_TAG('m', 'g', 'r', 0), // mgr = Mambwe-Lungu + TRUETYPE_TAG('m', 'g', 's', 0), // mgs = Manda (Tanzania) + TRUETYPE_TAG('m', 'g', 't', 0), // mgt = Mongol + TRUETYPE_TAG('m', 'g', 'u', 0), // mgu = Mailu + TRUETYPE_TAG('m', 'g', 'v', 0), // mgv = Matengo + TRUETYPE_TAG('m', 'g', 'w', 0), // mgw = Matumbi + TRUETYPE_TAG('m', 'g', 'x', 0), // mgx = Omati + TRUETYPE_TAG('m', 'g', 'y', 0), // mgy = Mbunga + TRUETYPE_TAG('m', 'g', 'z', 0), // mgz = Mbugwe + TRUETYPE_TAG('m', 'h', 'a', 0), // mha = Manda (India) + TRUETYPE_TAG('m', 'h', 'b', 0), // mhb = Mahongwe + TRUETYPE_TAG('m', 'h', 'c', 0), // mhc = Mocho + TRUETYPE_TAG('m', 'h', 'd', 0), // mhd = Mbugu + TRUETYPE_TAG('m', 'h', 'e', 0), // mhe = Besisi + TRUETYPE_TAG('m', 'h', 'f', 0), // mhf = Mamaa + TRUETYPE_TAG('m', 'h', 'g', 0), // mhg = Margu + TRUETYPE_TAG('m', 'h', 'h', 0), // mhh = Maskoy Pidgin + TRUETYPE_TAG('m', 'h', 'i', 0), // mhi = Ma'di + TRUETYPE_TAG('m', 'h', 'j', 0), // mhj = Mogholi + TRUETYPE_TAG('m', 'h', 'k', 0), // mhk = Mungaka + TRUETYPE_TAG('m', 'h', 'l', 0), // mhl = Mauwake + TRUETYPE_TAG('m', 'h', 'm', 0), // mhm = Makhuwa-Moniga + TRUETYPE_TAG('m', 'h', 'n', 0), // mhn = Mócheno + TRUETYPE_TAG('m', 'h', 'o', 0), // mho = Mashi (Zambia) + TRUETYPE_TAG('m', 'h', 'p', 0), // mhp = Balinese Malay + TRUETYPE_TAG('m', 'h', 'q', 0), // mhq = Mandan + TRUETYPE_TAG('m', 'h', 'r', 0), // mhr = Eastern Mari + TRUETYPE_TAG('m', 'h', 's', 0), // mhs = Buru (Indonesia) + TRUETYPE_TAG('m', 'h', 't', 0), // mht = Mandahuaca + TRUETYPE_TAG('m', 'h', 'u', 0), // mhu = Digaro-Mishmi + TRUETYPE_TAG('m', 'h', 'w', 0), // mhw = Mbukushu + TRUETYPE_TAG('m', 'h', 'x', 0), // mhx = Maru + TRUETYPE_TAG('m', 'h', 'y', 0), // mhy = Ma'anyan + TRUETYPE_TAG('m', 'h', 'z', 0), // mhz = Mor (Mor Islands) + TRUETYPE_TAG('m', 'i', 'a', 0), // mia = Miami + TRUETYPE_TAG('m', 'i', 'b', 0), // mib = Atatláhuca Mixtec + TRUETYPE_TAG('m', 'i', 'c', 0), // mic = Mi'kmaq + TRUETYPE_TAG('m', 'i', 'd', 0), // mid = Mandaic + TRUETYPE_TAG('m', 'i', 'e', 0), // mie = Ocotepec Mixtec + TRUETYPE_TAG('m', 'i', 'f', 0), // mif = Mofu-Gudur + TRUETYPE_TAG('m', 'i', 'g', 0), // mig = San Miguel El Grande Mixtec + TRUETYPE_TAG('m', 'i', 'h', 0), // mih = Chayuco Mixtec + TRUETYPE_TAG('m', 'i', 'i', 0), // mii = Chigmecatitlán Mixtec + TRUETYPE_TAG('m', 'i', 'j', 0), // mij = Abar + TRUETYPE_TAG('m', 'i', 'k', 0), // mik = Mikasuki + TRUETYPE_TAG('m', 'i', 'l', 0), // mil = Peñoles Mixtec + TRUETYPE_TAG('m', 'i', 'm', 0), // mim = Alacatlatzala Mixtec + TRUETYPE_TAG('m', 'i', 'n', 0), // min = Minangkabau + TRUETYPE_TAG('m', 'i', 'o', 0), // mio = Pinotepa Nacional Mixtec + TRUETYPE_TAG('m', 'i', 'p', 0), // mip = Apasco-Apoala Mixtec + TRUETYPE_TAG('m', 'i', 'q', 0), // miq = Mískito + TRUETYPE_TAG('m', 'i', 'r', 0), // mir = Isthmus Mixe + TRUETYPE_TAG('m', 'i', 's', 0), // mis = Uncoded languages + TRUETYPE_TAG('m', 'i', 't', 0), // mit = Southern Puebla Mixtec + TRUETYPE_TAG('m', 'i', 'u', 0), // miu = Cacaloxtepec Mixtec + TRUETYPE_TAG('m', 'i', 'w', 0), // miw = Akoye + TRUETYPE_TAG('m', 'i', 'x', 0), // mix = Mixtepec Mixtec + TRUETYPE_TAG('m', 'i', 'y', 0), // miy = Ayutla Mixtec + TRUETYPE_TAG('m', 'i', 'z', 0), // miz = Coatzospan Mixtec + TRUETYPE_TAG('m', 'j', 'a', 0), // mja = Mahei + TRUETYPE_TAG('m', 'j', 'c', 0), // mjc = San Juan Colorado Mixtec + TRUETYPE_TAG('m', 'j', 'd', 0), // mjd = Northwest Maidu + TRUETYPE_TAG('m', 'j', 'e', 0), // mje = Muskum + TRUETYPE_TAG('m', 'j', 'g', 0), // mjg = Tu + TRUETYPE_TAG('m', 'j', 'h', 0), // mjh = Mwera (Nyasa) + TRUETYPE_TAG('m', 'j', 'i', 0), // mji = Kim Mun + TRUETYPE_TAG('m', 'j', 'j', 0), // mjj = Mawak + TRUETYPE_TAG('m', 'j', 'k', 0), // mjk = Matukar + TRUETYPE_TAG('m', 'j', 'l', 0), // mjl = Mandeali + TRUETYPE_TAG('m', 'j', 'm', 0), // mjm = Medebur + TRUETYPE_TAG('m', 'j', 'n', 0), // mjn = Ma (Papua New Guinea) + TRUETYPE_TAG('m', 'j', 'o', 0), // mjo = Malankuravan + TRUETYPE_TAG('m', 'j', 'p', 0), // mjp = Malapandaram + TRUETYPE_TAG('m', 'j', 'q', 0), // mjq = Malaryan + TRUETYPE_TAG('m', 'j', 'r', 0), // mjr = Malavedan + TRUETYPE_TAG('m', 'j', 's', 0), // mjs = Miship + TRUETYPE_TAG('m', 'j', 't', 0), // mjt = Sauria Paharia + TRUETYPE_TAG('m', 'j', 'u', 0), // mju = Manna-Dora + TRUETYPE_TAG('m', 'j', 'v', 0), // mjv = Mannan + TRUETYPE_TAG('m', 'j', 'w', 0), // mjw = Karbi + TRUETYPE_TAG('m', 'j', 'x', 0), // mjx = Mahali + TRUETYPE_TAG('m', 'j', 'y', 0), // mjy = Mahican + TRUETYPE_TAG('m', 'j', 'z', 0), // mjz = Majhi + TRUETYPE_TAG('m', 'k', 'a', 0), // mka = Mbre + TRUETYPE_TAG('m', 'k', 'b', 0), // mkb = Mal Paharia + TRUETYPE_TAG('m', 'k', 'c', 0), // mkc = Siliput + TRUETYPE_TAG('m', 'k', 'e', 0), // mke = Mawchi + TRUETYPE_TAG('m', 'k', 'f', 0), // mkf = Miya + TRUETYPE_TAG('m', 'k', 'g', 0), // mkg = Mak (China) + TRUETYPE_TAG('m', 'k', 'h', 0), // mkh = Mon-Khmer languages + TRUETYPE_TAG('m', 'k', 'i', 0), // mki = Dhatki + TRUETYPE_TAG('m', 'k', 'j', 0), // mkj = Mokilese + TRUETYPE_TAG('m', 'k', 'k', 0), // mkk = Byep + TRUETYPE_TAG('m', 'k', 'l', 0), // mkl = Mokole + TRUETYPE_TAG('m', 'k', 'm', 0), // mkm = Moklen + TRUETYPE_TAG('m', 'k', 'n', 0), // mkn = Kupang Malay + TRUETYPE_TAG('m', 'k', 'o', 0), // mko = Mingang Doso + TRUETYPE_TAG('m', 'k', 'p', 0), // mkp = Moikodi + TRUETYPE_TAG('m', 'k', 'q', 0), // mkq = Bay Miwok + TRUETYPE_TAG('m', 'k', 'r', 0), // mkr = Malas + TRUETYPE_TAG('m', 'k', 's', 0), // mks = Silacayoapan Mixtec + TRUETYPE_TAG('m', 'k', 't', 0), // mkt = Vamale + TRUETYPE_TAG('m', 'k', 'u', 0), // mku = Konyanka Maninka + TRUETYPE_TAG('m', 'k', 'v', 0), // mkv = Mafea + TRUETYPE_TAG('m', 'k', 'w', 0), // mkw = Kituba (Congo) + TRUETYPE_TAG('m', 'k', 'x', 0), // mkx = Kinamiging Manobo + TRUETYPE_TAG('m', 'k', 'y', 0), // mky = East Makian + TRUETYPE_TAG('m', 'k', 'z', 0), // mkz = Makasae + TRUETYPE_TAG('m', 'l', 'a', 0), // mla = Malo + TRUETYPE_TAG('m', 'l', 'b', 0), // mlb = Mbule + TRUETYPE_TAG('m', 'l', 'c', 0), // mlc = Cao Lan + TRUETYPE_TAG('m', 'l', 'd', 0), // mld = Malakhel + TRUETYPE_TAG('m', 'l', 'e', 0), // mle = Manambu + TRUETYPE_TAG('m', 'l', 'f', 0), // mlf = Mal + TRUETYPE_TAG('m', 'l', 'h', 0), // mlh = Mape + TRUETYPE_TAG('m', 'l', 'i', 0), // mli = Malimpung + TRUETYPE_TAG('m', 'l', 'j', 0), // mlj = Miltu + TRUETYPE_TAG('m', 'l', 'k', 0), // mlk = Ilwana + TRUETYPE_TAG('m', 'l', 'l', 0), // mll = Malua Bay + TRUETYPE_TAG('m', 'l', 'm', 0), // mlm = Mulam + TRUETYPE_TAG('m', 'l', 'n', 0), // mln = Malango + TRUETYPE_TAG('m', 'l', 'o', 0), // mlo = Mlomp + TRUETYPE_TAG('m', 'l', 'p', 0), // mlp = Bargam + TRUETYPE_TAG('m', 'l', 'q', 0), // mlq = Western Maninkakan + TRUETYPE_TAG('m', 'l', 'r', 0), // mlr = Vame + TRUETYPE_TAG('m', 'l', 's', 0), // mls = Masalit + TRUETYPE_TAG('m', 'l', 'u', 0), // mlu = To'abaita + TRUETYPE_TAG('m', 'l', 'v', 0), // mlv = Motlav + TRUETYPE_TAG('m', 'l', 'w', 0), // mlw = Moloko + TRUETYPE_TAG('m', 'l', 'x', 0), // mlx = Malfaxal + TRUETYPE_TAG('m', 'l', 'z', 0), // mlz = Malaynon + TRUETYPE_TAG('m', 'm', 'a', 0), // mma = Mama + TRUETYPE_TAG('m', 'm', 'b', 0), // mmb = Momina + TRUETYPE_TAG('m', 'm', 'c', 0), // mmc = Michoacán Mazahua + TRUETYPE_TAG('m', 'm', 'd', 0), // mmd = Maonan + TRUETYPE_TAG('m', 'm', 'e', 0), // mme = Mae + TRUETYPE_TAG('m', 'm', 'f', 0), // mmf = Mundat + TRUETYPE_TAG('m', 'm', 'g', 0), // mmg = North Ambrym + TRUETYPE_TAG('m', 'm', 'h', 0), // mmh = Mehináku + TRUETYPE_TAG('m', 'm', 'i', 0), // mmi = Musar + TRUETYPE_TAG('m', 'm', 'j', 0), // mmj = Majhwar + TRUETYPE_TAG('m', 'm', 'k', 0), // mmk = Mukha-Dora + TRUETYPE_TAG('m', 'm', 'l', 0), // mml = Man Met + TRUETYPE_TAG('m', 'm', 'm', 0), // mmm = Maii + TRUETYPE_TAG('m', 'm', 'n', 0), // mmn = Mamanwa + TRUETYPE_TAG('m', 'm', 'o', 0), // mmo = Mangga Buang + TRUETYPE_TAG('m', 'm', 'p', 0), // mmp = Siawi + TRUETYPE_TAG('m', 'm', 'q', 0), // mmq = Musak + TRUETYPE_TAG('m', 'm', 'r', 0), // mmr = Western Xiangxi Miao + TRUETYPE_TAG('m', 'm', 't', 0), // mmt = Malalamai + TRUETYPE_TAG('m', 'm', 'u', 0), // mmu = Mmaala + TRUETYPE_TAG('m', 'm', 'v', 0), // mmv = Miriti + TRUETYPE_TAG('m', 'm', 'w', 0), // mmw = Emae + TRUETYPE_TAG('m', 'm', 'x', 0), // mmx = Madak + TRUETYPE_TAG('m', 'm', 'y', 0), // mmy = Migaama + TRUETYPE_TAG('m', 'm', 'z', 0), // mmz = Mabaale + TRUETYPE_TAG('m', 'n', 'a', 0), // mna = Mbula + TRUETYPE_TAG('m', 'n', 'b', 0), // mnb = Muna + TRUETYPE_TAG('m', 'n', 'c', 0), // mnc = Manchu + TRUETYPE_TAG('m', 'n', 'd', 0), // mnd = Mondé + TRUETYPE_TAG('m', 'n', 'e', 0), // mne = Naba + TRUETYPE_TAG('m', 'n', 'f', 0), // mnf = Mundani + TRUETYPE_TAG('m', 'n', 'g', 0), // mng = Eastern Mnong + TRUETYPE_TAG('m', 'n', 'h', + 0), // mnh = Mono (Democratic Republic of Congo) + TRUETYPE_TAG('m', 'n', 'i', 0), // mni = Manipuri + TRUETYPE_TAG('m', 'n', 'j', 0), // mnj = Munji + TRUETYPE_TAG('m', 'n', 'k', 0), // mnk = Mandinka + TRUETYPE_TAG('m', 'n', 'l', 0), // mnl = Tiale + TRUETYPE_TAG('m', 'n', 'm', 0), // mnm = Mapena + TRUETYPE_TAG('m', 'n', 'n', 0), // mnn = Southern Mnong + TRUETYPE_TAG('m', 'n', 'o', 0), // mno = Manobo languages + TRUETYPE_TAG('m', 'n', 'p', 0), // mnp = Min Bei Chinese + TRUETYPE_TAG('m', 'n', 'q', 0), // mnq = Minriq + TRUETYPE_TAG('m', 'n', 'r', 0), // mnr = Mono (USA) + TRUETYPE_TAG('m', 'n', 's', 0), // mns = Mansi + TRUETYPE_TAG('m', 'n', 't', 0), // mnt = Maykulan + TRUETYPE_TAG('m', 'n', 'u', 0), // mnu = Mer + TRUETYPE_TAG('m', 'n', 'v', 0), // mnv = Rennell-Bellona + TRUETYPE_TAG('m', 'n', 'w', 0), // mnw = Mon + TRUETYPE_TAG('m', 'n', 'x', 0), // mnx = Manikion + TRUETYPE_TAG('m', 'n', 'y', 0), // mny = Manyawa + TRUETYPE_TAG('m', 'n', 'z', 0), // mnz = Moni + TRUETYPE_TAG('m', 'o', 'a', 0), // moa = Mwan + TRUETYPE_TAG('m', 'o', 'c', 0), // moc = Mocoví + TRUETYPE_TAG('m', 'o', 'd', 0), // mod = Mobilian + TRUETYPE_TAG('m', 'o', 'e', 0), // moe = Montagnais + TRUETYPE_TAG('m', 'o', 'f', 0), // mof = Mohegan-Montauk-Narragansett + TRUETYPE_TAG('m', 'o', 'g', 0), // mog = Mongondow + TRUETYPE_TAG('m', 'o', 'h', 0), // moh = Mohawk + TRUETYPE_TAG('m', 'o', 'i', 0), // moi = Mboi + TRUETYPE_TAG('m', 'o', 'j', 0), // moj = Monzombo + TRUETYPE_TAG('m', 'o', 'k', 0), // mok = Morori + TRUETYPE_TAG('m', 'o', 'm', 0), // mom = Mangue + TRUETYPE_TAG('m', 'o', 'o', 0), // moo = Monom + TRUETYPE_TAG('m', 'o', 'p', 0), // mop = Mopán Maya + TRUETYPE_TAG('m', 'o', 'q', 0), // moq = Mor (Bomberai Peninsula) + TRUETYPE_TAG('m', 'o', 'r', 0), // mor = Moro + TRUETYPE_TAG('m', 'o', 's', 0), // mos = Mossi + TRUETYPE_TAG('m', 'o', 't', 0), // mot = Barí + TRUETYPE_TAG('m', 'o', 'u', 0), // mou = Mogum + TRUETYPE_TAG('m', 'o', 'v', 0), // mov = Mohave + TRUETYPE_TAG('m', 'o', 'w', 0), // mow = Moi (Congo) + TRUETYPE_TAG('m', 'o', 'x', 0), // mox = Molima + TRUETYPE_TAG('m', 'o', 'y', 0), // moy = Shekkacho + TRUETYPE_TAG('m', 'o', 'z', 0), // moz = Mukulu + TRUETYPE_TAG('m', 'p', 'a', 0), // mpa = Mpoto + TRUETYPE_TAG('m', 'p', 'b', 0), // mpb = Mullukmulluk + TRUETYPE_TAG('m', 'p', 'c', 0), // mpc = Mangarayi + TRUETYPE_TAG('m', 'p', 'd', 0), // mpd = Machinere + TRUETYPE_TAG('m', 'p', 'e', 0), // mpe = Majang + TRUETYPE_TAG('m', 'p', 'g', 0), // mpg = Marba + TRUETYPE_TAG('m', 'p', 'h', 0), // mph = Maung + TRUETYPE_TAG('m', 'p', 'i', 0), // mpi = Mpade + TRUETYPE_TAG('m', 'p', 'j', 0), // mpj = Martu Wangka + TRUETYPE_TAG('m', 'p', 'k', 0), // mpk = Mbara (Chad) + TRUETYPE_TAG('m', 'p', 'l', 0), // mpl = Middle Watut + TRUETYPE_TAG('m', 'p', 'm', 0), // mpm = Yosondúa Mixtec + TRUETYPE_TAG('m', 'p', 'n', 0), // mpn = Mindiri + TRUETYPE_TAG('m', 'p', 'o', 0), // mpo = Miu + TRUETYPE_TAG('m', 'p', 'p', 0), // mpp = Migabac + TRUETYPE_TAG('m', 'p', 'q', 0), // mpq = Matís + TRUETYPE_TAG('m', 'p', 'r', 0), // mpr = Vangunu + TRUETYPE_TAG('m', 'p', 's', 0), // mps = Dadibi + TRUETYPE_TAG('m', 'p', 't', 0), // mpt = Mian + TRUETYPE_TAG('m', 'p', 'u', 0), // mpu = Makuráp + TRUETYPE_TAG('m', 'p', 'v', 0), // mpv = Mungkip + TRUETYPE_TAG('m', 'p', 'w', 0), // mpw = Mapidian + TRUETYPE_TAG('m', 'p', 'x', 0), // mpx = Misima-Paneati + TRUETYPE_TAG('m', 'p', 'y', 0), // mpy = Mapia + TRUETYPE_TAG('m', 'p', 'z', 0), // mpz = Mpi + TRUETYPE_TAG('m', 'q', 'a', 0), // mqa = Maba (Indonesia) + TRUETYPE_TAG('m', 'q', 'b', 0), // mqb = Mbuko + TRUETYPE_TAG('m', 'q', 'c', 0), // mqc = Mangole + TRUETYPE_TAG('m', 'q', 'e', 0), // mqe = Matepi + TRUETYPE_TAG('m', 'q', 'f', 0), // mqf = Momuna + TRUETYPE_TAG('m', 'q', 'g', 0), // mqg = Kota Bangun Kutai Malay + TRUETYPE_TAG('m', 'q', 'h', 0), // mqh = Tlazoyaltepec Mixtec + TRUETYPE_TAG('m', 'q', 'i', 0), // mqi = Mariri + TRUETYPE_TAG('m', 'q', 'j', 0), // mqj = Mamasa + TRUETYPE_TAG('m', 'q', 'k', 0), // mqk = Rajah Kabunsuwan Manobo + TRUETYPE_TAG('m', 'q', 'l', 0), // mql = Mbelime + TRUETYPE_TAG('m', 'q', 'm', 0), // mqm = South Marquesan + TRUETYPE_TAG('m', 'q', 'n', 0), // mqn = Moronene + TRUETYPE_TAG('m', 'q', 'o', 0), // mqo = Modole + TRUETYPE_TAG('m', 'q', 'p', 0), // mqp = Manipa + TRUETYPE_TAG('m', 'q', 'q', 0), // mqq = Minokok + TRUETYPE_TAG('m', 'q', 'r', 0), // mqr = Mander + TRUETYPE_TAG('m', 'q', 's', 0), // mqs = West Makian + TRUETYPE_TAG('m', 'q', 't', 0), // mqt = Mok + TRUETYPE_TAG('m', 'q', 'u', 0), // mqu = Mandari + TRUETYPE_TAG('m', 'q', 'v', 0), // mqv = Mosimo + TRUETYPE_TAG('m', 'q', 'w', 0), // mqw = Murupi + TRUETYPE_TAG('m', 'q', 'x', 0), // mqx = Mamuju + TRUETYPE_TAG('m', 'q', 'y', 0), // mqy = Manggarai + TRUETYPE_TAG('m', 'q', 'z', 0), // mqz = Malasanga + TRUETYPE_TAG('m', 'r', 'a', 0), // mra = Mlabri + TRUETYPE_TAG('m', 'r', 'b', 0), // mrb = Marino + TRUETYPE_TAG('m', 'r', 'c', 0), // mrc = Maricopa + TRUETYPE_TAG('m', 'r', 'd', 0), // mrd = Western Magar + TRUETYPE_TAG('m', 'r', 'e', 0), // mre = Martha's Vineyard Sign Language + TRUETYPE_TAG('m', 'r', 'f', 0), // mrf = Elseng + TRUETYPE_TAG('m', 'r', 'g', 0), // mrg = Mising + TRUETYPE_TAG('m', 'r', 'h', 0), // mrh = Mara Chin + TRUETYPE_TAG('m', 'r', 'j', 0), // mrj = Western Mari + TRUETYPE_TAG('m', 'r', 'k', 0), // mrk = Hmwaveke + TRUETYPE_TAG('m', 'r', 'l', 0), // mrl = Mortlockese + TRUETYPE_TAG('m', 'r', 'm', 0), // mrm = Merlav + TRUETYPE_TAG('m', 'r', 'n', 0), // mrn = Cheke Holo + TRUETYPE_TAG('m', 'r', 'o', 0), // mro = Mru + TRUETYPE_TAG('m', 'r', 'p', 0), // mrp = Morouas + TRUETYPE_TAG('m', 'r', 'q', 0), // mrq = North Marquesan + TRUETYPE_TAG('m', 'r', 'r', 0), // mrr = Maria (India) + TRUETYPE_TAG('m', 'r', 's', 0), // mrs = Maragus + TRUETYPE_TAG('m', 'r', 't', 0), // mrt = Marghi Central + TRUETYPE_TAG('m', 'r', 'u', 0), // mru = Mono (Cameroon) + TRUETYPE_TAG('m', 'r', 'v', 0), // mrv = Mangareva + TRUETYPE_TAG('m', 'r', 'w', 0), // mrw = Maranao + TRUETYPE_TAG('m', 'r', 'x', 0), // mrx = Maremgi + TRUETYPE_TAG('m', 'r', 'y', 0), // mry = Mandaya + TRUETYPE_TAG('m', 'r', 'z', 0), // mrz = Marind + TRUETYPE_TAG('m', 's', 'b', 0), // msb = Masbatenyo + TRUETYPE_TAG('m', 's', 'c', 0), // msc = Sankaran Maninka + TRUETYPE_TAG('m', 's', 'd', 0), // msd = Yucatec Maya Sign Language + TRUETYPE_TAG('m', 's', 'e', 0), // mse = Musey + TRUETYPE_TAG('m', 's', 'f', 0), // msf = Mekwei + TRUETYPE_TAG('m', 's', 'g', 0), // msg = Moraid + TRUETYPE_TAG('m', 's', 'h', 0), // msh = Masikoro Malagasy + TRUETYPE_TAG('m', 's', 'i', 0), // msi = Sabah Malay + TRUETYPE_TAG('m', 's', 'j', 0), // msj = Ma (Democratic Republic of Congo) + TRUETYPE_TAG('m', 's', 'k', 0), // msk = Mansaka + TRUETYPE_TAG('m', 's', 'l', 0), // msl = Molof + TRUETYPE_TAG('m', 's', 'm', 0), // msm = Agusan Manobo + TRUETYPE_TAG('m', 's', 'n', 0), // msn = Vurës + TRUETYPE_TAG('m', 's', 'o', 0), // mso = Mombum + TRUETYPE_TAG('m', 's', 'p', 0), // msp = Maritsauá + TRUETYPE_TAG('m', 's', 'q', 0), // msq = Caac + TRUETYPE_TAG('m', 's', 'r', 0), // msr = Mongolian Sign Language + TRUETYPE_TAG('m', 's', 's', 0), // mss = West Masela + TRUETYPE_TAG('m', 's', 't', 0), // mst = Cataelano Mandaya + TRUETYPE_TAG('m', 's', 'u', 0), // msu = Musom + TRUETYPE_TAG('m', 's', 'v', 0), // msv = Maslam + TRUETYPE_TAG('m', 's', 'w', 0), // msw = Mansoanka + TRUETYPE_TAG('m', 's', 'x', 0), // msx = Moresada + TRUETYPE_TAG('m', 's', 'y', 0), // msy = Aruamu + TRUETYPE_TAG('m', 's', 'z', 0), // msz = Momare + TRUETYPE_TAG('m', 't', 'a', 0), // mta = Cotabato Manobo + TRUETYPE_TAG('m', 't', 'b', 0), // mtb = Anyin Morofo + TRUETYPE_TAG('m', 't', 'c', 0), // mtc = Munit + TRUETYPE_TAG('m', 't', 'd', 0), // mtd = Mualang + TRUETYPE_TAG('m', 't', 'e', 0), // mte = Mono (Solomon Islands) + TRUETYPE_TAG('m', 't', 'f', 0), // mtf = Murik (Papua New Guinea) + TRUETYPE_TAG('m', 't', 'g', 0), // mtg = Una + TRUETYPE_TAG('m', 't', 'h', 0), // mth = Munggui + TRUETYPE_TAG('m', 't', 'i', 0), // mti = Maiwa (Papua New Guinea) + TRUETYPE_TAG('m', 't', 'j', 0), // mtj = Moskona + TRUETYPE_TAG('m', 't', 'k', 0), // mtk = Mbe' + TRUETYPE_TAG('m', 't', 'l', 0), // mtl = Montol + TRUETYPE_TAG('m', 't', 'm', 0), // mtm = Mator + TRUETYPE_TAG('m', 't', 'n', 0), // mtn = Matagalpa + TRUETYPE_TAG('m', 't', 'o', 0), // mto = Totontepec Mixe + TRUETYPE_TAG('m', 't', 'p', 0), // mtp = Wichí Lhamtés Nocten + TRUETYPE_TAG('m', 't', 'q', 0), // mtq = Muong + TRUETYPE_TAG('m', 't', 'r', 0), // mtr = Mewari + TRUETYPE_TAG('m', 't', 's', 0), // mts = Yora + TRUETYPE_TAG('m', 't', 't', 0), // mtt = Mota + TRUETYPE_TAG('m', 't', 'u', 0), // mtu = Tututepec Mixtec + TRUETYPE_TAG('m', 't', 'v', 0), // mtv = Asaro'o + TRUETYPE_TAG('m', 't', 'w', 0), // mtw = Southern Binukidnon + TRUETYPE_TAG('m', 't', 'x', 0), // mtx = Tidaá Mixtec + TRUETYPE_TAG('m', 't', 'y', 0), // mty = Nabi + TRUETYPE_TAG('m', 'u', 'a', 0), // mua = Mundang + TRUETYPE_TAG('m', 'u', 'b', 0), // mub = Mubi + TRUETYPE_TAG('m', 'u', 'c', 0), // muc = Mbu' + TRUETYPE_TAG('m', 'u', 'd', 0), // mud = Mednyj Aleut + TRUETYPE_TAG('m', 'u', 'e', 0), // mue = Media Lengua + TRUETYPE_TAG('m', 'u', 'g', 0), // mug = Musgu + TRUETYPE_TAG('m', 'u', 'h', 0), // muh = Mündü + TRUETYPE_TAG('m', 'u', 'i', 0), // mui = Musi + TRUETYPE_TAG('m', 'u', 'j', 0), // muj = Mabire + TRUETYPE_TAG('m', 'u', 'k', 0), // muk = Mugom + TRUETYPE_TAG('m', 'u', 'l', 0), // mul = Multiple languages + TRUETYPE_TAG('m', 'u', 'm', 0), // mum = Maiwala + TRUETYPE_TAG('m', 'u', 'n', 0), // mun = Munda languages + TRUETYPE_TAG('m', 'u', 'o', 0), // muo = Nyong + TRUETYPE_TAG('m', 'u', 'p', 0), // mup = Malvi + TRUETYPE_TAG('m', 'u', 'q', 0), // muq = Eastern Xiangxi Miao + TRUETYPE_TAG('m', 'u', 'r', 0), // mur = Murle + TRUETYPE_TAG('m', 'u', 's', 0), // mus = Creek + TRUETYPE_TAG('m', 'u', 't', 0), // mut = Western Muria + TRUETYPE_TAG('m', 'u', 'u', 0), // muu = Yaaku + TRUETYPE_TAG('m', 'u', 'v', 0), // muv = Muthuvan + TRUETYPE_TAG('m', 'u', 'x', 0), // mux = Bo-Ung + TRUETYPE_TAG('m', 'u', 'y', 0), // muy = Muyang + TRUETYPE_TAG('m', 'u', 'z', 0), // muz = Mursi + TRUETYPE_TAG('m', 'v', 'a', 0), // mva = Manam + TRUETYPE_TAG('m', 'v', 'b', 0), // mvb = Mattole + TRUETYPE_TAG('m', 'v', 'd', 0), // mvd = Mamboru + TRUETYPE_TAG('m', 'v', 'e', 0), // mve = Marwari (Pakistan) + TRUETYPE_TAG('m', 'v', 'f', 0), // mvf = Peripheral Mongolian + TRUETYPE_TAG('m', 'v', 'g', 0), // mvg = Yucuañe Mixtec + TRUETYPE_TAG('m', 'v', 'h', 0), // mvh = Mire + TRUETYPE_TAG('m', 'v', 'i', 0), // mvi = Miyako + TRUETYPE_TAG('m', 'v', 'k', 0), // mvk = Mekmek + TRUETYPE_TAG('m', 'v', 'l', 0), // mvl = Mbara (Australia) + TRUETYPE_TAG('m', 'v', 'm', 0), // mvm = Muya + TRUETYPE_TAG('m', 'v', 'n', 0), // mvn = Minaveha + TRUETYPE_TAG('m', 'v', 'o', 0), // mvo = Marovo + TRUETYPE_TAG('m', 'v', 'p', 0), // mvp = Duri + TRUETYPE_TAG('m', 'v', 'q', 0), // mvq = Moere + TRUETYPE_TAG('m', 'v', 'r', 0), // mvr = Marau + TRUETYPE_TAG('m', 'v', 's', 0), // mvs = Massep + TRUETYPE_TAG('m', 'v', 't', 0), // mvt = Mpotovoro + TRUETYPE_TAG('m', 'v', 'u', 0), // mvu = Marfa + TRUETYPE_TAG('m', 'v', 'v', 0), // mvv = Tagal Murut + TRUETYPE_TAG('m', 'v', 'w', 0), // mvw = Machinga + TRUETYPE_TAG('m', 'v', 'x', 0), // mvx = Meoswar + TRUETYPE_TAG('m', 'v', 'y', 0), // mvy = Indus Kohistani + TRUETYPE_TAG('m', 'v', 'z', 0), // mvz = Mesqan + TRUETYPE_TAG('m', 'w', 'a', 0), // mwa = Mwatebu + TRUETYPE_TAG('m', 'w', 'b', 0), // mwb = Juwal + TRUETYPE_TAG('m', 'w', 'c', 0), // mwc = Are + TRUETYPE_TAG('m', 'w', 'd', 0), // mwd = Mudbura + TRUETYPE_TAG('m', 'w', 'e', 0), // mwe = Mwera (Chimwera) + TRUETYPE_TAG('m', 'w', 'f', 0), // mwf = Murrinh-Patha + TRUETYPE_TAG('m', 'w', 'g', 0), // mwg = Aiklep + TRUETYPE_TAG('m', 'w', 'h', 0), // mwh = Mouk-Aria + TRUETYPE_TAG('m', 'w', 'i', 0), // mwi = Labo + TRUETYPE_TAG('m', 'w', 'j', 0), // mwj = Maligo + TRUETYPE_TAG('m', 'w', 'k', 0), // mwk = Kita Maninkakan + TRUETYPE_TAG('m', 'w', 'l', 0), // mwl = Mirandese + TRUETYPE_TAG('m', 'w', 'm', 0), // mwm = Sar + TRUETYPE_TAG('m', 'w', 'n', 0), // mwn = Nyamwanga + TRUETYPE_TAG('m', 'w', 'o', 0), // mwo = Central Maewo + TRUETYPE_TAG('m', 'w', 'p', 0), // mwp = Kala Lagaw Ya + TRUETYPE_TAG('m', 'w', 'q', 0), // mwq = Mün Chin + TRUETYPE_TAG('m', 'w', 'r', 0), // mwr = Marwari + TRUETYPE_TAG('m', 'w', 's', 0), // mws = Mwimbi-Muthambi + TRUETYPE_TAG('m', 'w', 't', 0), // mwt = Moken + TRUETYPE_TAG('m', 'w', 'u', 0), // mwu = Mittu + TRUETYPE_TAG('m', 'w', 'v', 0), // mwv = Mentawai + TRUETYPE_TAG('m', 'w', 'w', 0), // mww = Hmong Daw + TRUETYPE_TAG('m', 'w', 'x', 0), // mwx = Mediak + TRUETYPE_TAG('m', 'w', 'y', 0), // mwy = Mosiro + TRUETYPE_TAG('m', 'w', 'z', 0), // mwz = Moingi + TRUETYPE_TAG('m', 'x', 'a', 0), // mxa = Northwest Oaxaca Mixtec + TRUETYPE_TAG('m', 'x', 'b', 0), // mxb = Tezoatlán Mixtec + TRUETYPE_TAG('m', 'x', 'c', 0), // mxc = Manyika + TRUETYPE_TAG('m', 'x', 'd', 0), // mxd = Modang + TRUETYPE_TAG('m', 'x', 'e', 0), // mxe = Mele-Fila + TRUETYPE_TAG('m', 'x', 'f', 0), // mxf = Malgbe + TRUETYPE_TAG('m', 'x', 'g', 0), // mxg = Mbangala + TRUETYPE_TAG('m', 'x', 'h', 0), // mxh = Mvuba + TRUETYPE_TAG('m', 'x', 'i', 0), // mxi = Mozarabic + TRUETYPE_TAG('m', 'x', 'j', 0), // mxj = Miju-Mishmi + TRUETYPE_TAG('m', 'x', 'k', 0), // mxk = Monumbo + TRUETYPE_TAG('m', 'x', 'l', 0), // mxl = Maxi Gbe + TRUETYPE_TAG('m', 'x', 'm', 0), // mxm = Meramera + TRUETYPE_TAG('m', 'x', 'n', 0), // mxn = Moi (Indonesia) + TRUETYPE_TAG('m', 'x', 'o', 0), // mxo = Mbowe + TRUETYPE_TAG('m', 'x', 'p', 0), // mxp = Tlahuitoltepec Mixe + TRUETYPE_TAG('m', 'x', 'q', 0), // mxq = Juquila Mixe + TRUETYPE_TAG('m', 'x', 'r', 0), // mxr = Murik (Malaysia) + TRUETYPE_TAG('m', 'x', 's', 0), // mxs = Huitepec Mixtec + TRUETYPE_TAG('m', 'x', 't', 0), // mxt = Jamiltepec Mixtec + TRUETYPE_TAG('m', 'x', 'u', 0), // mxu = Mada (Cameroon) + TRUETYPE_TAG('m', 'x', 'v', 0), // mxv = Metlatónoc Mixtec + TRUETYPE_TAG('m', 'x', 'w', 0), // mxw = Namo + TRUETYPE_TAG('m', 'x', 'x', 0), // mxx = Mahou + TRUETYPE_TAG('m', 'x', 'y', 0), // mxy = Southeastern Nochixtlán Mixtec + TRUETYPE_TAG('m', 'x', 'z', 0), // mxz = Central Masela + TRUETYPE_TAG('m', 'y', 'b', 0), // myb = Mbay + TRUETYPE_TAG('m', 'y', 'c', 0), // myc = Mayeka + TRUETYPE_TAG('m', 'y', 'd', 0), // myd = Maramba + TRUETYPE_TAG('m', 'y', 'e', 0), // mye = Myene + TRUETYPE_TAG('m', 'y', 'f', 0), // myf = Bambassi + TRUETYPE_TAG('m', 'y', 'g', 0), // myg = Manta + TRUETYPE_TAG('m', 'y', 'h', 0), // myh = Makah + TRUETYPE_TAG('m', 'y', 'i', 0), // myi = Mina (India) + TRUETYPE_TAG('m', 'y', 'j', 0), // myj = Mangayat + TRUETYPE_TAG('m', 'y', 'k', 0), // myk = Mamara Senoufo + TRUETYPE_TAG('m', 'y', 'l', 0), // myl = Moma + TRUETYPE_TAG('m', 'y', 'm', 0), // mym = Me'en + TRUETYPE_TAG('m', 'y', 'n', 0), // myn = Mayan languages + TRUETYPE_TAG('m', 'y', 'o', 0), // myo = Anfillo + TRUETYPE_TAG('m', 'y', 'p', 0), // myp = Pirahã + TRUETYPE_TAG('m', 'y', 'q', 0), // myq = Forest Maninka + TRUETYPE_TAG('m', 'y', 'r', 0), // myr = Muniche + TRUETYPE_TAG('m', 'y', 's', 0), // mys = Mesmes + TRUETYPE_TAG('m', 'y', 't', 0), // myt = Sangab Mandaya + TRUETYPE_TAG('m', 'y', 'u', 0), // myu = Mundurukú + TRUETYPE_TAG('m', 'y', 'v', 0), // myv = Erzya + TRUETYPE_TAG('m', 'y', 'w', 0), // myw = Muyuw + TRUETYPE_TAG('m', 'y', 'x', 0), // myx = Masaaba + TRUETYPE_TAG('m', 'y', 'y', 0), // myy = Macuna + TRUETYPE_TAG('m', 'y', 'z', 0), // myz = Classical Mandaic + TRUETYPE_TAG('m', 'z', 'a', 0), // mza = Santa María Zacatepec Mixtec + TRUETYPE_TAG('m', 'z', 'b', 0), // mzb = Tumzabt + TRUETYPE_TAG('m', 'z', 'c', 0), // mzc = Madagascar Sign Language + TRUETYPE_TAG('m', 'z', 'd', 0), // mzd = Malimba + TRUETYPE_TAG('m', 'z', 'e', 0), // mze = Morawa + TRUETYPE_TAG('m', 'z', 'g', 0), // mzg = Monastic Sign Language + TRUETYPE_TAG('m', 'z', 'h', 0), // mzh = Wichí Lhamtés Güisnay + TRUETYPE_TAG('m', 'z', 'i', 0), // mzi = Ixcatlán Mazatec + TRUETYPE_TAG('m', 'z', 'j', 0), // mzj = Manya + TRUETYPE_TAG('m', 'z', 'k', 0), // mzk = Nigeria Mambila + TRUETYPE_TAG('m', 'z', 'l', 0), // mzl = Mazatlán Mixe + TRUETYPE_TAG('m', 'z', 'm', 0), // mzm = Mumuye + TRUETYPE_TAG('m', 'z', 'n', 0), // mzn = Mazanderani + TRUETYPE_TAG('m', 'z', 'o', 0), // mzo = Matipuhy + TRUETYPE_TAG('m', 'z', 'p', 0), // mzp = Movima + TRUETYPE_TAG('m', 'z', 'q', 0), // mzq = Mori Atas + TRUETYPE_TAG('m', 'z', 'r', 0), // mzr = Marúbo + TRUETYPE_TAG('m', 'z', 's', 0), // mzs = Macanese + TRUETYPE_TAG('m', 'z', 't', 0), // mzt = Mintil + TRUETYPE_TAG('m', 'z', 'u', 0), // mzu = Inapang + TRUETYPE_TAG('m', 'z', 'v', 0), // mzv = Manza + TRUETYPE_TAG('m', 'z', 'w', 0), // mzw = Deg + TRUETYPE_TAG('m', 'z', 'x', 0), // mzx = Mawayana + TRUETYPE_TAG('m', 'z', 'y', 0), // mzy = Mozambican Sign Language + TRUETYPE_TAG('m', 'z', 'z', 0), // mzz = Maiadomu + TRUETYPE_TAG('n', 'a', 'a', 0), // naa = Namla + TRUETYPE_TAG('n', 'a', 'b', 0), // nab = Southern Nambikuára + TRUETYPE_TAG('n', 'a', 'c', 0), // nac = Narak + TRUETYPE_TAG('n', 'a', 'd', 0), // nad = Nijadali + TRUETYPE_TAG('n', 'a', 'e', 0), // nae = Naka'ela + TRUETYPE_TAG('n', 'a', 'f', 0), // naf = Nabak + TRUETYPE_TAG('n', 'a', 'g', 0), // nag = Naga Pidgin + TRUETYPE_TAG('n', 'a', 'h', 0), // nah = Nahuatl languages + TRUETYPE_TAG('n', 'a', 'i', 0), // nai = North American Indian languages + TRUETYPE_TAG('n', 'a', 'j', 0), // naj = Nalu + TRUETYPE_TAG('n', 'a', 'k', 0), // nak = Nakanai + TRUETYPE_TAG('n', 'a', 'l', 0), // nal = Nalik + TRUETYPE_TAG('n', 'a', 'm', 0), // nam = Nangikurrunggurr + TRUETYPE_TAG('n', 'a', 'n', 0), // nan = Min Nan Chinese + TRUETYPE_TAG('n', 'a', 'o', 0), // nao = Naaba + TRUETYPE_TAG('n', 'a', 'p', 0), // nap = Neapolitan + TRUETYPE_TAG('n', 'a', 'q', 0), // naq = Nama (Namibia) + TRUETYPE_TAG('n', 'a', 'r', 0), // nar = Iguta + TRUETYPE_TAG('n', 'a', 's', 0), // nas = Naasioi + TRUETYPE_TAG('n', 'a', 't', 0), // nat = Hungworo + TRUETYPE_TAG('n', 'a', 'w', 0), // naw = Nawuri + TRUETYPE_TAG('n', 'a', 'x', 0), // nax = Nakwi + TRUETYPE_TAG('n', 'a', 'y', 0), // nay = Narrinyeri + TRUETYPE_TAG('n', 'a', 'z', 0), // naz = Coatepec Nahuatl + TRUETYPE_TAG('n', 'b', 'a', 0), // nba = Nyemba + TRUETYPE_TAG('n', 'b', 'b', 0), // nbb = Ndoe + TRUETYPE_TAG('n', 'b', 'c', 0), // nbc = Chang Naga + TRUETYPE_TAG('n', 'b', 'd', 0), // nbd = Ngbinda + TRUETYPE_TAG('n', 'b', 'e', 0), // nbe = Konyak Naga + TRUETYPE_TAG('n', 'b', 'f', 0), // nbf = Naxi + TRUETYPE_TAG('n', 'b', 'g', 0), // nbg = Nagarchal + TRUETYPE_TAG('n', 'b', 'h', 0), // nbh = Ngamo + TRUETYPE_TAG('n', 'b', 'i', 0), // nbi = Mao Naga + TRUETYPE_TAG('n', 'b', 'j', 0), // nbj = Ngarinman + TRUETYPE_TAG('n', 'b', 'k', 0), // nbk = Nake + TRUETYPE_TAG('n', 'b', 'm', 0), // nbm = Ngbaka Ma'bo + TRUETYPE_TAG('n', 'b', 'n', 0), // nbn = Kuri + TRUETYPE_TAG('n', 'b', 'o', 0), // nbo = Nkukoli + TRUETYPE_TAG('n', 'b', 'p', 0), // nbp = Nnam + TRUETYPE_TAG('n', 'b', 'q', 0), // nbq = Nggem + TRUETYPE_TAG('n', 'b', 'r', 0), // nbr = Numana-Nunku-Gbantu-Numbu + TRUETYPE_TAG('n', 'b', 's', 0), // nbs = Namibian Sign Language + TRUETYPE_TAG('n', 'b', 't', 0), // nbt = Na + TRUETYPE_TAG('n', 'b', 'u', 0), // nbu = Rongmei Naga + TRUETYPE_TAG('n', 'b', 'v', 0), // nbv = Ngamambo + TRUETYPE_TAG('n', 'b', 'w', 0), // nbw = Southern Ngbandi + TRUETYPE_TAG('n', 'b', 'x', 0), // nbx = Ngura + TRUETYPE_TAG('n', 'b', 'y', 0), // nby = Ningera + TRUETYPE_TAG('n', 'c', 'a', 0), // nca = Iyo + TRUETYPE_TAG('n', 'c', 'b', 0), // ncb = Central Nicobarese + TRUETYPE_TAG('n', 'c', 'c', 0), // ncc = Ponam + TRUETYPE_TAG('n', 'c', 'd', 0), // ncd = Nachering + TRUETYPE_TAG('n', 'c', 'e', 0), // nce = Yale + TRUETYPE_TAG('n', 'c', 'f', 0), // ncf = Notsi + TRUETYPE_TAG('n', 'c', 'g', 0), // ncg = Nisga'a + TRUETYPE_TAG('n', 'c', 'h', 0), // nch = Central Huasteca Nahuatl + TRUETYPE_TAG('n', 'c', 'i', 0), // nci = Classical Nahuatl + TRUETYPE_TAG('n', 'c', 'j', 0), // ncj = Northern Puebla Nahuatl + TRUETYPE_TAG('n', 'c', 'k', 0), // nck = Nakara + TRUETYPE_TAG('n', 'c', 'l', 0), // ncl = Michoacán Nahuatl + TRUETYPE_TAG('n', 'c', 'm', 0), // ncm = Nambo + TRUETYPE_TAG('n', 'c', 'n', 0), // ncn = Nauna + TRUETYPE_TAG('n', 'c', 'o', 0), // nco = Sibe + TRUETYPE_TAG('n', 'c', 'p', 0), // ncp = Ndaktup + TRUETYPE_TAG('n', 'c', 'r', 0), // ncr = Ncane + TRUETYPE_TAG('n', 'c', 's', 0), // ncs = Nicaraguan Sign Language + TRUETYPE_TAG('n', 'c', 't', 0), // nct = Chothe Naga + TRUETYPE_TAG('n', 'c', 'u', 0), // ncu = Chumburung + TRUETYPE_TAG('n', 'c', 'x', 0), // ncx = Central Puebla Nahuatl + TRUETYPE_TAG('n', 'c', 'z', 0), // ncz = Natchez + TRUETYPE_TAG('n', 'd', 'a', 0), // nda = Ndasa + TRUETYPE_TAG('n', 'd', 'b', 0), // ndb = Kenswei Nsei + TRUETYPE_TAG('n', 'd', 'c', 0), // ndc = Ndau + TRUETYPE_TAG('n', 'd', 'd', 0), // ndd = Nde-Nsele-Nta + TRUETYPE_TAG('n', 'd', 'f', 0), // ndf = Nadruvian + TRUETYPE_TAG('n', 'd', 'g', 0), // ndg = Ndengereko + TRUETYPE_TAG('n', 'd', 'h', 0), // ndh = Ndali + TRUETYPE_TAG('n', 'd', 'i', 0), // ndi = Samba Leko + TRUETYPE_TAG('n', 'd', 'j', 0), // ndj = Ndamba + TRUETYPE_TAG('n', 'd', 'k', 0), // ndk = Ndaka + TRUETYPE_TAG('n', 'd', 'l', 0), // ndl = Ndolo + TRUETYPE_TAG('n', 'd', 'm', 0), // ndm = Ndam + TRUETYPE_TAG('n', 'd', 'n', 0), // ndn = Ngundi + TRUETYPE_TAG('n', 'd', 'p', 0), // ndp = Ndo + TRUETYPE_TAG('n', 'd', 'q', 0), // ndq = Ndombe + TRUETYPE_TAG('n', 'd', 'r', 0), // ndr = Ndoola + TRUETYPE_TAG('n', 'd', 's', 0), // nds = Low German + TRUETYPE_TAG('n', 'd', 't', 0), // ndt = Ndunga + TRUETYPE_TAG('n', 'd', 'u', 0), // ndu = Dugun + TRUETYPE_TAG('n', 'd', 'v', 0), // ndv = Ndut + TRUETYPE_TAG('n', 'd', 'w', 0), // ndw = Ndobo + TRUETYPE_TAG('n', 'd', 'x', 0), // ndx = Nduga + TRUETYPE_TAG('n', 'd', 'y', 0), // ndy = Lutos + TRUETYPE_TAG('n', 'd', 'z', 0), // ndz = Ndogo + TRUETYPE_TAG('n', 'e', 'a', 0), // nea = Eastern Ngad'a + TRUETYPE_TAG('n', 'e', 'b', 0), // neb = Toura (Côte d'Ivoire) + TRUETYPE_TAG('n', 'e', 'c', 0), // nec = Nedebang + TRUETYPE_TAG('n', 'e', 'd', 0), // ned = Nde-Gbite + TRUETYPE_TAG('n', 'e', 'e', 0), // nee = Nêlêmwa-Nixumwak + TRUETYPE_TAG('n', 'e', 'f', 0), // nef = Nefamese + TRUETYPE_TAG('n', 'e', 'g', 0), // neg = Negidal + TRUETYPE_TAG('n', 'e', 'h', 0), // neh = Nyenkha + TRUETYPE_TAG('n', 'e', 'i', 0), // nei = Neo-Hittite + TRUETYPE_TAG('n', 'e', 'j', 0), // nej = Neko + TRUETYPE_TAG('n', 'e', 'k', 0), // nek = Neku + TRUETYPE_TAG('n', 'e', 'm', 0), // nem = Nemi + TRUETYPE_TAG('n', 'e', 'n', 0), // nen = Nengone + TRUETYPE_TAG('n', 'e', 'o', 0), // neo = Ná-Meo + TRUETYPE_TAG('n', 'e', 'q', 0), // neq = North Central Mixe + TRUETYPE_TAG('n', 'e', 'r', 0), // ner = Yahadian + TRUETYPE_TAG('n', 'e', 's', 0), // nes = Bhoti Kinnauri + TRUETYPE_TAG('n', 'e', 't', 0), // net = Nete + TRUETYPE_TAG('n', 'e', 'v', 0), // nev = Nyaheun + TRUETYPE_TAG('n', 'e', 'w', 0), // new = Newari + TRUETYPE_TAG('n', 'e', 'x', 0), // nex = Neme + TRUETYPE_TAG('n', 'e', 'y', 0), // ney = Neyo + TRUETYPE_TAG('n', 'e', 'z', 0), // nez = Nez Perce + TRUETYPE_TAG('n', 'f', 'a', 0), // nfa = Dhao + TRUETYPE_TAG('n', 'f', 'd', 0), // nfd = Ahwai + TRUETYPE_TAG('n', 'f', 'l', 0), // nfl = Ayiwo + TRUETYPE_TAG('n', 'f', 'r', 0), // nfr = Nafaanra + TRUETYPE_TAG('n', 'f', 'u', 0), // nfu = Mfumte + TRUETYPE_TAG('n', 'g', 'a', 0), // nga = Ngbaka + TRUETYPE_TAG('n', 'g', 'b', 0), // ngb = Northern Ngbandi + TRUETYPE_TAG('n', 'g', 'c', + 0), // ngc = Ngombe (Democratic Republic of Congo) + TRUETYPE_TAG('n', 'g', 'd', 0), // ngd = Ngando (Central African Republic) + TRUETYPE_TAG('n', 'g', 'e', 0), // nge = Ngemba + TRUETYPE_TAG('n', 'g', 'f', 0), // ngf = Trans-New Guinea languages + TRUETYPE_TAG('n', 'g', 'g', 0), // ngg = Ngbaka Manza + TRUETYPE_TAG('n', 'g', 'h', 0), // ngh = N/u + TRUETYPE_TAG('n', 'g', 'i', 0), // ngi = Ngizim + TRUETYPE_TAG('n', 'g', 'j', 0), // ngj = Ngie + TRUETYPE_TAG('n', 'g', 'k', 0), // ngk = Ngalkbun + TRUETYPE_TAG('n', 'g', 'l', 0), // ngl = Lomwe + TRUETYPE_TAG('n', 'g', 'm', 0), // ngm = Ngatik Men's Creole + TRUETYPE_TAG('n', 'g', 'n', 0), // ngn = Ngwo + TRUETYPE_TAG('n', 'g', 'o', 0), // ngo = Ngoni + TRUETYPE_TAG('n', 'g', 'p', 0), // ngp = Ngulu + TRUETYPE_TAG('n', 'g', 'q', 0), // ngq = Ngurimi + TRUETYPE_TAG('n', 'g', 'r', 0), // ngr = Nanggu + TRUETYPE_TAG('n', 'g', 's', 0), // ngs = Gvoko + TRUETYPE_TAG('n', 'g', 't', 0), // ngt = Ngeq + TRUETYPE_TAG('n', 'g', 'u', 0), // ngu = Guerrero Nahuatl + TRUETYPE_TAG('n', 'g', 'v', 0), // ngv = Nagumi + TRUETYPE_TAG('n', 'g', 'w', 0), // ngw = Ngwaba + TRUETYPE_TAG('n', 'g', 'x', 0), // ngx = Nggwahyi + TRUETYPE_TAG('n', 'g', 'y', 0), // ngy = Tibea + TRUETYPE_TAG('n', 'g', 'z', 0), // ngz = Ngungwel + TRUETYPE_TAG('n', 'h', 'a', 0), // nha = Nhanda + TRUETYPE_TAG('n', 'h', 'b', 0), // nhb = Beng + TRUETYPE_TAG('n', 'h', 'c', 0), // nhc = Tabasco Nahuatl + TRUETYPE_TAG('n', 'h', 'd', 0), // nhd = Chiripá + TRUETYPE_TAG('n', 'h', 'e', 0), // nhe = Eastern Huasteca Nahuatl + TRUETYPE_TAG('n', 'h', 'f', 0), // nhf = Nhuwala + TRUETYPE_TAG('n', 'h', 'g', 0), // nhg = Tetelcingo Nahuatl + TRUETYPE_TAG('n', 'h', 'h', 0), // nhh = Nahari + TRUETYPE_TAG('n', 'h', 'i', + 0), // nhi = Zacatlán-Ahuacatlán-Tepetzintla Nahuatl + TRUETYPE_TAG('n', 'h', 'k', 0), // nhk = Isthmus-Cosoleacaque Nahuatl + TRUETYPE_TAG('n', 'h', 'm', 0), // nhm = Morelos Nahuatl + TRUETYPE_TAG('n', 'h', 'n', 0), // nhn = Central Nahuatl + TRUETYPE_TAG('n', 'h', 'o', 0), // nho = Takuu + TRUETYPE_TAG('n', 'h', 'p', 0), // nhp = Isthmus-Pajapan Nahuatl + TRUETYPE_TAG('n', 'h', 'q', 0), // nhq = Huaxcaleca Nahuatl + TRUETYPE_TAG('n', 'h', 'r', 0), // nhr = Naro + TRUETYPE_TAG('n', 'h', 't', 0), // nht = Ometepec Nahuatl + TRUETYPE_TAG('n', 'h', 'u', 0), // nhu = Noone + TRUETYPE_TAG('n', 'h', 'v', 0), // nhv = Temascaltepec Nahuatl + TRUETYPE_TAG('n', 'h', 'w', 0), // nhw = Western Huasteca Nahuatl + TRUETYPE_TAG('n', 'h', 'x', 0), // nhx = Isthmus-Mecayapan Nahuatl + TRUETYPE_TAG('n', 'h', 'y', 0), // nhy = Northern Oaxaca Nahuatl + TRUETYPE_TAG('n', 'h', 'z', 0), // nhz = Santa María La Alta Nahuatl + TRUETYPE_TAG('n', 'i', 'a', 0), // nia = Nias + TRUETYPE_TAG('n', 'i', 'b', 0), // nib = Nakame + TRUETYPE_TAG('n', 'i', 'c', 0), // nic = Niger-Kordofanian languages + TRUETYPE_TAG('n', 'i', 'd', 0), // nid = Ngandi + TRUETYPE_TAG('n', 'i', 'e', 0), // nie = Niellim + TRUETYPE_TAG('n', 'i', 'f', 0), // nif = Nek + TRUETYPE_TAG('n', 'i', 'g', 0), // nig = Ngalakan + TRUETYPE_TAG('n', 'i', 'h', 0), // nih = Nyiha (Tanzania) + TRUETYPE_TAG('n', 'i', 'i', 0), // nii = Nii + TRUETYPE_TAG('n', 'i', 'j', 0), // nij = Ngaju + TRUETYPE_TAG('n', 'i', 'k', 0), // nik = Southern Nicobarese + TRUETYPE_TAG('n', 'i', 'l', 0), // nil = Nila + TRUETYPE_TAG('n', 'i', 'm', 0), // nim = Nilamba + TRUETYPE_TAG('n', 'i', 'n', 0), // nin = Ninzo + TRUETYPE_TAG('n', 'i', 'o', 0), // nio = Nganasan + TRUETYPE_TAG('n', 'i', 'q', 0), // niq = Nandi + TRUETYPE_TAG('n', 'i', 'r', 0), // nir = Nimboran + TRUETYPE_TAG('n', 'i', 's', 0), // nis = Nimi + TRUETYPE_TAG('n', 'i', 't', 0), // nit = Southeastern Kolami + TRUETYPE_TAG('n', 'i', 'u', 0), // niu = Niuean + TRUETYPE_TAG('n', 'i', 'v', 0), // niv = Gilyak + TRUETYPE_TAG('n', 'i', 'w', 0), // niw = Nimo + TRUETYPE_TAG('n', 'i', 'x', 0), // nix = Hema + TRUETYPE_TAG('n', 'i', 'y', 0), // niy = Ngiti + TRUETYPE_TAG('n', 'i', 'z', 0), // niz = Ningil + TRUETYPE_TAG('n', 'j', 'a', 0), // nja = Nzanyi + TRUETYPE_TAG('n', 'j', 'b', 0), // njb = Nocte Naga + TRUETYPE_TAG('n', 'j', 'd', 0), // njd = Ndonde Hamba + TRUETYPE_TAG('n', 'j', 'h', 0), // njh = Lotha Naga + TRUETYPE_TAG('n', 'j', 'i', 0), // nji = Gudanji + TRUETYPE_TAG('n', 'j', 'j', 0), // njj = Njen + TRUETYPE_TAG('n', 'j', 'l', 0), // njl = Njalgulgule + TRUETYPE_TAG('n', 'j', 'm', 0), // njm = Angami Naga + TRUETYPE_TAG('n', 'j', 'n', 0), // njn = Liangmai Naga + TRUETYPE_TAG('n', 'j', 'o', 0), // njo = Ao Naga + TRUETYPE_TAG('n', 'j', 'r', 0), // njr = Njerep + TRUETYPE_TAG('n', 'j', 's', 0), // njs = Nisa + TRUETYPE_TAG('n', 'j', 't', 0), // njt = Ndyuka-Trio Pidgin + TRUETYPE_TAG('n', 'j', 'u', 0), // nju = Ngadjunmaya + TRUETYPE_TAG('n', 'j', 'x', 0), // njx = Kunyi + TRUETYPE_TAG('n', 'j', 'y', 0), // njy = Njyem + TRUETYPE_TAG('n', 'k', 'a', 0), // nka = Nkoya + TRUETYPE_TAG('n', 'k', 'b', 0), // nkb = Khoibu Naga + TRUETYPE_TAG('n', 'k', 'c', 0), // nkc = Nkongho + TRUETYPE_TAG('n', 'k', 'd', 0), // nkd = Koireng + TRUETYPE_TAG('n', 'k', 'e', 0), // nke = Duke + TRUETYPE_TAG('n', 'k', 'f', 0), // nkf = Inpui Naga + TRUETYPE_TAG('n', 'k', 'g', 0), // nkg = Nekgini + TRUETYPE_TAG('n', 'k', 'h', 0), // nkh = Khezha Naga + TRUETYPE_TAG('n', 'k', 'i', 0), // nki = Thangal Naga + TRUETYPE_TAG('n', 'k', 'j', 0), // nkj = Nakai + TRUETYPE_TAG('n', 'k', 'k', 0), // nkk = Nokuku + TRUETYPE_TAG('n', 'k', 'm', 0), // nkm = Namat + TRUETYPE_TAG('n', 'k', 'n', 0), // nkn = Nkangala + TRUETYPE_TAG('n', 'k', 'o', 0), // nko = Nkonya + TRUETYPE_TAG('n', 'k', 'p', 0), // nkp = Niuatoputapu + TRUETYPE_TAG('n', 'k', 'q', 0), // nkq = Nkami + TRUETYPE_TAG('n', 'k', 'r', 0), // nkr = Nukuoro + TRUETYPE_TAG('n', 'k', 's', 0), // nks = North Asmat + TRUETYPE_TAG('n', 'k', 't', 0), // nkt = Nyika (Tanzania) + TRUETYPE_TAG('n', 'k', 'u', 0), // nku = Bouna Kulango + TRUETYPE_TAG('n', 'k', 'v', 0), // nkv = Nyika (Malawi and Zambia) + TRUETYPE_TAG('n', 'k', 'w', 0), // nkw = Nkutu + TRUETYPE_TAG('n', 'k', 'x', 0), // nkx = Nkoroo + TRUETYPE_TAG('n', 'k', 'z', 0), // nkz = Nkari + TRUETYPE_TAG('n', 'l', 'a', 0), // nla = Ngombale + TRUETYPE_TAG('n', 'l', 'c', 0), // nlc = Nalca + TRUETYPE_TAG('n', 'l', 'e', 0), // nle = East Nyala + TRUETYPE_TAG('n', 'l', 'g', 0), // nlg = Gela + TRUETYPE_TAG('n', 'l', 'i', 0), // nli = Grangali + TRUETYPE_TAG('n', 'l', 'j', 0), // nlj = Nyali + TRUETYPE_TAG('n', 'l', 'k', 0), // nlk = Ninia Yali + TRUETYPE_TAG('n', 'l', 'l', 0), // nll = Nihali + TRUETYPE_TAG('n', 'l', 'n', 0), // nln = Durango Nahuatl + TRUETYPE_TAG('n', 'l', 'o', 0), // nlo = Ngul + TRUETYPE_TAG('n', 'l', 'r', 0), // nlr = Ngarla + TRUETYPE_TAG('n', 'l', 'u', 0), // nlu = Nchumbulu + TRUETYPE_TAG('n', 'l', 'v', 0), // nlv = Orizaba Nahuatl + TRUETYPE_TAG('n', 'l', 'x', 0), // nlx = Nahali + TRUETYPE_TAG('n', 'l', 'y', 0), // nly = Nyamal + TRUETYPE_TAG('n', 'l', 'z', 0), // nlz = Nalögo + TRUETYPE_TAG('n', 'm', 'a', 0), // nma = Maram Naga + TRUETYPE_TAG('n', 'm', 'b', 0), // nmb = Big Nambas + TRUETYPE_TAG('n', 'm', 'c', 0), // nmc = Ngam + TRUETYPE_TAG('n', 'm', 'd', 0), // nmd = Ndumu + TRUETYPE_TAG('n', 'm', 'e', 0), // nme = Mzieme Naga + TRUETYPE_TAG('n', 'm', 'f', 0), // nmf = Tangkhul Naga + TRUETYPE_TAG('n', 'm', 'g', 0), // nmg = Kwasio + TRUETYPE_TAG('n', 'm', 'h', 0), // nmh = Monsang Naga + TRUETYPE_TAG('n', 'm', 'i', 0), // nmi = Nyam + TRUETYPE_TAG('n', 'm', 'j', 0), // nmj = Ngombe (Central African Republic) + TRUETYPE_TAG('n', 'm', 'k', 0), // nmk = Namakura + TRUETYPE_TAG('n', 'm', 'l', 0), // nml = Ndemli + TRUETYPE_TAG('n', 'm', 'm', 0), // nmm = Manangba + TRUETYPE_TAG('n', 'm', 'n', 0), // nmn = !Xóõ + TRUETYPE_TAG('n', 'm', 'o', 0), // nmo = Moyon Naga + TRUETYPE_TAG('n', 'm', 'p', 0), // nmp = Nimanbur + TRUETYPE_TAG('n', 'm', 'q', 0), // nmq = Nambya + TRUETYPE_TAG('n', 'm', 'r', 0), // nmr = Nimbari + TRUETYPE_TAG('n', 'm', 's', 0), // nms = Letemboi + TRUETYPE_TAG('n', 'm', 't', 0), // nmt = Namonuito + TRUETYPE_TAG('n', 'm', 'u', 0), // nmu = Northeast Maidu + TRUETYPE_TAG('n', 'm', 'v', 0), // nmv = Ngamini + TRUETYPE_TAG('n', 'm', 'w', 0), // nmw = Nimoa + TRUETYPE_TAG('n', 'm', 'x', 0), // nmx = Nama (Papua New Guinea) + TRUETYPE_TAG('n', 'm', 'y', 0), // nmy = Namuyi + TRUETYPE_TAG('n', 'm', 'z', 0), // nmz = Nawdm + TRUETYPE_TAG('n', 'n', 'a', 0), // nna = Nyangumarta + TRUETYPE_TAG('n', 'n', 'b', 0), // nnb = Nande + TRUETYPE_TAG('n', 'n', 'c', 0), // nnc = Nancere + TRUETYPE_TAG('n', 'n', 'd', 0), // nnd = West Ambae + TRUETYPE_TAG('n', 'n', 'e', 0), // nne = Ngandyera + TRUETYPE_TAG('n', 'n', 'f', 0), // nnf = Ngaing + TRUETYPE_TAG('n', 'n', 'g', 0), // nng = Maring Naga + TRUETYPE_TAG('n', 'n', 'h', 0), // nnh = Ngiemboon + TRUETYPE_TAG('n', 'n', 'i', 0), // nni = North Nuaulu + TRUETYPE_TAG('n', 'n', 'j', 0), // nnj = Nyangatom + TRUETYPE_TAG('n', 'n', 'k', 0), // nnk = Nankina + TRUETYPE_TAG('n', 'n', 'l', 0), // nnl = Northern Rengma Naga + TRUETYPE_TAG('n', 'n', 'm', 0), // nnm = Namia + TRUETYPE_TAG('n', 'n', 'n', 0), // nnn = Ngete + TRUETYPE_TAG('n', 'n', 'p', 0), // nnp = Wancho Naga + TRUETYPE_TAG('n', 'n', 'q', 0), // nnq = Ngindo + TRUETYPE_TAG('n', 'n', 'r', 0), // nnr = Narungga + TRUETYPE_TAG('n', 'n', 's', 0), // nns = Ningye + TRUETYPE_TAG('n', 'n', 't', 0), // nnt = Nanticoke + TRUETYPE_TAG('n', 'n', 'u', 0), // nnu = Dwang + TRUETYPE_TAG('n', 'n', 'v', 0), // nnv = Nugunu (Australia) + TRUETYPE_TAG('n', 'n', 'w', 0), // nnw = Southern Nuni + TRUETYPE_TAG('n', 'n', 'x', 0), // nnx = Ngong + TRUETYPE_TAG('n', 'n', 'y', 0), // nny = Nyangga + TRUETYPE_TAG('n', 'n', 'z', 0), // nnz = Nda'nda' + TRUETYPE_TAG('n', 'o', 'a', 0), // noa = Woun Meu + TRUETYPE_TAG('n', 'o', 'c', 0), // noc = Nuk + TRUETYPE_TAG('n', 'o', 'd', 0), // nod = Northern Thai + TRUETYPE_TAG('n', 'o', 'e', 0), // noe = Nimadi + TRUETYPE_TAG('n', 'o', 'f', 0), // nof = Nomane + TRUETYPE_TAG('n', 'o', 'g', 0), // nog = Nogai + TRUETYPE_TAG('n', 'o', 'h', 0), // noh = Nomu + TRUETYPE_TAG('n', 'o', 'i', 0), // noi = Noiri + TRUETYPE_TAG('n', 'o', 'j', 0), // noj = Nonuya + TRUETYPE_TAG('n', 'o', 'k', 0), // nok = Nooksack + TRUETYPE_TAG('n', 'o', 'm', 0), // nom = Nocamán + TRUETYPE_TAG('n', 'o', 'n', 0), // non = Old Norse + TRUETYPE_TAG('n', 'o', 'o', 0), // noo = Nootka + TRUETYPE_TAG('n', 'o', 'p', 0), // nop = Numanggang + TRUETYPE_TAG('n', 'o', 'q', 0), // noq = Ngongo + TRUETYPE_TAG('n', 'o', 's', 0), // nos = Eastern Nisu + TRUETYPE_TAG('n', 'o', 't', 0), // not = Nomatsiguenga + TRUETYPE_TAG('n', 'o', 'u', 0), // nou = Ewage-Notu + TRUETYPE_TAG('n', 'o', 'v', 0), // nov = Novial + TRUETYPE_TAG('n', 'o', 'w', 0), // now = Nyambo + TRUETYPE_TAG('n', 'o', 'y', 0), // noy = Noy + TRUETYPE_TAG('n', 'o', 'z', 0), // noz = Nayi + TRUETYPE_TAG('n', 'p', 'a', 0), // npa = Nar Phu + TRUETYPE_TAG('n', 'p', 'b', 0), // npb = Nupbikha + TRUETYPE_TAG('n', 'p', 'h', 0), // nph = Phom Naga + TRUETYPE_TAG('n', 'p', 'l', 0), // npl = Southeastern Puebla Nahuatl + TRUETYPE_TAG('n', 'p', 'n', 0), // npn = Mondropolon + TRUETYPE_TAG('n', 'p', 'o', 0), // npo = Pochuri Naga + TRUETYPE_TAG('n', 'p', 's', 0), // nps = Nipsan + TRUETYPE_TAG('n', 'p', 'u', 0), // npu = Puimei Naga + TRUETYPE_TAG('n', 'p', 'y', 0), // npy = Napu + TRUETYPE_TAG('n', 'q', 'g', 0), // nqg = Southern Nago + TRUETYPE_TAG('n', 'q', 'k', 0), // nqk = Kura Ede Nago + TRUETYPE_TAG('n', 'q', 'm', 0), // nqm = Ndom + TRUETYPE_TAG('n', 'q', 'n', 0), // nqn = Nen + TRUETYPE_TAG('n', 'q', 'o', 0), // nqo = N'Ko + TRUETYPE_TAG('n', 'r', 'a', 0), // nra = Ngom + TRUETYPE_TAG('n', 'r', 'b', 0), // nrb = Nara + TRUETYPE_TAG('n', 'r', 'c', 0), // nrc = Noric + TRUETYPE_TAG('n', 'r', 'e', 0), // nre = Southern Rengma Naga + TRUETYPE_TAG('n', 'r', 'g', 0), // nrg = Narango + TRUETYPE_TAG('n', 'r', 'i', 0), // nri = Chokri Naga + TRUETYPE_TAG('n', 'r', 'l', 0), // nrl = Ngarluma + TRUETYPE_TAG('n', 'r', 'm', 0), // nrm = Narom + TRUETYPE_TAG('n', 'r', 'n', 0), // nrn = Norn + TRUETYPE_TAG('n', 'r', 'p', 0), // nrp = North Picene + TRUETYPE_TAG('n', 'r', 'r', 0), // nrr = Norra + TRUETYPE_TAG('n', 'r', 't', 0), // nrt = Northern Kalapuya + TRUETYPE_TAG('n', 'r', 'u', 0), // nru = Narua + TRUETYPE_TAG('n', 'r', 'x', 0), // nrx = Ngurmbur + TRUETYPE_TAG('n', 'r', 'z', 0), // nrz = Lala + TRUETYPE_TAG('n', 's', 'a', 0), // nsa = Sangtam Naga + TRUETYPE_TAG('n', 's', 'c', 0), // nsc = Nshi + TRUETYPE_TAG('n', 's', 'd', 0), // nsd = Southern Nisu + TRUETYPE_TAG('n', 's', 'e', 0), // nse = Nsenga + TRUETYPE_TAG('n', 's', 'g', 0), // nsg = Ngasa + TRUETYPE_TAG('n', 's', 'h', 0), // nsh = Ngoshie + TRUETYPE_TAG('n', 's', 'i', 0), // nsi = Nigerian Sign Language + TRUETYPE_TAG('n', 's', 'k', 0), // nsk = Naskapi + TRUETYPE_TAG('n', 's', 'l', 0), // nsl = Norwegian Sign Language + TRUETYPE_TAG('n', 's', 'm', 0), // nsm = Sumi Naga + TRUETYPE_TAG('n', 's', 'n', 0), // nsn = Nehan + TRUETYPE_TAG('n', 's', 'o', 0), // nso = Pedi + TRUETYPE_TAG('n', 's', 'p', 0), // nsp = Nepalese Sign Language + TRUETYPE_TAG('n', 's', 'q', 0), // nsq = Northern Sierra Miwok + TRUETYPE_TAG('n', 's', 'r', 0), // nsr = Maritime Sign Language + TRUETYPE_TAG('n', 's', 's', 0), // nss = Nali + TRUETYPE_TAG('n', 's', 't', 0), // nst = Tase Naga + TRUETYPE_TAG('n', 's', 'u', 0), // nsu = Sierra Negra Nahuatl + TRUETYPE_TAG('n', 's', 'v', 0), // nsv = Southwestern Nisu + TRUETYPE_TAG('n', 's', 'w', 0), // nsw = Navut + TRUETYPE_TAG('n', 's', 'x', 0), // nsx = Nsongo + TRUETYPE_TAG('n', 's', 'y', 0), // nsy = Nasal + TRUETYPE_TAG('n', 's', 'z', 0), // nsz = Nisenan + TRUETYPE_TAG('n', 't', 'e', 0), // nte = Nathembo + TRUETYPE_TAG('n', 't', 'i', 0), // nti = Natioro + TRUETYPE_TAG('n', 't', 'j', 0), // ntj = Ngaanyatjarra + TRUETYPE_TAG('n', 't', 'k', 0), // ntk = Ikoma-Nata-Isenye + TRUETYPE_TAG('n', 't', 'm', 0), // ntm = Nateni + TRUETYPE_TAG('n', 't', 'o', 0), // nto = Ntomba + TRUETYPE_TAG('n', 't', 'p', 0), // ntp = Northern Tepehuan + TRUETYPE_TAG('n', 't', 'r', 0), // ntr = Delo + TRUETYPE_TAG('n', 't', 's', 0), // nts = Natagaimas + TRUETYPE_TAG('n', 't', 'u', 0), // ntu = Natügu + TRUETYPE_TAG('n', 't', 'w', 0), // ntw = Nottoway + TRUETYPE_TAG('n', 't', 'y', 0), // nty = Mantsi + TRUETYPE_TAG('n', 't', 'z', 0), // ntz = Natanzi + TRUETYPE_TAG('n', 'u', 'a', 0), // nua = Yuaga + TRUETYPE_TAG('n', 'u', 'b', 0), // nub = Nubian languages + TRUETYPE_TAG('n', 'u', 'c', 0), // nuc = Nukuini + TRUETYPE_TAG('n', 'u', 'd', 0), // nud = Ngala + TRUETYPE_TAG('n', 'u', 'e', 0), // nue = Ngundu + TRUETYPE_TAG('n', 'u', 'f', 0), // nuf = Nusu + TRUETYPE_TAG('n', 'u', 'g', 0), // nug = Nungali + TRUETYPE_TAG('n', 'u', 'h', 0), // nuh = Ndunda + TRUETYPE_TAG('n', 'u', 'i', 0), // nui = Ngumbi + TRUETYPE_TAG('n', 'u', 'j', 0), // nuj = Nyole + TRUETYPE_TAG('n', 'u', 'k', 0), // nuk = Nuu-chah-nulth + TRUETYPE_TAG('n', 'u', 'l', 0), // nul = Nusa Laut + TRUETYPE_TAG('n', 'u', 'm', 0), // num = Niuafo'ou + TRUETYPE_TAG('n', 'u', 'n', 0), // nun = Anong + TRUETYPE_TAG('n', 'u', 'o', 0), // nuo = Nguôn + TRUETYPE_TAG('n', 'u', 'p', 0), // nup = Nupe-Nupe-Tako + TRUETYPE_TAG('n', 'u', 'q', 0), // nuq = Nukumanu + TRUETYPE_TAG('n', 'u', 'r', 0), // nur = Nukuria + TRUETYPE_TAG('n', 'u', 's', 0), // nus = Nuer + TRUETYPE_TAG('n', 'u', 't', 0), // nut = Nung (Viet Nam) + TRUETYPE_TAG('n', 'u', 'u', 0), // nuu = Ngbundu + TRUETYPE_TAG('n', 'u', 'v', 0), // nuv = Northern Nuni + TRUETYPE_TAG('n', 'u', 'w', 0), // nuw = Nguluwan + TRUETYPE_TAG('n', 'u', 'x', 0), // nux = Mehek + TRUETYPE_TAG('n', 'u', 'y', 0), // nuy = Nunggubuyu + TRUETYPE_TAG('n', 'u', 'z', 0), // nuz = Tlamacazapa Nahuatl + TRUETYPE_TAG('n', 'v', 'h', 0), // nvh = Nasarian + TRUETYPE_TAG('n', 'v', 'm', 0), // nvm = Namiae + TRUETYPE_TAG('n', 'w', 'a', 0), // nwa = Nawathinehena + TRUETYPE_TAG('n', 'w', 'b', 0), // nwb = Nyabwa + TRUETYPE_TAG('n', 'w', 'c', 0), // nwc = Classical Newari + TRUETYPE_TAG('n', 'w', 'e', 0), // nwe = Ngwe + TRUETYPE_TAG('n', 'w', 'i', 0), // nwi = Southwest Tanna + TRUETYPE_TAG('n', 'w', 'm', 0), // nwm = Nyamusa-Molo + TRUETYPE_TAG('n', 'w', 'r', 0), // nwr = Nawaru + TRUETYPE_TAG('n', 'w', 'x', 0), // nwx = Middle Newar + TRUETYPE_TAG('n', 'w', 'y', 0), // nwy = Nottoway-Meherrin + TRUETYPE_TAG('n', 'x', 'a', 0), // nxa = Nauete + TRUETYPE_TAG('n', 'x', 'd', + 0), // nxd = Ngando (Democratic Republic of Congo) + TRUETYPE_TAG('n', 'x', 'e', 0), // nxe = Nage + TRUETYPE_TAG('n', 'x', 'g', 0), // nxg = Ngad'a + TRUETYPE_TAG('n', 'x', 'i', 0), // nxi = Nindi + TRUETYPE_TAG('n', 'x', 'l', 0), // nxl = South Nuaulu + TRUETYPE_TAG('n', 'x', 'm', 0), // nxm = Numidian + TRUETYPE_TAG('n', 'x', 'n', 0), // nxn = Ngawun + TRUETYPE_TAG('n', 'x', 'q', 0), // nxq = Naxi + TRUETYPE_TAG('n', 'x', 'r', 0), // nxr = Ninggerum + TRUETYPE_TAG('n', 'x', 'u', 0), // nxu = Narau + TRUETYPE_TAG('n', 'x', 'x', 0), // nxx = Nafri + TRUETYPE_TAG('n', 'y', 'b', 0), // nyb = Nyangbo + TRUETYPE_TAG('n', 'y', 'c', 0), // nyc = Nyanga-li + TRUETYPE_TAG('n', 'y', 'd', 0), // nyd = Nyore + TRUETYPE_TAG('n', 'y', 'e', 0), // nye = Nyengo + TRUETYPE_TAG('n', 'y', 'f', 0), // nyf = Giryama + TRUETYPE_TAG('n', 'y', 'g', 0), // nyg = Nyindu + TRUETYPE_TAG('n', 'y', 'h', 0), // nyh = Nyigina + TRUETYPE_TAG('n', 'y', 'i', 0), // nyi = Ama (Sudan) + TRUETYPE_TAG('n', 'y', 'j', 0), // nyj = Nyanga + TRUETYPE_TAG('n', 'y', 'k', 0), // nyk = Nyaneka + TRUETYPE_TAG('n', 'y', 'l', 0), // nyl = Nyeu + TRUETYPE_TAG('n', 'y', 'm', 0), // nym = Nyamwezi + TRUETYPE_TAG('n', 'y', 'n', 0), // nyn = Nyankole + TRUETYPE_TAG('n', 'y', 'o', 0), // nyo = Nyoro + TRUETYPE_TAG('n', 'y', 'p', 0), // nyp = Nyang'i + TRUETYPE_TAG('n', 'y', 'q', 0), // nyq = Nayini + TRUETYPE_TAG('n', 'y', 'r', 0), // nyr = Nyiha (Malawi) + TRUETYPE_TAG('n', 'y', 's', 0), // nys = Nyunga + TRUETYPE_TAG('n', 'y', 't', 0), // nyt = Nyawaygi + TRUETYPE_TAG('n', 'y', 'u', 0), // nyu = Nyungwe + TRUETYPE_TAG('n', 'y', 'v', 0), // nyv = Nyulnyul + TRUETYPE_TAG('n', 'y', 'w', 0), // nyw = Nyaw + TRUETYPE_TAG('n', 'y', 'x', 0), // nyx = Nganyaywana + TRUETYPE_TAG('n', 'y', 'y', 0), // nyy = Nyakyusa-Ngonde + TRUETYPE_TAG('n', 'z', 'a', 0), // nza = Tigon Mbembe + TRUETYPE_TAG('n', 'z', 'b', 0), // nzb = Njebi + TRUETYPE_TAG('n', 'z', 'i', 0), // nzi = Nzima + TRUETYPE_TAG('n', 'z', 'k', 0), // nzk = Nzakara + TRUETYPE_TAG('n', 'z', 'm', 0), // nzm = Zeme Naga + TRUETYPE_TAG('n', 'z', 's', 0), // nzs = New Zealand Sign Language + TRUETYPE_TAG('n', 'z', 'u', 0), // nzu = Teke-Nzikou + TRUETYPE_TAG('n', 'z', 'y', 0), // nzy = Nzakambay + TRUETYPE_TAG('n', 'z', 'z', 0), // nzz = Nanga Dama Dogon + TRUETYPE_TAG('o', 'a', 'a', 0), // oaa = Orok + TRUETYPE_TAG('o', 'a', 'c', 0), // oac = Oroch + TRUETYPE_TAG('o', 'a', 'r', 0), // oar = Old Aramaic (up to 700 BCE) + TRUETYPE_TAG('o', 'a', 'v', 0), // oav = Old Avar + TRUETYPE_TAG('o', 'b', 'i', 0), // obi = Obispeño + TRUETYPE_TAG('o', 'b', 'k', 0), // obk = Southern Bontok + TRUETYPE_TAG('o', 'b', 'l', 0), // obl = Oblo + TRUETYPE_TAG('o', 'b', 'm', 0), // obm = Moabite + TRUETYPE_TAG('o', 'b', 'o', 0), // obo = Obo Manobo + TRUETYPE_TAG('o', 'b', 'r', 0), // obr = Old Burmese + TRUETYPE_TAG('o', 'b', 't', 0), // obt = Old Breton + TRUETYPE_TAG('o', 'b', 'u', 0), // obu = Obulom + TRUETYPE_TAG('o', 'c', 'a', 0), // oca = Ocaina + TRUETYPE_TAG('o', 'c', 'h', 0), // och = Old Chinese + TRUETYPE_TAG('o', 'c', 'o', 0), // oco = Old Cornish + TRUETYPE_TAG('o', 'c', 'u', 0), // ocu = Atzingo Matlatzinca + TRUETYPE_TAG('o', 'd', 'a', 0), // oda = Odut + TRUETYPE_TAG('o', 'd', 'k', 0), // odk = Od + TRUETYPE_TAG('o', 'd', 't', 0), // odt = Old Dutch + TRUETYPE_TAG('o', 'd', 'u', 0), // odu = Odual + TRUETYPE_TAG('o', 'f', 'o', 0), // ofo = Ofo + TRUETYPE_TAG('o', 'f', 's', 0), // ofs = Old Frisian + TRUETYPE_TAG('o', 'f', 'u', 0), // ofu = Efutop + TRUETYPE_TAG('o', 'g', 'b', 0), // ogb = Ogbia + TRUETYPE_TAG('o', 'g', 'c', 0), // ogc = Ogbah + TRUETYPE_TAG('o', 'g', 'e', 0), // oge = Old Georgian + TRUETYPE_TAG('o', 'g', 'g', 0), // ogg = Ogbogolo + TRUETYPE_TAG('o', 'g', 'o', 0), // ogo = Khana + TRUETYPE_TAG('o', 'g', 'u', 0), // ogu = Ogbronuagum + TRUETYPE_TAG('o', 'h', 't', 0), // oht = Old Hittite + TRUETYPE_TAG('o', 'h', 'u', 0), // ohu = Old Hungarian + TRUETYPE_TAG('o', 'i', 'a', 0), // oia = Oirata + TRUETYPE_TAG('o', 'i', 'n', 0), // oin = Inebu One + TRUETYPE_TAG('o', 'j', 'b', 0), // ojb = Northwestern Ojibwa + TRUETYPE_TAG('o', 'j', 'c', 0), // ojc = Central Ojibwa + TRUETYPE_TAG('o', 'j', 'g', 0), // ojg = Eastern Ojibwa + TRUETYPE_TAG('o', 'j', 'p', 0), // ojp = Old Japanese + TRUETYPE_TAG('o', 'j', 's', 0), // ojs = Severn Ojibwa + TRUETYPE_TAG('o', 'j', 'v', 0), // ojv = Ontong Java + TRUETYPE_TAG('o', 'j', 'w', 0), // ojw = Western Ojibwa + TRUETYPE_TAG('o', 'k', 'a', 0), // oka = Okanagan + TRUETYPE_TAG('o', 'k', 'b', 0), // okb = Okobo + TRUETYPE_TAG('o', 'k', 'd', 0), // okd = Okodia + TRUETYPE_TAG('o', 'k', 'e', 0), // oke = Okpe (Southwestern Edo) + TRUETYPE_TAG('o', 'k', 'h', 0), // okh = Koresh-e Rostam + TRUETYPE_TAG('o', 'k', 'i', 0), // oki = Okiek + TRUETYPE_TAG('o', 'k', 'j', 0), // okj = Oko-Juwoi + TRUETYPE_TAG('o', 'k', 'k', 0), // okk = Kwamtim One + TRUETYPE_TAG('o', 'k', 'l', 0), // okl = Old Kentish Sign Language + TRUETYPE_TAG('o', 'k', 'm', 0), // okm = Middle Korean (10th-16th cent.) + TRUETYPE_TAG('o', 'k', 'n', 0), // okn = Oki-No-Erabu + TRUETYPE_TAG('o', 'k', 'o', 0), // oko = Old Korean (3rd-9th cent.) + TRUETYPE_TAG('o', 'k', 'r', 0), // okr = Kirike + TRUETYPE_TAG('o', 'k', 's', 0), // oks = Oko-Eni-Osayen + TRUETYPE_TAG('o', 'k', 'u', 0), // oku = Oku + TRUETYPE_TAG('o', 'k', 'v', 0), // okv = Orokaiva + TRUETYPE_TAG('o', 'k', 'x', 0), // okx = Okpe (Northwestern Edo) + TRUETYPE_TAG('o', 'l', 'a', 0), // ola = Walungge + TRUETYPE_TAG('o', 'l', 'd', 0), // old = Mochi + TRUETYPE_TAG('o', 'l', 'e', 0), // ole = Olekha + TRUETYPE_TAG('o', 'l', 'm', 0), // olm = Oloma + TRUETYPE_TAG('o', 'l', 'o', 0), // olo = Livvi + TRUETYPE_TAG('o', 'l', 'r', 0), // olr = Olrat + TRUETYPE_TAG('o', 'm', 'a', 0), // oma = Omaha-Ponca + TRUETYPE_TAG('o', 'm', 'b', 0), // omb = East Ambae + TRUETYPE_TAG('o', 'm', 'c', 0), // omc = Mochica + TRUETYPE_TAG('o', 'm', 'e', 0), // ome = Omejes + TRUETYPE_TAG('o', 'm', 'g', 0), // omg = Omagua + TRUETYPE_TAG('o', 'm', 'i', 0), // omi = Omi + TRUETYPE_TAG('o', 'm', 'k', 0), // omk = Omok + TRUETYPE_TAG('o', 'm', 'l', 0), // oml = Ombo + TRUETYPE_TAG('o', 'm', 'n', 0), // omn = Minoan + TRUETYPE_TAG('o', 'm', 'o', 0), // omo = Utarmbung + TRUETYPE_TAG('o', 'm', 'p', 0), // omp = Old Manipuri + TRUETYPE_TAG('o', 'm', 'q', 0), // omq = Oto-Manguean languages + TRUETYPE_TAG('o', 'm', 'r', 0), // omr = Old Marathi + TRUETYPE_TAG('o', 'm', 't', 0), // omt = Omotik + TRUETYPE_TAG('o', 'm', 'u', 0), // omu = Omurano + TRUETYPE_TAG('o', 'm', 'v', 0), // omv = Omotic languages + TRUETYPE_TAG('o', 'm', 'w', 0), // omw = South Tairora + TRUETYPE_TAG('o', 'm', 'x', 0), // omx = Old Mon + TRUETYPE_TAG('o', 'n', 'a', 0), // ona = Ona + TRUETYPE_TAG('o', 'n', 'b', 0), // onb = Lingao + TRUETYPE_TAG('o', 'n', 'e', 0), // one = Oneida + TRUETYPE_TAG('o', 'n', 'g', 0), // ong = Olo + TRUETYPE_TAG('o', 'n', 'i', 0), // oni = Onin + TRUETYPE_TAG('o', 'n', 'j', 0), // onj = Onjob + TRUETYPE_TAG('o', 'n', 'k', 0), // onk = Kabore One + TRUETYPE_TAG('o', 'n', 'n', 0), // onn = Onobasulu + TRUETYPE_TAG('o', 'n', 'o', 0), // ono = Onondaga + TRUETYPE_TAG('o', 'n', 'p', 0), // onp = Sartang + TRUETYPE_TAG('o', 'n', 'r', 0), // onr = Northern One + TRUETYPE_TAG('o', 'n', 's', 0), // ons = Ono + TRUETYPE_TAG('o', 'n', 't', 0), // ont = Ontenu + TRUETYPE_TAG('o', 'n', 'u', 0), // onu = Unua + TRUETYPE_TAG('o', 'n', 'w', 0), // onw = Old Nubian + TRUETYPE_TAG('o', 'n', 'x', 0), // onx = Onin Based Pidgin + TRUETYPE_TAG('o', 'o', 'd', 0), // ood = Tohono O'odham + TRUETYPE_TAG('o', 'o', 'g', 0), // oog = Ong + TRUETYPE_TAG('o', 'o', 'n', 0), // oon = Önge + TRUETYPE_TAG('o', 'o', 'r', 0), // oor = Oorlams + TRUETYPE_TAG('o', 'o', 's', 0), // oos = Old Ossetic + TRUETYPE_TAG('o', 'p', 'a', 0), // opa = Okpamheri + TRUETYPE_TAG('o', 'p', 'k', 0), // opk = Kopkaka + TRUETYPE_TAG('o', 'p', 'm', 0), // opm = Oksapmin + TRUETYPE_TAG('o', 'p', 'o', 0), // opo = Opao + TRUETYPE_TAG('o', 'p', 't', 0), // opt = Opata + TRUETYPE_TAG('o', 'p', 'y', 0), // opy = Ofayé + TRUETYPE_TAG('o', 'r', 'a', 0), // ora = Oroha + TRUETYPE_TAG('o', 'r', 'c', 0), // orc = Orma + TRUETYPE_TAG('o', 'r', 'e', 0), // ore = Orejón + TRUETYPE_TAG('o', 'r', 'g', 0), // org = Oring + TRUETYPE_TAG('o', 'r', 'h', 0), // orh = Oroqen + TRUETYPE_TAG('o', 'r', 'n', 0), // orn = Orang Kanaq + TRUETYPE_TAG('o', 'r', 'o', 0), // oro = Orokolo + TRUETYPE_TAG('o', 'r', 'r', 0), // orr = Oruma + TRUETYPE_TAG('o', 'r', 's', 0), // ors = Orang Seletar + TRUETYPE_TAG('o', 'r', 't', 0), // ort = Adivasi Oriya + TRUETYPE_TAG('o', 'r', 'u', 0), // oru = Ormuri + TRUETYPE_TAG('o', 'r', 'v', 0), // orv = Old Russian + TRUETYPE_TAG('o', 'r', 'w', 0), // orw = Oro Win + TRUETYPE_TAG('o', 'r', 'x', 0), // orx = Oro + TRUETYPE_TAG('o', 'r', 'z', 0), // orz = Ormu + TRUETYPE_TAG('o', 's', 'a', 0), // osa = Osage + TRUETYPE_TAG('o', 's', 'c', 0), // osc = Oscan + TRUETYPE_TAG('o', 's', 'i', 0), // osi = Osing + TRUETYPE_TAG('o', 's', 'o', 0), // oso = Ososo + TRUETYPE_TAG('o', 's', 'p', 0), // osp = Old Spanish + TRUETYPE_TAG('o', 's', 't', 0), // ost = Osatu + TRUETYPE_TAG('o', 's', 'u', 0), // osu = Southern One + TRUETYPE_TAG('o', 's', 'x', 0), // osx = Old Saxon + TRUETYPE_TAG('o', 't', 'a', 0), // ota = Ottoman Turkish (1500-1928) + TRUETYPE_TAG('o', 't', 'b', 0), // otb = Old Tibetan + TRUETYPE_TAG('o', 't', 'd', 0), // otd = Ot Danum + TRUETYPE_TAG('o', 't', 'e', 0), // ote = Mezquital Otomi + TRUETYPE_TAG('o', 't', 'i', 0), // oti = Oti + TRUETYPE_TAG('o', 't', 'k', 0), // otk = Old Turkish + TRUETYPE_TAG('o', 't', 'l', 0), // otl = Tilapa Otomi + TRUETYPE_TAG('o', 't', 'm', 0), // otm = Eastern Highland Otomi + TRUETYPE_TAG('o', 't', 'n', 0), // otn = Tenango Otomi + TRUETYPE_TAG('o', 't', 'o', 0), // oto = Otomian languages + TRUETYPE_TAG('o', 't', 'q', 0), // otq = Querétaro Otomi + TRUETYPE_TAG('o', 't', 'r', 0), // otr = Otoro + TRUETYPE_TAG('o', 't', 's', 0), // ots = Estado de México Otomi + TRUETYPE_TAG('o', 't', 't', 0), // ott = Temoaya Otomi + TRUETYPE_TAG('o', 't', 'u', 0), // otu = Otuke + TRUETYPE_TAG('o', 't', 'w', 0), // otw = Ottawa + TRUETYPE_TAG('o', 't', 'x', 0), // otx = Texcatepec Otomi + TRUETYPE_TAG('o', 't', 'y', 0), // oty = Old Tamil + TRUETYPE_TAG('o', 't', 'z', 0), // otz = Ixtenco Otomi + TRUETYPE_TAG('o', 'u', 'a', 0), // oua = Tagargrent + TRUETYPE_TAG('o', 'u', 'b', 0), // oub = Glio-Oubi + TRUETYPE_TAG('o', 'u', 'e', 0), // oue = Oune + TRUETYPE_TAG('o', 'u', 'i', 0), // oui = Old Uighur + TRUETYPE_TAG('o', 'u', 'm', 0), // oum = Ouma + TRUETYPE_TAG('o', 'u', 'n', 0), // oun = !O!ung + TRUETYPE_TAG('o', 'w', 'i', 0), // owi = Owiniga + TRUETYPE_TAG('o', 'w', 'l', 0), // owl = Old Welsh + TRUETYPE_TAG('o', 'y', 'b', 0), // oyb = Oy + TRUETYPE_TAG('o', 'y', 'd', 0), // oyd = Oyda + TRUETYPE_TAG('o', 'y', 'm', 0), // oym = Wayampi + TRUETYPE_TAG('o', 'y', 'y', 0), // oyy = Oya'oya + TRUETYPE_TAG('o', 'z', 'm', 0), // ozm = Koonzime + TRUETYPE_TAG('p', 'a', 'a', 0), // paa = Papuan languages + TRUETYPE_TAG('p', 'a', 'b', 0), // pab = Parecís + TRUETYPE_TAG('p', 'a', 'c', 0), // pac = Pacoh + TRUETYPE_TAG('p', 'a', 'd', 0), // pad = Paumarí + TRUETYPE_TAG('p', 'a', 'e', 0), // pae = Pagibete + TRUETYPE_TAG('p', 'a', 'f', 0), // paf = Paranawát + TRUETYPE_TAG('p', 'a', 'g', 0), // pag = Pangasinan + TRUETYPE_TAG('p', 'a', 'h', 0), // pah = Tenharim + TRUETYPE_TAG('p', 'a', 'i', 0), // pai = Pe + TRUETYPE_TAG('p', 'a', 'k', 0), // pak = Parakanã + TRUETYPE_TAG('p', 'a', 'l', 0), // pal = Pahlavi + TRUETYPE_TAG('p', 'a', 'm', 0), // pam = Pampanga + TRUETYPE_TAG('p', 'a', 'o', 0), // pao = Northern Paiute + TRUETYPE_TAG('p', 'a', 'p', 0), // pap = Papiamento + TRUETYPE_TAG('p', 'a', 'q', 0), // paq = Parya + TRUETYPE_TAG('p', 'a', 'r', 0), // par = Panamint + TRUETYPE_TAG('p', 'a', 's', 0), // pas = Papasena + TRUETYPE_TAG('p', 'a', 't', 0), // pat = Papitalai + TRUETYPE_TAG('p', 'a', 'u', 0), // pau = Palauan + TRUETYPE_TAG('p', 'a', 'v', 0), // pav = Pakaásnovos + TRUETYPE_TAG('p', 'a', 'w', 0), // paw = Pawnee + TRUETYPE_TAG('p', 'a', 'x', 0), // pax = Pankararé + TRUETYPE_TAG('p', 'a', 'y', 0), // pay = Pech + TRUETYPE_TAG('p', 'a', 'z', 0), // paz = Pankararú + TRUETYPE_TAG('p', 'b', 'b', 0), // pbb = Páez + TRUETYPE_TAG('p', 'b', 'c', 0), // pbc = Patamona + TRUETYPE_TAG('p', 'b', 'e', 0), // pbe = Mezontla Popoloca + TRUETYPE_TAG('p', 'b', 'f', 0), // pbf = Coyotepec Popoloca + TRUETYPE_TAG('p', 'b', 'g', 0), // pbg = Paraujano + TRUETYPE_TAG('p', 'b', 'h', 0), // pbh = E'ñapa Woromaipu + TRUETYPE_TAG('p', 'b', 'i', 0), // pbi = Parkwa + TRUETYPE_TAG('p', 'b', 'l', 0), // pbl = Mak (Nigeria) + TRUETYPE_TAG('p', 'b', 'n', 0), // pbn = Kpasam + TRUETYPE_TAG('p', 'b', 'o', 0), // pbo = Papel + TRUETYPE_TAG('p', 'b', 'p', 0), // pbp = Badyara + TRUETYPE_TAG('p', 'b', 'r', 0), // pbr = Pangwa + TRUETYPE_TAG('p', 'b', 's', 0), // pbs = Central Pame + TRUETYPE_TAG('p', 'b', 't', 0), // pbt = Southern Pashto + TRUETYPE_TAG('p', 'b', 'u', 0), // pbu = Northern Pashto + TRUETYPE_TAG('p', 'b', 'v', 0), // pbv = Pnar + TRUETYPE_TAG('p', 'b', 'y', 0), // pby = Pyu + TRUETYPE_TAG('p', 'b', 'z', 0), // pbz = Palu + TRUETYPE_TAG('p', 'c', 'a', 0), // pca = Santa Inés Ahuatempan Popoloca + TRUETYPE_TAG('p', 'c', 'b', 0), // pcb = Pear + TRUETYPE_TAG('p', 'c', 'c', 0), // pcc = Bouyei + TRUETYPE_TAG('p', 'c', 'd', 0), // pcd = Picard + TRUETYPE_TAG('p', 'c', 'e', 0), // pce = Ruching Palaung + TRUETYPE_TAG('p', 'c', 'f', 0), // pcf = Paliyan + TRUETYPE_TAG('p', 'c', 'g', 0), // pcg = Paniya + TRUETYPE_TAG('p', 'c', 'h', 0), // pch = Pardhan + TRUETYPE_TAG('p', 'c', 'i', 0), // pci = Duruwa + TRUETYPE_TAG('p', 'c', 'j', 0), // pcj = Parenga + TRUETYPE_TAG('p', 'c', 'k', 0), // pck = Paite Chin + TRUETYPE_TAG('p', 'c', 'l', 0), // pcl = Pardhi + TRUETYPE_TAG('p', 'c', 'm', 0), // pcm = Nigerian Pidgin + TRUETYPE_TAG('p', 'c', 'n', 0), // pcn = Piti + TRUETYPE_TAG('p', 'c', 'p', 0), // pcp = Pacahuara + TRUETYPE_TAG('p', 'c', 'r', 0), // pcr = Panang + TRUETYPE_TAG('p', 'c', 'w', 0), // pcw = Pyapun + TRUETYPE_TAG('p', 'd', 'a', 0), // pda = Anam + TRUETYPE_TAG('p', 'd', 'c', 0), // pdc = Pennsylvania German + TRUETYPE_TAG('p', 'd', 'i', 0), // pdi = Pa Di + TRUETYPE_TAG('p', 'd', 'n', 0), // pdn = Podena + TRUETYPE_TAG('p', 'd', 'o', 0), // pdo = Padoe + TRUETYPE_TAG('p', 'd', 't', 0), // pdt = Plautdietsch + TRUETYPE_TAG('p', 'd', 'u', 0), // pdu = Kayan + TRUETYPE_TAG('p', 'e', 'a', 0), // pea = Peranakan Indonesian + TRUETYPE_TAG('p', 'e', 'b', 0), // peb = Eastern Pomo + TRUETYPE_TAG('p', 'e', 'd', 0), // ped = Mala (Papua New Guinea) + TRUETYPE_TAG('p', 'e', 'e', 0), // pee = Taje + TRUETYPE_TAG('p', 'e', 'f', 0), // pef = Northeastern Pomo + TRUETYPE_TAG('p', 'e', 'g', 0), // peg = Pengo + TRUETYPE_TAG('p', 'e', 'h', 0), // peh = Bonan + TRUETYPE_TAG('p', 'e', 'i', 0), // pei = Chichimeca-Jonaz + TRUETYPE_TAG('p', 'e', 'j', 0), // pej = Northern Pomo + TRUETYPE_TAG('p', 'e', 'k', 0), // pek = Penchal + TRUETYPE_TAG('p', 'e', 'l', 0), // pel = Pekal + TRUETYPE_TAG('p', 'e', 'm', 0), // pem = Phende + TRUETYPE_TAG('p', 'e', 'o', 0), // peo = Old Persian (ca. 600-400 B.C.) + TRUETYPE_TAG('p', 'e', 'p', 0), // pep = Kunja + TRUETYPE_TAG('p', 'e', 'q', 0), // peq = Southern Pomo + TRUETYPE_TAG('p', 'e', 's', 0), // pes = Iranian Persian + TRUETYPE_TAG('p', 'e', 'v', 0), // pev = Pémono + TRUETYPE_TAG('p', 'e', 'x', 0), // pex = Petats + TRUETYPE_TAG('p', 'e', 'y', 0), // pey = Petjo + TRUETYPE_TAG('p', 'e', 'z', 0), // pez = Eastern Penan + TRUETYPE_TAG('p', 'f', 'a', 0), // pfa = Pááfang + TRUETYPE_TAG('p', 'f', 'e', 0), // pfe = Peere + TRUETYPE_TAG('p', 'f', 'l', 0), // pfl = Pfaelzisch + TRUETYPE_TAG('p', 'g', 'a', 0), // pga = Sudanese Creole Arabic + TRUETYPE_TAG('p', 'g', 'g', 0), // pgg = Pangwali + TRUETYPE_TAG('p', 'g', 'i', 0), // pgi = Pagi + TRUETYPE_TAG('p', 'g', 'k', 0), // pgk = Rerep + TRUETYPE_TAG('p', 'g', 'l', 0), // pgl = Primitive Irish + TRUETYPE_TAG('p', 'g', 'n', 0), // pgn = Paelignian + TRUETYPE_TAG('p', 'g', 's', 0), // pgs = Pangseng + TRUETYPE_TAG('p', 'g', 'u', 0), // pgu = Pagu + TRUETYPE_TAG('p', 'g', 'y', 0), // pgy = Pongyong + TRUETYPE_TAG('p', 'h', 'a', 0), // pha = Pa-Hng + TRUETYPE_TAG('p', 'h', 'd', 0), // phd = Phudagi + TRUETYPE_TAG('p', 'h', 'g', 0), // phg = Phuong + TRUETYPE_TAG('p', 'h', 'h', 0), // phh = Phukha + TRUETYPE_TAG('p', 'h', 'i', 0), // phi = Philippine languages + TRUETYPE_TAG('p', 'h', 'k', 0), // phk = Phake + TRUETYPE_TAG('p', 'h', 'l', 0), // phl = Phalura + TRUETYPE_TAG('p', 'h', 'm', 0), // phm = Phimbi + TRUETYPE_TAG('p', 'h', 'n', 0), // phn = Phoenician + TRUETYPE_TAG('p', 'h', 'o', 0), // pho = Phunoi + TRUETYPE_TAG('p', 'h', 'q', 0), // phq = Phana' + TRUETYPE_TAG('p', 'h', 'r', 0), // phr = Pahari-Potwari + TRUETYPE_TAG('p', 'h', 't', 0), // pht = Phu Thai + TRUETYPE_TAG('p', 'h', 'u', 0), // phu = Phuan + TRUETYPE_TAG('p', 'h', 'v', 0), // phv = Pahlavani + TRUETYPE_TAG('p', 'h', 'w', 0), // phw = Phangduwali + TRUETYPE_TAG('p', 'i', 'a', 0), // pia = Pima Bajo + TRUETYPE_TAG('p', 'i', 'b', 0), // pib = Yine + TRUETYPE_TAG('p', 'i', 'c', 0), // pic = Pinji + TRUETYPE_TAG('p', 'i', 'd', 0), // pid = Piaroa + TRUETYPE_TAG('p', 'i', 'e', 0), // pie = Piro + TRUETYPE_TAG('p', 'i', 'f', 0), // pif = Pingelapese + TRUETYPE_TAG('p', 'i', 'g', 0), // pig = Pisabo + TRUETYPE_TAG('p', 'i', 'h', 0), // pih = Pitcairn-Norfolk + TRUETYPE_TAG('p', 'i', 'i', 0), // pii = Pini + TRUETYPE_TAG('p', 'i', 'j', 0), // pij = Pijao + TRUETYPE_TAG('p', 'i', 'l', 0), // pil = Yom + TRUETYPE_TAG('p', 'i', 'm', 0), // pim = Powhatan + TRUETYPE_TAG('p', 'i', 'n', 0), // pin = Piame + TRUETYPE_TAG('p', 'i', 'o', 0), // pio = Piapoco + TRUETYPE_TAG('p', 'i', 'p', 0), // pip = Pero + TRUETYPE_TAG('p', 'i', 'r', 0), // pir = Piratapuyo + TRUETYPE_TAG('p', 'i', 's', 0), // pis = Pijin + TRUETYPE_TAG('p', 'i', 't', 0), // pit = Pitta Pitta + TRUETYPE_TAG('p', 'i', 'u', 0), // piu = Pintupi-Luritja + TRUETYPE_TAG('p', 'i', 'v', 0), // piv = Pileni + TRUETYPE_TAG('p', 'i', 'w', 0), // piw = Pimbwe + TRUETYPE_TAG('p', 'i', 'x', 0), // pix = Piu + TRUETYPE_TAG('p', 'i', 'y', 0), // piy = Piya-Kwonci + TRUETYPE_TAG('p', 'i', 'z', 0), // piz = Pije + TRUETYPE_TAG('p', 'j', 't', 0), // pjt = Pitjantjatjara + TRUETYPE_TAG('p', 'k', 'a', 0), // pka = Ardhamāgadhī Prākrit + TRUETYPE_TAG('p', 'k', 'b', 0), // pkb = Pokomo + TRUETYPE_TAG('p', 'k', 'c', 0), // pkc = Paekche + TRUETYPE_TAG('p', 'k', 'g', 0), // pkg = Pak-Tong + TRUETYPE_TAG('p', 'k', 'h', 0), // pkh = Pankhu + TRUETYPE_TAG('p', 'k', 'n', 0), // pkn = Pakanha + TRUETYPE_TAG('p', 'k', 'o', 0), // pko = Pökoot + TRUETYPE_TAG('p', 'k', 'p', 0), // pkp = Pukapuka + TRUETYPE_TAG('p', 'k', 'r', 0), // pkr = Attapady Kurumba + TRUETYPE_TAG('p', 'k', 's', 0), // pks = Pakistan Sign Language + TRUETYPE_TAG('p', 'k', 't', 0), // pkt = Maleng + TRUETYPE_TAG('p', 'k', 'u', 0), // pku = Paku + TRUETYPE_TAG('p', 'l', 'a', 0), // pla = Miani + TRUETYPE_TAG('p', 'l', 'b', 0), // plb = Polonombauk + TRUETYPE_TAG('p', 'l', 'c', 0), // plc = Central Palawano + TRUETYPE_TAG('p', 'l', 'd', 0), // pld = Polari + TRUETYPE_TAG('p', 'l', 'e', 0), // ple = Palu'e + TRUETYPE_TAG('p', 'l', 'f', + 0), // plf = Central Malayo-Polynesian languages + TRUETYPE_TAG('p', 'l', 'g', 0), // plg = Pilagá + TRUETYPE_TAG('p', 'l', 'h', 0), // plh = Paulohi + TRUETYPE_TAG('p', 'l', 'j', 0), // plj = Polci + TRUETYPE_TAG('p', 'l', 'k', 0), // plk = Kohistani Shina + TRUETYPE_TAG('p', 'l', 'l', 0), // pll = Shwe Palaung + TRUETYPE_TAG('p', 'l', 'n', 0), // pln = Palenquero + TRUETYPE_TAG('p', 'l', 'o', 0), // plo = Oluta Popoluca + TRUETYPE_TAG('p', 'l', 'p', 0), // plp = Palpa + TRUETYPE_TAG('p', 'l', 'q', 0), // plq = Palaic + TRUETYPE_TAG('p', 'l', 'r', 0), // plr = Palaka Senoufo + TRUETYPE_TAG('p', 'l', 's', 0), // pls = San Marcos Tlalcoyalco Popoloca + TRUETYPE_TAG('p', 'l', 't', 0), // plt = Plateau Malagasy + TRUETYPE_TAG('p', 'l', 'u', 0), // plu = Palikúr + TRUETYPE_TAG('p', 'l', 'v', 0), // plv = Southwest Palawano + TRUETYPE_TAG('p', 'l', 'w', 0), // plw = Brooke's Point Palawano + TRUETYPE_TAG('p', 'l', 'y', 0), // ply = Bolyu + TRUETYPE_TAG('p', 'l', 'z', 0), // plz = Paluan + TRUETYPE_TAG('p', 'm', 'a', 0), // pma = Paama + TRUETYPE_TAG('p', 'm', 'b', 0), // pmb = Pambia + TRUETYPE_TAG('p', 'm', 'c', 0), // pmc = Palumata + TRUETYPE_TAG('p', 'm', 'e', 0), // pme = Pwaamei + TRUETYPE_TAG('p', 'm', 'f', 0), // pmf = Pamona + TRUETYPE_TAG('p', 'm', 'h', 0), // pmh = Māhārāṣṭri Prākrit + TRUETYPE_TAG('p', 'm', 'i', 0), // pmi = Northern Pumi + TRUETYPE_TAG('p', 'm', 'j', 0), // pmj = Southern Pumi + TRUETYPE_TAG('p', 'm', 'k', 0), // pmk = Pamlico + TRUETYPE_TAG('p', 'm', 'l', 0), // pml = Lingua Franca + TRUETYPE_TAG('p', 'm', 'm', 0), // pmm = Pomo + TRUETYPE_TAG('p', 'm', 'n', 0), // pmn = Pam + TRUETYPE_TAG('p', 'm', 'o', 0), // pmo = Pom + TRUETYPE_TAG('p', 'm', 'q', 0), // pmq = Northern Pame + TRUETYPE_TAG('p', 'm', 'r', 0), // pmr = Paynamar + TRUETYPE_TAG('p', 'm', 's', 0), // pms = Piemontese + TRUETYPE_TAG('p', 'm', 't', 0), // pmt = Tuamotuan + TRUETYPE_TAG('p', 'm', 'u', 0), // pmu = Mirpur Panjabi + TRUETYPE_TAG('p', 'm', 'w', 0), // pmw = Plains Miwok + TRUETYPE_TAG('p', 'm', 'x', 0), // pmx = Poumei Naga + TRUETYPE_TAG('p', 'm', 'y', 0), // pmy = Papuan Malay + TRUETYPE_TAG('p', 'm', 'z', 0), // pmz = Southern Pame + TRUETYPE_TAG('p', 'n', 'a', 0), // pna = Punan Bah-Biau + TRUETYPE_TAG('p', 'n', 'b', 0), // pnb = Western Panjabi + TRUETYPE_TAG('p', 'n', 'c', 0), // pnc = Pannei + TRUETYPE_TAG('p', 'n', 'e', 0), // pne = Western Penan + TRUETYPE_TAG('p', 'n', 'g', 0), // png = Pongu + TRUETYPE_TAG('p', 'n', 'h', 0), // pnh = Penrhyn + TRUETYPE_TAG('p', 'n', 'i', 0), // pni = Aoheng + TRUETYPE_TAG('p', 'n', 'm', 0), // pnm = Punan Batu 1 + TRUETYPE_TAG('p', 'n', 'n', 0), // pnn = Pinai-Hagahai + TRUETYPE_TAG('p', 'n', 'o', 0), // pno = Panobo + TRUETYPE_TAG('p', 'n', 'p', 0), // pnp = Pancana + TRUETYPE_TAG('p', 'n', 'q', 0), // pnq = Pana (Burkina Faso) + TRUETYPE_TAG('p', 'n', 'r', 0), // pnr = Panim + TRUETYPE_TAG('p', 'n', 's', 0), // pns = Ponosakan + TRUETYPE_TAG('p', 'n', 't', 0), // pnt = Pontic + TRUETYPE_TAG('p', 'n', 'u', 0), // pnu = Jiongnai Bunu + TRUETYPE_TAG('p', 'n', 'v', 0), // pnv = Pinigura + TRUETYPE_TAG('p', 'n', 'w', 0), // pnw = Panytyima + TRUETYPE_TAG('p', 'n', 'x', 0), // pnx = Phong-Kniang + TRUETYPE_TAG('p', 'n', 'y', 0), // pny = Pinyin + TRUETYPE_TAG('p', 'n', 'z', 0), // pnz = Pana (Central African Republic) + TRUETYPE_TAG('p', 'o', 'c', 0), // poc = Poqomam + TRUETYPE_TAG('p', 'o', 'd', 0), // pod = Ponares + TRUETYPE_TAG('p', 'o', 'e', 0), // poe = San Juan Atzingo Popoloca + TRUETYPE_TAG('p', 'o', 'f', 0), // pof = Poke + TRUETYPE_TAG('p', 'o', 'g', 0), // pog = Potiguára + TRUETYPE_TAG('p', 'o', 'h', 0), // poh = Poqomchi' + TRUETYPE_TAG('p', 'o', 'i', 0), // poi = Highland Popoluca + TRUETYPE_TAG('p', 'o', 'k', 0), // pok = Pokangá + TRUETYPE_TAG('p', 'o', 'm', 0), // pom = Southeastern Pomo + TRUETYPE_TAG('p', 'o', 'n', 0), // pon = Pohnpeian + TRUETYPE_TAG('p', 'o', 'o', 0), // poo = Central Pomo + TRUETYPE_TAG('p', 'o', 'p', 0), // pop = Pwapwa + TRUETYPE_TAG('p', 'o', 'q', 0), // poq = Texistepec Popoluca + TRUETYPE_TAG('p', 'o', 's', 0), // pos = Sayula Popoluca + TRUETYPE_TAG('p', 'o', 't', 0), // pot = Potawatomi + TRUETYPE_TAG('p', 'o', 'v', 0), // pov = Upper Guinea Crioulo + TRUETYPE_TAG('p', 'o', 'w', 0), // pow = San Felipe Otlaltepec Popoloca + TRUETYPE_TAG('p', 'o', 'x', 0), // pox = Polabian + TRUETYPE_TAG('p', 'o', 'y', 0), // poy = Pogolo + TRUETYPE_TAG('p', 'o', 'z', 0), // poz = Malayo-Polynesian languages + TRUETYPE_TAG('p', 'p', 'a', 0), // ppa = Pao + TRUETYPE_TAG('p', 'p', 'e', 0), // ppe = Papi + TRUETYPE_TAG('p', 'p', 'i', 0), // ppi = Paipai + TRUETYPE_TAG('p', 'p', 'k', 0), // ppk = Uma + TRUETYPE_TAG('p', 'p', 'l', 0), // ppl = Pipil + TRUETYPE_TAG('p', 'p', 'm', 0), // ppm = Papuma + TRUETYPE_TAG('p', 'p', 'n', 0), // ppn = Papapana + TRUETYPE_TAG('p', 'p', 'o', 0), // ppo = Folopa + TRUETYPE_TAG('p', 'p', 'p', 0), // ppp = Pelende + TRUETYPE_TAG('p', 'p', 'q', 0), // ppq = Pei + TRUETYPE_TAG('p', 'p', 'r', 0), // ppr = Piru + TRUETYPE_TAG('p', 'p', 's', 0), // pps = San Luís Temalacayuca Popoloca + TRUETYPE_TAG('p', 'p', 't', 0), // ppt = Pare + TRUETYPE_TAG('p', 'p', 'u', 0), // ppu = Papora + TRUETYPE_TAG('p', 'q', 'a', 0), // pqa = Pa'a + TRUETYPE_TAG('p', 'q', 'e', + 0), // pqe = Eastern Malayo-Polynesian languages + TRUETYPE_TAG('p', 'q', 'm', 0), // pqm = Malecite-Passamaquoddy + TRUETYPE_TAG('p', 'q', 'w', + 0), // pqw = Western Malayo-Polynesian languages + TRUETYPE_TAG('p', 'r', 'a', 0), // pra = Prakrit languages + TRUETYPE_TAG('p', 'r', 'b', 0), // prb = Lua' + TRUETYPE_TAG('p', 'r', 'c', 0), // prc = Parachi + TRUETYPE_TAG('p', 'r', 'd', 0), // prd = Parsi-Dari + TRUETYPE_TAG('p', 'r', 'e', 0), // pre = Principense + TRUETYPE_TAG('p', 'r', 'f', 0), // prf = Paranan + TRUETYPE_TAG('p', 'r', 'g', 0), // prg = Prussian + TRUETYPE_TAG('p', 'r', 'h', 0), // prh = Porohanon + TRUETYPE_TAG('p', 'r', 'i', 0), // pri = Paicî + TRUETYPE_TAG('p', 'r', 'k', 0), // prk = Parauk + TRUETYPE_TAG('p', 'r', 'l', 0), // prl = Peruvian Sign Language + TRUETYPE_TAG('p', 'r', 'm', 0), // prm = Kibiri + TRUETYPE_TAG('p', 'r', 'n', 0), // prn = Prasuni + TRUETYPE_TAG('p', 'r', 'o', 0), // pro = Old Provençal (to 1500) + TRUETYPE_TAG('p', 'r', 'p', 0), // prp = Parsi + TRUETYPE_TAG('p', 'r', 'q', 0), // prq = Ashéninka Perené + TRUETYPE_TAG('p', 'r', 'r', 0), // prr = Puri + TRUETYPE_TAG('p', 'r', 's', 0), // prs = Dari + TRUETYPE_TAG('p', 'r', 't', 0), // prt = Phai + TRUETYPE_TAG('p', 'r', 'u', 0), // pru = Puragi + TRUETYPE_TAG('p', 'r', 'w', 0), // prw = Parawen + TRUETYPE_TAG('p', 'r', 'x', 0), // prx = Purik + TRUETYPE_TAG('p', 'r', 'y', 0), // pry = Pray 3 + TRUETYPE_TAG('p', 'r', 'z', 0), // prz = Providencia Sign Language + TRUETYPE_TAG('p', 's', 'a', 0), // psa = Asue Awyu + TRUETYPE_TAG('p', 's', 'c', 0), // psc = Persian Sign Language + TRUETYPE_TAG('p', 's', 'd', 0), // psd = Plains Indian Sign Language + TRUETYPE_TAG('p', 's', 'e', 0), // pse = Central Malay + TRUETYPE_TAG('p', 's', 'g', 0), // psg = Penang Sign Language + TRUETYPE_TAG('p', 's', 'h', 0), // psh = Southwest Pashayi + TRUETYPE_TAG('p', 's', 'i', 0), // psi = Southeast Pashayi + TRUETYPE_TAG('p', 's', 'l', 0), // psl = Puerto Rican Sign Language + TRUETYPE_TAG('p', 's', 'm', 0), // psm = Pauserna + TRUETYPE_TAG('p', 's', 'n', 0), // psn = Panasuan + TRUETYPE_TAG('p', 's', 'o', 0), // pso = Polish Sign Language + TRUETYPE_TAG('p', 's', 'p', 0), // psp = Philippine Sign Language + TRUETYPE_TAG('p', 's', 'q', 0), // psq = Pasi + TRUETYPE_TAG('p', 's', 'r', 0), // psr = Portuguese Sign Language + TRUETYPE_TAG('p', 's', 's', 0), // pss = Kaulong + TRUETYPE_TAG('p', 's', 't', 0), // pst = Central Pashto + TRUETYPE_TAG('p', 's', 'u', 0), // psu = Sauraseni Prākrit + TRUETYPE_TAG('p', 's', 'w', 0), // psw = Port Sandwich + TRUETYPE_TAG('p', 's', 'y', 0), // psy = Piscataway + TRUETYPE_TAG('p', 't', 'a', 0), // pta = Pai Tavytera + TRUETYPE_TAG('p', 't', 'h', 0), // pth = Pataxó Hã-Ha-Hãe + TRUETYPE_TAG('p', 't', 'i', 0), // pti = Pintiini + TRUETYPE_TAG('p', 't', 'n', 0), // ptn = Patani + TRUETYPE_TAG('p', 't', 'o', 0), // pto = Zo'é + TRUETYPE_TAG('p', 't', 'p', 0), // ptp = Patep + TRUETYPE_TAG('p', 't', 'r', 0), // ptr = Piamatsina + TRUETYPE_TAG('p', 't', 't', 0), // ptt = Enrekang + TRUETYPE_TAG('p', 't', 'u', 0), // ptu = Bambam + TRUETYPE_TAG('p', 't', 'v', 0), // ptv = Port Vato + TRUETYPE_TAG('p', 't', 'w', 0), // ptw = Pentlatch + TRUETYPE_TAG('p', 't', 'y', 0), // pty = Pathiya + TRUETYPE_TAG('p', 'u', 'a', 0), // pua = Western Highland Purepecha + TRUETYPE_TAG('p', 'u', 'b', 0), // pub = Purum + TRUETYPE_TAG('p', 'u', 'c', 0), // puc = Punan Merap + TRUETYPE_TAG('p', 'u', 'd', 0), // pud = Punan Aput + TRUETYPE_TAG('p', 'u', 'e', 0), // pue = Puelche + TRUETYPE_TAG('p', 'u', 'f', 0), // puf = Punan Merah + TRUETYPE_TAG('p', 'u', 'g', 0), // pug = Phuie + TRUETYPE_TAG('p', 'u', 'i', 0), // pui = Puinave + TRUETYPE_TAG('p', 'u', 'j', 0), // puj = Punan Tubu + TRUETYPE_TAG('p', 'u', 'k', 0), // puk = Pu Ko + TRUETYPE_TAG('p', 'u', 'm', 0), // pum = Puma + TRUETYPE_TAG('p', 'u', 'o', 0), // puo = Puoc + TRUETYPE_TAG('p', 'u', 'p', 0), // pup = Pulabu + TRUETYPE_TAG('p', 'u', 'q', 0), // puq = Puquina + TRUETYPE_TAG('p', 'u', 'r', 0), // pur = Puruborá + TRUETYPE_TAG('p', 'u', 't', 0), // put = Putoh + TRUETYPE_TAG('p', 'u', 'u', 0), // puu = Punu + TRUETYPE_TAG('p', 'u', 'w', 0), // puw = Puluwatese + TRUETYPE_TAG('p', 'u', 'x', 0), // pux = Puare + TRUETYPE_TAG('p', 'u', 'y', 0), // puy = Purisimeño + TRUETYPE_TAG('p', 'u', 'z', 0), // puz = Purum Naga + TRUETYPE_TAG('p', 'w', 'a', 0), // pwa = Pawaia + TRUETYPE_TAG('p', 'w', 'b', 0), // pwb = Panawa + TRUETYPE_TAG('p', 'w', 'g', 0), // pwg = Gapapaiwa + TRUETYPE_TAG('p', 'w', 'm', 0), // pwm = Molbog + TRUETYPE_TAG('p', 'w', 'n', 0), // pwn = Paiwan + TRUETYPE_TAG('p', 'w', 'o', 0), // pwo = Pwo Western Karen + TRUETYPE_TAG('p', 'w', 'r', 0), // pwr = Powari + TRUETYPE_TAG('p', 'w', 'w', 0), // pww = Pwo Northern Karen + TRUETYPE_TAG('p', 'x', 'm', 0), // pxm = Quetzaltepec Mixe + TRUETYPE_TAG('p', 'y', 'e', 0), // pye = Pye Krumen + TRUETYPE_TAG('p', 'y', 'm', 0), // pym = Fyam + TRUETYPE_TAG('p', 'y', 'n', 0), // pyn = Poyanáwa + TRUETYPE_TAG('p', 'y', 's', 0), // pys = Paraguayan Sign Language + TRUETYPE_TAG('p', 'y', 'u', 0), // pyu = Puyuma + TRUETYPE_TAG('p', 'y', 'x', 0), // pyx = Pyu (Myanmar) + TRUETYPE_TAG('p', 'y', 'y', 0), // pyy = Pyen + TRUETYPE_TAG('p', 'z', 'n', 0), // pzn = Para Naga = Private use + TRUETYPE_TAG('q', 'u', 'a', 0), // qua = Quapaw + TRUETYPE_TAG('q', 'u', 'b', 0), // qub = Huallaga Huánuco Quechua + TRUETYPE_TAG('q', 'u', 'c', 0), // quc = K'iche' + TRUETYPE_TAG('q', 'u', 'd', 0), // qud = Calderón Highland Quichua + TRUETYPE_TAG('q', 'u', 'f', 0), // quf = Lambayeque Quechua + TRUETYPE_TAG('q', 'u', 'g', 0), // qug = Chimborazo Highland Quichua + TRUETYPE_TAG('q', 'u', 'h', 0), // quh = South Bolivian Quechua + TRUETYPE_TAG('q', 'u', 'i', 0), // qui = Quileute + TRUETYPE_TAG('q', 'u', 'k', 0), // quk = Chachapoyas Quechua + TRUETYPE_TAG('q', 'u', 'l', 0), // qul = North Bolivian Quechua + TRUETYPE_TAG('q', 'u', 'm', 0), // qum = Sipacapense + TRUETYPE_TAG('q', 'u', 'n', 0), // qun = Quinault + TRUETYPE_TAG('q', 'u', 'p', 0), // qup = Southern Pastaza Quechua + TRUETYPE_TAG('q', 'u', 'q', 0), // quq = Quinqui + TRUETYPE_TAG('q', 'u', 'r', 0), // qur = Yanahuanca Pasco Quechua + TRUETYPE_TAG('q', 'u', 's', 0), // qus = Santiago del Estero Quichua + TRUETYPE_TAG('q', 'u', 'v', 0), // quv = Sacapulteco + TRUETYPE_TAG('q', 'u', 'w', 0), // quw = Tena Lowland Quichua + TRUETYPE_TAG('q', 'u', 'x', 0), // qux = Yauyos Quechua + TRUETYPE_TAG('q', 'u', 'y', 0), // quy = Ayacucho Quechua + TRUETYPE_TAG('q', 'u', 'z', 0), // quz = Cusco Quechua + TRUETYPE_TAG('q', 'v', 'a', 0), // qva = Ambo-Pasco Quechua + TRUETYPE_TAG('q', 'v', 'c', 0), // qvc = Cajamarca Quechua + TRUETYPE_TAG('q', 'v', 'e', 0), // qve = Eastern Apurímac Quechua + TRUETYPE_TAG('q', 'v', 'h', + 0), // qvh = Huamalíes-Dos de Mayo Huánuco Quechua + TRUETYPE_TAG('q', 'v', 'i', 0), // qvi = Imbabura Highland Quichua + TRUETYPE_TAG('q', 'v', 'j', 0), // qvj = Loja Highland Quichua + TRUETYPE_TAG('q', 'v', 'l', 0), // qvl = Cajatambo North Lima Quechua + TRUETYPE_TAG('q', 'v', 'm', + 0), // qvm = Margos-Yarowilca-Lauricocha Quechua + TRUETYPE_TAG('q', 'v', 'n', 0), // qvn = North Junín Quechua + TRUETYPE_TAG('q', 'v', 'o', 0), // qvo = Napo Lowland Quechua + TRUETYPE_TAG('q', 'v', 'p', 0), // qvp = Pacaraos Quechua + TRUETYPE_TAG('q', 'v', 's', 0), // qvs = San Martín Quechua + TRUETYPE_TAG('q', 'v', 'w', 0), // qvw = Huaylla Wanca Quechua + TRUETYPE_TAG('q', 'v', 'y', 0), // qvy = Queyu + TRUETYPE_TAG('q', 'v', 'z', 0), // qvz = Northern Pastaza Quichua + TRUETYPE_TAG('q', 'w', 'a', 0), // qwa = Corongo Ancash Quechua + TRUETYPE_TAG('q', 'w', 'c', 0), // qwc = Classical Quechua + TRUETYPE_TAG('q', 'w', 'e', 0), // qwe = Quechuan (family) + TRUETYPE_TAG('q', 'w', 'h', 0), // qwh = Huaylas Ancash Quechua + TRUETYPE_TAG('q', 'w', 'm', 0), // qwm = Kuman (Russia) + TRUETYPE_TAG('q', 'w', 's', 0), // qws = Sihuas Ancash Quechua + TRUETYPE_TAG('q', 'w', 't', 0), // qwt = Kwalhioqua-Tlatskanai + TRUETYPE_TAG('q', 'x', 'a', 0), // qxa = Chiquián Ancash Quechua + TRUETYPE_TAG('q', 'x', 'c', 0), // qxc = Chincha Quechua + TRUETYPE_TAG('q', 'x', 'h', 0), // qxh = Panao Huánuco Quechua + TRUETYPE_TAG('q', 'x', 'l', 0), // qxl = Salasaca Highland Quichua + TRUETYPE_TAG('q', 'x', 'n', 0), // qxn = Northern Conchucos Ancash Quechua + TRUETYPE_TAG('q', 'x', 'o', 0), // qxo = Southern Conchucos Ancash Quechua + TRUETYPE_TAG('q', 'x', 'p', 0), // qxp = Puno Quechua + TRUETYPE_TAG('q', 'x', 'q', 0), // qxq = Qashqa'i + TRUETYPE_TAG('q', 'x', 'r', 0), // qxr = Cañar Highland Quichua + TRUETYPE_TAG('q', 'x', 's', 0), // qxs = Southern Qiang + TRUETYPE_TAG('q', 'x', 't', 0), // qxt = Santa Ana de Tusi Pasco Quechua + TRUETYPE_TAG('q', 'x', 'u', 0), // qxu = Arequipa-La Unión Quechua + TRUETYPE_TAG('q', 'x', 'w', 0), // qxw = Jauja Wanca Quechua + TRUETYPE_TAG('q', 'y', 'a', 0), // qya = Quenya + TRUETYPE_TAG('q', 'y', 'p', 0), // qyp = Quiripi + TRUETYPE_TAG('r', 'a', 'a', 0), // raa = Dungmali + TRUETYPE_TAG('r', 'a', 'b', 0), // rab = Camling + TRUETYPE_TAG('r', 'a', 'c', 0), // rac = Rasawa + TRUETYPE_TAG('r', 'a', 'd', 0), // rad = Rade + TRUETYPE_TAG('r', 'a', 'f', 0), // raf = Western Meohang + TRUETYPE_TAG('r', 'a', 'g', 0), // rag = Logooli + TRUETYPE_TAG('r', 'a', 'h', 0), // rah = Rabha + TRUETYPE_TAG('r', 'a', 'i', 0), // rai = Ramoaaina + TRUETYPE_TAG('r', 'a', 'j', 0), // raj = Rajasthani + TRUETYPE_TAG('r', 'a', 'k', 0), // rak = Tulu-Bohuai + TRUETYPE_TAG('r', 'a', 'l', 0), // ral = Ralte + TRUETYPE_TAG('r', 'a', 'm', 0), // ram = Canela + TRUETYPE_TAG('r', 'a', 'n', 0), // ran = Riantana + TRUETYPE_TAG('r', 'a', 'o', 0), // rao = Rao + TRUETYPE_TAG('r', 'a', 'p', 0), // rap = Rapanui + TRUETYPE_TAG('r', 'a', 'q', 0), // raq = Saam + TRUETYPE_TAG('r', 'a', 'r', 0), // rar = Rarotongan + TRUETYPE_TAG('r', 'a', 's', 0), // ras = Tegali + TRUETYPE_TAG('r', 'a', 't', 0), // rat = Razajerdi + TRUETYPE_TAG('r', 'a', 'u', 0), // rau = Raute + TRUETYPE_TAG('r', 'a', 'v', 0), // rav = Sampang + TRUETYPE_TAG('r', 'a', 'w', 0), // raw = Rawang + TRUETYPE_TAG('r', 'a', 'x', 0), // rax = Rang + TRUETYPE_TAG('r', 'a', 'y', 0), // ray = Rapa + TRUETYPE_TAG('r', 'a', 'z', 0), // raz = Rahambuu + TRUETYPE_TAG('r', 'b', 'b', 0), // rbb = Rumai Palaung + TRUETYPE_TAG('r', 'b', 'k', 0), // rbk = Northern Bontok + TRUETYPE_TAG('r', 'b', 'l', 0), // rbl = Miraya Bikol + TRUETYPE_TAG('r', 'c', 'f', 0), // rcf = Réunion Creole French + TRUETYPE_TAG('r', 'd', 'b', 0), // rdb = Rudbari + TRUETYPE_TAG('r', 'e', 'a', 0), // rea = Rerau + TRUETYPE_TAG('r', 'e', 'b', 0), // reb = Rembong + TRUETYPE_TAG('r', 'e', 'e', 0), // ree = Rejang Kayan + TRUETYPE_TAG('r', 'e', 'g', 0), // reg = Kara (Tanzania) + TRUETYPE_TAG('r', 'e', 'i', 0), // rei = Reli + TRUETYPE_TAG('r', 'e', 'j', 0), // rej = Rejang + TRUETYPE_TAG('r', 'e', 'l', 0), // rel = Rendille + TRUETYPE_TAG('r', 'e', 'm', 0), // rem = Remo + TRUETYPE_TAG('r', 'e', 'n', 0), // ren = Rengao + TRUETYPE_TAG('r', 'e', 'r', 0), // rer = Rer Bare + TRUETYPE_TAG('r', 'e', 's', 0), // res = Reshe + TRUETYPE_TAG('r', 'e', 't', 0), // ret = Retta + TRUETYPE_TAG('r', 'e', 'y', 0), // rey = Reyesano + TRUETYPE_TAG('r', 'g', 'a', 0), // rga = Roria + TRUETYPE_TAG('r', 'g', 'e', 0), // rge = Romano-Greek + TRUETYPE_TAG('r', 'g', 'k', 0), // rgk = Rangkas + TRUETYPE_TAG('r', 'g', 'n', 0), // rgn = Romagnol + TRUETYPE_TAG('r', 'g', 'r', 0), // rgr = Resígaro + TRUETYPE_TAG('r', 'g', 's', 0), // rgs = Southern Roglai + TRUETYPE_TAG('r', 'g', 'u', 0), // rgu = Ringgou + TRUETYPE_TAG('r', 'h', 'g', 0), // rhg = Rohingya + TRUETYPE_TAG('r', 'h', 'p', 0), // rhp = Yahang + TRUETYPE_TAG('r', 'i', 'a', 0), // ria = Riang (India) + TRUETYPE_TAG('r', 'i', 'e', 0), // rie = Rien + TRUETYPE_TAG('r', 'i', 'f', 0), // rif = Tarifit + TRUETYPE_TAG('r', 'i', 'l', 0), // ril = Riang (Myanmar) + TRUETYPE_TAG('r', 'i', 'm', 0), // rim = Nyaturu + TRUETYPE_TAG('r', 'i', 'n', 0), // rin = Nungu + TRUETYPE_TAG('r', 'i', 'r', 0), // rir = Ribun + TRUETYPE_TAG('r', 'i', 't', 0), // rit = Ritarungo + TRUETYPE_TAG('r', 'i', 'u', 0), // riu = Riung + TRUETYPE_TAG('r', 'j', 'g', 0), // rjg = Rajong + TRUETYPE_TAG('r', 'j', 'i', 0), // rji = Raji + TRUETYPE_TAG('r', 'j', 's', 0), // rjs = Rajbanshi + TRUETYPE_TAG('r', 'k', 'a', 0), // rka = Kraol + TRUETYPE_TAG('r', 'k', 'b', 0), // rkb = Rikbaktsa + TRUETYPE_TAG('r', 'k', 'h', 0), // rkh = Rakahanga-Manihiki + TRUETYPE_TAG('r', 'k', 'i', 0), // rki = Rakhine + TRUETYPE_TAG('r', 'k', 'm', 0), // rkm = Marka + TRUETYPE_TAG('r', 'k', 't', 0), // rkt = Rangpuri + TRUETYPE_TAG('r', 'm', 'a', 0), // rma = Rama + TRUETYPE_TAG('r', 'm', 'b', 0), // rmb = Rembarunga + TRUETYPE_TAG('r', 'm', 'c', 0), // rmc = Carpathian Romani + TRUETYPE_TAG('r', 'm', 'd', 0), // rmd = Traveller Danish + TRUETYPE_TAG('r', 'm', 'e', 0), // rme = Angloromani + TRUETYPE_TAG('r', 'm', 'f', 0), // rmf = Kalo Finnish Romani + TRUETYPE_TAG('r', 'm', 'g', 0), // rmg = Traveller Norwegian + TRUETYPE_TAG('r', 'm', 'h', 0), // rmh = Murkim + TRUETYPE_TAG('r', 'm', 'i', 0), // rmi = Lomavren + TRUETYPE_TAG('r', 'm', 'k', 0), // rmk = Romkun + TRUETYPE_TAG('r', 'm', 'l', 0), // rml = Baltic Romani + TRUETYPE_TAG('r', 'm', 'm', 0), // rmm = Roma + TRUETYPE_TAG('r', 'm', 'n', 0), // rmn = Balkan Romani + TRUETYPE_TAG('r', 'm', 'o', 0), // rmo = Sinte Romani + TRUETYPE_TAG('r', 'm', 'p', 0), // rmp = Rempi + TRUETYPE_TAG('r', 'm', 'q', 0), // rmq = Caló + TRUETYPE_TAG('r', 'm', 'r', 0), // rmr = Caló + TRUETYPE_TAG('r', 'm', 's', 0), // rms = Romanian Sign Language + TRUETYPE_TAG('r', 'm', 't', 0), // rmt = Domari + TRUETYPE_TAG('r', 'm', 'u', 0), // rmu = Tavringer Romani + TRUETYPE_TAG('r', 'm', 'v', 0), // rmv = Romanova + TRUETYPE_TAG('r', 'm', 'w', 0), // rmw = Welsh Romani + TRUETYPE_TAG('r', 'm', 'x', 0), // rmx = Romam + TRUETYPE_TAG('r', 'm', 'y', 0), // rmy = Vlax Romani + TRUETYPE_TAG('r', 'm', 'z', 0), // rmz = Marma + TRUETYPE_TAG('r', 'n', 'a', 0), // rna = Runa + TRUETYPE_TAG('r', 'n', 'd', 0), // rnd = Ruund + TRUETYPE_TAG('r', 'n', 'g', 0), // rng = Ronga + TRUETYPE_TAG('r', 'n', 'l', 0), // rnl = Ranglong + TRUETYPE_TAG('r', 'n', 'n', 0), // rnn = Roon + TRUETYPE_TAG('r', 'n', 'p', 0), // rnp = Rongpo + TRUETYPE_TAG('r', 'n', 'w', 0), // rnw = Rungwa + TRUETYPE_TAG('r', 'o', 'a', 0), // roa = Romance languages + TRUETYPE_TAG('r', 'o', 'b', 0), // rob = Tae' + TRUETYPE_TAG('r', 'o', 'c', 0), // roc = Cacgia Roglai + TRUETYPE_TAG('r', 'o', 'd', 0), // rod = Rogo + TRUETYPE_TAG('r', 'o', 'e', 0), // roe = Ronji + TRUETYPE_TAG('r', 'o', 'f', 0), // rof = Rombo + TRUETYPE_TAG('r', 'o', 'g', 0), // rog = Northern Roglai + TRUETYPE_TAG('r', 'o', 'l', 0), // rol = Romblomanon + TRUETYPE_TAG('r', 'o', 'm', 0), // rom = Romany + TRUETYPE_TAG('r', 'o', 'o', 0), // roo = Rotokas + TRUETYPE_TAG('r', 'o', 'p', 0), // rop = Kriol + TRUETYPE_TAG('r', 'o', 'r', 0), // ror = Rongga + TRUETYPE_TAG('r', 'o', 'u', 0), // rou = Runga + TRUETYPE_TAG('r', 'o', 'w', 0), // row = Dela-Oenale + TRUETYPE_TAG('r', 'p', 'n', 0), // rpn = Repanbitip + TRUETYPE_TAG('r', 'p', 't', 0), // rpt = Rapting + TRUETYPE_TAG('r', 'r', 'i', 0), // rri = Ririo + TRUETYPE_TAG('r', 'r', 'o', 0), // rro = Waima + TRUETYPE_TAG('r', 's', 'b', 0), // rsb = Romano-Serbian + TRUETYPE_TAG('r', 's', 'i', 0), // rsi = Rennellese Sign Language + TRUETYPE_TAG('r', 's', 'l', 0), // rsl = Russian Sign Language + TRUETYPE_TAG('r', 't', 'h', 0), // rth = Ratahan + TRUETYPE_TAG('r', 't', 'm', 0), // rtm = Rotuman + TRUETYPE_TAG('r', 't', 'w', 0), // rtw = Rathawi + TRUETYPE_TAG('r', 'u', 'b', 0), // rub = Gungu + TRUETYPE_TAG('r', 'u', 'c', 0), // ruc = Ruuli + TRUETYPE_TAG('r', 'u', 'e', 0), // rue = Rusyn + TRUETYPE_TAG('r', 'u', 'f', 0), // ruf = Luguru + TRUETYPE_TAG('r', 'u', 'g', 0), // rug = Roviana + TRUETYPE_TAG('r', 'u', 'h', 0), // ruh = Ruga + TRUETYPE_TAG('r', 'u', 'i', 0), // rui = Rufiji + TRUETYPE_TAG('r', 'u', 'k', 0), // ruk = Che + TRUETYPE_TAG('r', 'u', 'o', 0), // ruo = Istro Romanian + TRUETYPE_TAG('r', 'u', 'p', 0), // rup = Macedo-Romanian + TRUETYPE_TAG('r', 'u', 'q', 0), // ruq = Megleno Romanian + TRUETYPE_TAG('r', 'u', 't', 0), // rut = Rutul + TRUETYPE_TAG('r', 'u', 'u', 0), // ruu = Lanas Lobu + TRUETYPE_TAG('r', 'u', 'y', 0), // ruy = Mala (Nigeria) + TRUETYPE_TAG('r', 'u', 'z', 0), // ruz = Ruma + TRUETYPE_TAG('r', 'w', 'a', 0), // rwa = Rawo + TRUETYPE_TAG('r', 'w', 'k', 0), // rwk = Rwa + TRUETYPE_TAG('r', 'w', 'm', 0), // rwm = Amba (Uganda) + TRUETYPE_TAG('r', 'w', 'o', 0), // rwo = Rawa + TRUETYPE_TAG('r', 'w', 'r', 0), // rwr = Marwari (India) + TRUETYPE_TAG('r', 'y', 'n', 0), // ryn = Northern Amami-Oshima + TRUETYPE_TAG('r', 'y', 's', 0), // rys = Yaeyama + TRUETYPE_TAG('r', 'y', 'u', 0), // ryu = Central Okinawan + TRUETYPE_TAG('s', 'a', 'a', 0), // saa = Saba + TRUETYPE_TAG('s', 'a', 'b', 0), // sab = Buglere + TRUETYPE_TAG('s', 'a', 'c', 0), // sac = Meskwaki + TRUETYPE_TAG('s', 'a', 'd', 0), // sad = Sandawe + TRUETYPE_TAG('s', 'a', 'e', 0), // sae = Sabanê + TRUETYPE_TAG('s', 'a', 'f', 0), // saf = Safaliba + TRUETYPE_TAG('s', 'a', 'h', 0), // sah = Yakut + TRUETYPE_TAG('s', 'a', 'i', 0), // sai = South American Indian languages + TRUETYPE_TAG('s', 'a', 'j', 0), // saj = Sahu + TRUETYPE_TAG('s', 'a', 'k', 0), // sak = Sake + TRUETYPE_TAG('s', 'a', 'l', 0), // sal = Salishan languages + TRUETYPE_TAG('s', 'a', 'm', 0), // sam = Samaritan Aramaic + TRUETYPE_TAG('s', 'a', 'o', 0), // sao = Sause + TRUETYPE_TAG('s', 'a', 'p', 0), // sap = Sanapaná + TRUETYPE_TAG('s', 'a', 'q', 0), // saq = Samburu + TRUETYPE_TAG('s', 'a', 'r', 0), // sar = Saraveca + TRUETYPE_TAG('s', 'a', 's', 0), // sas = Sasak + TRUETYPE_TAG('s', 'a', 't', 0), // sat = Santali + TRUETYPE_TAG('s', 'a', 'u', 0), // sau = Saleman + TRUETYPE_TAG('s', 'a', 'v', 0), // sav = Saafi-Saafi + TRUETYPE_TAG('s', 'a', 'w', 0), // saw = Sawi + TRUETYPE_TAG('s', 'a', 'x', 0), // sax = Sa + TRUETYPE_TAG('s', 'a', 'y', 0), // say = Saya + TRUETYPE_TAG('s', 'a', 'z', 0), // saz = Saurashtra + TRUETYPE_TAG('s', 'b', 'a', 0), // sba = Ngambay + TRUETYPE_TAG('s', 'b', 'b', 0), // sbb = Simbo + TRUETYPE_TAG('s', 'b', 'c', 0), // sbc = Kele (Papua New Guinea) + TRUETYPE_TAG('s', 'b', 'd', 0), // sbd = Southern Samo + TRUETYPE_TAG('s', 'b', 'e', 0), // sbe = Saliba + TRUETYPE_TAG('s', 'b', 'f', 0), // sbf = Shabo + TRUETYPE_TAG('s', 'b', 'g', 0), // sbg = Seget + TRUETYPE_TAG('s', 'b', 'h', 0), // sbh = Sori-Harengan + TRUETYPE_TAG('s', 'b', 'i', 0), // sbi = Seti + TRUETYPE_TAG('s', 'b', 'j', 0), // sbj = Surbakhal + TRUETYPE_TAG('s', 'b', 'k', 0), // sbk = Safwa + TRUETYPE_TAG('s', 'b', 'l', 0), // sbl = Botolan Sambal + TRUETYPE_TAG('s', 'b', 'm', 0), // sbm = Sagala + TRUETYPE_TAG('s', 'b', 'n', 0), // sbn = Sindhi Bhil + TRUETYPE_TAG('s', 'b', 'o', 0), // sbo = Sabüm + TRUETYPE_TAG('s', 'b', 'p', 0), // sbp = Sangu (Tanzania) + TRUETYPE_TAG('s', 'b', 'q', 0), // sbq = Sileibi + TRUETYPE_TAG('s', 'b', 'r', 0), // sbr = Sembakung Murut + TRUETYPE_TAG('s', 'b', 's', 0), // sbs = Subiya + TRUETYPE_TAG('s', 'b', 't', 0), // sbt = Kimki + TRUETYPE_TAG('s', 'b', 'u', 0), // sbu = Stod Bhoti + TRUETYPE_TAG('s', 'b', 'v', 0), // sbv = Sabine + TRUETYPE_TAG('s', 'b', 'w', 0), // sbw = Simba + TRUETYPE_TAG('s', 'b', 'x', 0), // sbx = Seberuang + TRUETYPE_TAG('s', 'b', 'y', 0), // sby = Soli + TRUETYPE_TAG('s', 'b', 'z', 0), // sbz = Sara Kaba + TRUETYPE_TAG('s', 'c', 'a', 0), // sca = Sansu + TRUETYPE_TAG('s', 'c', 'b', 0), // scb = Chut + TRUETYPE_TAG('s', 'c', 'e', 0), // sce = Dongxiang + TRUETYPE_TAG('s', 'c', 'f', 0), // scf = San Miguel Creole French + TRUETYPE_TAG('s', 'c', 'g', 0), // scg = Sanggau + TRUETYPE_TAG('s', 'c', 'h', 0), // sch = Sakachep + TRUETYPE_TAG('s', 'c', 'i', 0), // sci = Sri Lankan Creole Malay + TRUETYPE_TAG('s', 'c', 'k', 0), // sck = Sadri + TRUETYPE_TAG('s', 'c', 'l', 0), // scl = Shina + TRUETYPE_TAG('s', 'c', 'n', 0), // scn = Sicilian + TRUETYPE_TAG('s', 'c', 'o', 0), // sco = Scots + TRUETYPE_TAG('s', 'c', 'p', 0), // scp = Helambu Sherpa + TRUETYPE_TAG('s', 'c', 'q', 0), // scq = Sa'och + TRUETYPE_TAG('s', 'c', 's', 0), // scs = North Slavey + TRUETYPE_TAG('s', 'c', 'u', 0), // scu = Shumcho + TRUETYPE_TAG('s', 'c', 'v', 0), // scv = Sheni + TRUETYPE_TAG('s', 'c', 'w', 0), // scw = Sha + TRUETYPE_TAG('s', 'c', 'x', 0), // scx = Sicel + TRUETYPE_TAG('s', 'd', 'a', 0), // sda = Toraja-Sa'dan + TRUETYPE_TAG('s', 'd', 'b', 0), // sdb = Shabak + TRUETYPE_TAG('s', 'd', 'c', 0), // sdc = Sassarese Sardinian + TRUETYPE_TAG('s', 'd', 'e', 0), // sde = Surubu + TRUETYPE_TAG('s', 'd', 'f', 0), // sdf = Sarli + TRUETYPE_TAG('s', 'd', 'g', 0), // sdg = Savi + TRUETYPE_TAG('s', 'd', 'h', 0), // sdh = Southern Kurdish + TRUETYPE_TAG('s', 'd', 'j', 0), // sdj = Suundi + TRUETYPE_TAG('s', 'd', 'k', 0), // sdk = Sos Kundi + TRUETYPE_TAG('s', 'd', 'l', 0), // sdl = Saudi Arabian Sign Language + TRUETYPE_TAG('s', 'd', 'm', 0), // sdm = Semandang + TRUETYPE_TAG('s', 'd', 'n', 0), // sdn = Gallurese Sardinian + TRUETYPE_TAG('s', 'd', 'o', 0), // sdo = Bukar-Sadung Bidayuh + TRUETYPE_TAG('s', 'd', 'p', 0), // sdp = Sherdukpen + TRUETYPE_TAG('s', 'd', 'r', 0), // sdr = Oraon Sadri + TRUETYPE_TAG('s', 'd', 's', 0), // sds = Sened + TRUETYPE_TAG('s', 'd', 't', 0), // sdt = Shuadit + TRUETYPE_TAG('s', 'd', 'u', 0), // sdu = Sarudu + TRUETYPE_TAG('s', 'd', 'v', 0), // sdv = Eastern Sudanic languages + TRUETYPE_TAG('s', 'd', 'x', 0), // sdx = Sibu Melanau + TRUETYPE_TAG('s', 'd', 'z', 0), // sdz = Sallands + TRUETYPE_TAG('s', 'e', 'a', 0), // sea = Semai + TRUETYPE_TAG('s', 'e', 'b', 0), // seb = Shempire Senoufo + TRUETYPE_TAG('s', 'e', 'c', 0), // sec = Sechelt + TRUETYPE_TAG('s', 'e', 'd', 0), // sed = Sedang + TRUETYPE_TAG('s', 'e', 'e', 0), // see = Seneca + TRUETYPE_TAG('s', 'e', 'f', 0), // sef = Cebaara Senoufo + TRUETYPE_TAG('s', 'e', 'g', 0), // seg = Segeju + TRUETYPE_TAG('s', 'e', 'h', 0), // seh = Sena + TRUETYPE_TAG('s', 'e', 'i', 0), // sei = Seri + TRUETYPE_TAG('s', 'e', 'j', 0), // sej = Sene + TRUETYPE_TAG('s', 'e', 'k', 0), // sek = Sekani + TRUETYPE_TAG('s', 'e', 'l', 0), // sel = Selkup + TRUETYPE_TAG('s', 'e', 'm', 0), // sem = Semitic languages + TRUETYPE_TAG('s', 'e', 'n', 0), // sen = Nanerigé Sénoufo + TRUETYPE_TAG('s', 'e', 'o', 0), // seo = Suarmin + TRUETYPE_TAG('s', 'e', 'p', 0), // sep = Sìcìté Sénoufo + TRUETYPE_TAG('s', 'e', 'q', 0), // seq = Senara Sénoufo + TRUETYPE_TAG('s', 'e', 'r', 0), // ser = Serrano + TRUETYPE_TAG('s', 'e', 's', 0), // ses = Koyraboro Senni Songhai + TRUETYPE_TAG('s', 'e', 't', 0), // set = Sentani + TRUETYPE_TAG('s', 'e', 'u', 0), // seu = Serui-Laut + TRUETYPE_TAG('s', 'e', 'v', 0), // sev = Nyarafolo Senoufo + TRUETYPE_TAG('s', 'e', 'w', 0), // sew = Sewa Bay + TRUETYPE_TAG('s', 'e', 'y', 0), // sey = Secoya + TRUETYPE_TAG('s', 'e', 'z', 0), // sez = Senthang Chin + TRUETYPE_TAG('s', 'f', 'b', + 0), // sfb = Langue des signes de Belgique Francophone + TRUETYPE_TAG('s', 'f', 'm', 0), // sfm = Small Flowery Miao + TRUETYPE_TAG('s', 'f', 's', 0), // sfs = South African Sign Language + TRUETYPE_TAG('s', 'f', 'w', 0), // sfw = Sehwi + TRUETYPE_TAG('s', 'g', 'a', 0), // sga = Old Irish (to 900) + TRUETYPE_TAG('s', 'g', 'b', 0), // sgb = Mag-antsi Ayta + TRUETYPE_TAG('s', 'g', 'c', 0), // sgc = Kipsigis + TRUETYPE_TAG('s', 'g', 'd', 0), // sgd = Surigaonon + TRUETYPE_TAG('s', 'g', 'e', 0), // sge = Segai + TRUETYPE_TAG('s', 'g', 'g', 0), // sgg = Swiss-German Sign Language + TRUETYPE_TAG('s', 'g', 'h', 0), // sgh = Shughni + TRUETYPE_TAG('s', 'g', 'i', 0), // sgi = Suga + TRUETYPE_TAG('s', 'g', 'k', 0), // sgk = Sangkong + TRUETYPE_TAG('s', 'g', 'l', 0), // sgl = Sanglechi-Ishkashimi + TRUETYPE_TAG('s', 'g', 'm', 0), // sgm = Singa + TRUETYPE_TAG('s', 'g', 'n', 0), // sgn = Sign languages + TRUETYPE_TAG('s', 'g', 'o', 0), // sgo = Songa + TRUETYPE_TAG('s', 'g', 'p', 0), // sgp = Singpho + TRUETYPE_TAG('s', 'g', 'r', 0), // sgr = Sangisari + TRUETYPE_TAG('s', 'g', 's', 0), // sgs = Samogitian + TRUETYPE_TAG('s', 'g', 't', 0), // sgt = Brokpake + TRUETYPE_TAG('s', 'g', 'u', 0), // sgu = Salas + TRUETYPE_TAG('s', 'g', 'w', 0), // sgw = Sebat Bet Gurage + TRUETYPE_TAG('s', 'g', 'x', 0), // sgx = Sierra Leone Sign Language + TRUETYPE_TAG('s', 'g', 'y', 0), // sgy = Sanglechi + TRUETYPE_TAG('s', 'g', 'z', 0), // sgz = Sursurunga + TRUETYPE_TAG('s', 'h', 'a', 0), // sha = Shall-Zwall + TRUETYPE_TAG('s', 'h', 'b', 0), // shb = Ninam + TRUETYPE_TAG('s', 'h', 'c', 0), // shc = Sonde + TRUETYPE_TAG('s', 'h', 'd', 0), // shd = Kundal Shahi + TRUETYPE_TAG('s', 'h', 'e', 0), // she = Sheko + TRUETYPE_TAG('s', 'h', 'g', 0), // shg = Shua + TRUETYPE_TAG('s', 'h', 'h', 0), // shh = Shoshoni + TRUETYPE_TAG('s', 'h', 'i', 0), // shi = Tachelhit + TRUETYPE_TAG('s', 'h', 'j', 0), // shj = Shatt + TRUETYPE_TAG('s', 'h', 'k', 0), // shk = Shilluk + TRUETYPE_TAG('s', 'h', 'l', 0), // shl = Shendu + TRUETYPE_TAG('s', 'h', 'm', 0), // shm = Shahrudi + TRUETYPE_TAG('s', 'h', 'n', 0), // shn = Shan + TRUETYPE_TAG('s', 'h', 'o', 0), // sho = Shanga + TRUETYPE_TAG('s', 'h', 'p', 0), // shp = Shipibo-Conibo + TRUETYPE_TAG('s', 'h', 'q', 0), // shq = Sala + TRUETYPE_TAG('s', 'h', 'r', 0), // shr = Shi + TRUETYPE_TAG('s', 'h', 's', 0), // shs = Shuswap + TRUETYPE_TAG('s', 'h', 't', 0), // sht = Shasta + TRUETYPE_TAG('s', 'h', 'u', 0), // shu = Chadian Arabic + TRUETYPE_TAG('s', 'h', 'v', 0), // shv = Shehri + TRUETYPE_TAG('s', 'h', 'w', 0), // shw = Shwai + TRUETYPE_TAG('s', 'h', 'x', 0), // shx = She + TRUETYPE_TAG('s', 'h', 'y', 0), // shy = Tachawit + TRUETYPE_TAG('s', 'h', 'z', 0), // shz = Syenara Senoufo + TRUETYPE_TAG('s', 'i', 'a', 0), // sia = Akkala Sami + TRUETYPE_TAG('s', 'i', 'b', 0), // sib = Sebop + TRUETYPE_TAG('s', 'i', 'd', 0), // sid = Sidamo + TRUETYPE_TAG('s', 'i', 'e', 0), // sie = Simaa + TRUETYPE_TAG('s', 'i', 'f', 0), // sif = Siamou + TRUETYPE_TAG('s', 'i', 'g', 0), // sig = Paasaal + TRUETYPE_TAG('s', 'i', 'h', 0), // sih = Zire + TRUETYPE_TAG('s', 'i', 'i', 0), // sii = Shom Peng + TRUETYPE_TAG('s', 'i', 'j', 0), // sij = Numbami + TRUETYPE_TAG('s', 'i', 'k', 0), // sik = Sikiana + TRUETYPE_TAG('s', 'i', 'l', 0), // sil = Tumulung Sisaala + TRUETYPE_TAG('s', 'i', 'm', 0), // sim = Mende (Papua New Guinea) + TRUETYPE_TAG('s', 'i', 'o', 0), // sio = Siouan languages + TRUETYPE_TAG('s', 'i', 'p', 0), // sip = Sikkimese + TRUETYPE_TAG('s', 'i', 'q', 0), // siq = Sonia + TRUETYPE_TAG('s', 'i', 'r', 0), // sir = Siri + TRUETYPE_TAG('s', 'i', 's', 0), // sis = Siuslaw + TRUETYPE_TAG('s', 'i', 't', 0), // sit = Sino-Tibetan languages + TRUETYPE_TAG('s', 'i', 'u', 0), // siu = Sinagen + TRUETYPE_TAG('s', 'i', 'v', 0), // siv = Sumariup + TRUETYPE_TAG('s', 'i', 'w', 0), // siw = Siwai + TRUETYPE_TAG('s', 'i', 'x', 0), // six = Sumau + TRUETYPE_TAG('s', 'i', 'y', 0), // siy = Sivandi + TRUETYPE_TAG('s', 'i', 'z', 0), // siz = Siwi + TRUETYPE_TAG('s', 'j', 'a', 0), // sja = Epena + TRUETYPE_TAG('s', 'j', 'b', 0), // sjb = Sajau Basap + TRUETYPE_TAG('s', 'j', 'd', 0), // sjd = Kildin Sami + TRUETYPE_TAG('s', 'j', 'e', 0), // sje = Pite Sami + TRUETYPE_TAG('s', 'j', 'g', 0), // sjg = Assangori + TRUETYPE_TAG('s', 'j', 'k', 0), // sjk = Kemi Sami + TRUETYPE_TAG('s', 'j', 'l', 0), // sjl = Sajalong + TRUETYPE_TAG('s', 'j', 'm', 0), // sjm = Mapun + TRUETYPE_TAG('s', 'j', 'n', 0), // sjn = Sindarin + TRUETYPE_TAG('s', 'j', 'o', 0), // sjo = Xibe + TRUETYPE_TAG('s', 'j', 'p', 0), // sjp = Surjapuri + TRUETYPE_TAG('s', 'j', 'r', 0), // sjr = Siar-Lak + TRUETYPE_TAG('s', 'j', 's', 0), // sjs = Senhaja De Srair + TRUETYPE_TAG('s', 'j', 't', 0), // sjt = Ter Sami + TRUETYPE_TAG('s', 'j', 'u', 0), // sju = Ume Sami + TRUETYPE_TAG('s', 'j', 'w', 0), // sjw = Shawnee + TRUETYPE_TAG('s', 'k', 'a', 0), // ska = Skagit + TRUETYPE_TAG('s', 'k', 'b', 0), // skb = Saek + TRUETYPE_TAG('s', 'k', 'c', 0), // skc = Sauk + TRUETYPE_TAG('s', 'k', 'd', 0), // skd = Southern Sierra Miwok + TRUETYPE_TAG('s', 'k', 'e', 0), // ske = Seke (Vanuatu) + TRUETYPE_TAG('s', 'k', 'f', 0), // skf = Sakirabiá + TRUETYPE_TAG('s', 'k', 'g', 0), // skg = Sakalava Malagasy + TRUETYPE_TAG('s', 'k', 'h', 0), // skh = Sikule + TRUETYPE_TAG('s', 'k', 'i', 0), // ski = Sika + TRUETYPE_TAG('s', 'k', 'j', 0), // skj = Seke (Nepal) + TRUETYPE_TAG('s', 'k', 'k', 0), // skk = Sok + TRUETYPE_TAG('s', 'k', 'm', 0), // skm = Sakam + TRUETYPE_TAG('s', 'k', 'n', 0), // skn = Kolibugan Subanon + TRUETYPE_TAG('s', 'k', 'o', 0), // sko = Seko Tengah + TRUETYPE_TAG('s', 'k', 'p', 0), // skp = Sekapan + TRUETYPE_TAG('s', 'k', 'q', 0), // skq = Sininkere + TRUETYPE_TAG('s', 'k', 'r', 0), // skr = Seraiki + TRUETYPE_TAG('s', 'k', 's', 0), // sks = Maia + TRUETYPE_TAG('s', 'k', 't', 0), // skt = Sakata + TRUETYPE_TAG('s', 'k', 'u', 0), // sku = Sakao + TRUETYPE_TAG('s', 'k', 'v', 0), // skv = Skou + TRUETYPE_TAG('s', 'k', 'w', 0), // skw = Skepi Creole Dutch + TRUETYPE_TAG('s', 'k', 'x', 0), // skx = Seko Padang + TRUETYPE_TAG('s', 'k', 'y', 0), // sky = Sikaiana + TRUETYPE_TAG('s', 'k', 'z', 0), // skz = Sekar + TRUETYPE_TAG('s', 'l', 'a', 0), // sla = Slavic languages + TRUETYPE_TAG('s', 'l', 'c', 0), // slc = Sáliba + TRUETYPE_TAG('s', 'l', 'd', 0), // sld = Sissala + TRUETYPE_TAG('s', 'l', 'e', 0), // sle = Sholaga + TRUETYPE_TAG('s', 'l', 'f', 0), // slf = Swiss-Italian Sign Language + TRUETYPE_TAG('s', 'l', 'g', 0), // slg = Selungai Murut + TRUETYPE_TAG('s', 'l', 'h', 0), // slh = Southern Puget Sound Salish + TRUETYPE_TAG('s', 'l', 'i', 0), // sli = Lower Silesian + TRUETYPE_TAG('s', 'l', 'j', 0), // slj = Salumá + TRUETYPE_TAG('s', 'l', 'l', 0), // sll = Salt-Yui + TRUETYPE_TAG('s', 'l', 'm', 0), // slm = Pangutaran Sama + TRUETYPE_TAG('s', 'l', 'n', 0), // sln = Salinan + TRUETYPE_TAG('s', 'l', 'p', 0), // slp = Lamaholot + TRUETYPE_TAG('s', 'l', 'q', 0), // slq = Salchuq + TRUETYPE_TAG('s', 'l', 'r', 0), // slr = Salar + TRUETYPE_TAG('s', 'l', 's', 0), // sls = Singapore Sign Language + TRUETYPE_TAG('s', 'l', 't', 0), // slt = Sila + TRUETYPE_TAG('s', 'l', 'u', 0), // slu = Selaru + TRUETYPE_TAG('s', 'l', 'w', 0), // slw = Sialum + TRUETYPE_TAG('s', 'l', 'x', 0), // slx = Salampasu + TRUETYPE_TAG('s', 'l', 'y', 0), // sly = Selayar + TRUETYPE_TAG('s', 'l', 'z', 0), // slz = Ma'ya + TRUETYPE_TAG('s', 'm', 'a', 0), // sma = Southern Sami + TRUETYPE_TAG('s', 'm', 'b', 0), // smb = Simbari + TRUETYPE_TAG('s', 'm', 'c', 0), // smc = Som + TRUETYPE_TAG('s', 'm', 'd', 0), // smd = Sama + TRUETYPE_TAG('s', 'm', 'f', 0), // smf = Auwe + TRUETYPE_TAG('s', 'm', 'g', 0), // smg = Simbali + TRUETYPE_TAG('s', 'm', 'h', 0), // smh = Samei + TRUETYPE_TAG('s', 'm', 'i', 0), // smi = Sami languages + TRUETYPE_TAG('s', 'm', 'j', 0), // smj = Lule Sami + TRUETYPE_TAG('s', 'm', 'k', 0), // smk = Bolinao + TRUETYPE_TAG('s', 'm', 'l', 0), // sml = Central Sama + TRUETYPE_TAG('s', 'm', 'm', 0), // smm = Musasa + TRUETYPE_TAG('s', 'm', 'n', 0), // smn = Inari Sami + TRUETYPE_TAG('s', 'm', 'p', 0), // smp = Samaritan + TRUETYPE_TAG('s', 'm', 'q', 0), // smq = Samo + TRUETYPE_TAG('s', 'm', 'r', 0), // smr = Simeulue + TRUETYPE_TAG('s', 'm', 's', 0), // sms = Skolt Sami + TRUETYPE_TAG('s', 'm', 't', 0), // smt = Simte + TRUETYPE_TAG('s', 'm', 'u', 0), // smu = Somray + TRUETYPE_TAG('s', 'm', 'v', 0), // smv = Samvedi + TRUETYPE_TAG('s', 'm', 'w', 0), // smw = Sumbawa + TRUETYPE_TAG('s', 'm', 'x', 0), // smx = Samba + TRUETYPE_TAG('s', 'm', 'y', 0), // smy = Semnani + TRUETYPE_TAG('s', 'm', 'z', 0), // smz = Simeku + TRUETYPE_TAG('s', 'n', 'b', 0), // snb = Sebuyau + TRUETYPE_TAG('s', 'n', 'c', 0), // snc = Sinaugoro + TRUETYPE_TAG('s', 'n', 'e', 0), // sne = Bau Bidayuh + TRUETYPE_TAG('s', 'n', 'f', 0), // snf = Noon + TRUETYPE_TAG('s', 'n', 'g', + 0), // sng = Sanga (Democratic Republic of Congo) + TRUETYPE_TAG('s', 'n', 'h', 0), // snh = Shinabo + TRUETYPE_TAG('s', 'n', 'i', 0), // sni = Sensi + TRUETYPE_TAG('s', 'n', 'j', 0), // snj = Riverain Sango + TRUETYPE_TAG('s', 'n', 'k', 0), // snk = Soninke + TRUETYPE_TAG('s', 'n', 'l', 0), // snl = Sangil + TRUETYPE_TAG('s', 'n', 'm', 0), // snm = Southern Ma'di + TRUETYPE_TAG('s', 'n', 'n', 0), // snn = Siona + TRUETYPE_TAG('s', 'n', 'o', 0), // sno = Snohomish + TRUETYPE_TAG('s', 'n', 'p', 0), // snp = Siane + TRUETYPE_TAG('s', 'n', 'q', 0), // snq = Sangu (Gabon) + TRUETYPE_TAG('s', 'n', 'r', 0), // snr = Sihan + TRUETYPE_TAG('s', 'n', 's', 0), // sns = South West Bay + TRUETYPE_TAG('s', 'n', 'u', 0), // snu = Senggi + TRUETYPE_TAG('s', 'n', 'v', 0), // snv = Sa'ban + TRUETYPE_TAG('s', 'n', 'w', 0), // snw = Selee + TRUETYPE_TAG('s', 'n', 'x', 0), // snx = Sam + TRUETYPE_TAG('s', 'n', 'y', 0), // sny = Saniyo-Hiyewe + TRUETYPE_TAG('s', 'n', 'z', 0), // snz = Sinsauru + TRUETYPE_TAG('s', 'o', 'a', 0), // soa = Thai Song + TRUETYPE_TAG('s', 'o', 'b', 0), // sob = Sobei + TRUETYPE_TAG('s', 'o', 'c', 0), // soc = So (Democratic Republic of Congo) + TRUETYPE_TAG('s', 'o', 'd', 0), // sod = Songoora + TRUETYPE_TAG('s', 'o', 'e', 0), // soe = Songomeno + TRUETYPE_TAG('s', 'o', 'g', 0), // sog = Sogdian + TRUETYPE_TAG('s', 'o', 'h', 0), // soh = Aka + TRUETYPE_TAG('s', 'o', 'i', 0), // soi = Sonha + TRUETYPE_TAG('s', 'o', 'j', 0), // soj = Soi + TRUETYPE_TAG('s', 'o', 'k', 0), // sok = Sokoro + TRUETYPE_TAG('s', 'o', 'l', 0), // sol = Solos + TRUETYPE_TAG('s', 'o', 'n', 0), // son = Songhai languages + TRUETYPE_TAG('s', 'o', 'o', 0), // soo = Songo + TRUETYPE_TAG('s', 'o', 'p', 0), // sop = Songe + TRUETYPE_TAG('s', 'o', 'q', 0), // soq = Kanasi + TRUETYPE_TAG('s', 'o', 'r', 0), // sor = Somrai + TRUETYPE_TAG('s', 'o', 's', 0), // sos = Seeku + TRUETYPE_TAG('s', 'o', 'u', 0), // sou = Southern Thai + TRUETYPE_TAG('s', 'o', 'v', 0), // sov = Sonsorol + TRUETYPE_TAG('s', 'o', 'w', 0), // sow = Sowanda + TRUETYPE_TAG('s', 'o', 'x', 0), // sox = So (Cameroon) + TRUETYPE_TAG('s', 'o', 'y', 0), // soy = Miyobe + TRUETYPE_TAG('s', 'o', 'z', 0), // soz = Temi + TRUETYPE_TAG('s', 'p', 'b', 0), // spb = Sepa (Indonesia) + TRUETYPE_TAG('s', 'p', 'c', 0), // spc = Sapé + TRUETYPE_TAG('s', 'p', 'd', 0), // spd = Saep + TRUETYPE_TAG('s', 'p', 'e', 0), // spe = Sepa (Papua New Guinea) + TRUETYPE_TAG('s', 'p', 'g', 0), // spg = Sian + TRUETYPE_TAG('s', 'p', 'i', 0), // spi = Saponi + TRUETYPE_TAG('s', 'p', 'k', 0), // spk = Sengo + TRUETYPE_TAG('s', 'p', 'l', 0), // spl = Selepet + TRUETYPE_TAG('s', 'p', 'm', 0), // spm = Sepen + TRUETYPE_TAG('s', 'p', 'o', 0), // spo = Spokane + TRUETYPE_TAG('s', 'p', 'p', 0), // spp = Supyire Senoufo + TRUETYPE_TAG('s', 'p', 'q', 0), // spq = Loreto-Ucayali Spanish + TRUETYPE_TAG('s', 'p', 'r', 0), // spr = Saparua + TRUETYPE_TAG('s', 'p', 's', 0), // sps = Saposa + TRUETYPE_TAG('s', 'p', 't', 0), // spt = Spiti Bhoti + TRUETYPE_TAG('s', 'p', 'u', 0), // spu = Sapuan + TRUETYPE_TAG('s', 'p', 'x', 0), // spx = South Picene + TRUETYPE_TAG('s', 'p', 'y', 0), // spy = Sabaot + TRUETYPE_TAG('s', 'q', 'a', 0), // sqa = Shama-Sambuga + TRUETYPE_TAG('s', 'q', 'h', 0), // sqh = Shau + TRUETYPE_TAG('s', 'q', 'j', 0), // sqj = Albanian languages + TRUETYPE_TAG('s', 'q', 'm', 0), // sqm = Suma + TRUETYPE_TAG('s', 'q', 'n', 0), // sqn = Susquehannock + TRUETYPE_TAG('s', 'q', 'o', 0), // sqo = Sorkhei + TRUETYPE_TAG('s', 'q', 'q', 0), // sqq = Sou + TRUETYPE_TAG('s', 'q', 'r', 0), // sqr = Siculo Arabic + TRUETYPE_TAG('s', 'q', 's', 0), // sqs = Sri Lankan Sign Language + TRUETYPE_TAG('s', 'q', 't', 0), // sqt = Soqotri + TRUETYPE_TAG('s', 'q', 'u', 0), // squ = Squamish + TRUETYPE_TAG('s', 'r', 'a', 0), // sra = Saruga + TRUETYPE_TAG('s', 'r', 'b', 0), // srb = Sora + TRUETYPE_TAG('s', 'r', 'c', 0), // src = Logudorese Sardinian + TRUETYPE_TAG('s', 'r', 'e', 0), // sre = Sara + TRUETYPE_TAG('s', 'r', 'f', 0), // srf = Nafi + TRUETYPE_TAG('s', 'r', 'g', 0), // srg = Sulod + TRUETYPE_TAG('s', 'r', 'h', 0), // srh = Sarikoli + TRUETYPE_TAG('s', 'r', 'i', 0), // sri = Siriano + TRUETYPE_TAG('s', 'r', 'k', 0), // srk = Serudung Murut + TRUETYPE_TAG('s', 'r', 'l', 0), // srl = Isirawa + TRUETYPE_TAG('s', 'r', 'm', 0), // srm = Saramaccan + TRUETYPE_TAG('s', 'r', 'n', 0), // srn = Sranan Tongo + TRUETYPE_TAG('s', 'r', 'o', 0), // sro = Campidanese Sardinian + TRUETYPE_TAG('s', 'r', 'q', 0), // srq = Sirionó + TRUETYPE_TAG('s', 'r', 'r', 0), // srr = Serer + TRUETYPE_TAG('s', 'r', 's', 0), // srs = Sarsi + TRUETYPE_TAG('s', 'r', 't', 0), // srt = Sauri + TRUETYPE_TAG('s', 'r', 'u', 0), // sru = Suruí + TRUETYPE_TAG('s', 'r', 'v', 0), // srv = Southern Sorsoganon + TRUETYPE_TAG('s', 'r', 'w', 0), // srw = Serua + TRUETYPE_TAG('s', 'r', 'x', 0), // srx = Sirmauri + TRUETYPE_TAG('s', 'r', 'y', 0), // sry = Sera + TRUETYPE_TAG('s', 'r', 'z', 0), // srz = Shahmirzadi + TRUETYPE_TAG('s', 's', 'a', 0), // ssa = Nilo-Saharan languages + TRUETYPE_TAG('s', 's', 'b', 0), // ssb = Southern Sama + TRUETYPE_TAG('s', 's', 'c', 0), // ssc = Suba-Simbiti + TRUETYPE_TAG('s', 's', 'd', 0), // ssd = Siroi + TRUETYPE_TAG('s', 's', 'e', 0), // sse = Balangingi + TRUETYPE_TAG('s', 's', 'f', 0), // ssf = Thao + TRUETYPE_TAG('s', 's', 'g', 0), // ssg = Seimat + TRUETYPE_TAG('s', 's', 'h', 0), // ssh = Shihhi Arabic + TRUETYPE_TAG('s', 's', 'i', 0), // ssi = Sansi + TRUETYPE_TAG('s', 's', 'j', 0), // ssj = Sausi + TRUETYPE_TAG('s', 's', 'k', 0), // ssk = Sunam + TRUETYPE_TAG('s', 's', 'l', 0), // ssl = Western Sisaala + TRUETYPE_TAG('s', 's', 'm', 0), // ssm = Semnam + TRUETYPE_TAG('s', 's', 'n', 0), // ssn = Waata + TRUETYPE_TAG('s', 's', 'o', 0), // sso = Sissano + TRUETYPE_TAG('s', 's', 'p', 0), // ssp = Spanish Sign Language + TRUETYPE_TAG('s', 's', 'q', 0), // ssq = So'a + TRUETYPE_TAG('s', 's', 'r', 0), // ssr = Swiss-French Sign Language + TRUETYPE_TAG('s', 's', 's', 0), // sss = Sô + TRUETYPE_TAG('s', 's', 't', 0), // sst = Sinasina + TRUETYPE_TAG('s', 's', 'u', 0), // ssu = Susuami + TRUETYPE_TAG('s', 's', 'v', 0), // ssv = Shark Bay + TRUETYPE_TAG('s', 's', 'x', 0), // ssx = Samberigi + TRUETYPE_TAG('s', 's', 'y', 0), // ssy = Saho + TRUETYPE_TAG('s', 's', 'z', 0), // ssz = Sengseng + TRUETYPE_TAG('s', 't', 'a', 0), // sta = Settla + TRUETYPE_TAG('s', 't', 'b', 0), // stb = Northern Subanen + TRUETYPE_TAG('s', 't', 'd', 0), // std = Sentinel + TRUETYPE_TAG('s', 't', 'e', 0), // ste = Liana-Seti + TRUETYPE_TAG('s', 't', 'f', 0), // stf = Seta + TRUETYPE_TAG('s', 't', 'g', 0), // stg = Trieng + TRUETYPE_TAG('s', 't', 'h', 0), // sth = Shelta + TRUETYPE_TAG('s', 't', 'i', 0), // sti = Bulo Stieng + TRUETYPE_TAG('s', 't', 'j', 0), // stj = Matya Samo + TRUETYPE_TAG('s', 't', 'k', 0), // stk = Arammba + TRUETYPE_TAG('s', 't', 'l', 0), // stl = Stellingwerfs + TRUETYPE_TAG('s', 't', 'm', 0), // stm = Setaman + TRUETYPE_TAG('s', 't', 'n', 0), // stn = Owa + TRUETYPE_TAG('s', 't', 'o', 0), // sto = Stoney + TRUETYPE_TAG('s', 't', 'p', 0), // stp = Southeastern Tepehuan + TRUETYPE_TAG('s', 't', 'q', 0), // stq = Saterfriesisch + TRUETYPE_TAG('s', 't', 'r', 0), // str = Straits Salish + TRUETYPE_TAG('s', 't', 's', 0), // sts = Shumashti + TRUETYPE_TAG('s', 't', 't', 0), // stt = Budeh Stieng + TRUETYPE_TAG('s', 't', 'u', 0), // stu = Samtao + TRUETYPE_TAG('s', 't', 'v', 0), // stv = Silt'e + TRUETYPE_TAG('s', 't', 'w', 0), // stw = Satawalese + TRUETYPE_TAG('s', 'u', 'a', 0), // sua = Sulka + TRUETYPE_TAG('s', 'u', 'b', 0), // sub = Suku + TRUETYPE_TAG('s', 'u', 'c', 0), // suc = Western Subanon + TRUETYPE_TAG('s', 'u', 'e', 0), // sue = Suena + TRUETYPE_TAG('s', 'u', 'g', 0), // sug = Suganga + TRUETYPE_TAG('s', 'u', 'i', 0), // sui = Suki + TRUETYPE_TAG('s', 'u', 'j', 0), // suj = Shubi + TRUETYPE_TAG('s', 'u', 'k', 0), // suk = Sukuma + TRUETYPE_TAG('s', 'u', 'l', 0), // sul = Surigaonon + TRUETYPE_TAG('s', 'u', 'm', 0), // sum = Sumo-Mayangna + TRUETYPE_TAG('s', 'u', 'q', 0), // suq = Suri + TRUETYPE_TAG('s', 'u', 'r', 0), // sur = Mwaghavul + TRUETYPE_TAG('s', 'u', 's', 0), // sus = Susu + TRUETYPE_TAG('s', 'u', 't', 0), // sut = Subtiaba + TRUETYPE_TAG('s', 'u', 'v', 0), // suv = Sulung + TRUETYPE_TAG('s', 'u', 'w', 0), // suw = Sumbwa + TRUETYPE_TAG('s', 'u', 'x', 0), // sux = Sumerian + TRUETYPE_TAG('s', 'u', 'y', 0), // suy = Suyá + TRUETYPE_TAG('s', 'u', 'z', 0), // suz = Sunwar + TRUETYPE_TAG('s', 'v', 'a', 0), // sva = Svan + TRUETYPE_TAG('s', 'v', 'b', 0), // svb = Ulau-Suain + TRUETYPE_TAG('s', 'v', 'c', 0), // svc = Vincentian Creole English + TRUETYPE_TAG('s', 'v', 'e', 0), // sve = Serili + TRUETYPE_TAG('s', 'v', 'k', 0), // svk = Slovakian Sign Language + TRUETYPE_TAG('s', 'v', 'r', 0), // svr = Savara + TRUETYPE_TAG('s', 'v', 's', 0), // svs = Savosavo + TRUETYPE_TAG('s', 'v', 'x', 0), // svx = Skalvian + TRUETYPE_TAG('s', 'w', 'b', 0), // swb = Maore Comorian + TRUETYPE_TAG('s', 'w', 'c', 0), // swc = Congo Swahili + TRUETYPE_TAG('s', 'w', 'f', 0), // swf = Sere + TRUETYPE_TAG('s', 'w', 'g', 0), // swg = Swabian + TRUETYPE_TAG('s', 'w', 'h', 0), // swh = Swahili (individual language) + TRUETYPE_TAG('s', 'w', 'i', 0), // swi = Sui + TRUETYPE_TAG('s', 'w', 'j', 0), // swj = Sira + TRUETYPE_TAG('s', 'w', 'k', 0), // swk = Malawi Sena + TRUETYPE_TAG('s', 'w', 'l', 0), // swl = Swedish Sign Language + TRUETYPE_TAG('s', 'w', 'm', 0), // swm = Samosa + TRUETYPE_TAG('s', 'w', 'n', 0), // swn = Sawknah + TRUETYPE_TAG('s', 'w', 'o', 0), // swo = Shanenawa + TRUETYPE_TAG('s', 'w', 'p', 0), // swp = Suau + TRUETYPE_TAG('s', 'w', 'q', 0), // swq = Sharwa + TRUETYPE_TAG('s', 'w', 'r', 0), // swr = Saweru + TRUETYPE_TAG('s', 'w', 's', 0), // sws = Seluwasan + TRUETYPE_TAG('s', 'w', 't', 0), // swt = Sawila + TRUETYPE_TAG('s', 'w', 'u', 0), // swu = Suwawa + TRUETYPE_TAG('s', 'w', 'v', 0), // swv = Shekhawati + TRUETYPE_TAG('s', 'w', 'w', 0), // sww = Sowa + TRUETYPE_TAG('s', 'w', 'x', 0), // swx = Suruahá + TRUETYPE_TAG('s', 'w', 'y', 0), // swy = Sarua + TRUETYPE_TAG('s', 'x', 'b', 0), // sxb = Suba + TRUETYPE_TAG('s', 'x', 'c', 0), // sxc = Sicanian + TRUETYPE_TAG('s', 'x', 'e', 0), // sxe = Sighu + TRUETYPE_TAG('s', 'x', 'g', 0), // sxg = Shixing + TRUETYPE_TAG('s', 'x', 'k', 0), // sxk = Southern Kalapuya + TRUETYPE_TAG('s', 'x', 'l', 0), // sxl = Selian + TRUETYPE_TAG('s', 'x', 'm', 0), // sxm = Samre + TRUETYPE_TAG('s', 'x', 'n', 0), // sxn = Sangir + TRUETYPE_TAG('s', 'x', 'o', 0), // sxo = Sorothaptic + TRUETYPE_TAG('s', 'x', 'r', 0), // sxr = Saaroa + TRUETYPE_TAG('s', 'x', 's', 0), // sxs = Sasaru + TRUETYPE_TAG('s', 'x', 'u', 0), // sxu = Upper Saxon + TRUETYPE_TAG('s', 'x', 'w', 0), // sxw = Saxwe Gbe + TRUETYPE_TAG('s', 'y', 'a', 0), // sya = Siang + TRUETYPE_TAG('s', 'y', 'b', 0), // syb = Central Subanen + TRUETYPE_TAG('s', 'y', 'c', 0), // syc = Classical Syriac + TRUETYPE_TAG('s', 'y', 'd', 0), // syd = Samoyedic languages + TRUETYPE_TAG('s', 'y', 'i', 0), // syi = Seki + TRUETYPE_TAG('s', 'y', 'k', 0), // syk = Sukur + TRUETYPE_TAG('s', 'y', 'l', 0), // syl = Sylheti + TRUETYPE_TAG('s', 'y', 'm', 0), // sym = Maya Samo + TRUETYPE_TAG('s', 'y', 'n', 0), // syn = Senaya + TRUETYPE_TAG('s', 'y', 'o', 0), // syo = Suoy + TRUETYPE_TAG('s', 'y', 'r', 0), // syr = Syriac + TRUETYPE_TAG('s', 'y', 's', 0), // sys = Sinyar + TRUETYPE_TAG('s', 'y', 'w', 0), // syw = Kagate + TRUETYPE_TAG('s', 'y', 'y', 0), // syy = Al-Sayyid Bedouin Sign Language + TRUETYPE_TAG('s', 'z', 'a', 0), // sza = Semelai + TRUETYPE_TAG('s', 'z', 'b', 0), // szb = Ngalum + TRUETYPE_TAG('s', 'z', 'c', 0), // szc = Semaq Beri + TRUETYPE_TAG('s', 'z', 'd', 0), // szd = Seru + TRUETYPE_TAG('s', 'z', 'e', 0), // sze = Seze + TRUETYPE_TAG('s', 'z', 'g', 0), // szg = Sengele + TRUETYPE_TAG('s', 'z', 'l', 0), // szl = Silesian + TRUETYPE_TAG('s', 'z', 'n', 0), // szn = Sula + TRUETYPE_TAG('s', 'z', 'p', 0), // szp = Suabo + TRUETYPE_TAG('s', 'z', 'v', 0), // szv = Isu (Fako Division) + TRUETYPE_TAG('s', 'z', 'w', 0), // szw = Sawai + TRUETYPE_TAG('t', 'a', 'a', 0), // taa = Lower Tanana + TRUETYPE_TAG('t', 'a', 'b', 0), // tab = Tabassaran + TRUETYPE_TAG('t', 'a', 'c', 0), // tac = Lowland Tarahumara + TRUETYPE_TAG('t', 'a', 'd', 0), // tad = Tause + TRUETYPE_TAG('t', 'a', 'e', 0), // tae = Tariana + TRUETYPE_TAG('t', 'a', 'f', 0), // taf = Tapirapé + TRUETYPE_TAG('t', 'a', 'g', 0), // tag = Tagoi + TRUETYPE_TAG('t', 'a', 'i', 0), // tai = Tai languages + TRUETYPE_TAG('t', 'a', 'j', 0), // taj = Eastern Tamang + TRUETYPE_TAG('t', 'a', 'k', 0), // tak = Tala + TRUETYPE_TAG('t', 'a', 'l', 0), // tal = Tal + TRUETYPE_TAG('t', 'a', 'n', 0), // tan = Tangale + TRUETYPE_TAG('t', 'a', 'o', 0), // tao = Yami + TRUETYPE_TAG('t', 'a', 'p', 0), // tap = Taabwa + TRUETYPE_TAG('t', 'a', 'q', 0), // taq = Tamasheq + TRUETYPE_TAG('t', 'a', 'r', 0), // tar = Central Tarahumara + TRUETYPE_TAG('t', 'a', 's', 0), // tas = Tay Boi + TRUETYPE_TAG('t', 'a', 'u', 0), // tau = Upper Tanana + TRUETYPE_TAG('t', 'a', 'v', 0), // tav = Tatuyo + TRUETYPE_TAG('t', 'a', 'w', 0), // taw = Tai + TRUETYPE_TAG('t', 'a', 'x', 0), // tax = Tamki + TRUETYPE_TAG('t', 'a', 'y', 0), // tay = Atayal + TRUETYPE_TAG('t', 'a', 'z', 0), // taz = Tocho + TRUETYPE_TAG('t', 'b', 'a', 0), // tba = Aikanã + TRUETYPE_TAG('t', 'b', 'b', 0), // tbb = Tapeba + TRUETYPE_TAG('t', 'b', 'c', 0), // tbc = Takia + TRUETYPE_TAG('t', 'b', 'd', 0), // tbd = Kaki Ae + TRUETYPE_TAG('t', 'b', 'e', 0), // tbe = Tanimbili + TRUETYPE_TAG('t', 'b', 'f', 0), // tbf = Mandara + TRUETYPE_TAG('t', 'b', 'g', 0), // tbg = North Tairora + TRUETYPE_TAG('t', 'b', 'h', 0), // tbh = Thurawal + TRUETYPE_TAG('t', 'b', 'i', 0), // tbi = Gaam + TRUETYPE_TAG('t', 'b', 'j', 0), // tbj = Tiang + TRUETYPE_TAG('t', 'b', 'k', 0), // tbk = Calamian Tagbanwa + TRUETYPE_TAG('t', 'b', 'l', 0), // tbl = Tboli + TRUETYPE_TAG('t', 'b', 'm', 0), // tbm = Tagbu + TRUETYPE_TAG('t', 'b', 'n', 0), // tbn = Barro Negro Tunebo + TRUETYPE_TAG('t', 'b', 'o', 0), // tbo = Tawala + TRUETYPE_TAG('t', 'b', 'p', 0), // tbp = Taworta + TRUETYPE_TAG('t', 'b', 'q', 0), // tbq = Tibeto-Burman languages + TRUETYPE_TAG('t', 'b', 'r', 0), // tbr = Tumtum + TRUETYPE_TAG('t', 'b', 's', 0), // tbs = Tanguat + TRUETYPE_TAG('t', 'b', 't', 0), // tbt = Tembo (Kitembo) + TRUETYPE_TAG('t', 'b', 'u', 0), // tbu = Tubar + TRUETYPE_TAG('t', 'b', 'v', 0), // tbv = Tobo + TRUETYPE_TAG('t', 'b', 'w', 0), // tbw = Tagbanwa + TRUETYPE_TAG('t', 'b', 'x', 0), // tbx = Kapin + TRUETYPE_TAG('t', 'b', 'y', 0), // tby = Tabaru + TRUETYPE_TAG('t', 'b', 'z', 0), // tbz = Ditammari + TRUETYPE_TAG('t', 'c', 'a', 0), // tca = Ticuna + TRUETYPE_TAG('t', 'c', 'b', 0), // tcb = Tanacross + TRUETYPE_TAG('t', 'c', 'c', 0), // tcc = Datooga + TRUETYPE_TAG('t', 'c', 'd', 0), // tcd = Tafi + TRUETYPE_TAG('t', 'c', 'e', 0), // tce = Southern Tutchone + TRUETYPE_TAG('t', 'c', 'f', 0), // tcf = Malinaltepec Me'phaa + TRUETYPE_TAG('t', 'c', 'g', 0), // tcg = Tamagario + TRUETYPE_TAG('t', 'c', 'h', 0), // tch = Turks And Caicos Creole English + TRUETYPE_TAG('t', 'c', 'i', 0), // tci = Wára + TRUETYPE_TAG('t', 'c', 'k', 0), // tck = Tchitchege + TRUETYPE_TAG('t', 'c', 'l', 0), // tcl = Taman (Myanmar) + TRUETYPE_TAG('t', 'c', 'm', 0), // tcm = Tanahmerah + TRUETYPE_TAG('t', 'c', 'n', 0), // tcn = Tichurong + TRUETYPE_TAG('t', 'c', 'o', 0), // tco = Taungyo + TRUETYPE_TAG('t', 'c', 'p', 0), // tcp = Tawr Chin + TRUETYPE_TAG('t', 'c', 'q', 0), // tcq = Kaiy + TRUETYPE_TAG('t', 'c', 's', 0), // tcs = Torres Strait Creole + TRUETYPE_TAG('t', 'c', 't', 0), // tct = T'en + TRUETYPE_TAG('t', 'c', 'u', 0), // tcu = Southeastern Tarahumara + TRUETYPE_TAG('t', 'c', 'w', 0), // tcw = Tecpatlán Totonac + TRUETYPE_TAG('t', 'c', 'x', 0), // tcx = Toda + TRUETYPE_TAG('t', 'c', 'y', 0), // tcy = Tulu + TRUETYPE_TAG('t', 'c', 'z', 0), // tcz = Thado Chin + TRUETYPE_TAG('t', 'd', 'a', 0), // tda = Tagdal + TRUETYPE_TAG('t', 'd', 'b', 0), // tdb = Panchpargania + TRUETYPE_TAG('t', 'd', 'c', 0), // tdc = Emberá-Tadó + TRUETYPE_TAG('t', 'd', 'd', 0), // tdd = Tai Nüa + TRUETYPE_TAG('t', 'd', 'e', 0), // tde = Tiranige Diga Dogon + TRUETYPE_TAG('t', 'd', 'f', 0), // tdf = Talieng + TRUETYPE_TAG('t', 'd', 'g', 0), // tdg = Western Tamang + TRUETYPE_TAG('t', 'd', 'h', 0), // tdh = Thulung + TRUETYPE_TAG('t', 'd', 'i', 0), // tdi = Tomadino + TRUETYPE_TAG('t', 'd', 'j', 0), // tdj = Tajio + TRUETYPE_TAG('t', 'd', 'k', 0), // tdk = Tambas + TRUETYPE_TAG('t', 'd', 'l', 0), // tdl = Sur + TRUETYPE_TAG('t', 'd', 'n', 0), // tdn = Tondano + TRUETYPE_TAG('t', 'd', 'o', 0), // tdo = Teme + TRUETYPE_TAG('t', 'd', 'q', 0), // tdq = Tita + TRUETYPE_TAG('t', 'd', 'r', 0), // tdr = Todrah + TRUETYPE_TAG('t', 'd', 's', 0), // tds = Doutai + TRUETYPE_TAG('t', 'd', 't', 0), // tdt = Tetun Dili + TRUETYPE_TAG('t', 'd', 'u', 0), // tdu = Tempasuk Dusun + TRUETYPE_TAG('t', 'd', 'v', 0), // tdv = Toro + TRUETYPE_TAG('t', 'd', 'x', 0), // tdx = Tandroy-Mahafaly Malagasy + TRUETYPE_TAG('t', 'd', 'y', 0), // tdy = Tadyawan + TRUETYPE_TAG('t', 'e', 'a', 0), // tea = Temiar + TRUETYPE_TAG('t', 'e', 'b', 0), // teb = Tetete + TRUETYPE_TAG('t', 'e', 'c', 0), // tec = Terik + TRUETYPE_TAG('t', 'e', 'd', 0), // ted = Tepo Krumen + TRUETYPE_TAG('t', 'e', 'e', 0), // tee = Huehuetla Tepehua + TRUETYPE_TAG('t', 'e', 'f', 0), // tef = Teressa + TRUETYPE_TAG('t', 'e', 'g', 0), // teg = Teke-Tege + TRUETYPE_TAG('t', 'e', 'h', 0), // teh = Tehuelche + TRUETYPE_TAG('t', 'e', 'i', 0), // tei = Torricelli + TRUETYPE_TAG('t', 'e', 'k', 0), // tek = Ibali Teke + TRUETYPE_TAG('t', 'e', 'm', 0), // tem = Timne + TRUETYPE_TAG('t', 'e', 'n', 0), // ten = Tama (Colombia) + TRUETYPE_TAG('t', 'e', 'o', 0), // teo = Teso + TRUETYPE_TAG('t', 'e', 'p', 0), // tep = Tepecano + TRUETYPE_TAG('t', 'e', 'q', 0), // teq = Temein + TRUETYPE_TAG('t', 'e', 'r', 0), // ter = Tereno + TRUETYPE_TAG('t', 'e', 's', 0), // tes = Tengger + TRUETYPE_TAG('t', 'e', 't', 0), // tet = Tetum + TRUETYPE_TAG('t', 'e', 'u', 0), // teu = Soo + TRUETYPE_TAG('t', 'e', 'v', 0), // tev = Teor + TRUETYPE_TAG('t', 'e', 'w', 0), // tew = Tewa (USA) + TRUETYPE_TAG('t', 'e', 'x', 0), // tex = Tennet + TRUETYPE_TAG('t', 'e', 'y', 0), // tey = Tulishi + TRUETYPE_TAG('t', 'f', 'i', 0), // tfi = Tofin Gbe + TRUETYPE_TAG('t', 'f', 'n', 0), // tfn = Tanaina + TRUETYPE_TAG('t', 'f', 'o', 0), // tfo = Tefaro + TRUETYPE_TAG('t', 'f', 'r', 0), // tfr = Teribe + TRUETYPE_TAG('t', 'f', 't', 0), // tft = Ternate + TRUETYPE_TAG('t', 'g', 'a', 0), // tga = Sagalla + TRUETYPE_TAG('t', 'g', 'b', 0), // tgb = Tobilung + TRUETYPE_TAG('t', 'g', 'c', 0), // tgc = Tigak + TRUETYPE_TAG('t', 'g', 'd', 0), // tgd = Ciwogai + TRUETYPE_TAG('t', 'g', 'e', 0), // tge = Eastern Gorkha Tamang + TRUETYPE_TAG('t', 'g', 'f', 0), // tgf = Chalikha + TRUETYPE_TAG('t', 'g', 'g', 0), // tgg = Tangga + TRUETYPE_TAG('t', 'g', 'h', 0), // tgh = Tobagonian Creole English + TRUETYPE_TAG('t', 'g', 'i', 0), // tgi = Lawunuia + TRUETYPE_TAG('t', 'g', 'n', 0), // tgn = Tandaganon + TRUETYPE_TAG('t', 'g', 'o', 0), // tgo = Sudest + TRUETYPE_TAG('t', 'g', 'p', 0), // tgp = Tangoa + TRUETYPE_TAG('t', 'g', 'q', 0), // tgq = Tring + TRUETYPE_TAG('t', 'g', 'r', 0), // tgr = Tareng + TRUETYPE_TAG('t', 'g', 's', 0), // tgs = Nume + TRUETYPE_TAG('t', 'g', 't', 0), // tgt = Central Tagbanwa + TRUETYPE_TAG('t', 'g', 'u', 0), // tgu = Tanggu + TRUETYPE_TAG('t', 'g', 'v', 0), // tgv = Tingui-Boto + TRUETYPE_TAG('t', 'g', 'w', 0), // tgw = Tagwana Senoufo + TRUETYPE_TAG('t', 'g', 'x', 0), // tgx = Tagish + TRUETYPE_TAG('t', 'g', 'y', 0), // tgy = Togoyo + TRUETYPE_TAG('t', 'h', 'c', 0), // thc = Tai Hang Tong + TRUETYPE_TAG('t', 'h', 'd', 0), // thd = Thayore + TRUETYPE_TAG('t', 'h', 'e', 0), // the = Chitwania Tharu + TRUETYPE_TAG('t', 'h', 'f', 0), // thf = Thangmi + TRUETYPE_TAG('t', 'h', 'h', 0), // thh = Northern Tarahumara + TRUETYPE_TAG('t', 'h', 'i', 0), // thi = Tai Long + TRUETYPE_TAG('t', 'h', 'k', 0), // thk = Tharaka + TRUETYPE_TAG('t', 'h', 'l', 0), // thl = Dangaura Tharu + TRUETYPE_TAG('t', 'h', 'm', 0), // thm = Aheu + TRUETYPE_TAG('t', 'h', 'n', 0), // thn = Thachanadan + TRUETYPE_TAG('t', 'h', 'p', 0), // thp = Thompson + TRUETYPE_TAG('t', 'h', 'q', 0), // thq = Kochila Tharu + TRUETYPE_TAG('t', 'h', 'r', 0), // thr = Rana Tharu + TRUETYPE_TAG('t', 'h', 's', 0), // ths = Thakali + TRUETYPE_TAG('t', 'h', 't', 0), // tht = Tahltan + TRUETYPE_TAG('t', 'h', 'u', 0), // thu = Thuri + TRUETYPE_TAG('t', 'h', 'v', 0), // thv = Tahaggart Tamahaq + TRUETYPE_TAG('t', 'h', 'w', 0), // thw = Thudam + TRUETYPE_TAG('t', 'h', 'x', 0), // thx = The + TRUETYPE_TAG('t', 'h', 'y', 0), // thy = Tha + TRUETYPE_TAG('t', 'h', 'z', 0), // thz = Tayart Tamajeq + TRUETYPE_TAG('t', 'i', 'a', 0), // tia = Tidikelt Tamazight + TRUETYPE_TAG('t', 'i', 'c', 0), // tic = Tira + TRUETYPE_TAG('t', 'i', 'd', 0), // tid = Tidong + TRUETYPE_TAG('t', 'i', 'e', 0), // tie = Tingal + TRUETYPE_TAG('t', 'i', 'f', 0), // tif = Tifal + TRUETYPE_TAG('t', 'i', 'g', 0), // tig = Tigre + TRUETYPE_TAG('t', 'i', 'h', 0), // tih = Timugon Murut + TRUETYPE_TAG('t', 'i', 'i', 0), // tii = Tiene + TRUETYPE_TAG('t', 'i', 'j', 0), // tij = Tilung + TRUETYPE_TAG('t', 'i', 'k', 0), // tik = Tikar + TRUETYPE_TAG('t', 'i', 'l', 0), // til = Tillamook + TRUETYPE_TAG('t', 'i', 'm', 0), // tim = Timbe + TRUETYPE_TAG('t', 'i', 'n', 0), // tin = Tindi + TRUETYPE_TAG('t', 'i', 'o', 0), // tio = Teop + TRUETYPE_TAG('t', 'i', 'p', 0), // tip = Trimuris + TRUETYPE_TAG('t', 'i', 'q', 0), // tiq = Tiéfo + TRUETYPE_TAG('t', 'i', 's', 0), // tis = Masadiit Itneg + TRUETYPE_TAG('t', 'i', 't', 0), // tit = Tinigua + TRUETYPE_TAG('t', 'i', 'u', 0), // tiu = Adasen + TRUETYPE_TAG('t', 'i', 'v', 0), // tiv = Tiv + TRUETYPE_TAG('t', 'i', 'w', 0), // tiw = Tiwi + TRUETYPE_TAG('t', 'i', 'x', 0), // tix = Southern Tiwa + TRUETYPE_TAG('t', 'i', 'y', 0), // tiy = Tiruray + TRUETYPE_TAG('t', 'i', 'z', 0), // tiz = Tai Hongjin + TRUETYPE_TAG('t', 'j', 'a', 0), // tja = Tajuasohn + TRUETYPE_TAG('t', 'j', 'g', 0), // tjg = Tunjung + TRUETYPE_TAG('t', 'j', 'i', 0), // tji = Northern Tujia + TRUETYPE_TAG('t', 'j', 'm', 0), // tjm = Timucua + TRUETYPE_TAG('t', 'j', 'n', 0), // tjn = Tonjon + TRUETYPE_TAG('t', 'j', 'o', 0), // tjo = Temacine Tamazight + TRUETYPE_TAG('t', 'j', 's', 0), // tjs = Southern Tujia + TRUETYPE_TAG('t', 'j', 'u', 0), // tju = Tjurruru + TRUETYPE_TAG('t', 'k', 'a', 0), // tka = Truká + TRUETYPE_TAG('t', 'k', 'b', 0), // tkb = Buksa + TRUETYPE_TAG('t', 'k', 'd', 0), // tkd = Tukudede + TRUETYPE_TAG('t', 'k', 'e', 0), // tke = Takwane + TRUETYPE_TAG('t', 'k', 'f', 0), // tkf = Tukumanféd + TRUETYPE_TAG('t', 'k', 'g', 0), // tkg = Tesaka Malagasy + TRUETYPE_TAG('t', 'k', 'k', 0), // tkk = Takpa + TRUETYPE_TAG('t', 'k', 'l', 0), // tkl = Tokelau + TRUETYPE_TAG('t', 'k', 'm', 0), // tkm = Takelma + TRUETYPE_TAG('t', 'k', 'n', 0), // tkn = Toku-No-Shima + TRUETYPE_TAG('t', 'k', 'p', 0), // tkp = Tikopia + TRUETYPE_TAG('t', 'k', 'q', 0), // tkq = Tee + TRUETYPE_TAG('t', 'k', 'r', 0), // tkr = Tsakhur + TRUETYPE_TAG('t', 'k', 's', 0), // tks = Takestani + TRUETYPE_TAG('t', 'k', 't', 0), // tkt = Kathoriya Tharu + TRUETYPE_TAG('t', 'k', 'u', 0), // tku = Upper Necaxa Totonac + TRUETYPE_TAG('t', 'k', 'w', 0), // tkw = Teanu + TRUETYPE_TAG('t', 'k', 'x', 0), // tkx = Tangko + TRUETYPE_TAG('t', 'k', 'z', 0), // tkz = Takua + TRUETYPE_TAG('t', 'l', 'a', 0), // tla = Southwestern Tepehuan + TRUETYPE_TAG('t', 'l', 'b', 0), // tlb = Tobelo + TRUETYPE_TAG('t', 'l', 'c', 0), // tlc = Yecuatla Totonac + TRUETYPE_TAG('t', 'l', 'd', 0), // tld = Talaud + TRUETYPE_TAG('t', 'l', 'f', 0), // tlf = Telefol + TRUETYPE_TAG('t', 'l', 'g', 0), // tlg = Tofanma + TRUETYPE_TAG('t', 'l', 'h', 0), // tlh = Klingon + TRUETYPE_TAG('t', 'l', 'i', 0), // tli = Tlingit + TRUETYPE_TAG('t', 'l', 'j', 0), // tlj = Talinga-Bwisi + TRUETYPE_TAG('t', 'l', 'k', 0), // tlk = Taloki + TRUETYPE_TAG('t', 'l', 'l', 0), // tll = Tetela + TRUETYPE_TAG('t', 'l', 'm', 0), // tlm = Tolomako + TRUETYPE_TAG('t', 'l', 'n', 0), // tln = Talondo' + TRUETYPE_TAG('t', 'l', 'o', 0), // tlo = Talodi + TRUETYPE_TAG('t', 'l', 'p', 0), // tlp = Filomena Mata-Coahuitlán Totonac + TRUETYPE_TAG('t', 'l', 'q', 0), // tlq = Tai Loi + TRUETYPE_TAG('t', 'l', 'r', 0), // tlr = Talise + TRUETYPE_TAG('t', 'l', 's', 0), // tls = Tambotalo + TRUETYPE_TAG('t', 'l', 't', 0), // tlt = Teluti + TRUETYPE_TAG('t', 'l', 'u', 0), // tlu = Tulehu + TRUETYPE_TAG('t', 'l', 'v', 0), // tlv = Taliabu + TRUETYPE_TAG('t', 'l', 'w', 0), // tlw = South Wemale + TRUETYPE_TAG('t', 'l', 'x', 0), // tlx = Khehek + TRUETYPE_TAG('t', 'l', 'y', 0), // tly = Talysh + TRUETYPE_TAG('t', 'm', 'a', 0), // tma = Tama (Chad) + TRUETYPE_TAG('t', 'm', 'b', 0), // tmb = Katbol + TRUETYPE_TAG('t', 'm', 'c', 0), // tmc = Tumak + TRUETYPE_TAG('t', 'm', 'd', 0), // tmd = Haruai + TRUETYPE_TAG('t', 'm', 'e', 0), // tme = Tremembé + TRUETYPE_TAG('t', 'm', 'f', 0), // tmf = Toba-Maskoy + TRUETYPE_TAG('t', 'm', 'g', 0), // tmg = Ternateño + TRUETYPE_TAG('t', 'm', 'h', 0), // tmh = Tamashek + TRUETYPE_TAG('t', 'm', 'i', 0), // tmi = Tutuba + TRUETYPE_TAG('t', 'm', 'j', 0), // tmj = Samarokena + TRUETYPE_TAG('t', 'm', 'k', 0), // tmk = Northwestern Tamang + TRUETYPE_TAG('t', 'm', 'l', 0), // tml = Tamnim Citak + TRUETYPE_TAG('t', 'm', 'm', 0), // tmm = Tai Thanh + TRUETYPE_TAG('t', 'm', 'n', 0), // tmn = Taman (Indonesia) + TRUETYPE_TAG('t', 'm', 'o', 0), // tmo = Temoq + TRUETYPE_TAG('t', 'm', 'p', 0), // tmp = Tai Mène + TRUETYPE_TAG('t', 'm', 'q', 0), // tmq = Tumleo + TRUETYPE_TAG('t', 'm', 'r', + 0), // tmr = Jewish Babylonian Aramaic (ca. 200-1200 CE) + TRUETYPE_TAG('t', 'm', 's', 0), // tms = Tima + TRUETYPE_TAG('t', 'm', 't', 0), // tmt = Tasmate + TRUETYPE_TAG('t', 'm', 'u', 0), // tmu = Iau + TRUETYPE_TAG('t', 'm', 'v', 0), // tmv = Tembo (Motembo) + TRUETYPE_TAG('t', 'm', 'w', 0), // tmw = Temuan + TRUETYPE_TAG('t', 'm', 'y', 0), // tmy = Tami + TRUETYPE_TAG('t', 'm', 'z', 0), // tmz = Tamanaku + TRUETYPE_TAG('t', 'n', 'a', 0), // tna = Tacana + TRUETYPE_TAG('t', 'n', 'b', 0), // tnb = Western Tunebo + TRUETYPE_TAG('t', 'n', 'c', 0), // tnc = Tanimuca-Retuarã + TRUETYPE_TAG('t', 'n', 'd', 0), // tnd = Angosturas Tunebo + TRUETYPE_TAG('t', 'n', 'e', 0), // tne = Tinoc Kallahan + TRUETYPE_TAG('t', 'n', 'f', 0), // tnf = Tangshewi + TRUETYPE_TAG('t', 'n', 'g', 0), // tng = Tobanga + TRUETYPE_TAG('t', 'n', 'h', 0), // tnh = Maiani + TRUETYPE_TAG('t', 'n', 'i', 0), // tni = Tandia + TRUETYPE_TAG('t', 'n', 'k', 0), // tnk = Kwamera + TRUETYPE_TAG('t', 'n', 'l', 0), // tnl = Lenakel + TRUETYPE_TAG('t', 'n', 'm', 0), // tnm = Tabla + TRUETYPE_TAG('t', 'n', 'n', 0), // tnn = North Tanna + TRUETYPE_TAG('t', 'n', 'o', 0), // tno = Toromono + TRUETYPE_TAG('t', 'n', 'p', 0), // tnp = Whitesands + TRUETYPE_TAG('t', 'n', 'q', 0), // tnq = Taino + TRUETYPE_TAG('t', 'n', 'r', 0), // tnr = Bedik + TRUETYPE_TAG('t', 'n', 's', 0), // tns = Tenis + TRUETYPE_TAG('t', 'n', 't', 0), // tnt = Tontemboan + TRUETYPE_TAG('t', 'n', 'u', 0), // tnu = Tay Khang + TRUETYPE_TAG('t', 'n', 'v', 0), // tnv = Tangchangya + TRUETYPE_TAG('t', 'n', 'w', 0), // tnw = Tonsawang + TRUETYPE_TAG('t', 'n', 'x', 0), // tnx = Tanema + TRUETYPE_TAG('t', 'n', 'y', 0), // tny = Tongwe + TRUETYPE_TAG('t', 'n', 'z', 0), // tnz = Tonga (Thailand) + TRUETYPE_TAG('t', 'o', 'b', 0), // tob = Toba + TRUETYPE_TAG('t', 'o', 'c', 0), // toc = Coyutla Totonac + TRUETYPE_TAG('t', 'o', 'd', 0), // tod = Toma + TRUETYPE_TAG('t', 'o', 'e', 0), // toe = Tomedes + TRUETYPE_TAG('t', 'o', 'f', 0), // tof = Gizrra + TRUETYPE_TAG('t', 'o', 'g', 0), // tog = Tonga (Nyasa) + TRUETYPE_TAG('t', 'o', 'h', 0), // toh = Gitonga + TRUETYPE_TAG('t', 'o', 'i', 0), // toi = Tonga (Zambia) + TRUETYPE_TAG('t', 'o', 'j', 0), // toj = Tojolabal + TRUETYPE_TAG('t', 'o', 'l', 0), // tol = Tolowa + TRUETYPE_TAG('t', 'o', 'm', 0), // tom = Tombulu + TRUETYPE_TAG('t', 'o', 'o', 0), // too = Xicotepec De Juárez Totonac + TRUETYPE_TAG('t', 'o', 'p', 0), // top = Papantla Totonac + TRUETYPE_TAG('t', 'o', 'q', 0), // toq = Toposa + TRUETYPE_TAG('t', 'o', 'r', 0), // tor = Togbo-Vara Banda + TRUETYPE_TAG('t', 'o', 's', 0), // tos = Highland Totonac + TRUETYPE_TAG('t', 'o', 'u', 0), // tou = Tho + TRUETYPE_TAG('t', 'o', 'v', 0), // tov = Upper Taromi + TRUETYPE_TAG('t', 'o', 'w', 0), // tow = Jemez + TRUETYPE_TAG('t', 'o', 'x', 0), // tox = Tobian + TRUETYPE_TAG('t', 'o', 'y', 0), // toy = Topoiyo + TRUETYPE_TAG('t', 'o', 'z', 0), // toz = To + TRUETYPE_TAG('t', 'p', 'a', 0), // tpa = Taupota + TRUETYPE_TAG('t', 'p', 'c', 0), // tpc = Azoyú Me'phaa + TRUETYPE_TAG('t', 'p', 'e', 0), // tpe = Tippera + TRUETYPE_TAG('t', 'p', 'f', 0), // tpf = Tarpia + TRUETYPE_TAG('t', 'p', 'g', 0), // tpg = Kula + TRUETYPE_TAG('t', 'p', 'i', 0), // tpi = Tok Pisin + TRUETYPE_TAG('t', 'p', 'j', 0), // tpj = Tapieté + TRUETYPE_TAG('t', 'p', 'k', 0), // tpk = Tupinikin + TRUETYPE_TAG('t', 'p', 'l', 0), // tpl = Tlacoapa Me'phaa + TRUETYPE_TAG('t', 'p', 'm', 0), // tpm = Tampulma + TRUETYPE_TAG('t', 'p', 'n', 0), // tpn = Tupinambá + TRUETYPE_TAG('t', 'p', 'o', 0), // tpo = Tai Pao + TRUETYPE_TAG('t', 'p', 'p', 0), // tpp = Pisaflores Tepehua + TRUETYPE_TAG('t', 'p', 'q', 0), // tpq = Tukpa + TRUETYPE_TAG('t', 'p', 'r', 0), // tpr = Tuparí + TRUETYPE_TAG('t', 'p', 't', 0), // tpt = Tlachichilco Tepehua + TRUETYPE_TAG('t', 'p', 'u', 0), // tpu = Tampuan + TRUETYPE_TAG('t', 'p', 'v', 0), // tpv = Tanapag + TRUETYPE_TAG('t', 'p', 'w', 0), // tpw = Tupí + TRUETYPE_TAG('t', 'p', 'x', 0), // tpx = Acatepec Me'phaa + TRUETYPE_TAG('t', 'p', 'y', 0), // tpy = Trumai + TRUETYPE_TAG('t', 'p', 'z', 0), // tpz = Tinputz + TRUETYPE_TAG('t', 'q', 'b', 0), // tqb = Tembé + TRUETYPE_TAG('t', 'q', 'l', 0), // tql = Lehali + TRUETYPE_TAG('t', 'q', 'm', 0), // tqm = Turumsa + TRUETYPE_TAG('t', 'q', 'n', 0), // tqn = Tenino + TRUETYPE_TAG('t', 'q', 'o', 0), // tqo = Toaripi + TRUETYPE_TAG('t', 'q', 'p', 0), // tqp = Tomoip + TRUETYPE_TAG('t', 'q', 'q', 0), // tqq = Tunni + TRUETYPE_TAG('t', 'q', 'r', 0), // tqr = Torona + TRUETYPE_TAG('t', 'q', 't', 0), // tqt = Western Totonac + TRUETYPE_TAG('t', 'q', 'u', 0), // tqu = Touo + TRUETYPE_TAG('t', 'q', 'w', 0), // tqw = Tonkawa + TRUETYPE_TAG('t', 'r', 'a', 0), // tra = Tirahi + TRUETYPE_TAG('t', 'r', 'b', 0), // trb = Terebu + TRUETYPE_TAG('t', 'r', 'c', 0), // trc = Copala Triqui + TRUETYPE_TAG('t', 'r', 'd', 0), // trd = Turi + TRUETYPE_TAG('t', 'r', 'e', 0), // tre = East Tarangan + TRUETYPE_TAG('t', 'r', 'f', 0), // trf = Trinidadian Creole English + TRUETYPE_TAG('t', 'r', 'g', 0), // trg = Lishán Didán + TRUETYPE_TAG('t', 'r', 'h', 0), // trh = Turaka + TRUETYPE_TAG('t', 'r', 'i', 0), // tri = Trió + TRUETYPE_TAG('t', 'r', 'j', 0), // trj = Toram + TRUETYPE_TAG('t', 'r', 'k', 0), // trk = Turkic languages + TRUETYPE_TAG('t', 'r', 'l', 0), // trl = Traveller Scottish + TRUETYPE_TAG('t', 'r', 'm', 0), // trm = Tregami + TRUETYPE_TAG('t', 'r', 'n', 0), // trn = Trinitario + TRUETYPE_TAG('t', 'r', 'o', 0), // tro = Tarao Naga + TRUETYPE_TAG('t', 'r', 'p', 0), // trp = Kok Borok + TRUETYPE_TAG('t', 'r', 'q', 0), // trq = San Martín Itunyoso Triqui + TRUETYPE_TAG('t', 'r', 'r', 0), // trr = Taushiro + TRUETYPE_TAG('t', 'r', 's', 0), // trs = Chicahuaxtla Triqui + TRUETYPE_TAG('t', 'r', 't', 0), // trt = Tunggare + TRUETYPE_TAG('t', 'r', 'u', 0), // tru = Turoyo + TRUETYPE_TAG('t', 'r', 'v', 0), // trv = Taroko + TRUETYPE_TAG('t', 'r', 'w', 0), // trw = Torwali + TRUETYPE_TAG('t', 'r', 'x', 0), // trx = Tringgus-Sembaan Bidayuh + TRUETYPE_TAG('t', 'r', 'y', 0), // try = Turung + TRUETYPE_TAG('t', 'r', 'z', 0), // trz = Torá + TRUETYPE_TAG('t', 's', 'a', 0), // tsa = Tsaangi + TRUETYPE_TAG('t', 's', 'b', 0), // tsb = Tsamai + TRUETYPE_TAG('t', 's', 'c', 0), // tsc = Tswa + TRUETYPE_TAG('t', 's', 'd', 0), // tsd = Tsakonian + TRUETYPE_TAG('t', 's', 'e', 0), // tse = Tunisian Sign Language + TRUETYPE_TAG('t', 's', 'f', 0), // tsf = Southwestern Tamang + TRUETYPE_TAG('t', 's', 'g', 0), // tsg = Tausug + TRUETYPE_TAG('t', 's', 'h', 0), // tsh = Tsuvan + TRUETYPE_TAG('t', 's', 'i', 0), // tsi = Tsimshian + TRUETYPE_TAG('t', 's', 'j', 0), // tsj = Tshangla + TRUETYPE_TAG('t', 's', 'k', 0), // tsk = Tseku + TRUETYPE_TAG('t', 's', 'l', 0), // tsl = Ts'ün-Lao + TRUETYPE_TAG('t', 's', 'm', 0), // tsm = Turkish Sign Language + TRUETYPE_TAG('t', 's', 'p', 0), // tsp = Northern Toussian + TRUETYPE_TAG('t', 's', 'q', 0), // tsq = Thai Sign Language + TRUETYPE_TAG('t', 's', 'r', 0), // tsr = Akei + TRUETYPE_TAG('t', 's', 's', 0), // tss = Taiwan Sign Language + TRUETYPE_TAG('t', 's', 't', 0), // tst = Tondi Songway Kiini + TRUETYPE_TAG('t', 's', 'u', 0), // tsu = Tsou + TRUETYPE_TAG('t', 's', 'v', 0), // tsv = Tsogo + TRUETYPE_TAG('t', 's', 'w', 0), // tsw = Tsishingini + TRUETYPE_TAG('t', 's', 'x', 0), // tsx = Mubami + TRUETYPE_TAG('t', 's', 'y', 0), // tsy = Tebul Sign Language + TRUETYPE_TAG('t', 's', 'z', 0), // tsz = Purepecha + TRUETYPE_TAG('t', 't', 'a', 0), // tta = Tutelo + TRUETYPE_TAG('t', 't', 'b', 0), // ttb = Gaa + TRUETYPE_TAG('t', 't', 'c', 0), // ttc = Tektiteko + TRUETYPE_TAG('t', 't', 'd', 0), // ttd = Tauade + TRUETYPE_TAG('t', 't', 'e', 0), // tte = Bwanabwana + TRUETYPE_TAG('t', 't', 'f', 0), // ttf = Tuotomb + TRUETYPE_TAG('t', 't', 'g', 0), // ttg = Tutong + TRUETYPE_TAG('t', 't', 'h', 0), // tth = Upper Ta'oih + TRUETYPE_TAG('t', 't', 'i', 0), // tti = Tobati + TRUETYPE_TAG('t', 't', 'j', 0), // ttj = Tooro + TRUETYPE_TAG('t', 't', 'k', 0), // ttk = Totoro + TRUETYPE_TAG('t', 't', 'l', 0), // ttl = Totela + TRUETYPE_TAG('t', 't', 'm', 0), // ttm = Northern Tutchone + TRUETYPE_TAG('t', 't', 'n', 0), // ttn = Towei + TRUETYPE_TAG('t', 't', 'o', 0), // tto = Lower Ta'oih + TRUETYPE_TAG('t', 't', 'p', 0), // ttp = Tombelala + TRUETYPE_TAG('t', 't', 'q', 0), // ttq = Tawallammat Tamajaq + TRUETYPE_TAG('t', 't', 'r', 0), // ttr = Tera + TRUETYPE_TAG('t', 't', 's', 0), // tts = Northeastern Thai + TRUETYPE_TAG('t', 't', 't', 0), // ttt = Muslim Tat + TRUETYPE_TAG('t', 't', 'u', 0), // ttu = Torau + TRUETYPE_TAG('t', 't', 'v', 0), // ttv = Titan + TRUETYPE_TAG('t', 't', 'w', 0), // ttw = Long Wat + TRUETYPE_TAG('t', 't', 'y', 0), // tty = Sikaritai + TRUETYPE_TAG('t', 't', 'z', 0), // ttz = Tsum + TRUETYPE_TAG('t', 'u', 'a', 0), // tua = Wiarumus + TRUETYPE_TAG('t', 'u', 'b', 0), // tub = Tübatulabal + TRUETYPE_TAG('t', 'u', 'c', 0), // tuc = Mutu + TRUETYPE_TAG('t', 'u', 'd', 0), // tud = Tuxá + TRUETYPE_TAG('t', 'u', 'e', 0), // tue = Tuyuca + TRUETYPE_TAG('t', 'u', 'f', 0), // tuf = Central Tunebo + TRUETYPE_TAG('t', 'u', 'g', 0), // tug = Tunia + TRUETYPE_TAG('t', 'u', 'h', 0), // tuh = Taulil + TRUETYPE_TAG('t', 'u', 'i', 0), // tui = Tupuri + TRUETYPE_TAG('t', 'u', 'j', 0), // tuj = Tugutil + TRUETYPE_TAG('t', 'u', 'l', 0), // tul = Tula + TRUETYPE_TAG('t', 'u', 'm', 0), // tum = Tumbuka + TRUETYPE_TAG('t', 'u', 'n', 0), // tun = Tunica + TRUETYPE_TAG('t', 'u', 'o', 0), // tuo = Tucano + TRUETYPE_TAG('t', 'u', 'p', 0), // tup = Tupi languages + TRUETYPE_TAG('t', 'u', 'q', 0), // tuq = Tedaga + TRUETYPE_TAG('t', 'u', 's', 0), // tus = Tuscarora + TRUETYPE_TAG('t', 'u', 't', 0), // tut = Altaic languages + TRUETYPE_TAG('t', 'u', 'u', 0), // tuu = Tututni + TRUETYPE_TAG('t', 'u', 'v', 0), // tuv = Turkana + TRUETYPE_TAG('t', 'u', 'w', 0), // tuw = Tungus languages + TRUETYPE_TAG('t', 'u', 'x', 0), // tux = Tuxináwa + TRUETYPE_TAG('t', 'u', 'y', 0), // tuy = Tugen + TRUETYPE_TAG('t', 'u', 'z', 0), // tuz = Turka + TRUETYPE_TAG('t', 'v', 'a', 0), // tva = Vaghua + TRUETYPE_TAG('t', 'v', 'd', 0), // tvd = Tsuvadi + TRUETYPE_TAG('t', 'v', 'e', 0), // tve = Te'un + TRUETYPE_TAG('t', 'v', 'k', 0), // tvk = Southeast Ambrym + TRUETYPE_TAG('t', 'v', 'l', 0), // tvl = Tuvalu + TRUETYPE_TAG('t', 'v', 'm', 0), // tvm = Tela-Masbuar + TRUETYPE_TAG('t', 'v', 'n', 0), // tvn = Tavoyan + TRUETYPE_TAG('t', 'v', 'o', 0), // tvo = Tidore + TRUETYPE_TAG('t', 'v', 's', 0), // tvs = Taveta + TRUETYPE_TAG('t', 'v', 't', 0), // tvt = Tutsa Naga + TRUETYPE_TAG('t', 'v', 'w', 0), // tvw = Sedoa + TRUETYPE_TAG('t', 'v', 'y', 0), // tvy = Timor Pidgin + TRUETYPE_TAG('t', 'w', 'a', 0), // twa = Twana + TRUETYPE_TAG('t', 'w', 'b', 0), // twb = Western Tawbuid + TRUETYPE_TAG('t', 'w', 'c', 0), // twc = Teshenawa + TRUETYPE_TAG('t', 'w', 'd', 0), // twd = Twents + TRUETYPE_TAG('t', 'w', 'e', 0), // twe = Tewa (Indonesia) + TRUETYPE_TAG('t', 'w', 'f', 0), // twf = Northern Tiwa + TRUETYPE_TAG('t', 'w', 'g', 0), // twg = Tereweng + TRUETYPE_TAG('t', 'w', 'h', 0), // twh = Tai Dón + TRUETYPE_TAG('t', 'w', 'l', 0), // twl = Tawara + TRUETYPE_TAG('t', 'w', 'm', 0), // twm = Tawang Monpa + TRUETYPE_TAG('t', 'w', 'n', 0), // twn = Twendi + TRUETYPE_TAG('t', 'w', 'o', 0), // two = Tswapong + TRUETYPE_TAG('t', 'w', 'p', 0), // twp = Ere + TRUETYPE_TAG('t', 'w', 'q', 0), // twq = Tasawaq + TRUETYPE_TAG('t', 'w', 'r', 0), // twr = Southwestern Tarahumara + TRUETYPE_TAG('t', 'w', 't', 0), // twt = Turiwára + TRUETYPE_TAG('t', 'w', 'u', 0), // twu = Termanu + TRUETYPE_TAG('t', 'w', 'w', 0), // tww = Tuwari + TRUETYPE_TAG('t', 'w', 'x', 0), // twx = Tewe + TRUETYPE_TAG('t', 'w', 'y', 0), // twy = Tawoyan + TRUETYPE_TAG('t', 'x', 'a', 0), // txa = Tombonuo + TRUETYPE_TAG('t', 'x', 'b', 0), // txb = Tokharian B + TRUETYPE_TAG('t', 'x', 'c', 0), // txc = Tsetsaut + TRUETYPE_TAG('t', 'x', 'e', 0), // txe = Totoli + TRUETYPE_TAG('t', 'x', 'g', 0), // txg = Tangut + TRUETYPE_TAG('t', 'x', 'h', 0), // txh = Thracian + TRUETYPE_TAG('t', 'x', 'i', 0), // txi = Ikpeng + TRUETYPE_TAG('t', 'x', 'm', 0), // txm = Tomini + TRUETYPE_TAG('t', 'x', 'n', 0), // txn = West Tarangan + TRUETYPE_TAG('t', 'x', 'o', 0), // txo = Toto + TRUETYPE_TAG('t', 'x', 'q', 0), // txq = Tii + TRUETYPE_TAG('t', 'x', 'r', 0), // txr = Tartessian + TRUETYPE_TAG('t', 'x', 's', 0), // txs = Tonsea + TRUETYPE_TAG('t', 'x', 't', 0), // txt = Citak + TRUETYPE_TAG('t', 'x', 'u', 0), // txu = Kayapó + TRUETYPE_TAG('t', 'x', 'x', 0), // txx = Tatana + TRUETYPE_TAG('t', 'x', 'y', 0), // txy = Tanosy Malagasy + TRUETYPE_TAG('t', 'y', 'a', 0), // tya = Tauya + TRUETYPE_TAG('t', 'y', 'e', 0), // tye = Kyenga + TRUETYPE_TAG('t', 'y', 'h', 0), // tyh = O'du + TRUETYPE_TAG('t', 'y', 'i', 0), // tyi = Teke-Tsaayi + TRUETYPE_TAG('t', 'y', 'j', 0), // tyj = Tai Do + TRUETYPE_TAG('t', 'y', 'l', 0), // tyl = Thu Lao + TRUETYPE_TAG('t', 'y', 'n', 0), // tyn = Kombai + TRUETYPE_TAG('t', 'y', 'p', 0), // typ = Thaypan + TRUETYPE_TAG('t', 'y', 'r', 0), // tyr = Tai Daeng + TRUETYPE_TAG('t', 'y', 's', 0), // tys = Tày Sa Pa + TRUETYPE_TAG('t', 'y', 't', 0), // tyt = Tày Tac + TRUETYPE_TAG('t', 'y', 'u', 0), // tyu = Kua + TRUETYPE_TAG('t', 'y', 'v', 0), // tyv = Tuvinian + TRUETYPE_TAG('t', 'y', 'x', 0), // tyx = Teke-Tyee + TRUETYPE_TAG('t', 'y', 'z', 0), // tyz = Tày + TRUETYPE_TAG('t', 'z', 'a', 0), // tza = Tanzanian Sign Language + TRUETYPE_TAG('t', 'z', 'h', 0), // tzh = Tzeltal + TRUETYPE_TAG('t', 'z', 'j', 0), // tzj = Tz'utujil + TRUETYPE_TAG('t', 'z', 'm', 0), // tzm = Central Atlas Tamazight + TRUETYPE_TAG('t', 'z', 'n', 0), // tzn = Tugun + TRUETYPE_TAG('t', 'z', 'o', 0), // tzo = Tzotzil + TRUETYPE_TAG('t', 'z', 'x', 0), // tzx = Tabriak + TRUETYPE_TAG('u', 'a', 'm', 0), // uam = Uamué + TRUETYPE_TAG('u', 'a', 'n', 0), // uan = Kuan + TRUETYPE_TAG('u', 'a', 'r', 0), // uar = Tairuma + TRUETYPE_TAG('u', 'b', 'a', 0), // uba = Ubang + TRUETYPE_TAG('u', 'b', 'i', 0), // ubi = Ubi + TRUETYPE_TAG('u', 'b', 'l', 0), // ubl = Buhi'non Bikol + TRUETYPE_TAG('u', 'b', 'r', 0), // ubr = Ubir + TRUETYPE_TAG('u', 'b', 'u', 0), // ubu = Umbu-Ungu + TRUETYPE_TAG('u', 'b', 'y', 0), // uby = Ubykh + TRUETYPE_TAG('u', 'd', 'a', 0), // uda = Uda + TRUETYPE_TAG('u', 'd', 'e', 0), // ude = Udihe + TRUETYPE_TAG('u', 'd', 'g', 0), // udg = Muduga + TRUETYPE_TAG('u', 'd', 'i', 0), // udi = Udi + TRUETYPE_TAG('u', 'd', 'j', 0), // udj = Ujir + TRUETYPE_TAG('u', 'd', 'l', 0), // udl = Wuzlam + TRUETYPE_TAG('u', 'd', 'm', 0), // udm = Udmurt + TRUETYPE_TAG('u', 'd', 'u', 0), // udu = Uduk + TRUETYPE_TAG('u', 'e', 's', 0), // ues = Kioko + TRUETYPE_TAG('u', 'f', 'i', 0), // ufi = Ufim + TRUETYPE_TAG('u', 'g', 'a', 0), // uga = Ugaritic + TRUETYPE_TAG('u', 'g', 'b', 0), // ugb = Kuku-Ugbanh + TRUETYPE_TAG('u', 'g', 'e', 0), // uge = Ughele + TRUETYPE_TAG('u', 'g', 'n', 0), // ugn = Ugandan Sign Language + TRUETYPE_TAG('u', 'g', 'o', 0), // ugo = Ugong + TRUETYPE_TAG('u', 'g', 'y', 0), // ugy = Uruguayan Sign Language + TRUETYPE_TAG('u', 'h', 'a', 0), // uha = Uhami + TRUETYPE_TAG('u', 'h', 'n', 0), // uhn = Damal + TRUETYPE_TAG('u', 'i', 's', 0), // uis = Uisai + TRUETYPE_TAG('u', 'i', 'v', 0), // uiv = Iyive + TRUETYPE_TAG('u', 'j', 'i', 0), // uji = Tanjijili + TRUETYPE_TAG('u', 'k', 'a', 0), // uka = Kaburi + TRUETYPE_TAG('u', 'k', 'g', 0), // ukg = Ukuriguma + TRUETYPE_TAG('u', 'k', 'h', 0), // ukh = Ukhwejo + TRUETYPE_TAG('u', 'k', 'l', 0), // ukl = Ukrainian Sign Language + TRUETYPE_TAG('u', 'k', 'p', 0), // ukp = Ukpe-Bayobiri + TRUETYPE_TAG('u', 'k', 'q', 0), // ukq = Ukwa + TRUETYPE_TAG('u', 'k', 's', 0), // uks = Urubú-Kaapor Sign Language + TRUETYPE_TAG('u', 'k', 'u', 0), // uku = Ukue + TRUETYPE_TAG('u', 'k', 'w', 0), // ukw = Ukwuani-Aboh-Ndoni + TRUETYPE_TAG('u', 'l', 'a', 0), // ula = Fungwa + TRUETYPE_TAG('u', 'l', 'b', 0), // ulb = Ulukwumi + TRUETYPE_TAG('u', 'l', 'c', 0), // ulc = Ulch + TRUETYPE_TAG('u', 'l', 'f', 0), // ulf = Usku + TRUETYPE_TAG('u', 'l', 'i', 0), // uli = Ulithian + TRUETYPE_TAG('u', 'l', 'k', 0), // ulk = Meriam + TRUETYPE_TAG('u', 'l', 'l', 0), // ull = Ullatan + TRUETYPE_TAG('u', 'l', 'm', 0), // ulm = Ulumanda' + TRUETYPE_TAG('u', 'l', 'n', 0), // uln = Unserdeutsch + TRUETYPE_TAG('u', 'l', 'u', 0), // ulu = Uma' Lung + TRUETYPE_TAG('u', 'l', 'w', 0), // ulw = Ulwa + TRUETYPE_TAG('u', 'm', 'a', 0), // uma = Umatilla + TRUETYPE_TAG('u', 'm', 'b', 0), // umb = Umbundu + TRUETYPE_TAG('u', 'm', 'c', 0), // umc = Marrucinian + TRUETYPE_TAG('u', 'm', 'd', 0), // umd = Umbindhamu + TRUETYPE_TAG('u', 'm', 'g', 0), // umg = Umbuygamu + TRUETYPE_TAG('u', 'm', 'i', 0), // umi = Ukit + TRUETYPE_TAG('u', 'm', 'm', 0), // umm = Umon + TRUETYPE_TAG('u', 'm', 'n', 0), // umn = Makyan Naga + TRUETYPE_TAG('u', 'm', 'o', 0), // umo = Umotína + TRUETYPE_TAG('u', 'm', 'p', 0), // ump = Umpila + TRUETYPE_TAG('u', 'm', 'r', 0), // umr = Umbugarla + TRUETYPE_TAG('u', 'm', 's', 0), // ums = Pendau + TRUETYPE_TAG('u', 'm', 'u', 0), // umu = Munsee + TRUETYPE_TAG('u', 'n', 'a', 0), // una = North Watut + TRUETYPE_TAG('u', 'n', 'd', 0), // und = Undetermined + TRUETYPE_TAG('u', 'n', 'e', 0), // une = Uneme + TRUETYPE_TAG('u', 'n', 'g', 0), // ung = Ngarinyin + TRUETYPE_TAG('u', 'n', 'k', 0), // unk = Enawené-Nawé + TRUETYPE_TAG('u', 'n', 'm', 0), // unm = Unami + TRUETYPE_TAG('u', 'n', 'p', 0), // unp = Worora + TRUETYPE_TAG('u', 'n', 'r', 0), // unr = Mundari + TRUETYPE_TAG('u', 'n', 'x', 0), // unx = Munda + TRUETYPE_TAG('u', 'n', 'z', 0), // unz = Unde Kaili + TRUETYPE_TAG('u', 'o', 'k', 0), // uok = Uokha + TRUETYPE_TAG('u', 'p', 'i', 0), // upi = Umeda + TRUETYPE_TAG('u', 'p', 'v', 0), // upv = Uripiv-Wala-Rano-Atchin + TRUETYPE_TAG('u', 'r', 'a', 0), // ura = Urarina + TRUETYPE_TAG('u', 'r', 'b', 0), // urb = Urubú-Kaapor + TRUETYPE_TAG('u', 'r', 'c', 0), // urc = Urningangg + TRUETYPE_TAG('u', 'r', 'e', 0), // ure = Uru + TRUETYPE_TAG('u', 'r', 'f', 0), // urf = Uradhi + TRUETYPE_TAG('u', 'r', 'g', 0), // urg = Urigina + TRUETYPE_TAG('u', 'r', 'h', 0), // urh = Urhobo + TRUETYPE_TAG('u', 'r', 'i', 0), // uri = Urim + TRUETYPE_TAG('u', 'r', 'j', 0), // urj = Uralic languages + TRUETYPE_TAG('u', 'r', 'k', 0), // urk = Urak Lawoi' + TRUETYPE_TAG('u', 'r', 'l', 0), // url = Urali + TRUETYPE_TAG('u', 'r', 'm', 0), // urm = Urapmin + TRUETYPE_TAG('u', 'r', 'n', 0), // urn = Uruangnirin + TRUETYPE_TAG('u', 'r', 'o', 0), // uro = Ura (Papua New Guinea) + TRUETYPE_TAG('u', 'r', 'p', 0), // urp = Uru-Pa-In + TRUETYPE_TAG('u', 'r', 'r', 0), // urr = Lehalurup + TRUETYPE_TAG('u', 'r', 't', 0), // urt = Urat + TRUETYPE_TAG('u', 'r', 'u', 0), // uru = Urumi + TRUETYPE_TAG('u', 'r', 'v', 0), // urv = Uruava + TRUETYPE_TAG('u', 'r', 'w', 0), // urw = Sop + TRUETYPE_TAG('u', 'r', 'x', 0), // urx = Urimo + TRUETYPE_TAG('u', 'r', 'y', 0), // ury = Orya + TRUETYPE_TAG('u', 'r', 'z', 0), // urz = Uru-Eu-Wau-Wau + TRUETYPE_TAG('u', 's', 'a', 0), // usa = Usarufa + TRUETYPE_TAG('u', 's', 'h', 0), // ush = Ushojo + TRUETYPE_TAG('u', 's', 'i', 0), // usi = Usui + TRUETYPE_TAG('u', 's', 'k', 0), // usk = Usaghade + TRUETYPE_TAG('u', 's', 'p', 0), // usp = Uspanteco + TRUETYPE_TAG('u', 's', 'u', 0), // usu = Uya + TRUETYPE_TAG('u', 't', 'a', 0), // uta = Otank + TRUETYPE_TAG('u', 't', 'e', 0), // ute = Ute-Southern Paiute + TRUETYPE_TAG('u', 't', 'p', 0), // utp = Amba (Solomon Islands) + TRUETYPE_TAG('u', 't', 'r', 0), // utr = Etulo + TRUETYPE_TAG('u', 't', 'u', 0), // utu = Utu + TRUETYPE_TAG('u', 'u', 'm', 0), // uum = Urum + TRUETYPE_TAG('u', 'u', 'n', 0), // uun = Kulon-Pazeh + TRUETYPE_TAG('u', 'u', 'r', 0), // uur = Ura (Vanuatu) + TRUETYPE_TAG('u', 'u', 'u', 0), // uuu = U + TRUETYPE_TAG('u', 'v', 'e', 0), // uve = West Uvean + TRUETYPE_TAG('u', 'v', 'h', 0), // uvh = Uri + TRUETYPE_TAG('u', 'v', 'l', 0), // uvl = Lote + TRUETYPE_TAG('u', 'w', 'a', 0), // uwa = Kuku-Uwanh + TRUETYPE_TAG('u', 'y', 'a', 0), // uya = Doko-Uyanga + TRUETYPE_TAG('u', 'z', 'n', 0), // uzn = Northern Uzbek + TRUETYPE_TAG('u', 'z', 's', 0), // uzs = Southern Uzbek + TRUETYPE_TAG('v', 'a', 'a', 0), // vaa = Vaagri Booli + TRUETYPE_TAG('v', 'a', 'e', 0), // vae = Vale + TRUETYPE_TAG('v', 'a', 'f', 0), // vaf = Vafsi + TRUETYPE_TAG('v', 'a', 'g', 0), // vag = Vagla + TRUETYPE_TAG('v', 'a', 'h', 0), // vah = Varhadi-Nagpuri + TRUETYPE_TAG('v', 'a', 'i', 0), // vai = Vai + TRUETYPE_TAG('v', 'a', 'j', 0), // vaj = Vasekela Bushman + TRUETYPE_TAG('v', 'a', 'l', 0), // val = Vehes + TRUETYPE_TAG('v', 'a', 'm', 0), // vam = Vanimo + TRUETYPE_TAG('v', 'a', 'n', 0), // van = Valman + TRUETYPE_TAG('v', 'a', 'o', 0), // vao = Vao + TRUETYPE_TAG('v', 'a', 'p', 0), // vap = Vaiphei + TRUETYPE_TAG('v', 'a', 'r', 0), // var = Huarijio + TRUETYPE_TAG('v', 'a', 's', 0), // vas = Vasavi + TRUETYPE_TAG('v', 'a', 'u', 0), // vau = Vanuma + TRUETYPE_TAG('v', 'a', 'v', 0), // vav = Varli + TRUETYPE_TAG('v', 'a', 'y', 0), // vay = Wayu + TRUETYPE_TAG('v', 'b', 'b', 0), // vbb = Southeast Babar + TRUETYPE_TAG('v', 'b', 'k', 0), // vbk = Southwestern Bontok + TRUETYPE_TAG('v', 'e', 'c', 0), // vec = Venetian + TRUETYPE_TAG('v', 'e', 'd', 0), // ved = Veddah + TRUETYPE_TAG('v', 'e', 'l', 0), // vel = Veluws + TRUETYPE_TAG('v', 'e', 'm', 0), // vem = Vemgo-Mabas + TRUETYPE_TAG('v', 'e', 'o', 0), // veo = Ventureño + TRUETYPE_TAG('v', 'e', 'p', 0), // vep = Veps + TRUETYPE_TAG('v', 'e', 'r', 0), // ver = Mom Jango + TRUETYPE_TAG('v', 'g', 'r', 0), // vgr = Vaghri + TRUETYPE_TAG('v', 'g', 't', 0), // vgt = Vlaamse Gebarentaal + TRUETYPE_TAG('v', 'i', 'c', 0), // vic = Virgin Islands Creole English + TRUETYPE_TAG('v', 'i', 'd', 0), // vid = Vidunda + TRUETYPE_TAG('v', 'i', 'f', 0), // vif = Vili + TRUETYPE_TAG('v', 'i', 'g', 0), // vig = Viemo + TRUETYPE_TAG('v', 'i', 'l', 0), // vil = Vilela + TRUETYPE_TAG('v', 'i', 'n', 0), // vin = Vinza + TRUETYPE_TAG('v', 'i', 's', 0), // vis = Vishavan + TRUETYPE_TAG('v', 'i', 't', 0), // vit = Viti + TRUETYPE_TAG('v', 'i', 'v', 0), // viv = Iduna + TRUETYPE_TAG('v', 'k', 'a', 0), // vka = Kariyarra + TRUETYPE_TAG('v', 'k', 'i', 0), // vki = Ija-Zuba + TRUETYPE_TAG('v', 'k', 'j', 0), // vkj = Kujarge + TRUETYPE_TAG('v', 'k', 'k', 0), // vkk = Kaur + TRUETYPE_TAG('v', 'k', 'l', 0), // vkl = Kulisusu + TRUETYPE_TAG('v', 'k', 'm', 0), // vkm = Kamakan + TRUETYPE_TAG('v', 'k', 'o', 0), // vko = Kodeoha + TRUETYPE_TAG('v', 'k', 'p', 0), // vkp = Korlai Creole Portuguese + TRUETYPE_TAG('v', 'k', 't', 0), // vkt = Tenggarong Kutai Malay + TRUETYPE_TAG('v', 'k', 'u', 0), // vku = Kurrama + TRUETYPE_TAG('v', 'l', 'p', 0), // vlp = Valpei + TRUETYPE_TAG('v', 'l', 's', 0), // vls = Vlaams + TRUETYPE_TAG('v', 'm', 'a', 0), // vma = Martuyhunira + TRUETYPE_TAG('v', 'm', 'b', 0), // vmb = Mbabaram + TRUETYPE_TAG('v', 'm', 'c', 0), // vmc = Juxtlahuaca Mixtec + TRUETYPE_TAG('v', 'm', 'd', 0), // vmd = Mudu Koraga + TRUETYPE_TAG('v', 'm', 'e', 0), // vme = East Masela + TRUETYPE_TAG('v', 'm', 'f', 0), // vmf = Mainfränkisch + TRUETYPE_TAG('v', 'm', 'g', 0), // vmg = Minigir + TRUETYPE_TAG('v', 'm', 'h', 0), // vmh = Maraghei + TRUETYPE_TAG('v', 'm', 'i', 0), // vmi = Miwa + TRUETYPE_TAG('v', 'm', 'j', 0), // vmj = Ixtayutla Mixtec + TRUETYPE_TAG('v', 'm', 'k', 0), // vmk = Makhuwa-Shirima + TRUETYPE_TAG('v', 'm', 'l', 0), // vml = Malgana + TRUETYPE_TAG('v', 'm', 'm', 0), // vmm = Mitlatongo Mixtec + TRUETYPE_TAG('v', 'm', 'p', 0), // vmp = Soyaltepec Mazatec + TRUETYPE_TAG('v', 'm', 'q', 0), // vmq = Soyaltepec Mixtec + TRUETYPE_TAG('v', 'm', 'r', 0), // vmr = Marenje + TRUETYPE_TAG('v', 'm', 's', 0), // vms = Moksela + TRUETYPE_TAG('v', 'm', 'u', 0), // vmu = Muluridyi + TRUETYPE_TAG('v', 'm', 'v', 0), // vmv = Valley Maidu + TRUETYPE_TAG('v', 'm', 'w', 0), // vmw = Makhuwa + TRUETYPE_TAG('v', 'm', 'x', 0), // vmx = Tamazola Mixtec + TRUETYPE_TAG('v', 'm', 'y', 0), // vmy = Ayautla Mazatec + TRUETYPE_TAG('v', 'm', 'z', 0), // vmz = Mazatlán Mazatec + TRUETYPE_TAG('v', 'n', 'k', 0), // vnk = Vano + TRUETYPE_TAG('v', 'n', 'm', 0), // vnm = Vinmavis + TRUETYPE_TAG('v', 'n', 'p', 0), // vnp = Vunapu + TRUETYPE_TAG('v', 'o', 'r', 0), // vor = Voro + TRUETYPE_TAG('v', 'o', 't', 0), // vot = Votic + TRUETYPE_TAG('v', 'r', 'a', 0), // vra = Vera'a + TRUETYPE_TAG('v', 'r', 'o', 0), // vro = Võro + TRUETYPE_TAG('v', 'r', 's', 0), // vrs = Varisi + TRUETYPE_TAG('v', 'r', 't', 0), // vrt = Burmbar + TRUETYPE_TAG('v', 's', 'i', 0), // vsi = Moldova Sign Language + TRUETYPE_TAG('v', 's', 'l', 0), // vsl = Venezuelan Sign Language + TRUETYPE_TAG('v', 's', 'v', 0), // vsv = Valencian Sign Language + TRUETYPE_TAG('v', 't', 'o', 0), // vto = Vitou + TRUETYPE_TAG('v', 'u', 'm', 0), // vum = Vumbu + TRUETYPE_TAG('v', 'u', 'n', 0), // vun = Vunjo + TRUETYPE_TAG('v', 'u', 't', 0), // vut = Vute + TRUETYPE_TAG('v', 'w', 'a', 0), // vwa = Awa (China) + TRUETYPE_TAG('w', 'a', 'a', 0), // waa = Walla Walla + TRUETYPE_TAG('w', 'a', 'b', 0), // wab = Wab + TRUETYPE_TAG('w', 'a', 'c', 0), // wac = Wasco-Wishram + TRUETYPE_TAG('w', 'a', 'd', 0), // wad = Wandamen + TRUETYPE_TAG('w', 'a', 'e', 0), // wae = Walser + TRUETYPE_TAG('w', 'a', 'f', 0), // waf = Wakoná + TRUETYPE_TAG('w', 'a', 'g', 0), // wag = Wa'ema + TRUETYPE_TAG('w', 'a', 'h', 0), // wah = Watubela + TRUETYPE_TAG('w', 'a', 'i', 0), // wai = Wares + TRUETYPE_TAG('w', 'a', 'j', 0), // waj = Waffa + TRUETYPE_TAG('w', 'a', 'k', 0), // wak = Wakashan languages + TRUETYPE_TAG('w', 'a', 'l', 0), // wal = Wolaytta + TRUETYPE_TAG('w', 'a', 'm', 0), // wam = Wampanoag + TRUETYPE_TAG('w', 'a', 'n', 0), // wan = Wan + TRUETYPE_TAG('w', 'a', 'o', 0), // wao = Wappo + TRUETYPE_TAG('w', 'a', 'p', 0), // wap = Wapishana + TRUETYPE_TAG('w', 'a', 'q', 0), // waq = Wageman + TRUETYPE_TAG('w', 'a', 'r', 0), // war = Waray (Philippines) + TRUETYPE_TAG('w', 'a', 's', 0), // was = Washo + TRUETYPE_TAG('w', 'a', 't', 0), // wat = Kaninuwa + TRUETYPE_TAG('w', 'a', 'u', 0), // wau = Waurá + TRUETYPE_TAG('w', 'a', 'v', 0), // wav = Waka + TRUETYPE_TAG('w', 'a', 'w', 0), // waw = Waiwai + TRUETYPE_TAG('w', 'a', 'x', 0), // wax = Watam + TRUETYPE_TAG('w', 'a', 'y', 0), // way = Wayana + TRUETYPE_TAG('w', 'a', 'z', 0), // waz = Wampur + TRUETYPE_TAG('w', 'b', 'a', 0), // wba = Warao + TRUETYPE_TAG('w', 'b', 'b', 0), // wbb = Wabo + TRUETYPE_TAG('w', 'b', 'e', 0), // wbe = Waritai + TRUETYPE_TAG('w', 'b', 'f', 0), // wbf = Wara + TRUETYPE_TAG('w', 'b', 'h', 0), // wbh = Wanda + TRUETYPE_TAG('w', 'b', 'i', 0), // wbi = Vwanji + TRUETYPE_TAG('w', 'b', 'j', 0), // wbj = Alagwa + TRUETYPE_TAG('w', 'b', 'k', 0), // wbk = Waigali + TRUETYPE_TAG('w', 'b', 'l', 0), // wbl = Wakhi + TRUETYPE_TAG('w', 'b', 'm', 0), // wbm = Wa + TRUETYPE_TAG('w', 'b', 'p', 0), // wbp = Warlpiri + TRUETYPE_TAG('w', 'b', 'q', 0), // wbq = Waddar + TRUETYPE_TAG('w', 'b', 'r', 0), // wbr = Wagdi + TRUETYPE_TAG('w', 'b', 't', 0), // wbt = Wanman + TRUETYPE_TAG('w', 'b', 'v', 0), // wbv = Wajarri + TRUETYPE_TAG('w', 'b', 'w', 0), // wbw = Woi + TRUETYPE_TAG('w', 'c', 'a', 0), // wca = Yanomámi + TRUETYPE_TAG('w', 'c', 'i', 0), // wci = Waci Gbe + TRUETYPE_TAG('w', 'd', 'd', 0), // wdd = Wandji + TRUETYPE_TAG('w', 'd', 'g', 0), // wdg = Wadaginam + TRUETYPE_TAG('w', 'd', 'j', 0), // wdj = Wadjiginy + TRUETYPE_TAG('w', 'd', 'u', 0), // wdu = Wadjigu + TRUETYPE_TAG('w', 'e', 'a', 0), // wea = Wewaw + TRUETYPE_TAG('w', 'e', 'c', 0), // wec = Wè Western + TRUETYPE_TAG('w', 'e', 'd', 0), // wed = Wedau + TRUETYPE_TAG('w', 'e', 'h', 0), // weh = Weh + TRUETYPE_TAG('w', 'e', 'i', 0), // wei = Kiunum + TRUETYPE_TAG('w', 'e', 'm', 0), // wem = Weme Gbe + TRUETYPE_TAG('w', 'e', 'n', 0), // wen = Sorbian languages + TRUETYPE_TAG('w', 'e', 'o', 0), // weo = North Wemale + TRUETYPE_TAG('w', 'e', 'p', 0), // wep = Westphalien + TRUETYPE_TAG('w', 'e', 'r', 0), // wer = Weri + TRUETYPE_TAG('w', 'e', 's', 0), // wes = Cameroon Pidgin + TRUETYPE_TAG('w', 'e', 't', 0), // wet = Perai + TRUETYPE_TAG('w', 'e', 'u', 0), // weu = Welaung + TRUETYPE_TAG('w', 'e', 'w', 0), // wew = Wejewa + TRUETYPE_TAG('w', 'f', 'g', 0), // wfg = Yafi + TRUETYPE_TAG('w', 'g', 'a', 0), // wga = Wagaya + TRUETYPE_TAG('w', 'g', 'b', 0), // wgb = Wagawaga + TRUETYPE_TAG('w', 'g', 'g', 0), // wgg = Wangganguru + TRUETYPE_TAG('w', 'g', 'i', 0), // wgi = Wahgi + TRUETYPE_TAG('w', 'g', 'o', 0), // wgo = Waigeo + TRUETYPE_TAG('w', 'g', 'w', 0), // wgw = Wagawaga + TRUETYPE_TAG('w', 'g', 'y', 0), // wgy = Warrgamay + TRUETYPE_TAG('w', 'h', 'a', 0), // wha = Manusela + TRUETYPE_TAG('w', 'h', 'g', 0), // whg = North Wahgi + TRUETYPE_TAG('w', 'h', 'k', 0), // whk = Wahau Kenyah + TRUETYPE_TAG('w', 'h', 'u', 0), // whu = Wahau Kayan + TRUETYPE_TAG('w', 'i', 'b', 0), // wib = Southern Toussian + TRUETYPE_TAG('w', 'i', 'c', 0), // wic = Wichita + TRUETYPE_TAG('w', 'i', 'e', 0), // wie = Wik-Epa + TRUETYPE_TAG('w', 'i', 'f', 0), // wif = Wik-Keyangan + TRUETYPE_TAG('w', 'i', 'g', 0), // wig = Wik-Ngathana + TRUETYPE_TAG('w', 'i', 'h', 0), // wih = Wik-Me'anha + TRUETYPE_TAG('w', 'i', 'i', 0), // wii = Minidien + TRUETYPE_TAG('w', 'i', 'j', 0), // wij = Wik-Iiyanh + TRUETYPE_TAG('w', 'i', 'k', 0), // wik = Wikalkan + TRUETYPE_TAG('w', 'i', 'l', 0), // wil = Wilawila + TRUETYPE_TAG('w', 'i', 'm', 0), // wim = Wik-Mungkan + TRUETYPE_TAG('w', 'i', 'n', 0), // win = Ho-Chunk + TRUETYPE_TAG('w', 'i', 'r', 0), // wir = Wiraféd + TRUETYPE_TAG('w', 'i', 't', 0), // wit = Wintu + TRUETYPE_TAG('w', 'i', 'u', 0), // wiu = Wiru + TRUETYPE_TAG('w', 'i', 'v', 0), // wiv = Muduapa + TRUETYPE_TAG('w', 'i', 'w', 0), // wiw = Wirangu + TRUETYPE_TAG('w', 'i', 'y', 0), // wiy = Wiyot + TRUETYPE_TAG('w', 'j', 'a', 0), // wja = Waja + TRUETYPE_TAG('w', 'j', 'i', 0), // wji = Warji + TRUETYPE_TAG('w', 'k', 'a', 0), // wka = Kw'adza + TRUETYPE_TAG('w', 'k', 'b', 0), // wkb = Kumbaran + TRUETYPE_TAG('w', 'k', 'd', 0), // wkd = Wakde + TRUETYPE_TAG('w', 'k', 'l', 0), // wkl = Kalanadi + TRUETYPE_TAG('w', 'k', 'u', 0), // wku = Kunduvadi + TRUETYPE_TAG('w', 'k', 'w', 0), // wkw = Wakawaka + TRUETYPE_TAG('w', 'l', 'a', 0), // wla = Walio + TRUETYPE_TAG('w', 'l', 'c', 0), // wlc = Mwali Comorian + TRUETYPE_TAG('w', 'l', 'e', 0), // wle = Wolane + TRUETYPE_TAG('w', 'l', 'g', 0), // wlg = Kunbarlang + TRUETYPE_TAG('w', 'l', 'i', 0), // wli = Waioli + TRUETYPE_TAG('w', 'l', 'k', 0), // wlk = Wailaki + TRUETYPE_TAG('w', 'l', 'l', 0), // wll = Wali (Sudan) + TRUETYPE_TAG('w', 'l', 'm', 0), // wlm = Middle Welsh + TRUETYPE_TAG('w', 'l', 'o', 0), // wlo = Wolio + TRUETYPE_TAG('w', 'l', 'r', 0), // wlr = Wailapa + TRUETYPE_TAG('w', 'l', 's', 0), // wls = Wallisian + TRUETYPE_TAG('w', 'l', 'u', 0), // wlu = Wuliwuli + TRUETYPE_TAG('w', 'l', 'v', 0), // wlv = Wichí Lhamtés Vejoz + TRUETYPE_TAG('w', 'l', 'w', 0), // wlw = Walak + TRUETYPE_TAG('w', 'l', 'x', 0), // wlx = Wali (Ghana) + TRUETYPE_TAG('w', 'l', 'y', 0), // wly = Waling + TRUETYPE_TAG('w', 'm', 'a', 0), // wma = Mawa (Nigeria) + TRUETYPE_TAG('w', 'm', 'b', 0), // wmb = Wambaya + TRUETYPE_TAG('w', 'm', 'c', 0), // wmc = Wamas + TRUETYPE_TAG('w', 'm', 'd', 0), // wmd = Mamaindé + TRUETYPE_TAG('w', 'm', 'e', 0), // wme = Wambule + TRUETYPE_TAG('w', 'm', 'h', 0), // wmh = Waima'a + TRUETYPE_TAG('w', 'm', 'i', 0), // wmi = Wamin + TRUETYPE_TAG('w', 'm', 'm', 0), // wmm = Maiwa (Indonesia) + TRUETYPE_TAG('w', 'm', 'n', 0), // wmn = Waamwang + TRUETYPE_TAG('w', 'm', 'o', 0), // wmo = Wom (Papua New Guinea) + TRUETYPE_TAG('w', 'm', 's', 0), // wms = Wambon + TRUETYPE_TAG('w', 'm', 't', 0), // wmt = Walmajarri + TRUETYPE_TAG('w', 'm', 'w', 0), // wmw = Mwani + TRUETYPE_TAG('w', 'm', 'x', 0), // wmx = Womo + TRUETYPE_TAG('w', 'n', 'b', 0), // wnb = Wanambre + TRUETYPE_TAG('w', 'n', 'c', 0), // wnc = Wantoat + TRUETYPE_TAG('w', 'n', 'd', 0), // wnd = Wandarang + TRUETYPE_TAG('w', 'n', 'e', 0), // wne = Waneci + TRUETYPE_TAG('w', 'n', 'g', 0), // wng = Wanggom + TRUETYPE_TAG('w', 'n', 'i', 0), // wni = Ndzwani Comorian + TRUETYPE_TAG('w', 'n', 'k', 0), // wnk = Wanukaka + TRUETYPE_TAG('w', 'n', 'm', 0), // wnm = Wanggamala + TRUETYPE_TAG('w', 'n', 'o', 0), // wno = Wano + TRUETYPE_TAG('w', 'n', 'p', 0), // wnp = Wanap + TRUETYPE_TAG('w', 'n', 'u', 0), // wnu = Usan + TRUETYPE_TAG('w', 'o', 'a', 0), // woa = Tyaraity + TRUETYPE_TAG('w', 'o', 'b', 0), // wob = Wè Northern + TRUETYPE_TAG('w', 'o', 'c', 0), // woc = Wogeo + TRUETYPE_TAG('w', 'o', 'd', 0), // wod = Wolani + TRUETYPE_TAG('w', 'o', 'e', 0), // woe = Woleaian + TRUETYPE_TAG('w', 'o', 'f', 0), // wof = Gambian Wolof + TRUETYPE_TAG('w', 'o', 'g', 0), // wog = Wogamusin + TRUETYPE_TAG('w', 'o', 'i', 0), // woi = Kamang + TRUETYPE_TAG('w', 'o', 'k', 0), // wok = Longto + TRUETYPE_TAG('w', 'o', 'm', 0), // wom = Wom (Nigeria) + TRUETYPE_TAG('w', 'o', 'n', 0), // won = Wongo + TRUETYPE_TAG('w', 'o', 'o', 0), // woo = Manombai + TRUETYPE_TAG('w', 'o', 'r', 0), // wor = Woria + TRUETYPE_TAG('w', 'o', 's', 0), // wos = Hanga Hundi + TRUETYPE_TAG('w', 'o', 'w', 0), // wow = Wawonii + TRUETYPE_TAG('w', 'o', 'y', 0), // woy = Weyto + TRUETYPE_TAG('w', 'p', 'c', 0), // wpc = Maco + TRUETYPE_TAG('w', 'r', 'a', 0), // wra = Warapu + TRUETYPE_TAG('w', 'r', 'b', 0), // wrb = Warluwara + TRUETYPE_TAG('w', 'r', 'd', 0), // wrd = Warduji + TRUETYPE_TAG('w', 'r', 'g', 0), // wrg = Warungu + TRUETYPE_TAG('w', 'r', 'h', 0), // wrh = Wiradhuri + TRUETYPE_TAG('w', 'r', 'i', 0), // wri = Wariyangga + TRUETYPE_TAG('w', 'r', 'l', 0), // wrl = Warlmanpa + TRUETYPE_TAG('w', 'r', 'm', 0), // wrm = Warumungu + TRUETYPE_TAG('w', 'r', 'n', 0), // wrn = Warnang + TRUETYPE_TAG('w', 'r', 'p', 0), // wrp = Waropen + TRUETYPE_TAG('w', 'r', 'r', 0), // wrr = Wardaman + TRUETYPE_TAG('w', 'r', 's', 0), // wrs = Waris + TRUETYPE_TAG('w', 'r', 'u', 0), // wru = Waru + TRUETYPE_TAG('w', 'r', 'v', 0), // wrv = Waruna + TRUETYPE_TAG('w', 'r', 'w', 0), // wrw = Gugu Warra + TRUETYPE_TAG('w', 'r', 'x', 0), // wrx = Wae Rana + TRUETYPE_TAG('w', 'r', 'y', 0), // wry = Merwari + TRUETYPE_TAG('w', 'r', 'z', 0), // wrz = Waray (Australia) + TRUETYPE_TAG('w', 's', 'a', 0), // wsa = Warembori + TRUETYPE_TAG('w', 's', 'i', 0), // wsi = Wusi + TRUETYPE_TAG('w', 's', 'k', 0), // wsk = Waskia + TRUETYPE_TAG('w', 's', 'r', 0), // wsr = Owenia + TRUETYPE_TAG('w', 's', 's', 0), // wss = Wasa + TRUETYPE_TAG('w', 's', 'u', 0), // wsu = Wasu + TRUETYPE_TAG('w', 's', 'v', 0), // wsv = Wotapuri-Katarqalai + TRUETYPE_TAG('w', 't', 'f', 0), // wtf = Watiwa + TRUETYPE_TAG('w', 't', 'i', 0), // wti = Berta + TRUETYPE_TAG('w', 't', 'k', 0), // wtk = Watakataui + TRUETYPE_TAG('w', 't', 'm', 0), // wtm = Mewati + TRUETYPE_TAG('w', 't', 'w', 0), // wtw = Wotu + TRUETYPE_TAG('w', 'u', 'a', 0), // wua = Wikngenchera + TRUETYPE_TAG('w', 'u', 'b', 0), // wub = Wunambal + TRUETYPE_TAG('w', 'u', 'd', 0), // wud = Wudu + TRUETYPE_TAG('w', 'u', 'h', 0), // wuh = Wutunhua + TRUETYPE_TAG('w', 'u', 'l', 0), // wul = Silimo + TRUETYPE_TAG('w', 'u', 'm', 0), // wum = Wumbvu + TRUETYPE_TAG('w', 'u', 'n', 0), // wun = Bungu + TRUETYPE_TAG('w', 'u', 'r', 0), // wur = Wurrugu + TRUETYPE_TAG('w', 'u', 't', 0), // wut = Wutung + TRUETYPE_TAG('w', 'u', 'u', 0), // wuu = Wu Chinese + TRUETYPE_TAG('w', 'u', 'v', 0), // wuv = Wuvulu-Aua + TRUETYPE_TAG('w', 'u', 'x', 0), // wux = Wulna + TRUETYPE_TAG('w', 'u', 'y', 0), // wuy = Wauyai + TRUETYPE_TAG('w', 'w', 'a', 0), // wwa = Waama + TRUETYPE_TAG('w', 'w', 'o', 0), // wwo = Wetamut + TRUETYPE_TAG('w', 'w', 'r', 0), // wwr = Warrwa + TRUETYPE_TAG('w', 'w', 'w', 0), // www = Wawa + TRUETYPE_TAG('w', 'x', 'a', 0), // wxa = Waxianghua + TRUETYPE_TAG('w', 'y', 'a', 0), // wya = Wyandot + TRUETYPE_TAG('w', 'y', 'b', 0), // wyb = Wangaaybuwan-Ngiyambaa + TRUETYPE_TAG('w', 'y', 'm', 0), // wym = Wymysorys + TRUETYPE_TAG('w', 'y', 'r', 0), // wyr = Wayoró + TRUETYPE_TAG('w', 'y', 'y', 0), // wyy = Western Fijian + TRUETYPE_TAG('x', 'a', 'a', 0), // xaa = Andalusian Arabic + TRUETYPE_TAG('x', 'a', 'b', 0), // xab = Sambe + TRUETYPE_TAG('x', 'a', 'c', 0), // xac = Kachari + TRUETYPE_TAG('x', 'a', 'd', 0), // xad = Adai + TRUETYPE_TAG('x', 'a', 'e', 0), // xae = Aequian + TRUETYPE_TAG('x', 'a', 'g', 0), // xag = Aghwan + TRUETYPE_TAG('x', 'a', 'i', 0), // xai = Kaimbé + TRUETYPE_TAG('x', 'a', 'l', 0), // xal = Kalmyk + TRUETYPE_TAG('x', 'a', 'm', 0), // xam = /Xam + TRUETYPE_TAG('x', 'a', 'n', 0), // xan = Xamtanga + TRUETYPE_TAG('x', 'a', 'o', 0), // xao = Khao + TRUETYPE_TAG('x', 'a', 'p', 0), // xap = Apalachee + TRUETYPE_TAG('x', 'a', 'q', 0), // xaq = Aquitanian + TRUETYPE_TAG('x', 'a', 'r', 0), // xar = Karami + TRUETYPE_TAG('x', 'a', 's', 0), // xas = Kamas + TRUETYPE_TAG('x', 'a', 't', 0), // xat = Katawixi + TRUETYPE_TAG('x', 'a', 'u', 0), // xau = Kauwera + TRUETYPE_TAG('x', 'a', 'v', 0), // xav = Xavánte + TRUETYPE_TAG('x', 'a', 'w', 0), // xaw = Kawaiisu + TRUETYPE_TAG('x', 'a', 'y', 0), // xay = Kayan Mahakam + TRUETYPE_TAG('x', 'b', 'a', 0), // xba = Kamba (Brazil) + TRUETYPE_TAG('x', 'b', 'b', 0), // xbb = Lower Burdekin + TRUETYPE_TAG('x', 'b', 'c', 0), // xbc = Bactrian + TRUETYPE_TAG('x', 'b', 'i', 0), // xbi = Kombio + TRUETYPE_TAG('x', 'b', 'm', 0), // xbm = Middle Breton + TRUETYPE_TAG('x', 'b', 'n', 0), // xbn = Kenaboi + TRUETYPE_TAG('x', 'b', 'o', 0), // xbo = Bolgarian + TRUETYPE_TAG('x', 'b', 'r', 0), // xbr = Kambera + TRUETYPE_TAG('x', 'b', 'w', 0), // xbw = Kambiwá + TRUETYPE_TAG('x', 'b', 'x', 0), // xbx = Kabixí + TRUETYPE_TAG('x', 'c', 'b', 0), // xcb = Cumbric + TRUETYPE_TAG('x', 'c', 'c', 0), // xcc = Camunic + TRUETYPE_TAG('x', 'c', 'e', 0), // xce = Celtiberian + TRUETYPE_TAG('x', 'c', 'g', 0), // xcg = Cisalpine Gaulish + TRUETYPE_TAG('x', 'c', 'h', 0), // xch = Chemakum + TRUETYPE_TAG('x', 'c', 'l', 0), // xcl = Classical Armenian + TRUETYPE_TAG('x', 'c', 'm', 0), // xcm = Comecrudo + TRUETYPE_TAG('x', 'c', 'n', 0), // xcn = Cotoname + TRUETYPE_TAG('x', 'c', 'o', 0), // xco = Chorasmian + TRUETYPE_TAG('x', 'c', 'r', 0), // xcr = Carian + TRUETYPE_TAG('x', 'c', 't', 0), // xct = Classical Tibetan + TRUETYPE_TAG('x', 'c', 'u', 0), // xcu = Curonian + TRUETYPE_TAG('x', 'c', 'v', 0), // xcv = Chuvantsy + TRUETYPE_TAG('x', 'c', 'w', 0), // xcw = Coahuilteco + TRUETYPE_TAG('x', 'c', 'y', 0), // xcy = Cayuse + TRUETYPE_TAG('x', 'd', 'c', 0), // xdc = Dacian + TRUETYPE_TAG('x', 'd', 'm', 0), // xdm = Edomite + TRUETYPE_TAG('x', 'd', 'y', 0), // xdy = Malayic Dayak + TRUETYPE_TAG('x', 'e', 'b', 0), // xeb = Eblan + TRUETYPE_TAG('x', 'e', 'd', 0), // xed = Hdi + TRUETYPE_TAG('x', 'e', 'g', 0), // xeg = //Xegwi + TRUETYPE_TAG('x', 'e', 'l', 0), // xel = Kelo + TRUETYPE_TAG('x', 'e', 'm', 0), // xem = Kembayan + TRUETYPE_TAG('x', 'e', 'p', 0), // xep = Epi-Olmec + TRUETYPE_TAG('x', 'e', 'r', 0), // xer = Xerénte + TRUETYPE_TAG('x', 'e', 's', 0), // xes = Kesawai + TRUETYPE_TAG('x', 'e', 't', 0), // xet = Xetá + TRUETYPE_TAG('x', 'e', 'u', 0), // xeu = Keoru-Ahia + TRUETYPE_TAG('x', 'f', 'a', 0), // xfa = Faliscan + TRUETYPE_TAG('x', 'g', 'a', 0), // xga = Galatian + TRUETYPE_TAG('x', 'g', 'f', 0), // xgf = Gabrielino-Fernandeño + TRUETYPE_TAG('x', 'g', 'l', 0), // xgl = Galindan + TRUETYPE_TAG('x', 'g', 'n', 0), // xgn = Mongolian languages + TRUETYPE_TAG('x', 'g', 'r', 0), // xgr = Garza + TRUETYPE_TAG('x', 'h', 'a', 0), // xha = Harami + TRUETYPE_TAG('x', 'h', 'c', 0), // xhc = Hunnic + TRUETYPE_TAG('x', 'h', 'd', 0), // xhd = Hadrami + TRUETYPE_TAG('x', 'h', 'e', 0), // xhe = Khetrani + TRUETYPE_TAG('x', 'h', 'r', 0), // xhr = Hernican + TRUETYPE_TAG('x', 'h', 't', 0), // xht = Hattic + TRUETYPE_TAG('x', 'h', 'u', 0), // xhu = Hurrian + TRUETYPE_TAG('x', 'h', 'v', 0), // xhv = Khua + TRUETYPE_TAG('x', 'i', 'a', 0), // xia = Xiandao + TRUETYPE_TAG('x', 'i', 'b', 0), // xib = Iberian + TRUETYPE_TAG('x', 'i', 'i', 0), // xii = Xiri + TRUETYPE_TAG('x', 'i', 'l', 0), // xil = Illyrian + TRUETYPE_TAG('x', 'i', 'n', 0), // xin = Xinca + TRUETYPE_TAG('x', 'i', 'p', 0), // xip = Xipináwa + TRUETYPE_TAG('x', 'i', 'r', 0), // xir = Xiriâna + TRUETYPE_TAG('x', 'i', 'v', 0), // xiv = Indus Valley Language + TRUETYPE_TAG('x', 'i', 'y', 0), // xiy = Xipaya + TRUETYPE_TAG('x', 'k', 'a', 0), // xka = Kalkoti + TRUETYPE_TAG('x', 'k', 'b', 0), // xkb = Northern Nago + TRUETYPE_TAG('x', 'k', 'c', 0), // xkc = Kho'ini + TRUETYPE_TAG('x', 'k', 'd', 0), // xkd = Mendalam Kayan + TRUETYPE_TAG('x', 'k', 'e', 0), // xke = Kereho + TRUETYPE_TAG('x', 'k', 'f', 0), // xkf = Khengkha + TRUETYPE_TAG('x', 'k', 'g', 0), // xkg = Kagoro + TRUETYPE_TAG('x', 'k', 'h', 0), // xkh = Karahawyana + TRUETYPE_TAG('x', 'k', 'i', 0), // xki = Kenyan Sign Language + TRUETYPE_TAG('x', 'k', 'j', 0), // xkj = Kajali + TRUETYPE_TAG('x', 'k', 'k', 0), // xkk = Kaco' + TRUETYPE_TAG('x', 'k', 'l', 0), // xkl = Mainstream Kenyah + TRUETYPE_TAG('x', 'k', 'n', 0), // xkn = Kayan River Kayan + TRUETYPE_TAG('x', 'k', 'o', 0), // xko = Kiorr + TRUETYPE_TAG('x', 'k', 'p', 0), // xkp = Kabatei + TRUETYPE_TAG('x', 'k', 'q', 0), // xkq = Koroni + TRUETYPE_TAG('x', 'k', 'r', 0), // xkr = Xakriabá + TRUETYPE_TAG('x', 'k', 's', 0), // xks = Kumbewaha + TRUETYPE_TAG('x', 'k', 't', 0), // xkt = Kantosi + TRUETYPE_TAG('x', 'k', 'u', 0), // xku = Kaamba + TRUETYPE_TAG('x', 'k', 'v', 0), // xkv = Kgalagadi + TRUETYPE_TAG('x', 'k', 'w', 0), // xkw = Kembra + TRUETYPE_TAG('x', 'k', 'x', 0), // xkx = Karore + TRUETYPE_TAG('x', 'k', 'y', 0), // xky = Uma' Lasan + TRUETYPE_TAG('x', 'k', 'z', 0), // xkz = Kurtokha + TRUETYPE_TAG('x', 'l', 'a', 0), // xla = Kamula + TRUETYPE_TAG('x', 'l', 'b', 0), // xlb = Loup B + TRUETYPE_TAG('x', 'l', 'c', 0), // xlc = Lycian + TRUETYPE_TAG('x', 'l', 'd', 0), // xld = Lydian + TRUETYPE_TAG('x', 'l', 'e', 0), // xle = Lemnian + TRUETYPE_TAG('x', 'l', 'g', 0), // xlg = Ligurian (Ancient) + TRUETYPE_TAG('x', 'l', 'i', 0), // xli = Liburnian + TRUETYPE_TAG('x', 'l', 'n', 0), // xln = Alanic + TRUETYPE_TAG('x', 'l', 'o', 0), // xlo = Loup A + TRUETYPE_TAG('x', 'l', 'p', 0), // xlp = Lepontic + TRUETYPE_TAG('x', 'l', 's', 0), // xls = Lusitanian + TRUETYPE_TAG('x', 'l', 'u', 0), // xlu = Cuneiform Luwian + TRUETYPE_TAG('x', 'l', 'y', 0), // xly = Elymian + TRUETYPE_TAG('x', 'm', 'a', 0), // xma = Mushungulu + TRUETYPE_TAG('x', 'm', 'b', 0), // xmb = Mbonga + TRUETYPE_TAG('x', 'm', 'c', 0), // xmc = Makhuwa-Marrevone + TRUETYPE_TAG('x', 'm', 'd', 0), // xmd = Mbudum + TRUETYPE_TAG('x', 'm', 'e', 0), // xme = Median + TRUETYPE_TAG('x', 'm', 'f', 0), // xmf = Mingrelian + TRUETYPE_TAG('x', 'm', 'g', 0), // xmg = Mengaka + TRUETYPE_TAG('x', 'm', 'h', 0), // xmh = Kuku-Muminh + TRUETYPE_TAG('x', 'm', 'j', 0), // xmj = Majera + TRUETYPE_TAG('x', 'm', 'k', 0), // xmk = Ancient Macedonian + TRUETYPE_TAG('x', 'm', 'l', 0), // xml = Malaysian Sign Language + TRUETYPE_TAG('x', 'm', 'm', 0), // xmm = Manado Malay + TRUETYPE_TAG('x', 'm', 'n', 0), // xmn = Manichaean Middle Persian + TRUETYPE_TAG('x', 'm', 'o', 0), // xmo = Morerebi + TRUETYPE_TAG('x', 'm', 'p', 0), // xmp = Kuku-Mu'inh + TRUETYPE_TAG('x', 'm', 'q', 0), // xmq = Kuku-Mangk + TRUETYPE_TAG('x', 'm', 'r', 0), // xmr = Meroitic + TRUETYPE_TAG('x', 'm', 's', 0), // xms = Moroccan Sign Language + TRUETYPE_TAG('x', 'm', 't', 0), // xmt = Matbat + TRUETYPE_TAG('x', 'm', 'u', 0), // xmu = Kamu + TRUETYPE_TAG('x', 'm', 'v', 0), // xmv = Antankarana Malagasy + TRUETYPE_TAG('x', 'm', 'w', 0), // xmw = Tsimihety Malagasy + TRUETYPE_TAG('x', 'm', 'x', 0), // xmx = Maden + TRUETYPE_TAG('x', 'm', 'y', 0), // xmy = Mayaguduna + TRUETYPE_TAG('x', 'm', 'z', 0), // xmz = Mori Bawah + TRUETYPE_TAG('x', 'n', 'a', 0), // xna = Ancient North Arabian + TRUETYPE_TAG('x', 'n', 'b', 0), // xnb = Kanakanabu + TRUETYPE_TAG('x', 'n', 'd', 0), // xnd = Na-Dene languages + TRUETYPE_TAG('x', 'n', 'g', 0), // xng = Middle Mongolian + TRUETYPE_TAG('x', 'n', 'h', 0), // xnh = Kuanhua + TRUETYPE_TAG('x', 'n', 'n', 0), // xnn = Northern Kankanay + TRUETYPE_TAG('x', 'n', 'o', 0), // xno = Anglo-Norman + TRUETYPE_TAG('x', 'n', 'r', 0), // xnr = Kangri + TRUETYPE_TAG('x', 'n', 's', 0), // xns = Kanashi + TRUETYPE_TAG('x', 'n', 't', 0), // xnt = Narragansett + TRUETYPE_TAG('x', 'o', 'c', 0), // xoc = O'chi'chi' + TRUETYPE_TAG('x', 'o', 'd', 0), // xod = Kokoda + TRUETYPE_TAG('x', 'o', 'g', 0), // xog = Soga + TRUETYPE_TAG('x', 'o', 'i', 0), // xoi = Kominimung + TRUETYPE_TAG('x', 'o', 'k', 0), // xok = Xokleng + TRUETYPE_TAG('x', 'o', 'm', 0), // xom = Komo (Sudan) + TRUETYPE_TAG('x', 'o', 'n', 0), // xon = Konkomba + TRUETYPE_TAG('x', 'o', 'o', 0), // xoo = Xukurú + TRUETYPE_TAG('x', 'o', 'p', 0), // xop = Kopar + TRUETYPE_TAG('x', 'o', 'r', 0), // xor = Korubo + TRUETYPE_TAG('x', 'o', 'w', 0), // xow = Kowaki + TRUETYPE_TAG('x', 'p', 'c', 0), // xpc = Pecheneg + TRUETYPE_TAG('x', 'p', 'e', 0), // xpe = Liberia Kpelle + TRUETYPE_TAG('x', 'p', 'g', 0), // xpg = Phrygian + TRUETYPE_TAG('x', 'p', 'i', 0), // xpi = Pictish + TRUETYPE_TAG('x', 'p', 'k', 0), // xpk = Kulina Pano + TRUETYPE_TAG('x', 'p', 'm', 0), // xpm = Pumpokol + TRUETYPE_TAG('x', 'p', 'n', 0), // xpn = Kapinawá + TRUETYPE_TAG('x', 'p', 'o', 0), // xpo = Pochutec + TRUETYPE_TAG('x', 'p', 'p', 0), // xpp = Puyo-Paekche + TRUETYPE_TAG('x', 'p', 'q', 0), // xpq = Mohegan-Pequot + TRUETYPE_TAG('x', 'p', 'r', 0), // xpr = Parthian + TRUETYPE_TAG('x', 'p', 's', 0), // xps = Pisidian + TRUETYPE_TAG('x', 'p', 'u', 0), // xpu = Punic + TRUETYPE_TAG('x', 'p', 'y', 0), // xpy = Puyo + TRUETYPE_TAG('x', 'q', 'a', 0), // xqa = Karakhanid + TRUETYPE_TAG('x', 'q', 't', 0), // xqt = Qatabanian + TRUETYPE_TAG('x', 'r', 'a', 0), // xra = Krahô + TRUETYPE_TAG('x', 'r', 'b', 0), // xrb = Eastern Karaboro + TRUETYPE_TAG('x', 'r', 'e', 0), // xre = Kreye + TRUETYPE_TAG('x', 'r', 'i', 0), // xri = Krikati-Timbira + TRUETYPE_TAG('x', 'r', 'm', 0), // xrm = Armazic + TRUETYPE_TAG('x', 'r', 'n', 0), // xrn = Arin + TRUETYPE_TAG('x', 'r', 'r', 0), // xrr = Raetic + TRUETYPE_TAG('x', 'r', 't', 0), // xrt = Aranama-Tamique + TRUETYPE_TAG('x', 'r', 'u', 0), // xru = Marriammu + TRUETYPE_TAG('x', 'r', 'w', 0), // xrw = Karawa + TRUETYPE_TAG('x', 's', 'a', 0), // xsa = Sabaean + TRUETYPE_TAG('x', 's', 'b', 0), // xsb = Tinà Sambal + TRUETYPE_TAG('x', 's', 'c', 0), // xsc = Scythian + TRUETYPE_TAG('x', 's', 'd', 0), // xsd = Sidetic + TRUETYPE_TAG('x', 's', 'e', 0), // xse = Sempan + TRUETYPE_TAG('x', 's', 'h', 0), // xsh = Shamang + TRUETYPE_TAG('x', 's', 'i', 0), // xsi = Sio + TRUETYPE_TAG('x', 's', 'j', 0), // xsj = Subi + TRUETYPE_TAG('x', 's', 'l', 0), // xsl = South Slavey + TRUETYPE_TAG('x', 's', 'm', 0), // xsm = Kasem + TRUETYPE_TAG('x', 's', 'n', 0), // xsn = Sanga (Nigeria) + TRUETYPE_TAG('x', 's', 'o', 0), // xso = Solano + TRUETYPE_TAG('x', 's', 'p', 0), // xsp = Silopi + TRUETYPE_TAG('x', 's', 'q', 0), // xsq = Makhuwa-Saka + TRUETYPE_TAG('x', 's', 'r', 0), // xsr = Sherpa + TRUETYPE_TAG('x', 's', 's', 0), // xss = Assan + TRUETYPE_TAG('x', 's', 'u', 0), // xsu = Sanumá + TRUETYPE_TAG('x', 's', 'v', 0), // xsv = Sudovian + TRUETYPE_TAG('x', 's', 'y', 0), // xsy = Saisiyat + TRUETYPE_TAG('x', 't', 'a', 0), // xta = Alcozauca Mixtec + TRUETYPE_TAG('x', 't', 'b', 0), // xtb = Chazumba Mixtec + TRUETYPE_TAG('x', 't', 'c', 0), // xtc = Katcha-Kadugli-Miri + TRUETYPE_TAG('x', 't', 'd', 0), // xtd = Diuxi-Tilantongo Mixtec + TRUETYPE_TAG('x', 't', 'e', 0), // xte = Ketengban + TRUETYPE_TAG('x', 't', 'g', 0), // xtg = Transalpine Gaulish + TRUETYPE_TAG('x', 't', 'i', 0), // xti = Sinicahua Mixtec + TRUETYPE_TAG('x', 't', 'j', 0), // xtj = San Juan Teita Mixtec + TRUETYPE_TAG('x', 't', 'l', 0), // xtl = Tijaltepec Mixtec + TRUETYPE_TAG('x', 't', 'm', 0), // xtm = Magdalena Peñasco Mixtec + TRUETYPE_TAG('x', 't', 'n', 0), // xtn = Northern Tlaxiaco Mixtec + TRUETYPE_TAG('x', 't', 'o', 0), // xto = Tokharian A + TRUETYPE_TAG('x', 't', 'p', 0), // xtp = San Miguel Piedras Mixtec + TRUETYPE_TAG('x', 't', 'q', 0), // xtq = Tumshuqese + TRUETYPE_TAG('x', 't', 'r', 0), // xtr = Early Tripuri + TRUETYPE_TAG('x', 't', 's', 0), // xts = Sindihui Mixtec + TRUETYPE_TAG('x', 't', 't', 0), // xtt = Tacahua Mixtec + TRUETYPE_TAG('x', 't', 'u', 0), // xtu = Cuyamecalco Mixtec + TRUETYPE_TAG('x', 't', 'w', 0), // xtw = Tawandê + TRUETYPE_TAG('x', 't', 'y', 0), // xty = Yoloxochitl Mixtec + TRUETYPE_TAG('x', 't', 'z', 0), // xtz = Tasmanian + TRUETYPE_TAG('x', 'u', 'a', 0), // xua = Alu Kurumba + TRUETYPE_TAG('x', 'u', 'b', 0), // xub = Betta Kurumba + TRUETYPE_TAG('x', 'u', 'g', 0), // xug = Kunigami + TRUETYPE_TAG('x', 'u', 'j', 0), // xuj = Jennu Kurumba + TRUETYPE_TAG('x', 'u', 'm', 0), // xum = Umbrian + TRUETYPE_TAG('x', 'u', 'o', 0), // xuo = Kuo + TRUETYPE_TAG('x', 'u', 'p', 0), // xup = Upper Umpqua + TRUETYPE_TAG('x', 'u', 'r', 0), // xur = Urartian + TRUETYPE_TAG('x', 'u', 't', 0), // xut = Kuthant + TRUETYPE_TAG('x', 'u', 'u', 0), // xuu = Kxoe + TRUETYPE_TAG('x', 'v', 'e', 0), // xve = Venetic + TRUETYPE_TAG('x', 'v', 'i', 0), // xvi = Kamviri + TRUETYPE_TAG('x', 'v', 'n', 0), // xvn = Vandalic + TRUETYPE_TAG('x', 'v', 'o', 0), // xvo = Volscian + TRUETYPE_TAG('x', 'v', 's', 0), // xvs = Vestinian + TRUETYPE_TAG('x', 'w', 'a', 0), // xwa = Kwaza + TRUETYPE_TAG('x', 'w', 'c', 0), // xwc = Woccon + TRUETYPE_TAG('x', 'w', 'e', 0), // xwe = Xwela Gbe + TRUETYPE_TAG('x', 'w', 'g', 0), // xwg = Kwegu + TRUETYPE_TAG('x', 'w', 'l', 0), // xwl = Western Xwla Gbe + TRUETYPE_TAG('x', 'w', 'o', 0), // xwo = Written Oirat + TRUETYPE_TAG('x', 'w', 'r', 0), // xwr = Kwerba Mamberamo + TRUETYPE_TAG('x', 'x', 'b', 0), // xxb = Boro (Ghana) + TRUETYPE_TAG('x', 'x', 'k', 0), // xxk = Ke'o + TRUETYPE_TAG('x', 'x', 'r', 0), // xxr = Koropó + TRUETYPE_TAG('x', 'x', 't', 0), // xxt = Tambora + TRUETYPE_TAG('x', 'y', 'l', 0), // xyl = Yalakalore + TRUETYPE_TAG('x', 'z', 'h', 0), // xzh = Zhang-Zhung + TRUETYPE_TAG('x', 'z', 'm', 0), // xzm = Zemgalian + TRUETYPE_TAG('x', 'z', 'p', 0), // xzp = Ancient Zapotec + TRUETYPE_TAG('y', 'a', 'a', 0), // yaa = Yaminahua + TRUETYPE_TAG('y', 'a', 'b', 0), // yab = Yuhup + TRUETYPE_TAG('y', 'a', 'c', 0), // yac = Pass Valley Yali + TRUETYPE_TAG('y', 'a', 'd', 0), // yad = Yagua + TRUETYPE_TAG('y', 'a', 'e', 0), // yae = Pumé + TRUETYPE_TAG('y', 'a', 'f', + 0), // yaf = Yaka (Democratic Republic of Congo) + TRUETYPE_TAG('y', 'a', 'g', 0), // yag = Yámana + TRUETYPE_TAG('y', 'a', 'h', 0), // yah = Yazgulyam + TRUETYPE_TAG('y', 'a', 'i', 0), // yai = Yagnobi + TRUETYPE_TAG('y', 'a', 'j', 0), // yaj = Banda-Yangere + TRUETYPE_TAG('y', 'a', 'k', 0), // yak = Yakama + TRUETYPE_TAG('y', 'a', 'l', 0), // yal = Yalunka + TRUETYPE_TAG('y', 'a', 'm', 0), // yam = Yamba + TRUETYPE_TAG('y', 'a', 'n', 0), // yan = Mayangna + TRUETYPE_TAG('y', 'a', 'o', 0), // yao = Yao + TRUETYPE_TAG('y', 'a', 'p', 0), // yap = Yapese + TRUETYPE_TAG('y', 'a', 'q', 0), // yaq = Yaqui + TRUETYPE_TAG('y', 'a', 'r', 0), // yar = Yabarana + TRUETYPE_TAG('y', 'a', 's', 0), // yas = Nugunu (Cameroon) + TRUETYPE_TAG('y', 'a', 't', 0), // yat = Yambeta + TRUETYPE_TAG('y', 'a', 'u', 0), // yau = Yuwana + TRUETYPE_TAG('y', 'a', 'v', 0), // yav = Yangben + TRUETYPE_TAG('y', 'a', 'w', 0), // yaw = Yawalapití + TRUETYPE_TAG('y', 'a', 'x', 0), // yax = Yauma + TRUETYPE_TAG('y', 'a', 'y', 0), // yay = Agwagwune + TRUETYPE_TAG('y', 'a', 'z', 0), // yaz = Lokaa + TRUETYPE_TAG('y', 'b', 'a', 0), // yba = Yala + TRUETYPE_TAG('y', 'b', 'b', 0), // ybb = Yemba + TRUETYPE_TAG('y', 'b', 'd', 0), // ybd = Yangbye + TRUETYPE_TAG('y', 'b', 'e', 0), // ybe = West Yugur + TRUETYPE_TAG('y', 'b', 'h', 0), // ybh = Yakha + TRUETYPE_TAG('y', 'b', 'i', 0), // ybi = Yamphu + TRUETYPE_TAG('y', 'b', 'j', 0), // ybj = Hasha + TRUETYPE_TAG('y', 'b', 'k', 0), // ybk = Bokha + TRUETYPE_TAG('y', 'b', 'l', 0), // ybl = Yukuben + TRUETYPE_TAG('y', 'b', 'm', 0), // ybm = Yaben + TRUETYPE_TAG('y', 'b', 'n', 0), // ybn = Yabaâna + TRUETYPE_TAG('y', 'b', 'o', 0), // ybo = Yabong + TRUETYPE_TAG('y', 'b', 'x', 0), // ybx = Yawiyo + TRUETYPE_TAG('y', 'b', 'y', 0), // yby = Yaweyuha + TRUETYPE_TAG('y', 'c', 'h', 0), // ych = Chesu + TRUETYPE_TAG('y', 'c', 'l', 0), // ycl = Lolopo + TRUETYPE_TAG('y', 'c', 'n', 0), // ycn = Yucuna + TRUETYPE_TAG('y', 'c', 'p', 0), // ycp = Chepya + TRUETYPE_TAG('y', 'd', 'd', 0), // ydd = Eastern Yiddish + TRUETYPE_TAG('y', 'd', 'e', 0), // yde = Yangum Dey + TRUETYPE_TAG('y', 'd', 'g', 0), // ydg = Yidgha + TRUETYPE_TAG('y', 'd', 'k', 0), // ydk = Yoidik + TRUETYPE_TAG('y', 'd', 's', 0), // yds = Yiddish Sign Language + TRUETYPE_TAG('y', 'e', 'a', 0), // yea = Ravula + TRUETYPE_TAG('y', 'e', 'c', 0), // yec = Yeniche + TRUETYPE_TAG('y', 'e', 'e', 0), // yee = Yimas + TRUETYPE_TAG('y', 'e', 'i', 0), // yei = Yeni + TRUETYPE_TAG('y', 'e', 'j', 0), // yej = Yevanic + TRUETYPE_TAG('y', 'e', 'l', 0), // yel = Yela + TRUETYPE_TAG('y', 'e', 'n', 0), // yen = Yendang + TRUETYPE_TAG('y', 'e', 'r', 0), // yer = Tarok + TRUETYPE_TAG('y', 'e', 's', 0), // yes = Yeskwa + TRUETYPE_TAG('y', 'e', 't', 0), // yet = Yetfa + TRUETYPE_TAG('y', 'e', 'u', 0), // yeu = Yerukula + TRUETYPE_TAG('y', 'e', 'v', 0), // yev = Yapunda + TRUETYPE_TAG('y', 'e', 'y', 0), // yey = Yeyi + TRUETYPE_TAG('y', 'g', 'l', 0), // ygl = Yangum Gel + TRUETYPE_TAG('y', 'g', 'm', 0), // ygm = Yagomi + TRUETYPE_TAG('y', 'g', 'p', 0), // ygp = Gepo + TRUETYPE_TAG('y', 'g', 'r', 0), // ygr = Yagaria + TRUETYPE_TAG('y', 'g', 'w', 0), // ygw = Yagwoia + TRUETYPE_TAG('y', 'h', 'a', 0), // yha = Baha Buyang + TRUETYPE_TAG('y', 'h', 'd', 0), // yhd = Judeo-Iraqi Arabic + TRUETYPE_TAG('y', 'h', 'l', 0), // yhl = Hlepho Phowa + TRUETYPE_TAG('y', 'i', 'a', 0), // yia = Yinggarda + TRUETYPE_TAG('y', 'i', 'f', 0), // yif = Ache + TRUETYPE_TAG('y', 'i', 'g', 0), // yig = Wusa Nasu + TRUETYPE_TAG('y', 'i', 'h', 0), // yih = Western Yiddish + TRUETYPE_TAG('y', 'i', 'i', 0), // yii = Yidiny + TRUETYPE_TAG('y', 'i', 'j', 0), // yij = Yindjibarndi + TRUETYPE_TAG('y', 'i', 'k', 0), // yik = Dongshanba Lalo + TRUETYPE_TAG('y', 'i', 'l', 0), // yil = Yindjilandji + TRUETYPE_TAG('y', 'i', 'm', 0), // yim = Yimchungru Naga + TRUETYPE_TAG('y', 'i', 'n', 0), // yin = Yinchia + TRUETYPE_TAG('y', 'i', 'p', 0), // yip = Pholo + TRUETYPE_TAG('y', 'i', 'q', 0), // yiq = Miqie + TRUETYPE_TAG('y', 'i', 'r', 0), // yir = North Awyu + TRUETYPE_TAG('y', 'i', 's', 0), // yis = Yis + TRUETYPE_TAG('y', 'i', 't', 0), // yit = Eastern Lalu + TRUETYPE_TAG('y', 'i', 'u', 0), // yiu = Awu + TRUETYPE_TAG('y', 'i', 'v', 0), // yiv = Northern Nisu + TRUETYPE_TAG('y', 'i', 'x', 0), // yix = Axi Yi + TRUETYPE_TAG('y', 'i', 'y', 0), // yiy = Yir Yoront + TRUETYPE_TAG('y', 'i', 'z', 0), // yiz = Azhe + TRUETYPE_TAG('y', 'k', 'a', 0), // yka = Yakan + TRUETYPE_TAG('y', 'k', 'g', 0), // ykg = Northern Yukaghir + TRUETYPE_TAG('y', 'k', 'i', 0), // yki = Yoke + TRUETYPE_TAG('y', 'k', 'k', 0), // ykk = Yakaikeke + TRUETYPE_TAG('y', 'k', 'l', 0), // ykl = Khlula + TRUETYPE_TAG('y', 'k', 'm', 0), // ykm = Kap + TRUETYPE_TAG('y', 'k', 'o', 0), // yko = Yasa + TRUETYPE_TAG('y', 'k', 'r', 0), // ykr = Yekora + TRUETYPE_TAG('y', 'k', 't', 0), // ykt = Kathu + TRUETYPE_TAG('y', 'k', 'y', 0), // yky = Yakoma + TRUETYPE_TAG('y', 'l', 'a', 0), // yla = Yaul + TRUETYPE_TAG('y', 'l', 'b', 0), // ylb = Yaleba + TRUETYPE_TAG('y', 'l', 'e', 0), // yle = Yele + TRUETYPE_TAG('y', 'l', 'g', 0), // ylg = Yelogu + TRUETYPE_TAG('y', 'l', 'i', 0), // yli = Angguruk Yali + TRUETYPE_TAG('y', 'l', 'l', 0), // yll = Yil + TRUETYPE_TAG('y', 'l', 'm', 0), // ylm = Limi + TRUETYPE_TAG('y', 'l', 'n', 0), // yln = Langnian Buyang + TRUETYPE_TAG('y', 'l', 'o', 0), // ylo = Naluo Yi + TRUETYPE_TAG('y', 'l', 'r', 0), // ylr = Yalarnnga + TRUETYPE_TAG('y', 'l', 'u', 0), // ylu = Aribwaung + TRUETYPE_TAG('y', 'l', 'y', 0), // yly = Nyâlayu + TRUETYPE_TAG('y', 'm', 'a', 0), // yma = Yamphe + TRUETYPE_TAG('y', 'm', 'b', 0), // ymb = Yambes + TRUETYPE_TAG('y', 'm', 'c', 0), // ymc = Southern Muji + TRUETYPE_TAG('y', 'm', 'd', 0), // ymd = Muda + TRUETYPE_TAG('y', 'm', 'e', 0), // yme = Yameo + TRUETYPE_TAG('y', 'm', 'g', 0), // ymg = Yamongeri + TRUETYPE_TAG('y', 'm', 'h', 0), // ymh = Mili + TRUETYPE_TAG('y', 'm', 'i', 0), // ymi = Moji + TRUETYPE_TAG('y', 'm', 'k', 0), // ymk = Makwe + TRUETYPE_TAG('y', 'm', 'l', 0), // yml = Iamalele + TRUETYPE_TAG('y', 'm', 'm', 0), // ymm = Maay + TRUETYPE_TAG('y', 'm', 'n', 0), // ymn = Yamna + TRUETYPE_TAG('y', 'm', 'o', 0), // ymo = Yangum Mon + TRUETYPE_TAG('y', 'm', 'p', 0), // ymp = Yamap + TRUETYPE_TAG('y', 'm', 'q', 0), // ymq = Qila Muji + TRUETYPE_TAG('y', 'm', 'r', 0), // ymr = Malasar + TRUETYPE_TAG('y', 'm', 's', 0), // yms = Mysian + TRUETYPE_TAG('y', 'm', 't', 0), // ymt = Mator-Taygi-Karagas + TRUETYPE_TAG('y', 'm', 'x', 0), // ymx = Northern Muji + TRUETYPE_TAG('y', 'm', 'z', 0), // ymz = Muzi + TRUETYPE_TAG('y', 'n', 'a', 0), // yna = Aluo + TRUETYPE_TAG('y', 'n', 'd', 0), // ynd = Yandruwandha + TRUETYPE_TAG('y', 'n', 'e', 0), // yne = Lang'e + TRUETYPE_TAG('y', 'n', 'g', 0), // yng = Yango + TRUETYPE_TAG('y', 'n', 'h', 0), // ynh = Yangho + TRUETYPE_TAG('y', 'n', 'k', 0), // ynk = Naukan Yupik + TRUETYPE_TAG('y', 'n', 'l', 0), // ynl = Yangulam + TRUETYPE_TAG('y', 'n', 'n', 0), // ynn = Yana + TRUETYPE_TAG('y', 'n', 'o', 0), // yno = Yong + TRUETYPE_TAG('y', 'n', 's', 0), // yns = Yansi + TRUETYPE_TAG('y', 'n', 'u', 0), // ynu = Yahuna + TRUETYPE_TAG('y', 'o', 'b', 0), // yob = Yoba + TRUETYPE_TAG('y', 'o', 'g', 0), // yog = Yogad + TRUETYPE_TAG('y', 'o', 'i', 0), // yoi = Yonaguni + TRUETYPE_TAG('y', 'o', 'k', 0), // yok = Yokuts + TRUETYPE_TAG('y', 'o', 'l', 0), // yol = Yola + TRUETYPE_TAG('y', 'o', 'm', 0), // yom = Yombe + TRUETYPE_TAG('y', 'o', 'n', 0), // yon = Yongkom + TRUETYPE_TAG('y', 'o', 's', 0), // yos = Yos + TRUETYPE_TAG('y', 'o', 'x', 0), // yox = Yoron + TRUETYPE_TAG('y', 'o', 'y', 0), // yoy = Yoy + TRUETYPE_TAG('y', 'p', 'a', 0), // ypa = Phala + TRUETYPE_TAG('y', 'p', 'b', 0), // ypb = Labo Phowa + TRUETYPE_TAG('y', 'p', 'g', 0), // ypg = Phola + TRUETYPE_TAG('y', 'p', 'h', 0), // yph = Phupha + TRUETYPE_TAG('y', 'p', 'k', 0), // ypk = Yupik languages + TRUETYPE_TAG('y', 'p', 'm', 0), // ypm = Phuma + TRUETYPE_TAG('y', 'p', 'n', 0), // ypn = Ani Phowa + TRUETYPE_TAG('y', 'p', 'o', 0), // ypo = Alo Phola + TRUETYPE_TAG('y', 'p', 'p', 0), // ypp = Phupa + TRUETYPE_TAG('y', 'p', 'z', 0), // ypz = Phuza + TRUETYPE_TAG('y', 'r', 'a', 0), // yra = Yerakai + TRUETYPE_TAG('y', 'r', 'b', 0), // yrb = Yareba + TRUETYPE_TAG('y', 'r', 'e', 0), // yre = Yaouré + TRUETYPE_TAG('y', 'r', 'i', 0), // yri = Yarí + TRUETYPE_TAG('y', 'r', 'k', 0), // yrk = Nenets + TRUETYPE_TAG('y', 'r', 'l', 0), // yrl = Nhengatu + TRUETYPE_TAG('y', 'r', 'n', 0), // yrn = Yerong + TRUETYPE_TAG('y', 'r', 's', 0), // yrs = Yarsun + TRUETYPE_TAG('y', 'r', 'w', 0), // yrw = Yarawata + TRUETYPE_TAG('y', 's', 'c', 0), // ysc = Yassic + TRUETYPE_TAG('y', 's', 'd', 0), // ysd = Samatao + TRUETYPE_TAG('y', 's', 'l', 0), // ysl = Yugoslavian Sign Language + TRUETYPE_TAG('y', 's', 'n', 0), // ysn = Sani + TRUETYPE_TAG('y', 's', 'o', 0), // yso = Nisi (China) + TRUETYPE_TAG('y', 's', 'p', 0), // ysp = Southern Lolopo + TRUETYPE_TAG('y', 's', 'r', 0), // ysr = Sirenik Yupik + TRUETYPE_TAG('y', 's', 's', 0), // yss = Yessan-Mayo + TRUETYPE_TAG('y', 's', 'y', 0), // ysy = Sanie + TRUETYPE_TAG('y', 't', 'a', 0), // yta = Talu + TRUETYPE_TAG('y', 't', 'l', 0), // ytl = Tanglang + TRUETYPE_TAG('y', 't', 'p', 0), // ytp = Thopho + TRUETYPE_TAG('y', 't', 'w', 0), // ytw = Yout Wam + TRUETYPE_TAG('y', 'u', 'a', 0), // yua = Yucateco + TRUETYPE_TAG('y', 'u', 'b', 0), // yub = Yugambal + TRUETYPE_TAG('y', 'u', 'c', 0), // yuc = Yuchi + TRUETYPE_TAG('y', 'u', 'd', 0), // yud = Judeo-Tripolitanian Arabic + TRUETYPE_TAG('y', 'u', 'e', 0), // yue = Yue Chinese + TRUETYPE_TAG('y', 'u', 'f', 0), // yuf = Havasupai-Walapai-Yavapai + TRUETYPE_TAG('y', 'u', 'g', 0), // yug = Yug + TRUETYPE_TAG('y', 'u', 'i', 0), // yui = Yurutí + TRUETYPE_TAG('y', 'u', 'j', 0), // yuj = Karkar-Yuri + TRUETYPE_TAG('y', 'u', 'k', 0), // yuk = Yuki + TRUETYPE_TAG('y', 'u', 'l', 0), // yul = Yulu + TRUETYPE_TAG('y', 'u', 'm', 0), // yum = Quechan + TRUETYPE_TAG('y', 'u', 'n', 0), // yun = Bena (Nigeria) + TRUETYPE_TAG('y', 'u', 'p', 0), // yup = Yukpa + TRUETYPE_TAG('y', 'u', 'q', 0), // yuq = Yuqui + TRUETYPE_TAG('y', 'u', 'r', 0), // yur = Yurok + TRUETYPE_TAG('y', 'u', 't', 0), // yut = Yopno + TRUETYPE_TAG('y', 'u', 'u', 0), // yuu = Yugh + TRUETYPE_TAG('y', 'u', 'w', 0), // yuw = Yau (Morobe Province) + TRUETYPE_TAG('y', 'u', 'x', 0), // yux = Southern Yukaghir + TRUETYPE_TAG('y', 'u', 'y', 0), // yuy = East Yugur + TRUETYPE_TAG('y', 'u', 'z', 0), // yuz = Yuracare + TRUETYPE_TAG('y', 'v', 'a', 0), // yva = Yawa + TRUETYPE_TAG('y', 'v', 't', 0), // yvt = Yavitero + TRUETYPE_TAG('y', 'w', 'a', 0), // ywa = Kalou + TRUETYPE_TAG('y', 'w', 'l', 0), // ywl = Western Lalu + TRUETYPE_TAG('y', 'w', 'n', 0), // ywn = Yawanawa + TRUETYPE_TAG('y', 'w', 'q', 0), // ywq = Wuding-Luquan Yi + TRUETYPE_TAG('y', 'w', 'r', 0), // ywr = Yawuru + TRUETYPE_TAG('y', 'w', 't', 0), // ywt = Xishanba Lalo + TRUETYPE_TAG('y', 'w', 'u', 0), // ywu = Wumeng Nasu + TRUETYPE_TAG('y', 'w', 'w', 0), // yww = Yawarawarga + TRUETYPE_TAG('y', 'y', 'u', 0), // yyu = Yau (Sandaun Province) + TRUETYPE_TAG('y', 'y', 'z', 0), // yyz = Ayizi + TRUETYPE_TAG('y', 'z', 'g', 0), // yzg = E'ma Buyang + TRUETYPE_TAG('y', 'z', 'k', 0), // yzk = Zokhuo + TRUETYPE_TAG('z', 'a', 'a', 0), // zaa = Sierra de Juárez Zapotec + TRUETYPE_TAG('z', 'a', 'b', 0), // zab = San Juan Guelavía Zapotec + TRUETYPE_TAG('z', 'a', 'c', 0), // zac = Ocotlán Zapotec + TRUETYPE_TAG('z', 'a', 'd', 0), // zad = Cajonos Zapotec + TRUETYPE_TAG('z', 'a', 'e', 0), // zae = Yareni Zapotec + TRUETYPE_TAG('z', 'a', 'f', 0), // zaf = Ayoquesco Zapotec + TRUETYPE_TAG('z', 'a', 'g', 0), // zag = Zaghawa + TRUETYPE_TAG('z', 'a', 'h', 0), // zah = Zangwal + TRUETYPE_TAG('z', 'a', 'i', 0), // zai = Isthmus Zapotec + TRUETYPE_TAG('z', 'a', 'j', 0), // zaj = Zaramo + TRUETYPE_TAG('z', 'a', 'k', 0), // zak = Zanaki + TRUETYPE_TAG('z', 'a', 'l', 0), // zal = Zauzou + TRUETYPE_TAG('z', 'a', 'm', 0), // zam = Miahuatlán Zapotec + TRUETYPE_TAG('z', 'a', 'o', 0), // zao = Ozolotepec Zapotec + TRUETYPE_TAG('z', 'a', 'p', 0), // zap = Zapotec + TRUETYPE_TAG('z', 'a', 'q', 0), // zaq = Aloápam Zapotec + TRUETYPE_TAG('z', 'a', 'r', 0), // zar = Rincón Zapotec + TRUETYPE_TAG('z', 'a', 's', 0), // zas = Santo Domingo Albarradas Zapotec + TRUETYPE_TAG('z', 'a', 't', 0), // zat = Tabaa Zapotec + TRUETYPE_TAG('z', 'a', 'u', 0), // zau = Zangskari + TRUETYPE_TAG('z', 'a', 'v', 0), // zav = Yatzachi Zapotec + TRUETYPE_TAG('z', 'a', 'w', 0), // zaw = Mitla Zapotec + TRUETYPE_TAG('z', 'a', 'x', 0), // zax = Xadani Zapotec + TRUETYPE_TAG('z', 'a', 'y', 0), // zay = Zayse-Zergulla + TRUETYPE_TAG('z', 'a', 'z', 0), // zaz = Zari + TRUETYPE_TAG('z', 'b', 'c', 0), // zbc = Central Berawan + TRUETYPE_TAG('z', 'b', 'e', 0), // zbe = East Berawan + TRUETYPE_TAG('z', 'b', 'l', 0), // zbl = Blissymbols + TRUETYPE_TAG('z', 'b', 't', 0), // zbt = Batui + TRUETYPE_TAG('z', 'b', 'w', 0), // zbw = West Berawan + TRUETYPE_TAG('z', 'c', 'a', 0), // zca = Coatecas Altas Zapotec + TRUETYPE_TAG('z', 'c', 'h', 0), // zch = Central Hongshuihe Zhuang + TRUETYPE_TAG('z', 'd', 'j', 0), // zdj = Ngazidja Comorian + TRUETYPE_TAG('z', 'e', 'a', 0), // zea = Zeeuws + TRUETYPE_TAG('z', 'e', 'g', 0), // zeg = Zenag + TRUETYPE_TAG('z', 'e', 'h', 0), // zeh = Eastern Hongshuihe Zhuang + TRUETYPE_TAG('z', 'e', 'n', 0), // zen = Zenaga + TRUETYPE_TAG('z', 'g', 'a', 0), // zga = Kinga + TRUETYPE_TAG('z', 'g', 'b', 0), // zgb = Guibei Zhuang + TRUETYPE_TAG('z', 'g', 'm', 0), // zgm = Minz Zhuang + TRUETYPE_TAG('z', 'g', 'n', 0), // zgn = Guibian Zhuang + TRUETYPE_TAG('z', 'g', 'r', 0), // zgr = Magori + TRUETYPE_TAG('z', 'h', 'b', 0), // zhb = Zhaba + TRUETYPE_TAG('z', 'h', 'd', 0), // zhd = Dai Zhuang + TRUETYPE_TAG('z', 'h', 'i', 0), // zhi = Zhire + TRUETYPE_TAG('z', 'h', 'n', 0), // zhn = Nong Zhuang + TRUETYPE_TAG('z', 'h', 'w', 0), // zhw = Zhoa + TRUETYPE_TAG('z', 'h', 'x', 0), // zhx = Chinese (family) + TRUETYPE_TAG('z', 'i', 'a', 0), // zia = Zia + TRUETYPE_TAG('z', 'i', 'b', 0), // zib = Zimbabwe Sign Language + TRUETYPE_TAG('z', 'i', 'k', 0), // zik = Zimakani + TRUETYPE_TAG('z', 'i', 'l', 0), // zil = Zialo + TRUETYPE_TAG('z', 'i', 'm', 0), // zim = Mesme + TRUETYPE_TAG('z', 'i', 'n', 0), // zin = Zinza + TRUETYPE_TAG('z', 'i', 'r', 0), // zir = Ziriya + TRUETYPE_TAG('z', 'i', 'w', 0), // ziw = Zigula + TRUETYPE_TAG('z', 'i', 'z', 0), // ziz = Zizilivakan + TRUETYPE_TAG('z', 'k', 'a', 0), // zka = Kaimbulawa + TRUETYPE_TAG('z', 'k', 'b', 0), // zkb = Koibal + TRUETYPE_TAG('z', 'k', 'g', 0), // zkg = Koguryo + TRUETYPE_TAG('z', 'k', 'h', 0), // zkh = Khorezmian + TRUETYPE_TAG('z', 'k', 'k', 0), // zkk = Karankawa + TRUETYPE_TAG('z', 'k', 'o', 0), // zko = Kott + TRUETYPE_TAG('z', 'k', 'p', 0), // zkp = São Paulo Kaingáng + TRUETYPE_TAG('z', 'k', 'r', 0), // zkr = Zakhring + TRUETYPE_TAG('z', 'k', 't', 0), // zkt = Kitan + TRUETYPE_TAG('z', 'k', 'u', 0), // zku = Kaurna + TRUETYPE_TAG('z', 'k', 'v', 0), // zkv = Krevinian + TRUETYPE_TAG('z', 'k', 'z', 0), // zkz = Khazar + TRUETYPE_TAG('z', 'l', 'e', 0), // zle = East Slavic languages + TRUETYPE_TAG('z', 'l', 'j', 0), // zlj = Liujiang Zhuang + TRUETYPE_TAG('z', 'l', 'm', 0), // zlm = Malay (individual language) + TRUETYPE_TAG('z', 'l', 'n', 0), // zln = Lianshan Zhuang + TRUETYPE_TAG('z', 'l', 'q', 0), // zlq = Liuqian Zhuang + TRUETYPE_TAG('z', 'l', 's', 0), // zls = South Slavic languages + TRUETYPE_TAG('z', 'l', 'w', 0), // zlw = West Slavic languages + TRUETYPE_TAG('z', 'm', 'a', 0), // zma = Manda (Australia) + TRUETYPE_TAG('z', 'm', 'b', 0), // zmb = Zimba + TRUETYPE_TAG('z', 'm', 'c', 0), // zmc = Margany + TRUETYPE_TAG('z', 'm', 'd', 0), // zmd = Maridan + TRUETYPE_TAG('z', 'm', 'e', 0), // zme = Mangerr + TRUETYPE_TAG('z', 'm', 'f', 0), // zmf = Mfinu + TRUETYPE_TAG('z', 'm', 'g', 0), // zmg = Marti Ke + TRUETYPE_TAG('z', 'm', 'h', 0), // zmh = Makolkol + TRUETYPE_TAG('z', 'm', 'i', 0), // zmi = Negeri Sembilan Malay + TRUETYPE_TAG('z', 'm', 'j', 0), // zmj = Maridjabin + TRUETYPE_TAG('z', 'm', 'k', 0), // zmk = Mandandanyi + TRUETYPE_TAG('z', 'm', 'l', 0), // zml = Madngele + TRUETYPE_TAG('z', 'm', 'm', 0), // zmm = Marimanindji + TRUETYPE_TAG('z', 'm', 'n', 0), // zmn = Mbangwe + TRUETYPE_TAG('z', 'm', 'o', 0), // zmo = Molo + TRUETYPE_TAG('z', 'm', 'p', 0), // zmp = Mpuono + TRUETYPE_TAG('z', 'm', 'q', 0), // zmq = Mituku + TRUETYPE_TAG('z', 'm', 'r', 0), // zmr = Maranunggu + TRUETYPE_TAG('z', 'm', 's', 0), // zms = Mbesa + TRUETYPE_TAG('z', 'm', 't', 0), // zmt = Maringarr + TRUETYPE_TAG('z', 'm', 'u', 0), // zmu = Muruwari + TRUETYPE_TAG('z', 'm', 'v', 0), // zmv = Mbariman-Gudhinma + TRUETYPE_TAG('z', 'm', 'w', 0), // zmw = Mbo (Democratic Republic of Congo) + TRUETYPE_TAG('z', 'm', 'x', 0), // zmx = Bomitaba + TRUETYPE_TAG('z', 'm', 'y', 0), // zmy = Mariyedi + TRUETYPE_TAG('z', 'm', 'z', 0), // zmz = Mbandja + TRUETYPE_TAG('z', 'n', 'a', 0), // zna = Zan Gula + TRUETYPE_TAG('z', 'n', 'd', 0), // znd = Zande languages + TRUETYPE_TAG('z', 'n', 'e', 0), // zne = Zande (individual language) + TRUETYPE_TAG('z', 'n', 'g', 0), // zng = Mang + TRUETYPE_TAG('z', 'n', 'k', 0), // znk = Manangkari + TRUETYPE_TAG('z', 'n', 's', 0), // zns = Mangas + TRUETYPE_TAG('z', 'o', 'c', 0), // zoc = Copainalá Zoque + TRUETYPE_TAG('z', 'o', 'h', 0), // zoh = Chimalapa Zoque + TRUETYPE_TAG('z', 'o', 'm', 0), // zom = Zou + TRUETYPE_TAG('z', 'o', 'o', 0), // zoo = Asunción Mixtepec Zapotec + TRUETYPE_TAG('z', 'o', 'q', 0), // zoq = Tabasco Zoque + TRUETYPE_TAG('z', 'o', 'r', 0), // zor = Rayón Zoque + TRUETYPE_TAG('z', 'o', 's', 0), // zos = Francisco León Zoque + TRUETYPE_TAG('z', 'p', 'a', 0), // zpa = Lachiguiri Zapotec + TRUETYPE_TAG('z', 'p', 'b', 0), // zpb = Yautepec Zapotec + TRUETYPE_TAG('z', 'p', 'c', 0), // zpc = Choapan Zapotec + TRUETYPE_TAG('z', 'p', 'd', 0), // zpd = Southeastern Ixtlán Zapotec + TRUETYPE_TAG('z', 'p', 'e', 0), // zpe = Petapa Zapotec + TRUETYPE_TAG('z', 'p', 'f', 0), // zpf = San Pedro Quiatoni Zapotec + TRUETYPE_TAG('z', 'p', 'g', 0), // zpg = Guevea De Humboldt Zapotec + TRUETYPE_TAG('z', 'p', 'h', 0), // zph = Totomachapan Zapotec + TRUETYPE_TAG('z', 'p', 'i', 0), // zpi = Santa María Quiegolani Zapotec + TRUETYPE_TAG('z', 'p', 'j', 0), // zpj = Quiavicuzas Zapotec + TRUETYPE_TAG('z', 'p', 'k', 0), // zpk = Tlacolulita Zapotec + TRUETYPE_TAG('z', 'p', 'l', 0), // zpl = Lachixío Zapotec + TRUETYPE_TAG('z', 'p', 'm', 0), // zpm = Mixtepec Zapotec + TRUETYPE_TAG('z', 'p', 'n', 0), // zpn = Santa Inés Yatzechi Zapotec + TRUETYPE_TAG('z', 'p', 'o', 0), // zpo = Amatlán Zapotec + TRUETYPE_TAG('z', 'p', 'p', 0), // zpp = El Alto Zapotec + TRUETYPE_TAG('z', 'p', 'q', 0), // zpq = Zoogocho Zapotec + TRUETYPE_TAG('z', 'p', 'r', 0), // zpr = Santiago Xanica Zapotec + TRUETYPE_TAG('z', 'p', 's', 0), // zps = Coatlán Zapotec + TRUETYPE_TAG('z', 'p', 't', 0), // zpt = San Vicente Coatlán Zapotec + TRUETYPE_TAG('z', 'p', 'u', 0), // zpu = Yalálag Zapotec + TRUETYPE_TAG('z', 'p', 'v', 0), // zpv = Chichicapan Zapotec + TRUETYPE_TAG('z', 'p', 'w', 0), // zpw = Zaniza Zapotec + TRUETYPE_TAG('z', 'p', 'x', 0), // zpx = San Baltazar Loxicha Zapotec + TRUETYPE_TAG('z', 'p', 'y', 0), // zpy = Mazaltepec Zapotec + TRUETYPE_TAG('z', 'p', 'z', 0), // zpz = Texmelucan Zapotec + TRUETYPE_TAG('z', 'q', 'e', 0), // zqe = Qiubei Zhuang + TRUETYPE_TAG('z', 'r', 'a', 0), // zra = Kara (Korea) + TRUETYPE_TAG('z', 'r', 'g', 0), // zrg = Mirgan + TRUETYPE_TAG('z', 'r', 'n', 0), // zrn = Zerenkel + TRUETYPE_TAG('z', 'r', 'o', 0), // zro = Záparo + TRUETYPE_TAG('z', 'r', 'p', 0), // zrp = Zarphatic + TRUETYPE_TAG('z', 'r', 's', 0), // zrs = Mairasi + TRUETYPE_TAG('z', 's', 'a', 0), // zsa = Sarasira + TRUETYPE_TAG('z', 's', 'k', 0), // zsk = Kaskean + TRUETYPE_TAG('z', 's', 'l', 0), // zsl = Zambian Sign Language + TRUETYPE_TAG('z', 's', 'm', 0), // zsm = Standard Malay + TRUETYPE_TAG('z', 's', 'r', 0), // zsr = Southern Rincon Zapotec + TRUETYPE_TAG('z', 's', 'u', 0), // zsu = Sukurum + TRUETYPE_TAG('z', 't', 'e', 0), // zte = Elotepec Zapotec + TRUETYPE_TAG('z', 't', 'g', 0), // ztg = Xanaguía Zapotec + TRUETYPE_TAG('z', 't', 'l', 0), // ztl = Lapaguía-Guivini Zapotec + TRUETYPE_TAG('z', 't', 'm', 0), // ztm = San Agustín Mixtepec Zapotec + TRUETYPE_TAG('z', 't', 'n', 0), // ztn = Santa Catarina Albarradas Zapotec + TRUETYPE_TAG('z', 't', 'p', 0), // ztp = Loxicha Zapotec + TRUETYPE_TAG('z', 't', 'q', 0), // ztq = Quioquitani-Quierí Zapotec + TRUETYPE_TAG('z', 't', 's', 0), // zts = Tilquiapan Zapotec + TRUETYPE_TAG('z', 't', 't', 0), // ztt = Tejalapan Zapotec + TRUETYPE_TAG('z', 't', 'u', 0), // ztu = Güilá Zapotec + TRUETYPE_TAG('z', 't', 'x', 0), // ztx = Zaachila Zapotec + TRUETYPE_TAG('z', 't', 'y', 0), // zty = Yatee Zapotec + TRUETYPE_TAG('z', 'u', 'a', 0), // zua = Zeem + TRUETYPE_TAG('z', 'u', 'h', 0), // zuh = Tokano + TRUETYPE_TAG('z', 'u', 'm', 0), // zum = Kumzari + TRUETYPE_TAG('z', 'u', 'n', 0), // zun = Zuni + TRUETYPE_TAG('z', 'u', 'y', 0), // zuy = Zumaya + TRUETYPE_TAG('z', 'w', 'a', 0), // zwa = Zay + TRUETYPE_TAG('z', 'x', 'x', 0), // zxx = No linguistic content + TRUETYPE_TAG('z', 'y', 'b', 0), // zyb = Yongbei Zhuang + TRUETYPE_TAG('z', 'y', 'g', 0), // zyg = Yang Zhuang + TRUETYPE_TAG('z', 'y', 'j', 0), // zyj = Youjiang Zhuang + TRUETYPE_TAG('z', 'y', 'n', 0), // zyn = Yongnan Zhuang + TRUETYPE_TAG('z', 'y', 'p', 0), // zyp = Zyphe + TRUETYPE_TAG('z', 'z', 'a', 0), // zza = Zaza + TRUETYPE_TAG('z', 'z', 'j', 0), // zzj = Zuojiang Zhuang + 0x0 // end of language code list +}; + +/* + * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * * + */ diff --git a/gfx/thebes/gfxLineSegment.h b/gfx/thebes/gfxLineSegment.h new file mode 100644 index 0000000000..7f4ad13f61 --- /dev/null +++ b/gfx/thebes/gfxLineSegment.h @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_LINESEGMENT_H +#define GFX_LINESEGMENT_H + +#include "gfxTypes.h" +#include "gfxPoint.h" + +struct gfxLineSegment { + gfxLineSegment() {} + gfxLineSegment(const gfxPoint& aStart, const gfxPoint& aEnd) + : mStart(aStart), mEnd(aEnd) {} + + bool PointsOnSameSide(const gfxPoint& aOne, const gfxPoint& aTwo) { + // Solve the equation + // y - mStart.y - ((mEnd.y - mStart.y)/(mEnd.x - mStart.x))(x - mStart.x) + // for both points + + gfxFloat deltaY = (mEnd.y - mStart.y); + gfxFloat deltaX = (mEnd.x - mStart.x); + + gfxFloat one = deltaX * (aOne.y - mStart.y) - deltaY * (aOne.x - mStart.x); + gfxFloat two = deltaX * (aTwo.y - mStart.y) - deltaY * (aTwo.x - mStart.x); + + // If both results have the same sign, then we're on the correct side of the + // line. 0 (on the line) is always considered in. + + if ((one >= 0 && two >= 0) || (one <= 0 && two <= 0)) return true; + return false; + } + + /** + * Determines if two line segments intersect, and returns the intersection + * point in aIntersection if they do. + * + * Coincident lines are considered not intersecting as they don't have an + * intersection point. + */ + bool Intersects(const gfxLineSegment& aOther, gfxPoint& aIntersection) { + gfxFloat denominator = + (aOther.mEnd.y - aOther.mStart.y).value * (mEnd.x - mStart.x).value - + (aOther.mEnd.x - aOther.mStart.x).value * (mEnd.y - mStart.y).value; + + // Parallel or coincident. We treat coincident as not intersecting since + // these lines are guaranteed to have corners that intersect instead. + if (!denominator) { + return false; + } + + gfxFloat anumerator = (aOther.mEnd.x - aOther.mStart.x).value * + (mStart.y - aOther.mStart.y).value - + (aOther.mEnd.y - aOther.mStart.y).value * + (mStart.x - aOther.mStart.x).value; + + gfxFloat bnumerator = + (mEnd.x - mStart.x).value * (mStart.y - aOther.mStart.y).value - + (mEnd.y - mStart.y).value * (mStart.x - aOther.mStart.x).value; + + gfxFloat ua = anumerator / denominator; + gfxFloat ub = bnumerator / denominator; + + if (ua <= 0.0 || ua >= 1.0 || ub <= 0.0 || ub >= 1.0) { + // Intersection is outside of the segment + return false; + } + + aIntersection = mStart + (mEnd - mStart) * ua; + return true; + } + + gfxPoint mStart; + gfxPoint mEnd; +}; + +#endif /* GFX_LINESEGMENT_H */ diff --git a/gfx/thebes/gfxMacFont.cpp b/gfx/thebes/gfxMacFont.cpp new file mode 100644 index 0000000000..c1aee3b80d --- /dev/null +++ b/gfx/thebes/gfxMacFont.cpp @@ -0,0 +1,592 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxMacFont.h" + +#include "mozilla/MemoryReporting.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/gfx/ScaledFontMac.h" + +#include <algorithm> + +#include "CoreTextFontList.h" +#include "gfxCoreTextShaper.h" +#include "gfxPlatformMac.h" +#include "gfxContext.h" +#include "gfxFontUtils.h" +#include "gfxHarfBuzzShaper.h" +#include "gfxFontConstants.h" +#include "gfxTextRun.h" +#include "gfxUtils.h" +#include "AppleUtils.h" +#include "cairo-quartz.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +template <class T> +struct TagEquals { + bool Equals(const T& aIter, uint32_t aTag) const { + return aIter.mTag == aTag; + } +}; + +gfxMacFont::gfxMacFont(const RefPtr<UnscaledFontMac>& aUnscaledFont, + CTFontEntry* aFontEntry, const gfxFontStyle* aFontStyle) + : gfxFont(aUnscaledFont, aFontEntry, aFontStyle), + mCGFont(nullptr), + mCTFont(nullptr), + mVariationFont(aFontEntry->HasVariations()) { + mApplySyntheticBold = aFontStyle->NeedsSyntheticBold(aFontEntry); + + if (mVariationFont) { + CGFontRef baseFont = aUnscaledFont->GetFont(); + if (!baseFont) { + mIsValid = false; + return; + } + + // Get the variation settings needed to instantiate the fontEntry + // for a particular fontStyle. + AutoTArray<gfxFontVariation, 4> vars; + aFontEntry->GetVariationsForStyle(vars, *aFontStyle); + + if (aFontEntry->HasOpticalSize()) { + // Because of a Core Text bug, we need to ensure that if the font has + // an 'opsz' axis, it is always explicitly set, and NOT to the font's + // default value. (See bug 1457417, bug 1478720.) + // We record the result of searching the font's axes in the font entry, + // so that this only has to be done by the first instance created for + // a given font resource. + const uint32_t kOpszTag = HB_TAG('o', 'p', 's', 'z'); + const float kOpszFudgeAmount = 0.01f; + + // Record the opsz axis details in the font entry, if not already done. + if (!aFontEntry->mOpszAxis.mTag) { + AutoTArray<gfxFontVariationAxis, 4> axes; + aFontEntry->GetVariationAxes(axes); + auto index = + axes.IndexOf(kOpszTag, 0, TagEquals<gfxFontVariationAxis>()); + MOZ_ASSERT(index != axes.NoIndex); + if (index != axes.NoIndex) { + const auto& axis = axes[index]; + aFontEntry->mOpszAxis = axis; + // Pick a slightly-adjusted version of the default that we'll + // use to work around Core Text's habit of ignoring any attempt + // to explicitly set the default value. + aFontEntry->mAdjustedDefaultOpsz = + axis.mDefaultValue == axis.mMinValue + ? axis.mDefaultValue + kOpszFudgeAmount + : axis.mDefaultValue - kOpszFudgeAmount; + } + } + + // Add 'opsz' if not present, or tweak its value if it looks too close + // to the default (after clamping to the font's available range). + auto index = vars.IndexOf(kOpszTag, 0, TagEquals<gfxFontVariation>()); + if (index == vars.NoIndex) { + // No explicit opsz; set to the font's default. + vars.AppendElement( + gfxFontVariation{kOpszTag, aFontEntry->mAdjustedDefaultOpsz}); + } else { + // An 'opsz' value was already present; use it, but adjust if necessary + // to a "safe" value that Core Text won't ignore. + auto& value = vars[index].mValue; + auto& axis = aFontEntry->mOpszAxis; + value = fmin(fmax(value, axis.mMinValue), axis.mMaxValue); + if (std::abs(value - axis.mDefaultValue) < kOpszFudgeAmount) { + value = aFontEntry->mAdjustedDefaultOpsz; + } + } + } + + mCGFont = UnscaledFontMac::CreateCGFontWithVariations( + baseFont, aUnscaledFont->CGAxesCache(), aUnscaledFont->CTAxesCache(), + vars.Length(), vars.Elements()); + if (!mCGFont) { + ::CFRetain(baseFont); + mCGFont = baseFont; + } + } else { + mCGFont = aUnscaledFont->GetFont(); + if (!mCGFont) { + mIsValid = false; + return; + } + ::CFRetain(mCGFont); + } + + // InitMetrics will handle the sizeAdjust factor and set mAdjustedSize + InitMetrics(); + if (!mIsValid) { + return; + } + + // turn off font anti-aliasing based on user pref setting + if (mAdjustedSize <= + (gfxFloat)gfxPlatformMac::GetPlatform()->GetAntiAliasingThreshold()) { + mAntialiasOption = kAntialiasNone; + } else if (mStyle.useGrayscaleAntialiasing) { + mAntialiasOption = kAntialiasGrayscale; + } +} + +gfxMacFont::~gfxMacFont() { + if (mCGFont) { + ::CFRelease(mCGFont); + } + if (mCTFont) { + ::CFRelease(mCTFont); + } +} + +bool gfxMacFont::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, + RoundingFlags aRounding, + gfxShapedText* aShapedText) { + if (!mIsValid) { + NS_WARNING("invalid font! expect incorrect text rendering"); + return false; + } + + // Currently, we don't support vertical shaping via CoreText, + // so we ignore RequiresAATLayout if vertical is requested. + auto ctFontEntry = static_cast<CTFontEntry*>(GetFontEntry()); + if (ctFontEntry->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 (ctFontEntry->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 = + ctFontEntry->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->ApplyTrackingToClusters(tracking, aOffset, aLength); + } + return true; + } + } + + return gfxFont::ShapeText(aDrawTarget, aText, aOffset, aLength, aScript, + aLanguage, aVertical, aRounding, aShapedText); +} + +gfxFont::RunMetrics gfxMacFont::Measure(const gfxTextRun* aTextRun, + uint32_t aStart, uint32_t aEnd, + BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, + Spacing* aSpacing, + gfx::ShapedTextFlags aOrientation) { + gfxFont::RunMetrics metrics = + gfxFont::Measure(aTextRun, aStart, aEnd, aBoundingBoxType, aRefDrawTarget, + aSpacing, aOrientation); + + // if aBoundingBoxType is not TIGHT_HINTED_OUTLINE_EXTENTS then we need to add + // a pixel column each side of the bounding box in case of antialiasing + // "bleed" + if (aBoundingBoxType != TIGHT_HINTED_OUTLINE_EXTENTS && + metrics.mBoundingBox.width > 0) { + metrics.mBoundingBox.x -= aTextRun->GetAppUnitsPerDevUnit(); + metrics.mBoundingBox.width += aTextRun->GetAppUnitsPerDevUnit() * 2; + } + + return metrics; +} + +void gfxMacFont::InitMetrics() { + mIsValid = false; + ::memset(&mMetrics, 0, sizeof(mMetrics)); + + uint32_t upem = 0; + + // try to get unitsPerEm from sfnt head table, to avoid calling CGFont + // if possible (bug 574368) and because CGFontGetUnitsPerEm does not + // return the true value for OpenType/CFF fonts (it normalizes to 1000, + // which then leads to metrics errors when we read the 'hmtx' table to + // get glyph advances for HarfBuzz, see bug 580863) + AutoCFRelease<CFDataRef> headData = + ::CGFontCopyTableForTag(mCGFont, TRUETYPE_TAG('h', 'e', 'a', 'd')); + if (headData) { + if (size_t(::CFDataGetLength(headData)) >= sizeof(HeadTable)) { + const HeadTable* head = + reinterpret_cast<const HeadTable*>(::CFDataGetBytePtr(headData)); + upem = head->unitsPerEm; + } + } + if (!upem) { + upem = ::CGFontGetUnitsPerEm(mCGFont); + } + + if (upem < 16 || upem > 16384) { + // See http://www.microsoft.com/typography/otspec/head.htm +#ifdef DEBUG + char warnBuf[1024]; + SprintfLiteral(warnBuf, + "Bad font metrics for: %s (invalid unitsPerEm value)", + mFontEntry->Name().get()); + NS_WARNING(warnBuf); +#endif + return; + } + + // Apply any size-adjust from the font enty to the given size; this may be + // re-adjusted below if font-size-adjust is in effect. + mAdjustedSize = GetAdjustedSize(); + mFUnitsConvFactor = mAdjustedSize / upem; + + // For CFF fonts, when scaling values read from CGFont* APIs, we need to + // use CG's idea of unitsPerEm, which may differ from the "true" value in + // the head table of the font (see bug 580863) + gfxFloat cgConvFactor; + if (static_cast<CTFontEntry*>(mFontEntry.get())->IsCFF()) { + cgConvFactor = mAdjustedSize / ::CGFontGetUnitsPerEm(mCGFont); + } else { + cgConvFactor = mFUnitsConvFactor; + } + + // Try to read 'sfnt' metrics; for local, non-sfnt fonts ONLY, fall back to + // platform APIs. The InitMetrics...() functions will set mIsValid on success. + if (!InitMetricsFromSfntTables(mMetrics) && + (!mFontEntry->IsUserFont() || mFontEntry->IsLocalUserFont())) { + InitMetricsFromPlatform(); + } + if (!mIsValid) { + return; + } + + if (mMetrics.xHeight == 0.0) { + mMetrics.xHeight = ::CGFontGetXHeight(mCGFont) * cgConvFactor; + } + if (mMetrics.capHeight == 0.0) { + mMetrics.capHeight = ::CGFontGetCapHeight(mCGFont) * cgConvFactor; + } + + AutoCFRelease<CFDataRef> cmap = + ::CGFontCopyTableForTag(mCGFont, TRUETYPE_TAG('c', 'm', 'a', 'p')); + + uint32_t glyphID; + mMetrics.zeroWidth = GetCharWidth(cmap, '0', &glyphID, cgConvFactor); + if (glyphID == 0) { + mMetrics.zeroWidth = -1.0; // indicates not found + } + + if (FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) != + FontSizeAdjust::Tag::None && + mStyle.sizeAdjust >= 0.0 && GetAdjustedSize() > 0.0) { + // apply font-size-adjust, and recalculate metrics + gfxFloat aspect; + switch (FontSizeAdjust::Tag(mStyle.sizeAdjustBasis)) { + default: + MOZ_ASSERT_UNREACHABLE("unhandled sizeAdjustBasis?"); + aspect = 0.0; + break; + case FontSizeAdjust::Tag::ExHeight: + aspect = mMetrics.xHeight / mAdjustedSize; + break; + case FontSizeAdjust::Tag::CapHeight: + aspect = mMetrics.capHeight / mAdjustedSize; + break; + case FontSizeAdjust::Tag::ChWidth: + aspect = + mMetrics.zeroWidth < 0.0 ? 0.5 : mMetrics.zeroWidth / mAdjustedSize; + break; + case FontSizeAdjust::Tag::IcWidth: + case FontSizeAdjust::Tag::IcHeight: { + bool vertical = FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) == + FontSizeAdjust::Tag::IcHeight; + gfxFloat advance = GetCharAdvance(kWaterIdeograph, vertical); + aspect = advance > 0.0 ? advance / mAdjustedSize : 1.0; + break; + } + } + if (aspect > 0.0) { + // If we created a shaper above (to measure glyphs), discard it so we + // get a new one for the adjusted scaling. + delete mHarfBuzzShaper.exchange(nullptr); + mAdjustedSize = mStyle.GetAdjustedSize(aspect); + mFUnitsConvFactor = mAdjustedSize / upem; + if (static_cast<CTFontEntry*>(mFontEntry.get())->IsCFF()) { + cgConvFactor = mAdjustedSize / ::CGFontGetUnitsPerEm(mCGFont); + } else { + cgConvFactor = mFUnitsConvFactor; + } + mMetrics.xHeight = 0.0; + if (!InitMetricsFromSfntTables(mMetrics) && + (!mFontEntry->IsUserFont() || mFontEntry->IsLocalUserFont())) { + InitMetricsFromPlatform(); + } + if (!mIsValid) { + // this shouldn't happen, as we succeeded earlier before applying + // the size-adjust factor! But check anyway, for paranoia's sake. + return; + } + // Update metrics from the re-scaled font. + if (mMetrics.xHeight == 0.0) { + mMetrics.xHeight = ::CGFontGetXHeight(mCGFont) * cgConvFactor; + } + if (mMetrics.capHeight == 0.0) { + mMetrics.capHeight = ::CGFontGetCapHeight(mCGFont) * cgConvFactor; + } + mMetrics.zeroWidth = GetCharWidth(cmap, '0', &glyphID, cgConvFactor); + if (glyphID == 0) { + mMetrics.zeroWidth = -1.0; // indicates not found + } + } + } + + // Once we reach here, we've got basic metrics and set mIsValid = TRUE; + // there should be no further points of actual failure in InitMetrics(). + // (If one is introduced, be sure to reset mIsValid to FALSE!) + + mMetrics.emHeight = mAdjustedSize; + + // Measure/calculate additional metrics, independent of whether we used + // the tables directly or ATS metrics APIs + + if (mMetrics.aveCharWidth <= 0) { + mMetrics.aveCharWidth = GetCharWidth(cmap, 'x', &glyphID, cgConvFactor); + if (glyphID == 0) { + // we didn't find 'x', so use maxAdvance rather than zero + mMetrics.aveCharWidth = mMetrics.maxAdvance; + } + } + + mMetrics.spaceWidth = GetCharWidth(cmap, ' ', &glyphID, cgConvFactor); + if (glyphID == 0) { + // no space glyph?! + mMetrics.spaceWidth = mMetrics.aveCharWidth; + } + mSpaceGlyph = glyphID; + + mMetrics.ideographicWidth = + GetCharWidth(cmap, kWaterIdeograph, &glyphID, cgConvFactor); + if (glyphID == 0) { + // Indicate "not found". + mMetrics.ideographicWidth = -1.0; + } + + CalculateDerivedMetrics(mMetrics); + + SanitizeMetrics(&mMetrics, mFontEntry->mIsBadUnderlineFont); + + if (ApplySyntheticBold()) { + auto delta = GetSyntheticBoldOffset(); + mMetrics.spaceWidth += delta; + mMetrics.aveCharWidth += delta; + mMetrics.maxAdvance += delta; + if (mMetrics.zeroWidth > 0) { + mMetrics.zeroWidth += delta; + } + if (mMetrics.ideographicWidth > 0) { + mMetrics.ideographicWidth += delta; + } + } + +#if 0 + fprintf (stderr, "Font: %p (%s) size: %f\n", this, + NS_ConvertUTF16toUTF8(GetName()).get(), mStyle.size); +// fprintf (stderr, " fbounds.origin.x %f y %f size.width %f height %f\n", fbounds.origin.x, fbounds.origin.y, fbounds.size.width, fbounds.size.height); + fprintf (stderr, " emHeight: %f emAscent: %f emDescent: %f\n", mMetrics.emHeight, mMetrics.emAscent, mMetrics.emDescent); + fprintf (stderr, " maxAscent: %f maxDescent: %f maxAdvance: %f\n", mMetrics.maxAscent, mMetrics.maxDescent, mMetrics.maxAdvance); + fprintf (stderr, " internalLeading: %f externalLeading: %f\n", mMetrics.internalLeading, mMetrics.externalLeading); + fprintf (stderr, " spaceWidth: %f aveCharWidth: %f xHeight: %f capHeight: %f\n", mMetrics.spaceWidth, mMetrics.aveCharWidth, mMetrics.xHeight, mMetrics.capHeight); + fprintf (stderr, " uOff: %f uSize: %f stOff: %f stSize: %f\n", mMetrics.underlineOffset, mMetrics.underlineSize, mMetrics.strikeoutOffset, mMetrics.strikeoutSize); +#endif +} + +gfxFloat gfxMacFont::GetCharWidth(CFDataRef aCmap, char16_t aUniChar, + uint32_t* aGlyphID, gfxFloat aConvFactor) { + CGGlyph glyph = 0; + + if (aCmap) { + glyph = gfxFontUtils::MapCharToGlyph(::CFDataGetBytePtr(aCmap), + ::CFDataGetLength(aCmap), aUniChar); + } + + if (aGlyphID) { + *aGlyphID = glyph; + } + + if (glyph) { + int advance; + if (::CGFontGetGlyphAdvances(mCGFont, &glyph, 1, &advance)) { + return advance * aConvFactor; + } + } + + return 0; +} + +int32_t gfxMacFont::GetGlyphWidth(uint16_t aGID) { + if (mVariationFont) { + // Avoid a potential Core Text crash (bug 1450209) by using + // CoreGraphics glyph advance API. This is inferior for 'sbix' + // fonts, but those won't have variations, so it's OK. + int cgAdvance; + if (::CGFontGetGlyphAdvances(mCGFont, &aGID, 1, &cgAdvance)) { + return cgAdvance * mFUnitsConvFactor * 0x10000; + } + } + + if (!mCTFont) { + bool isInstalledFont = + !mFontEntry->IsUserFont() || mFontEntry->IsLocalUserFont(); + mCTFont = CreateCTFontFromCGFontWithVariations(mCGFont, mAdjustedSize, + isInstalledFont); + if (!mCTFont) { // shouldn't happen, but let's be safe + NS_WARNING("failed to create CTFontRef to measure glyph width"); + return 0; + } + } + + CGSize advance; + ::CTFontGetAdvancesForGlyphs(mCTFont, kCTFontOrientationDefault, &aGID, + &advance, 1); + return advance.width * 0x10000; +} + +bool gfxMacFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) { + CGRect bb; + if (!::CGFontGetGlyphBBoxes(mCGFont, &aGID, 1, &bb)) { + return false; + } + + // broken fonts can return incorrect bounds for some null characters, + // see https://bugzilla.mozilla.org/show_bug.cgi?id=534260 + if (bb.origin.x == -32767 && bb.origin.y == -32767 && + bb.size.width == 65534 && bb.size.height == 65534) { + *aBounds = gfxRect(0, 0, 0, 0); + return true; + } + + gfxRect bounds(bb.origin.x, -(bb.origin.y + bb.size.height), bb.size.width, + bb.size.height); + bounds.Scale(mFUnitsConvFactor); + + // For bitmap fonts (like Apple Color Emoji), CoreGraphics does not return + // accurate bounds, so to try and avoid clipping when the bounds are used + // to determine the area to render (e.g. when implementing canvas2d filters), + // we inflate the bounds based on global metrics from the font. + if (GetFontEntry()->HasColorBitmapTable()) { + aBounds->x = std::min(bounds.x, 0.0); + aBounds->width = std::max(bounds.width, mMetrics.maxAdvance); + // Note that y-coordinates are downwards here, and bounds.y is MINUS the + // glyph ascent as it measures from the baseline. + aBounds->y = std::min(bounds.y, -mMetrics.maxAscent); + aBounds->height = + std::max(bounds.YMost(), mMetrics.maxDescent) - aBounds->y; + } else { + *aBounds = bounds; + } + + return true; +} + +// Try to initialize font metrics via platform APIs (CG/CT), +// and set mIsValid = TRUE on success. +// We ONLY call this for local (platform) fonts that are not sfnt format; +// for sfnts, including ALL downloadable fonts, we prefer to use +// InitMetricsFromSfntTables and avoid platform APIs. +void gfxMacFont::InitMetricsFromPlatform() { + AutoCFRelease<CTFontRef> ctFont = + ::CTFontCreateWithGraphicsFont(mCGFont, mAdjustedSize, nullptr, nullptr); + if (!ctFont) { + return; + } + + mMetrics.underlineOffset = ::CTFontGetUnderlinePosition(ctFont); + mMetrics.underlineSize = ::CTFontGetUnderlineThickness(ctFont); + + mMetrics.externalLeading = ::CTFontGetLeading(ctFont); + + mMetrics.maxAscent = ::CTFontGetAscent(ctFont); + mMetrics.maxDescent = ::CTFontGetDescent(ctFont); + + // this is not strictly correct, but neither CTFont nor CGFont seems to + // provide maxAdvance, unless we were to iterate over all the glyphs + // (which isn't worth the cost here) + CGRect r = ::CTFontGetBoundingBox(ctFont); + mMetrics.maxAdvance = r.size.width; + + // aveCharWidth is also not provided, so leave it at zero + // (fallback code in gfxMacFont::InitMetrics will then try measuring 'x'); + // this could lead to less-than-"perfect" text field sizing when width is + // specified as a number of characters, and the font in use is a non-sfnt + // legacy font, but that's a sufficiently obscure edge case that we can + // ignore the potential discrepancy. + mMetrics.aveCharWidth = 0; + + mMetrics.xHeight = ::CTFontGetXHeight(ctFont); + mMetrics.capHeight = ::CTFontGetCapHeight(ctFont); + + mIsValid = true; +} + +already_AddRefed<ScaledFont> gfxMacFont::GetScaledFont( + const TextRunDrawParams& aRunParams) { + if (ScaledFont* scaledFont = mAzureScaledFont) { + return do_AddRef(scaledFont); + } + + gfxFontEntry* fe = GetFontEntry(); + bool hasColorGlyphs = fe->HasColorBitmapTable() || fe->TryGetColorGlyphs(); + RefPtr<ScaledFont> newScaledFont = Factory::CreateScaledFontForMacFont( + GetCGFontRef(), GetUnscaledFont(), GetAdjustedSize(), + !mStyle.useGrayscaleAntialiasing, ApplySyntheticBold(), hasColorGlyphs); + if (!newScaledFont) { + return nullptr; + } + + InitializeScaledFont(newScaledFont); + + if (mAzureScaledFont.compareExchange(nullptr, newScaledFont.get())) { + Unused << newScaledFont.forget(); + } + ScaledFont* scaledFont = mAzureScaledFont; + return do_AddRef(scaledFont); +} + +bool gfxMacFont::ShouldRoundXOffset(cairo_t* aCairo) const { + // Quartz surfaces implement show_glyphs for Quartz fonts + return aCairo && cairo_surface_get_type(cairo_get_target(aCairo)) != + CAIRO_SURFACE_TYPE_QUARTZ; +} + +bool gfxMacFont::UseNativeColrFontSupport() const { + /* + auto* colr = GetFontEntry()->GetCOLR(); + if (colr && COLRFonts::GetColrTableVersion(colr) == 0) { + return true; + } + */ + return false; +} + +void gfxMacFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const { + gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); + // mCGFont is shared with the font entry, so not counted here; +} + +void gfxMacFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const { + aSizes->mFontInstances += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} diff --git a/gfx/thebes/gfxMacFont.h b/gfx/thebes/gfxMacFont.h new file mode 100644 index 0000000000..f33ad478ec --- /dev/null +++ b/gfx/thebes/gfxMacFont.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_MACFONT_H +#define GFX_MACFONT_H + +#include "mozilla/MemoryReporting.h" +#include "gfxFont.h" +#include <ApplicationServices/ApplicationServices.h> + +#include "mozilla/gfx/UnscaledFontMac.h" + +class CTFontEntry; + +class gfxMacFont final : public gfxFont { + public: + gfxMacFont(const RefPtr<mozilla::gfx::UnscaledFontMac>& aUnscaledFont, + CTFontEntry* aFontEntry, const gfxFontStyle* aFontStyle); + + CGFontRef GetCGFontRef() const { return mCGFont; } + + /* override Measure to add padding for antialiasing */ + RunMetrics Measure(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd, + BoundingBoxType aBoundingBoxType, + DrawTarget* aDrawTargetForTightBoundingBox, + Spacing* aSpacing, + mozilla::gfx::ShapedTextFlags aOrientation) override; + + // We need to provide hinted (non-linear) glyph widths if using a font + // with embedded color bitmaps (Apple Color Emoji), as Core Text renders + // the glyphs with non-linear scaling at small pixel sizes. + bool ProvidesGlyphWidths() const override { + return mVariationFont || + mFontEntry->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x')); + } + + int32_t GetGlyphWidth(uint16_t aGID) override; + + bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override; + + already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont( + const TextRunDrawParams& aRunParams) override; + + bool ShouldRoundXOffset(cairo_t* aCairo) const override; + + void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const override; + void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const override; + + FontType GetType() const override { return FONT_TYPE_MAC; } + + bool UseNativeColrFontSupport() const override; + + protected: + ~gfxMacFont() override; + + const Metrics& GetHorizontalMetrics() const override { return mMetrics; } + + // override to prefer CoreText shaping with fonts that depend on AAT + bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, + uint32_t aOffset, uint32_t aLength, Script aScript, + nsAtom* aLanguage, bool aVertical, RoundingFlags aRounding, + gfxShapedText* aShapedText) override; + + void InitMetrics(); + void InitMetricsFromPlatform(); + + // Get width and glyph ID for a character; uses aConvFactor + // to convert font units as returned by CG to actual dimensions + gfxFloat GetCharWidth(CFDataRef aCmap, char16_t aUniChar, uint32_t* aGlyphID, + gfxFloat aConvFactor); + + // a strong reference to the CoreGraphics font + CGFontRef mCGFont; + + // a Core Text font reference, created only if we're using CT to measure + // glyph widths; otherwise null. + CTFontRef mCTFont; + + mozilla::UniquePtr<gfxFontShaper> mCoreTextShaper; + + Metrics mMetrics; + + 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..ac0d8927fe --- /dev/null +++ b/gfx/thebes/gfxMacPlatformFontList.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 gfxMacPlatformFontList_H_ +#define gfxMacPlatformFontList_H_ + +#include <CoreFoundation/CoreFoundation.h> + +#include "CoreTextFontList.h" + +class gfxMacPlatformFontList final : public CoreTextFontList { + public: + static gfxMacPlatformFontList* PlatformFontList() { + return static_cast<gfxMacPlatformFontList*>( + gfxPlatformFontList::PlatformFontList()); + } + + static void LookupSystemFont(mozilla::LookAndFeel::FontID aSystemFontID, + nsACString& aSystemFontName, + gfxFontStyle& aFontStyle); + + protected: + bool DeprecatedFamilyIsAvailable(const nsACString& aName) override; + FontVisibility GetVisibilityForFamily(const nsACString& aName) const override; + + FontFamily GetDefaultFontForPlatform(nsPresContext* aPresContext, + const gfxFontStyle* aStyle, + nsAtom* aLanguage = nullptr) + MOZ_REQUIRES(mLock) override; + + private: + friend class gfxPlatformMac; + + gfxMacPlatformFontList(); + virtual ~gfxMacPlatformFontList() = default; + + // Special-case font faces treated as font families (set via prefs) + void InitSingleFaceList() MOZ_REQUIRES(mLock) override; + void InitAliasesForSingleFaceList() MOZ_REQUIRES(mLock) override; + + // initialize system fonts + void InitSystemFontNames() override MOZ_REQUIRES(mLock); + + nsTArray<nsCString> mSingleFaceFonts; +}; + +#endif /* gfxMacPlatformFontList_H_ */ diff --git a/gfx/thebes/gfxMacPlatformFontList.mm b/gfx/thebes/gfxMacPlatformFontList.mm new file mode 100644 index 0000000000..e36c9c8a25 --- /dev/null +++ b/gfx/thebes/gfxMacPlatformFontList.mm @@ -0,0 +1,534 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 <algorithm> + +#import <AppKit/AppKit.h> + +#include "gfxFontConstants.h" +#include "gfxPlatformMac.h" +#include "gfxMacPlatformFontList.h" +#include "gfxMacFont.h" +#include "gfxUserFontSet.h" +#include "SharedFontList-impl.h" + +#include "harfbuzz/hb.h" + +#include "AppleUtils.h" +#include "MainThreadUtils.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsIDirectoryEnumerator.h" +#include "nsCharTraits.h" +#include "nsCocoaUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsTArray.h" + +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/Telemetry.h" +#include "mozilla/gfx/2D.h" + +#include <unistd.h> +#include <time.h> +#include <dlfcn.h> + +#include "StandardFonts-macos.inc" + +using namespace mozilla; +using namespace mozilla::gfx; + +// 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) + +class gfxMacFontFamily final : public CTFontFamily { + public: + gfxMacFontFamily(const nsACString& aName, NSFont* aSystemFont) + : CTFontFamily(aName, FontVisibility::Unknown), + mForSystemFont(aSystemFont) { + // I don't think the system font instance is at much risk of being deleted, + // but to be on the safe side let's retain a reference until we're finished + // using it for lazy initialization. + [mForSystemFont retain]; + } + + void FindStyleVariationsLocked(FontInfoData* aFontInfoData = nullptr) + MOZ_REQUIRES(mLock) override; + + protected: + // If non-null, this is a family representing the system UI font, and should + // use the given NSFont as the basis for initialization as the normal + // font-manager APIs based on family name won't handle it. + NSFont* mForSystemFont = nullptr; +}; + +void gfxMacFontFamily::FindStyleVariationsLocked(FontInfoData* aFontInfoData) { + if (mHasStyles) { + return; + } + + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("gfxMacFontFamily::FindStyleVariations", + LAYOUT, mName); + + nsAutoreleasePool localPool; + + if (mForSystemFont) { + MOZ_ASSERT(gfxPlatform::HasVariationFontSupport()); + + auto addToFamily = [&](NSFont* aNSFont) MOZ_REQUIRES(mLock) { + NSString* psNameNS = aNSFont.fontDescriptor.postscriptName; + nsAutoString nameUTF16; + nsAutoCString psName; + nsCocoaUtils::GetStringForNSString(psNameNS, nameUTF16); + CopyUTF16toUTF8(nameUTF16, psName); + + auto* fe = + new CTFontEntry(psName, WeightRange(FontWeight::NORMAL), true, 0.0); + + // Set the appropriate style, assuming it may not have a variation range. + fe->mStyleRange = SlantStyleRange( + (aNSFont.fontDescriptor.symbolicTraits & NSFontItalicTrait) + ? FontSlantStyle::ITALIC + : FontSlantStyle::NORMAL); + + // Set up weight (and width, if present) ranges. + fe->SetupVariationRanges(); + AddFontEntryLocked(fe); + }; + + addToFamily(mForSystemFont); + + // See if there is a corresponding italic face, and add it to the family. + NSFont* italicFont = [sFontManager convertFont:mForSystemFont + toHaveTrait:NSItalicFontMask]; + if (italicFont != mForSystemFont) { + addToFamily(italicFont); + } + + [mForSystemFont release]; + mForSystemFont = nullptr; + SetHasStyles(true); + + return; + } + + CTFontFamily::FindStyleVariationsLocked(aFontInfoData); +} + +/* gfxSingleFaceMacFontFamily */ + +class gfxSingleFaceMacFontFamily final : public gfxFontFamily { + public: + gfxSingleFaceMacFontFamily(const nsACString& aName, + FontVisibility aVisibility) + : gfxFontFamily(aName, aVisibility) { + mFaceNamesInitialized = true; // omit from face name lists + } + + virtual ~gfxSingleFaceMacFontFamily() = default; + + void FindStyleVariationsLocked(FontInfoData* aFontInfoData = nullptr) + MOZ_REQUIRES(mLock) override{}; + + void LocalizedName(nsACString& aLocalizedName) override; + + void ReadOtherFamilyNames(gfxPlatformFontList* aPlatformFontList) override; + + bool IsSingleFaceFamily() const override { return true; } +}; + +void gfxSingleFaceMacFontFamily::LocalizedName(nsACString& aLocalizedName) { + nsAutoreleasePool localPool; + + AutoReadLock lock(mLock); + + if (!HasOtherFamilyNames()) { + aLocalizedName = mName; + return; + } + + gfxFontEntry* fe = mAvailableFonts[0]; + NSFont* font = [NSFont + fontWithName:GetNSStringForString(NS_ConvertUTF8toUTF16(fe->Name())) + size:0.0]; + if (font) { + NSString* localized = [font displayName]; + if (localized) { + nsAutoString locName; + GetStringForNSString(localized, locName); + CopyUTF16toUTF8(locName, aLocalizedName); + return; + } + } + + // failed to get localized name, just use the canonical one + aLocalizedName = mName; +} + +void gfxSingleFaceMacFontFamily::ReadOtherFamilyNames( + gfxPlatformFontList* aPlatformFontList) { + AutoWriteLock lock(mLock); + if (mOtherFamilyNamesInitialized) { + return; + } + + gfxFontEntry* fe = mAvailableFonts[0]; + if (!fe) { + return; + } + + const uint32_t kNAME = TRUETYPE_TAG('n', 'a', 'm', 'e'); + + gfxFontEntry::AutoTable nameTable(fe, kNAME); + if (!nameTable) { + return; + } + + mHasOtherFamilyNames = + ReadOtherFamilyNamesForFace(aPlatformFontList, nameTable, true); + + mOtherFamilyNamesInitialized = true; +} + +/* gfxMacPlatformFontList */ +#pragma mark - + +gfxMacPlatformFontList::gfxMacPlatformFontList() : CoreTextFontList() { + CheckFamilyList(kBaseFonts); + + // cache this in a static variable so that gfxMacFontFamily objects + // don't have to repeatedly look it up + sFontManager = [NSFontManager sharedFontManager]; + + // Load the font-list preferences now, so that we don't have to do it from + // Init[Shared]FontListForPlatform, which may be called off-main-thread. + gfxFontUtils::GetPrefsFontList("font.single-face-list", mSingleFaceFonts); +} + +FontVisibility gfxMacPlatformFontList::GetVisibilityForFamily( + const nsACString& aName) const { + if (aName[0] == '.' || aName.LowerCaseEqualsLiteral("lastresort")) { + return FontVisibility::Hidden; + } + if (FamilyInList(aName, kBaseFonts)) { + return FontVisibility::Base; + } +#ifdef MOZ_BUNDLED_FONTS + if (mBundledFamilies.Contains(aName)) { + return FontVisibility::Base; + } +#endif + return FontVisibility::User; +} + +bool gfxMacPlatformFontList::DeprecatedFamilyIsAvailable( + const nsACString& aName) { + NSString* family = GetNSStringForString(NS_ConvertUTF8toUTF16(aName)); + return [[sFontManager availableMembersOfFontFamily:family] count] > 0; +} + +void gfxMacPlatformFontList::InitAliasesForSingleFaceList() { + for (const auto& familyName : mSingleFaceFonts) { + LOG_FONTLIST(("(fontlist-singleface) face name: %s\n", familyName.get())); + // Each entry in the "single face families" list is expected to be a + // colon-separated pair of FaceName:Family, + // where FaceName is the individual face name (psname) of a font + // that should be exposed as a separate family name, + // and Family is the standard family to which that face belongs. + // The only such face listed by default is + // Osaka-Mono:Osaka + auto colon = familyName.FindChar(':'); + if (colon == kNotFound) { + continue; + } + + // Look for the parent family in the main font family list, + // and ensure we have loaded its list of available faces. + nsAutoCString key; + GenerateFontListKey(Substring(familyName, colon + 1), key); + fontlist::Family* family = SharedFontList()->FindFamily(key); + if (!family) { + // The parent family is not present, so just ignore this entry. + continue; + } + if (!family->IsInitialized()) { + if (!gfxPlatformFontList::InitializeFamily(family)) { + // This shouldn't ever fail, but if it does, we can safely ignore it. + MOZ_ASSERT(false, "failed to initialize font family"); + continue; + } + } + + // Truncate the entry from prefs at the colon, so now it is just the + // desired single-face-family name. + nsAutoCString aliasName(Substring(familyName, 0, colon)); + + // Look through the family's faces to see if this one is present. + fontlist::FontList* list = SharedFontList(); + const fontlist::Pointer* facePtrs = family->Faces(list); + for (size_t i = 0; i < family->NumFaces(); i++) { + if (facePtrs[i].IsNull()) { + continue; + } + auto* face = facePtrs[i].ToPtr<const fontlist::Face>(list); + if (face->mDescriptor.AsString(list).Equals(aliasName)) { + // Found it! Create an entry in the Alias table. + GenerateFontListKey(aliasName, key); + if (SharedFontList()->FindFamily(key) || mAliasTable.Get(key)) { + // If the family name is already known, something's misconfigured; + // just ignore it. + MOZ_ASSERT(false, "single-face family already known"); + break; + } + auto aliasData = mAliasTable.GetOrInsertNew(key); + // The "alias" here isn't based on an existing family, so we don't call + // aliasData->InitFromFamily(); the various flags are left as defaults. + aliasData->mFaces.AppendElement(facePtrs[i]); + aliasData->mBaseFamily = aliasName; + aliasData->mVisibility = family->Visibility(); + break; + } + } + } + if (!mAliasTable.IsEmpty()) { + // This will be updated when the font loader completes, but we require + // at least the Osaka-Mono alias to be available immediately. + SharedFontList()->SetAliases(mAliasTable); + } +} + +void gfxMacPlatformFontList::InitSingleFaceList() { + for (const auto& familyName : mSingleFaceFonts) { + LOG_FONTLIST(("(fontlist-singleface) face name: %s\n", familyName.get())); + // Each entry in the "single face families" list is expected to be a + // colon-separated pair of FaceName:Family, + // where FaceName is the individual face name (psname) of a font + // that should be exposed as a separate family name, + // and Family is the standard family to which that face belongs. + // The only such face listed by default is + // Osaka-Mono:Osaka + auto colon = familyName.FindChar(':'); + if (colon == kNotFound) { + continue; + } + + // Look for the parent family in the main font family list, + // and ensure we have loaded its list of available faces. + nsAutoCString key(Substring(familyName, colon + 1)); + ToLowerCase(key); + gfxFontFamily* family = mFontFamilies.GetWeak(key); + if (!family || family->IsHidden()) { + continue; + } + family->FindStyleVariations(); + + // Truncate the entry from prefs at the colon, so now it is just the + // desired single-face-family name. + nsAutoCString aliasName(Substring(familyName, 0, colon)); + + // Look through the family's faces to see if this one is present. + const gfxFontEntry* fe = nullptr; + family->ReadLock(); + for (const auto& face : family->GetFontList()) { + if (face->Name().Equals(aliasName)) { + fe = face; + break; + } + } + family->ReadUnlock(); + if (!fe) { + continue; + } + + // We found the correct face, so create the single-face family record. + GenerateFontListKey(aliasName, key); + LOG_FONTLIST(("(fontlist-singleface) family name: %s, key: %s\n", + aliasName.get(), key.get())); + + // add only if doesn't exist already + if (!mFontFamilies.GetWeak(key)) { + RefPtr<gfxFontFamily> familyEntry = + new gfxSingleFaceMacFontFamily(aliasName, family->Visibility()); + // We need a separate font entry, because its family name will + // differ from the one we found in the main list. + CTFontEntry* fontEntry = + new CTFontEntry(fe->Name(), fe->Weight(), true, + static_cast<const CTFontEntry*>(fe)->mSizeHint); + familyEntry->AddFontEntry(fontEntry); + familyEntry->SetHasStyles(true); + mFontFamilies.InsertOrUpdate(key, std::move(familyEntry)); + LOG_FONTLIST(("(fontlist-singleface) added new family: %s, key: %s\n", + aliasName.get(), key.get())); + } + } +} + +// System fonts under OSX may contain weird "meta" names but if we create +// a new font using just the Postscript name, the NSFont api returns an object +// with the actual real family name. For example, under OSX 10.11: +// +// [[NSFont menuFontOfSize:8.0] familyName] ==> .AppleSystemUIFont +// [[NSFont fontWithName:[[[NSFont menuFontOfSize:8.0] fontDescriptor] +// postscriptName] +// size:8.0] familyName] ==> .SF NS Text + +static NSString* GetRealFamilyName(NSFont* aFont) { + NSString* psName = [[aFont fontDescriptor] postscriptName]; + // With newer macOS versions and SDKs (e.g. when compiled against SDK 10.15), + // [NSFont fontWithName:] fails for hidden system fonts, because the + // underlying Core Text functions it uses reject such names and tell us to use + // the special CTFontCreateUIFontForLanguage API instead. To work around this, + // as we don't yet work directly with the CTFontUIFontType identifiers, we + // create a Core Graphics font (as it doesn't reject system font names), and + // use this to create a Core Text font that we can query for the family name. + // Eventually we should move to using CTFontUIFontType constants to identify + // system fonts, and eliminate the need to instantiate them (indirectly) from + // their postscript names. + AutoCFRelease<CGFontRef> cgFont = + CGFontCreateWithFontName(CFStringRef(psName)); + if (!cgFont) { + return [aFont familyName]; + } + + AutoCFRelease<CTFontRef> ctFont = + CTFontCreateWithGraphicsFont(cgFont, 0.0, nullptr, nullptr); + if (!ctFont) { + return [aFont familyName]; + } + NSString* familyName = (NSString*)CTFontCopyFamilyName(ctFont); + + return [familyName autorelease]; +} + +void gfxMacPlatformFontList::InitSystemFontNames() { + // text font family + NSFont* sys = [NSFont systemFontOfSize:0.0]; + NSString* textFamilyName = GetRealFamilyName(sys); + nsAutoString familyName; + nsCocoaUtils::GetStringForNSString(textFamilyName, familyName); + CopyUTF16toUTF8(familyName, mSystemFontFamilyName); + + // 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. This family will be + // populated based on the given NSFont. + RefPtr<gfxFontFamily> fam = new gfxMacFontFamily(mSystemFontFamilyName, sys); + if (fam) { + nsAutoCString key; + GenerateFontListKey(mSystemFontFamilyName, key); + mFontFamilies.InsertOrUpdate(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 +} + +FontFamily gfxMacPlatformFontList::GetDefaultFontForPlatform( + nsPresContext* aPresContext, const gfxFontStyle* aStyle, + nsAtom* aLanguage) { + nsAutoreleasePool localPool; + + NSString* defaultFamily = [[NSFont userFontOfSize:aStyle->size] familyName]; + nsAutoString familyName; + + GetStringForNSString(defaultFamily, familyName); + return FindFamily(aPresContext, NS_ConvertUTF16toUTF8(familyName)); +} + +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; + switch (aSystemFontID) { + case LookAndFeel::FontID::MessageBox: + case LookAndFeel::FontID::StatusBar: + case LookAndFeel::FontID::MozList: + case LookAndFeel::FontID::MozField: + case LookAndFeel::FontID::MozButton: + font = [NSFont systemFontOfSize:NSFont.smallSystemFontSize]; + break; + + case LookAndFeel::FontID::SmallCaption: + font = [NSFont boldSystemFontOfSize:NSFont.smallSystemFontSize]; + break; + + case LookAndFeel::FontID::Icon: // used in urlbar; tried labelFont, but too + // small + font = [NSFont controlContentFontOfSize:0.0]; + break; + + case LookAndFeel::FontID::MozPullDownMenu: + font = [NSFont menuBarFontOfSize:0.0]; + break; + + case LookAndFeel::FontID::Caption: + case LookAndFeel::FontID::Menu: + default: + font = [NSFont systemFontOfSize:0.0]; + break; + } + NS_ASSERTION(font, "system font not set"); + + aSystemFontName.AssignASCII("-apple-system"); + + 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; +} diff --git a/gfx/thebes/gfxMacUtils.cpp b/gfx/thebes/gfxMacUtils.cpp new file mode 100644 index 0000000000..0c0b66eb14 --- /dev/null +++ b/gfx/thebes/gfxMacUtils.cpp @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxMacUtils.h" +#include <CoreVideo/CoreVideo.h> + +/* static */ CFStringRef gfxMacUtils::CFStringForTransferFunction( + mozilla::gfx::TransferFunction aTransferFunction) { + switch (aTransferFunction) { + case mozilla::gfx::TransferFunction::BT709: + return kCVImageBufferTransferFunction_ITU_R_709_2; + + case mozilla::gfx::TransferFunction::SRGB: + return kCVImageBufferTransferFunction_sRGB; + + case mozilla::gfx::TransferFunction::PQ: + return kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ; + + case mozilla::gfx::TransferFunction::HLG: + return kCVImageBufferTransferFunction_ITU_R_2100_HLG; + + default: + MOZ_ASSERT_UNREACHABLE("Unknown TransferFunction."); + return kCVImageBufferTransferFunction_ITU_R_709_2; + } +} diff --git a/gfx/thebes/gfxMacUtils.h b/gfx/thebes/gfxMacUtils.h new file mode 100644 index 0000000000..b256f7bde1 --- /dev/null +++ b/gfx/thebes/gfxMacUtils.h @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_MAC_UTILS_H +#define GFX_MAC_UTILS_H + +#include <CoreFoundation/CoreFoundation.h> +#include "mozilla/gfx/2D.h" + +class gfxMacUtils { + public: + // This takes a TransferFunction and returns a constant CFStringRef, which + // uses get semantics and does not need to be released. + static CFStringRef CFStringForTransferFunction( + mozilla::gfx::TransferFunction); +}; + +#endif diff --git a/gfx/thebes/gfxMathTable.cpp b/gfx/thebes/gfxMathTable.cpp new file mode 100644 index 0000000000..6a27efff21 --- /dev/null +++ b/gfx/thebes/gfxMathTable.cpp @@ -0,0 +1,200 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxMathTable.h" + +#include "harfbuzz/hb.h" +#include "harfbuzz/hb-ot.h" + +#define FloatToFixed(f) (65536 * (f)) +#define FixedToFloat(f) ((f) * (1.0 / 65536.0)) + +using namespace mozilla; + +gfxMathTable::gfxMathTable(hb_face_t* aFace, gfxFloat aSize) { + mMathVariantCache.vertical = false; + mHBFont = hb_font_create(aFace); + if (mHBFont) { + hb_font_set_ppem(mHBFont, aSize, aSize); + uint32_t scale = FloatToFixed(aSize); + hb_font_set_scale(mHBFont, scale, scale); + } + + mMathVariantCache.glyphID = 0; + ClearCache(); +} + +gfxMathTable::~gfxMathTable() { + if (mHBFont) { + hb_font_destroy(mHBFont); + } +} + +gfxFloat gfxMathTable::Constant(MathConstant aConstant) const { + int32_t value = hb_ot_math_get_constant( + mHBFont, static_cast<hb_ot_math_constant_t>(aConstant)); + if (aConstant == ScriptPercentScaleDown || + aConstant == ScriptScriptPercentScaleDown || + aConstant == RadicalDegreeBottomRaisePercent) { + return value / 100.0; + } + return FixedToFloat(value); +} + +gfxFloat gfxMathTable::ItalicsCorrection(uint32_t aGlyphID) const { + return FixedToFloat( + hb_ot_math_get_glyph_italics_correction(mHBFont, aGlyphID)); +} + +uint32_t gfxMathTable::VariantsSize(uint32_t aGlyphID, bool aVertical, + uint16_t aSize) const { + UpdateMathVariantCache(aGlyphID, aVertical); + if (aSize < kMaxCachedSizeCount) { + return mMathVariantCache.sizes[aSize]; + } + + // If the size index exceeds the cache size, we just read the value with + // hb_ot_math_get_glyph_variants. + hb_direction_t direction = aVertical ? HB_DIRECTION_BTT : HB_DIRECTION_LTR; + hb_ot_math_glyph_variant_t variant; + unsigned int count = 1; + hb_ot_math_get_glyph_variants(mHBFont, aGlyphID, direction, aSize, &count, + &variant); + return count > 0 ? variant.glyph : 0; +} + +bool gfxMathTable::VariantsParts(uint32_t aGlyphID, bool aVertical, + uint32_t aGlyphs[4]) const { + UpdateMathVariantCache(aGlyphID, aVertical); + memcpy(aGlyphs, mMathVariantCache.parts, sizeof(mMathVariantCache.parts)); + return mMathVariantCache.arePartsValid; +} + +void gfxMathTable::ClearCache() const { + memset(mMathVariantCache.sizes, 0, sizeof(mMathVariantCache.sizes)); + memset(mMathVariantCache.parts, 0, sizeof(mMathVariantCache.parts)); + mMathVariantCache.arePartsValid = false; +} + +void gfxMathTable::UpdateMathVariantCache(uint32_t aGlyphID, + bool aVertical) const { + if (aGlyphID == mMathVariantCache.glyphID && + aVertical == mMathVariantCache.vertical) + return; + + mMathVariantCache.glyphID = aGlyphID; + mMathVariantCache.vertical = aVertical; + ClearCache(); + + // Cache the first size variants. + hb_direction_t direction = aVertical ? HB_DIRECTION_BTT : HB_DIRECTION_LTR; + hb_ot_math_glyph_variant_t variant[kMaxCachedSizeCount]; + unsigned int count = kMaxCachedSizeCount; + hb_ot_math_get_glyph_variants(mHBFont, aGlyphID, direction, 0, &count, + variant); + for (unsigned int i = 0; i < count; i++) { + mMathVariantCache.sizes[i] = variant[i].glyph; + } + + // Try and cache the parts of the glyph assembly. + // XXXfredw The structure of the Open Type Math table is a bit more general + // than the one currently used by the nsMathMLChar code, so we try to fallback + // in reasonable way. We use the approach of the copyComponents function in + // github.com/mathjax/MathJax-dev/blob/master/fonts/OpenTypeMath/fontUtil.py + // + // The nsMathMLChar code can use at most 3 non extender pieces (aGlyphs[0], + // aGlyphs[1] and aGlyphs[2]) and the extenders between these pieces should + // all be the same (aGlyphs[4]). Also, the parts of vertical assembly are + // stored from bottom to top in the Open Type MATH table while they are + // stored from top to bottom in nsMathMLChar. + + hb_ot_math_glyph_part_t parts[5]; + count = MOZ_ARRAY_LENGTH(parts); + unsigned int offset = 0; + if (hb_ot_math_get_glyph_assembly(mHBFont, aGlyphID, direction, offset, + &count, parts, + NULL) > MOZ_ARRAY_LENGTH(parts)) + return; // Not supported: Too many pieces. + if (count <= 0) return; // Not supported: No pieces. + + // Count the number of non extender pieces + uint16_t nonExtenderCount = 0; + for (uint16_t i = 0; i < count; i++) { + if (!(parts[i].flags & HB_MATH_GLYPH_PART_FLAG_EXTENDER)) { + nonExtenderCount++; + } + } + if (nonExtenderCount > 3) { + // Not supported: too many pieces + return; + } + + // Now browse the list of pieces + + // 0 = look for a left/bottom glyph + // 1 = look for an extender between left/bottom and mid + // 2 = look for a middle glyph + // 3 = look for an extender between middle and right/top + // 4 = look for a right/top glyph + // 5 = no more piece expected + uint8_t state = 0; + + // First extender char found. + uint32_t extenderChar = 0; + + for (uint16_t i = 0; i < count; i++) { + bool isExtender = parts[i].flags & HB_MATH_GLYPH_PART_FLAG_EXTENDER; + uint32_t glyph = parts[i].glyph; + + if ((state == 1 || state == 2) && nonExtenderCount < 3) { + // do not try to find a middle glyph + state += 2; + } + + if (isExtender) { + if (!extenderChar) { + extenderChar = glyph; + mMathVariantCache.parts[3] = extenderChar; + } else if (extenderChar != glyph) { + // Not supported: different extenders + return; + } + + if (state == 0) { // or state == 1 + // ignore left/bottom piece and multiple successive extenders + state = 1; + } else if (state == 2) { // or state == 3 + // ignore middle piece and multiple successive extenders + state = 3; + } else if (state >= 4) { + // Not supported: unexpected extender + return; + } + + continue; + } + + if (state == 0) { + // copy left/bottom part + mMathVariantCache.parts[aVertical ? 2 : 0] = glyph; + state = 1; + continue; + } + + if (state == 1 || state == 2) { + // copy middle part + mMathVariantCache.parts[1] = glyph; + state = 3; + continue; + } + + if (state == 3 || state == 4) { + // copy right/top part + mMathVariantCache.parts[aVertical ? 0 : 2] = glyph; + state = 5; + } + } + + mMathVariantCache.arePartsValid = true; +} diff --git a/gfx/thebes/gfxMathTable.h b/gfx/thebes/gfxMathTable.h new file mode 100644 index 0000000000..bb7595c36d --- /dev/null +++ b/gfx/thebes/gfxMathTable.h @@ -0,0 +1,152 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_MATH_TABLE_H +#define GFX_MATH_TABLE_H + +#include "gfxFont.h" + +/** + * Used by |gfxFont| to represent the MATH table of an OpenType font. + * Each |gfxFont| owns at most one |gfxMathTable| instance. + */ +class gfxMathTable { + public: + /** + * @param aFace The HarfBuzz face containing the math table. + * @param aSize The font size to pass to HarfBuzz. + */ + gfxMathTable(hb_face_t* aFace, gfxFloat aSize); + + /** + * Releases our reference to the MATH table and cleans up everything else. + */ + ~gfxMathTable(); + + enum MathConstant { + // The order of the constants must match the order of the fields + // defined in the MATH table. + ScriptPercentScaleDown, + ScriptScriptPercentScaleDown, + DelimitedSubFormulaMinHeight, + DisplayOperatorMinHeight, + MathLeading, + AxisHeight, + AccentBaseHeight, + FlattenedAccentBaseHeight, + SubscriptShiftDown, + SubscriptTopMax, + SubscriptBaselineDropMin, + SuperscriptShiftUp, + SuperscriptShiftUpCramped, + SuperscriptBottomMin, + SuperscriptBaselineDropMax, + SubSuperscriptGapMin, + SuperscriptBottomMaxWithSubscript, + SpaceAfterScript, + UpperLimitGapMin, + UpperLimitBaselineRiseMin, + LowerLimitGapMin, + LowerLimitBaselineDropMin, + StackTopShiftUp, + StackTopDisplayStyleShiftUp, + StackBottomShiftDown, + StackBottomDisplayStyleShiftDown, + StackGapMin, + StackDisplayStyleGapMin, + StretchStackTopShiftUp, + StretchStackBottomShiftDown, + StretchStackGapAboveMin, + StretchStackGapBelowMin, + FractionNumeratorShiftUp, + FractionNumeratorDisplayStyleShiftUp, + FractionDenominatorShiftDown, + FractionDenominatorDisplayStyleShiftDown, + FractionNumeratorGapMin, + FractionNumDisplayStyleGapMin, + FractionRuleThickness, + FractionDenominatorGapMin, + FractionDenomDisplayStyleGapMin, + SkewedFractionHorizontalGap, + SkewedFractionVerticalGap, + OverbarVerticalGap, + OverbarRuleThickness, + OverbarExtraAscender, + UnderbarVerticalGap, + UnderbarRuleThickness, + UnderbarExtraDescender, + RadicalVerticalGap, + RadicalDisplayStyleVerticalGap, + RadicalRuleThickness, + RadicalExtraAscender, + RadicalKernBeforeDegree, + RadicalKernAfterDegree, + RadicalDegreeBottomRaisePercent + }; + + /** + * Returns the value of the specified constant from the MATH table. + */ + gfxFloat Constant(MathConstant aConstant) const; + + /** + * Returns the value of the specified constant in app units. + */ + nscoord Constant(MathConstant aConstant, + uint32_t aAppUnitsPerDevPixel) const { + return NSToCoordRound(Constant(aConstant) * aAppUnitsPerDevPixel); + } + + /** + * If the MATH table contains an italic correction for that glyph, this + * function returns the corresponding value. Otherwise it returns 0. + */ + gfxFloat ItalicsCorrection(uint32_t aGlyphID) const; + + /** + * @param aGlyphID glyph index of the character we want to stretch + * @param aVertical direction of the stretching (vertical/horizontal) + * @param aSize the desired size variant + * + * Returns the glyph index of the desired size variant or 0 if there is not + * any such size variant. + */ + uint32_t VariantsSize(uint32_t aGlyphID, bool aVertical, + uint16_t aSize) const; + + /** + * @param aGlyphID glyph index of the character we want to stretch + * @param aVertical direction of the stretching (vertical/horizontal) + * @param aGlyphs pre-allocated buffer of 4 elements where the glyph + * indexes (or 0 for absent parts) will be stored. The parts are stored in + * the order expected by the nsMathMLChar: Top (or Left), Middle, Bottom + * (or Right), Glue. + * + * Tries to fill-in aGlyphs with the relevant glyph indexes and returns + * whether the operation was successful. The function returns false if + * there is not any assembly for the character we want to stretch or if + * the format is not supported by the nsMathMLChar code. + * + */ + bool VariantsParts(uint32_t aGlyphID, bool aVertical, + uint32_t aGlyphs[4]) const; + + private: + // size-specific font object, owned by the gfxMathTable + hb_font_t* mHBFont; + + static const unsigned int kMaxCachedSizeCount = 10; + struct MathVariantCacheEntry { + uint32_t glyphID; + bool vertical; + uint32_t sizes[kMaxCachedSizeCount]; + uint32_t parts[4]; + bool arePartsValid; + }; + mutable MathVariantCacheEntry mMathVariantCache; + void ClearCache() const; + void UpdateMathVariantCache(uint32_t aGlyphID, bool aVertical) const; +}; + +#endif diff --git a/gfx/thebes/gfxMatrix.h b/gfx/thebes/gfxMatrix.h new file mode 100644 index 0000000000..7ff90c8f39 --- /dev/null +++ b/gfx/thebes/gfxMatrix.h @@ -0,0 +1,13 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_MATRIX_H +#define GFX_MATRIX_H + +#include "mozilla/gfx/MatrixFwd.h" + +typedef mozilla::gfx::MatrixDouble gfxMatrix; + +#endif /* GFX_MATRIX_H */ diff --git a/gfx/thebes/gfxOTSUtils.h b/gfx/thebes/gfxOTSUtils.h new file mode 100644 index 0000000000..e73fe264bb --- /dev/null +++ b/gfx/thebes/gfxOTSUtils.h @@ -0,0 +1,182 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_OTS_UTILS_H +#define GFX_OTS_UTILS_H + +#include "gfxFontUtils.h" + +#include "opentype-sanitiser.h" + +struct gfxOTSMozAlloc { + void* Grow(void* aPtr, size_t aLength) { return moz_xrealloc(aPtr, aLength); } + void* ShrinkToFit(void* aPtr, size_t aLength) { + return moz_xrealloc(aPtr, aLength); + } + void Free(void* aPtr) { free(aPtr); } +}; + +// Based on ots::ExpandingMemoryStream from ots-memory-stream.h, +// adapted to use Mozilla allocators and to allow the final +// memory buffer to be adopted by the client. +template <typename AllocT = gfxOTSMozAlloc> +class gfxOTSExpandingMemoryStream : public ots::OTSStream { + public: + // limit output/expansion to 256MB by default + enum { DEFAULT_LIMIT = 256 * 1024 * 1024 }; + + explicit gfxOTSExpandingMemoryStream(size_t initial, + size_t limit = DEFAULT_LIMIT) + : mLength(initial), mLimit(limit), mOff(0) { + mPtr = mAlloc.Grow(nullptr, mLength); + } + + ~gfxOTSExpandingMemoryStream() { mAlloc.Free(mPtr); } + + size_t size() override { return mLimit; } + + // Return the buffer, resized to fit its contents (as it may have been + // over-allocated during growth), and give up ownership of it so the + // caller becomes responsible to call free() when finished with it. + auto forget() { + auto p = mAlloc.ShrinkToFit(mPtr, mOff); + mPtr = nullptr; + return p; + } + + bool WriteRaw(const void* data, size_t length) override { + if ((mOff + length > mLength) || + (mLength > std::numeric_limits<size_t>::max() - mOff)) { + if (mLength == mLimit) { + return false; + } + size_t newLength = (mLength + 1) * 2; + if (newLength < mLength) { + return false; + } + if (newLength > mLimit) { + newLength = mLimit; + } + mPtr = mAlloc.Grow(mPtr, newLength); + mLength = newLength; + return WriteRaw(data, length); + } + std::memcpy(static_cast<char*>(mPtr) + mOff, data, length); + mOff += length; + return true; + } + + bool Seek(off_t position) override { + if (position < 0) { + return false; + } + if (static_cast<size_t>(position) > mLength) { + return false; + } + mOff = position; + return true; + } + + off_t Tell() const override { return mOff; } + + private: + AllocT mAlloc; + void* mPtr; + size_t mLength; + const size_t mLimit; + off_t mOff; +}; + +class MOZ_STACK_CLASS gfxOTSContext : public ots::OTSContext { + public: + gfxOTSContext() { + using namespace mozilla; + + // Whether to apply OTS validation to OpenType Layout tables + mCheckOTLTables = StaticPrefs::gfx_downloadable_fonts_otl_validation(); + // Whether to preserve Variation tables in downloaded fonts + mCheckVariationTables = + StaticPrefs::gfx_downloadable_fonts_validate_variation_tables(); + // Whether to preserve color bitmap glyphs + mKeepColorBitmaps = + StaticPrefs::gfx_downloadable_fonts_keep_color_bitmaps(); + // Whether to preserve SVG glyphs (which can be expensive in Core Text, + // so better to drop them if we're not going to render them anyhow). + mKeepSVG = StaticPrefs::gfx_font_rendering_opentype_svg_enabled(); + } + + virtual ots::TableAction GetTableAction(uint32_t aTag) override { + // Pass through or validate OTL and Variation tables, depending on prefs. + if ((!mCheckOTLTables && (aTag == TRUETYPE_TAG('G', 'D', 'E', 'F') || + aTag == TRUETYPE_TAG('G', 'P', 'O', 'S') || + aTag == TRUETYPE_TAG('G', 'S', 'U', 'B')))) { + return ots::TABLE_ACTION_PASSTHRU; + } + auto isVariationTable = [](uint32_t aTag) -> bool { + return aTag == TRUETYPE_TAG('a', 'v', 'a', 'r') || + aTag == TRUETYPE_TAG('c', 'v', 'a', 'r') || + aTag == TRUETYPE_TAG('f', 'v', 'a', 'r') || + aTag == TRUETYPE_TAG('g', 'v', 'a', 'r') || + aTag == TRUETYPE_TAG('H', 'V', 'A', 'R') || + aTag == TRUETYPE_TAG('M', 'V', 'A', 'R') || + aTag == TRUETYPE_TAG('S', 'T', 'A', 'T') || + aTag == TRUETYPE_TAG('V', 'V', 'A', 'R'); + }; + if (!mCheckVariationTables && isVariationTable(aTag)) { + return ots::TABLE_ACTION_PASSTHRU; + } + if (!gfxPlatform::HasVariationFontSupport() && isVariationTable(aTag)) { + return ots::TABLE_ACTION_DROP; + } + // Preserve SVG table if OpenType-SVG rendering is enabled. + if (aTag == TRUETYPE_TAG('S', 'V', 'G', ' ')) { + return mKeepSVG ? ots::TABLE_ACTION_PASSTHRU : ots::TABLE_ACTION_DROP; + } + // Preserve BASE table; harfbuzz will sanitize it before using. + if (aTag == TRUETYPE_TAG('B', 'A', 'S', 'E')) { + return ots::TABLE_ACTION_PASSTHRU; + } + if (mKeepColorBitmaps && (aTag == TRUETYPE_TAG('C', 'B', 'D', 'T') || + aTag == TRUETYPE_TAG('C', 'B', 'L', 'C'))) { + return ots::TABLE_ACTION_PASSTHRU; + } + return ots::TABLE_ACTION_DEFAULT; + } + + static size_t GuessSanitizedFontSize(size_t aLength, + gfxUserFontType aFontType, + bool aStrict = true) { + switch (aFontType) { + case GFX_USERFONT_UNKNOWN: + // If being permissive of unknown types, make a reasonable guess + // at how much room the sanitized font may take, if it passes. Just + // enough extra space to accomodate some growth without excessive + // bloat in case of large fonts. 1.5x is a reasonable compromise + // for growable vectors in general. + return aStrict || !aLength ? 0 : (aLength * 3) / 2; + case GFX_USERFONT_WOFF: + return aLength * 2; + case GFX_USERFONT_WOFF2: + return aLength * 3; + default: + return aLength; + } + } + + static size_t GuessSanitizedFontSize(const uint8_t* aData, size_t aLength, + bool aStrict = true) { + gfxUserFontType fontType = + gfxFontUtils::DetermineFontDataType(aData, aLength); + return GuessSanitizedFontSize(aLength, fontType, aStrict); + } + + private: + bool mCheckOTLTables; + bool mCheckVariationTables; + bool mKeepColorBitmaps; + bool mKeepSVG; +}; + +#endif /* GFX_OTS_UTILS_H */ diff --git a/gfx/thebes/gfxPattern.cpp b/gfx/thebes/gfxPattern.cpp new file mode 100644 index 0000000000..709776dbed --- /dev/null +++ b/gfx/thebes/gfxPattern.cpp @@ -0,0 +1,203 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxPattern.h" + +#include "gfxUtils.h" +#include "gfxTypes.h" +#include "gfxPlatform.h" +#include "gfx2DGlue.h" +#include "gfxGradientCache.h" +#include "mozilla/gfx/2D.h" + +#include "cairo.h" + +#include <vector> + +using namespace mozilla::gfx; + +gfxPattern::gfxPattern(const DeviceColor& aColor) : mExtend(ExtendMode::CLAMP) { + mGfxPattern.InitColorPattern(aColor); +} + +// linear +gfxPattern::gfxPattern(gfxFloat x0, gfxFloat y0, gfxFloat x1, gfxFloat y1) + : mExtend(ExtendMode::CLAMP) { + mGfxPattern.InitLinearGradientPattern(Point(x0, y0), Point(x1, y1), nullptr); +} + +// radial +gfxPattern::gfxPattern(gfxFloat cx0, gfxFloat cy0, gfxFloat radius0, + gfxFloat cx1, gfxFloat cy1, gfxFloat radius1) + : mExtend(ExtendMode::CLAMP) { + mGfxPattern.InitRadialGradientPattern(Point(cx0, cy0), Point(cx1, cy1), + radius0, radius1, nullptr); +} + +// conic +gfxPattern::gfxPattern(gfxFloat cx, gfxFloat cy, gfxFloat angle, + gfxFloat startOffset, gfxFloat endOffset) + : mExtend(ExtendMode::CLAMP) { + mGfxPattern.InitConicGradientPattern(Point(cx, cy), angle, startOffset, + endOffset, nullptr); +} + +// Azure +gfxPattern::gfxPattern(SourceSurface* aSurface, + const Matrix& aPatternToUserSpace) + : mPatternToUserSpace(aPatternToUserSpace), mExtend(ExtendMode::CLAMP) { + mGfxPattern.InitSurfacePattern( + aSurface, mExtend, Matrix(), // matrix is overridden in GetPattern() + mozilla::gfx::SamplingFilter::GOOD); +} + +void gfxPattern::AddColorStop(gfxFloat offset, const DeviceColor& c) { + if (mGfxPattern.GetPattern()->GetType() != PatternType::LINEAR_GRADIENT && + mGfxPattern.GetPattern()->GetType() != PatternType::RADIAL_GRADIENT && + mGfxPattern.GetPattern()->GetType() != PatternType::CONIC_GRADIENT) { + return; + } + + mStops = nullptr; + + GradientStop stop; + stop.offset = offset; + stop.color = c; + mStopsList.AppendElement(stop); +} + +void gfxPattern::SetColorStops(GradientStops* aStops) { mStops = aStops; } + +void gfxPattern::CacheColorStops(const DrawTarget* aDT) { + mStops = gfxGradientCache::GetOrCreateGradientStops(aDT, mStopsList, mExtend); +} + +void gfxPattern::SetMatrix(const gfxMatrix& aPatternToUserSpace) { + mPatternToUserSpace = ToMatrix(aPatternToUserSpace); + // Cairo-pattern matrices specify the conversion from DrawTarget to pattern + // space. Azure pattern matrices specify the conversion from pattern to + // DrawTarget space. + mPatternToUserSpace.Invert(); +} + +gfxMatrix gfxPattern::GetMatrix() const { + // invert at the higher precision of gfxMatrix + // cause we need to convert at some point anyways + gfxMatrix mat = ThebesMatrix(mPatternToUserSpace); + mat.Invert(); + return mat; +} + +gfxMatrix gfxPattern::GetInverseMatrix() const { + return ThebesMatrix(mPatternToUserSpace); +} + +Pattern* gfxPattern::GetPattern(const DrawTarget* aTarget, + const Matrix* aOriginalUserToDevice) { + Matrix patternToUser = mPatternToUserSpace; + + if (aOriginalUserToDevice && + !aOriginalUserToDevice->FuzzyEquals(aTarget->GetTransform())) { + // mPatternToUserSpace maps from pattern space to the original user space, + // but aTarget now has a transform to a different user space. In order for + // the Pattern* that we return to be usable in aTarget's new user space we + // need the Pattern's mMatrix to be the transform from pattern space to + // aTarget's -new- user space. That transform is equivalent to the + // transform from pattern space to original user space (patternToUser), + // multiplied by the transform from original user space to device space, + // multiplied by the transform from device space to current user space. + + Matrix deviceToCurrentUser = aTarget->GetTransform(); + deviceToCurrentUser.Invert(); + + patternToUser = + patternToUser * *aOriginalUserToDevice * deviceToCurrentUser; + } + patternToUser.NudgeToIntegers(); + + if (!mStops && !mStopsList.IsEmpty()) { + mStops = aTarget->CreateGradientStops(mStopsList.Elements(), + mStopsList.Length(), mExtend); + } + + switch (mGfxPattern.GetPattern()->GetType()) { + case PatternType::SURFACE: { + SurfacePattern* surfacePattern = + static_cast<SurfacePattern*>(mGfxPattern.GetPattern()); + surfacePattern->mMatrix = patternToUser; + surfacePattern->mExtendMode = mExtend; + break; + } + case PatternType::LINEAR_GRADIENT: { + LinearGradientPattern* linearGradientPattern = + static_cast<LinearGradientPattern*>(mGfxPattern.GetPattern()); + linearGradientPattern->mMatrix = patternToUser; + linearGradientPattern->mStops = mStops; + break; + } + case PatternType::RADIAL_GRADIENT: { + RadialGradientPattern* radialGradientPattern = + static_cast<RadialGradientPattern*>(mGfxPattern.GetPattern()); + radialGradientPattern->mMatrix = patternToUser; + radialGradientPattern->mStops = mStops; + break; + } + case PatternType::CONIC_GRADIENT: { + ConicGradientPattern* conicGradientPattern = + static_cast<ConicGradientPattern*>(mGfxPattern.GetPattern()); + conicGradientPattern->mMatrix = patternToUser; + conicGradientPattern->mStops = mStops; + break; + } + default: + /* Reassure the compiler we are handling all the enum values. */ + break; + } + + return mGfxPattern.GetPattern(); +} + +void gfxPattern::SetExtend(ExtendMode aExtend) { + mExtend = aExtend; + mStops = nullptr; +} + +bool gfxPattern::IsOpaque() { + if (mGfxPattern.GetPattern()->GetType() != PatternType::SURFACE) { + return false; + } + + if (static_cast<SurfacePattern*>(mGfxPattern.GetPattern()) + ->mSurface->GetFormat() == SurfaceFormat::B8G8R8X8) { + return true; + } + return false; +} + +void gfxPattern::SetSamplingFilter(mozilla::gfx::SamplingFilter filter) { + if (mGfxPattern.GetPattern()->GetType() != PatternType::SURFACE) { + return; + } + + static_cast<SurfacePattern*>(mGfxPattern.GetPattern())->mSamplingFilter = + filter; +} + +SamplingFilter gfxPattern::SamplingFilter() const { + if (mGfxPattern.GetPattern()->GetType() != PatternType::SURFACE) { + return mozilla::gfx::SamplingFilter::GOOD; + } + return static_cast<const SurfacePattern*>(mGfxPattern.GetPattern()) + ->mSamplingFilter; +} + +bool gfxPattern::GetSolidColor(DeviceColor& aColorOut) { + if (mGfxPattern.GetPattern()->GetType() == PatternType::COLOR) { + aColorOut = static_cast<ColorPattern*>(mGfxPattern.GetPattern())->mColor; + return true; + } + + return false; +} diff --git a/gfx/thebes/gfxPattern.h b/gfx/thebes/gfxPattern.h new file mode 100644 index 0000000000..cc8a7d5231 --- /dev/null +++ b/gfx/thebes/gfxPattern.h @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_PATTERN_H +#define GFX_PATTERN_H + +#include "gfxTypes.h" + +#include "gfxMatrix.h" +#include "mozilla/Alignment.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/PatternHelpers.h" +#include "nsISupportsImpl.h" +#include "nsTArray.h" + +typedef struct _cairo_pattern cairo_pattern_t; + +class gfxPattern final { + NS_INLINE_DECL_REFCOUNTING(gfxPattern) + + public: + explicit gfxPattern(const mozilla::gfx::DeviceColor& aColor); + // gradients + gfxPattern(gfxFloat x0, gfxFloat y0, gfxFloat x1, gfxFloat y1); // linear + gfxPattern(gfxFloat cx0, gfxFloat cy0, gfxFloat radius0, gfxFloat cx1, + gfxFloat cy1, gfxFloat radius1); // radial + gfxPattern(gfxFloat cx, gfxFloat cy, gfxFloat angle, gfxFloat startOffset, + gfxFloat endOffset); // conic + gfxPattern(mozilla::gfx::SourceSurface* aSurface, + const mozilla::gfx::Matrix& aPatternToUserSpace); + + void AddColorStop(gfxFloat offset, const mozilla::gfx::DeviceColor& c); + void SetColorStops(mozilla::gfx::GradientStops* aStops); + + // This should only be called on a cairo pattern that we want to use with + // Azure. We will read back the color stops from cairo and try to look + // them up in the cache. + void CacheColorStops(const mozilla::gfx::DrawTarget* aDT); + + void SetMatrix(const gfxMatrix& matrix); + gfxMatrix GetMatrix() const; + gfxMatrix GetInverseMatrix() const; + + /* Get an Azure Pattern for the current Cairo pattern. aPattern transform + * specifies the transform that was set on the DrawTarget when the pattern + * was set. When this is nullptr it is assumed the transform is identical + * to the current transform. + */ + mozilla::gfx::Pattern* GetPattern( + const mozilla::gfx::DrawTarget* aTarget, + const mozilla::gfx::Matrix* aOriginalUserToDevice = nullptr); + bool IsOpaque(); + + // clamp, repeat, reflect + void SetExtend(mozilla::gfx::ExtendMode aExtend); + + void SetSamplingFilter(mozilla::gfx::SamplingFilter aSamplingFilter); + mozilla::gfx::SamplingFilter SamplingFilter() const; + + /* returns TRUE if it succeeded */ + bool GetSolidColor(mozilla::gfx::DeviceColor& aColorOut); + + private: + // Private destructor, to discourage deletion outside of Release(): + ~gfxPattern() = default; + + mozilla::gfx::GeneralPattern mGfxPattern; + RefPtr<mozilla::gfx::SourceSurface> mSourceSurface; + mozilla::gfx::Matrix mPatternToUserSpace; + RefPtr<mozilla::gfx::GradientStops> mStops; + nsTArray<mozilla::gfx::GradientStop> mStopsList; + mozilla::gfx::ExtendMode mExtend; +}; + +#endif /* GFX_PATTERN_H */ diff --git a/gfx/thebes/gfxPlatform.cpp b/gfx/thebes/gfxPlatform.cpp new file mode 100644 index 0000000000..ccc76def7e --- /dev/null +++ b/gfx/thebes/gfxPlatform.cpp @@ -0,0 +1,4012 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/RDDProcessManager.h" +#include "mozilla/image/ImageMemoryReporter.h" +#include "mozilla/layers/CompositorManagerChild.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/ImageBridgeChild.h" +#include "mozilla/layers/ISurfaceAllocator.h" // for GfxMemoryImageReporter +#include "mozilla/layers/CompositorBridgeChild.h" +#include "mozilla/layers/RemoteTextureMap.h" +#include "mozilla/layers/VideoBridgeParent.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "mozilla/webrender/webrender_ffi.h" +#include "mozilla/gfx/BuildConstants.h" +#include "mozilla/gfx/gfxConfigManager.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/gfx/GraphicsMessages.h" +#include "mozilla/gfx/CanvasManagerChild.h" +#include "mozilla/gfx/CanvasRenderThread.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/EnumTypeTraits.h" +#include "mozilla/StaticPrefs_accessibility.h" +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPrefs_bidi.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/StaticPrefs_media.h" +#include "mozilla/StaticPrefs_privacy.h" +#include "mozilla/StaticPrefs_webgl.h" +#include "mozilla/StaticPrefs_widget.h" +#include "mozilla/Telemetry.h" +#include "mozilla/glean/GleanMetrics.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Unused.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Base64.h" +#include "mozilla/VsyncDispatcher.h" + +#include "mozilla/Logging.h" +#include "mozilla/Components.h" +#include "nsAppRunner.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsCSSProps.h" +#include "nsContentUtils.h" + +#include "gfxCrashReporterUtils.h" +#include "gfxPlatform.h" +#include "gfxPlatformWorker.h" + +#include "gfxBlur.h" +#include "gfxEnv.h" +#include "gfxTextRun.h" +#include "gfxUserFontSet.h" +#include "gfxConfig.h" +#include "GfxDriverInfo.h" +#include "VRProcessManager.h" +#include "VRThread.h" + +#ifdef XP_WIN +# include <process.h> +# define getpid _getpid +#else +# include <unistd.h> +#endif + +#include "nsXULAppAPI.h" +#include "nsIXULAppInfo.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" + +#if defined(XP_WIN) +# include "gfxWindowsPlatform.h" +# include "mozilla/widget/WinWindowOcclusionTracker.h" +#elif defined(XP_MACOSX) +# include "gfxPlatformMac.h" +# include "gfxQuartzSurface.h" +#elif defined(MOZ_WIDGET_GTK) +# include "gfxPlatformGtk.h" +#elif defined(ANDROID) +# include "gfxAndroidPlatform.h" +#endif +#if defined(MOZ_WIDGET_ANDROID) +# include "mozilla/jni/Utils.h" // for IsFennec +#endif + +#ifdef XP_WIN +# include "mozilla/WindowsVersion.h" +# include "WinUtils.h" +#endif + +#include "nsGkAtoms.h" +#include "gfxPlatformFontList.h" +#include "gfxContext.h" +#include "gfxImageSurface.h" +#include "nsUnicodeProperties.h" +#include "harfbuzz/hb.h" +#include "gfxGraphiteShaper.h" +#include "gfx2DGlue.h" +#include "gfxGradientCache.h" +#include "gfxUtils.h" // for NextPowerOfTwo +#include "gfxFontMissingGlyphs.h" + +#include "nsExceptionHandler.h" +#include "nsServiceManagerUtils.h" +#include "nsTArray.h" +#include "nsIObserverService.h" +#include "mozilla/widget/Screen.h" +#include "mozilla/widget/ScreenManager.h" +#include "MainThreadUtils.h" + +#include "nsWeakReference.h" + +#include "cairo.h" +#include "qcms.h" + +#include "imgITools.h" + +#include "nsCRT.h" +#include "GLContext.h" +#include "GLContextProvider.h" +#include "mozilla/gfx/Logging.h" + +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wshadow" +#endif +#include "skia/include/core/SkGraphics.h" +#ifdef MOZ_ENABLE_FREETYPE +# include "skia/include/ports/SkTypeface_cairo.h" +#endif +#include "mozilla/gfx/SkMemoryReporter.h" +#ifdef __GNUC__ +# pragma GCC diagnostic pop // -Wshadow +#endif +static const uint32_t kDefaultGlyphCacheSize = -1; + +#include "mozilla/Preferences.h" +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" + +#include "nsAlgorithm.h" +#include "nsIGfxInfo.h" +#include "nsIXULRuntime.h" +#include "VsyncSource.h" +#include "SoftwareVsyncSource.h" +#include "nscore.h" // for NS_FREE_PERMANENT_DATA +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/TouchEvent.h" +#include "gfxVR.h" +#include "VRManager.h" +#include "VRManagerChild.h" +#include "mozilla/gfx/GPUParent.h" +#include "prsystem.h" + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/SourceSurfaceCairo.h" + +using namespace mozilla; +using namespace mozilla::layers; +using namespace mozilla::gl; +using namespace mozilla::gfx; + +gfxPlatform* gPlatform = nullptr; +static bool gEverInitialized = false; + +const ContentDeviceData* gContentDeviceInitData = nullptr; +Maybe<nsTArray<uint8_t>> gCMSOutputProfileData; + +Atomic<bool, MemoryOrdering::ReleaseAcquire> gfxPlatform::gCMSInitialized; +CMSMode gfxPlatform::gCMSMode = CMSMode::Off; + +// These two may point to the same profile +qcms_profile* gfxPlatform::gCMSOutputProfile = nullptr; +qcms_profile* gfxPlatform::gCMSsRGBProfile = nullptr; + +qcms_transform* gfxPlatform::gCMSRGBTransform = nullptr; +qcms_transform* gfxPlatform::gCMSInverseRGBTransform = nullptr; +qcms_transform* gfxPlatform::gCMSRGBATransform = nullptr; +qcms_transform* gfxPlatform::gCMSBGRATransform = nullptr; + +/// This override of the LogForwarder, initially used for the critical graphics +/// errors, is sending the log to the crash annotations as well, but only +/// if the capacity set with the method below is >= 2. We always retain the +/// very first critical message, and the latest capacity-1 messages are +/// rotated through. Note that we don't expect the total number of times +/// this gets called to be large - it is meant for critical errors only. + +class CrashStatsLogForwarder : public mozilla::gfx::LogForwarder { + public: + explicit CrashStatsLogForwarder(CrashReporter::Annotation aKey); + void Log(const std::string& aString) override; + void CrashAction(LogReason aReason) override; + bool UpdateStringsVector(const std::string& aString) override; + + LoggingRecord LoggingRecordCopy() override; + + void SetCircularBufferSize(uint32_t aCapacity); + + private: + // Helper for the Log() + void UpdateCrashReport(); + + private: + LoggingRecord mBuffer; + CrashReporter::Annotation mCrashCriticalKey; + uint32_t mMaxCapacity; + int32_t mIndex; + Mutex mMutex MOZ_UNANNOTATED; +}; + +CrashStatsLogForwarder::CrashStatsLogForwarder(CrashReporter::Annotation aKey) + : mCrashCriticalKey(aKey), + mMaxCapacity(0), + mIndex(-1), + mMutex("CrashStatsLogForwarder") {} + +void CrashStatsLogForwarder::SetCircularBufferSize(uint32_t aCapacity) { + MutexAutoLock lock(mMutex); + + mMaxCapacity = aCapacity; + mBuffer.reserve(static_cast<size_t>(aCapacity)); +} + +LoggingRecord CrashStatsLogForwarder::LoggingRecordCopy() { + MutexAutoLock lock(mMutex); + return mBuffer; +} + +bool CrashStatsLogForwarder::UpdateStringsVector(const std::string& aString) { + // We want at least the first one and the last one. Otherwise, no point. + if (mMaxCapacity < 2) { + return false; + } + + mIndex += 1; + MOZ_ASSERT(mIndex >= 0); + + // index will count 0, 1, 2, ..., max-1, 1, 2, ..., max-1, 1, 2, ... + int32_t index = mIndex ? (mIndex - 1) % (mMaxCapacity - 1) + 1 : 0; + MOZ_ASSERT(index >= 0 && index < (int32_t)mMaxCapacity); + MOZ_ASSERT(index <= mIndex && index <= (int32_t)mBuffer.size()); + + double tStamp = (TimeStamp::NowLoRes() - TimeStamp::ProcessCreation()) + .ToSecondsSigDigits(); + + // Checking for index >= mBuffer.size(), rather than index == mBuffer.size() + // just out of paranoia, but we know index <= mBuffer.size(). + LoggingRecordEntry newEntry(mIndex, aString, tStamp); + if (index >= static_cast<int32_t>(mBuffer.size())) { + mBuffer.push_back(newEntry); + } else { + mBuffer[index] = newEntry; + } + return true; +} + +void CrashStatsLogForwarder::UpdateCrashReport() { + std::stringstream message; + std::string logAnnotation; + + switch (XRE_GetProcessType()) { + case GeckoProcessType_Default: + logAnnotation = "|["; + break; + case GeckoProcessType_Content: + logAnnotation = "|[C"; + break; + case GeckoProcessType_GPU: + logAnnotation = "|[G"; + break; + default: + logAnnotation = "|[X"; + break; + } + + for (auto& it : mBuffer) { + message << logAnnotation << std::get<0>(it) << "]" << std::get<1>(it) + << " (t=" << std::get<2>(it) << ") "; + } + + nsCString reportString(message.str().c_str()); + nsresult annotated = + CrashReporter::AnnotateCrashReport(mCrashCriticalKey, reportString); + + if (annotated != NS_OK) { + printf("Crash Annotation %s: %s", + CrashReporter::AnnotationToString(mCrashCriticalKey), + message.str().c_str()); + } +} + +class LogForwarderEvent : public Runnable { + virtual ~LogForwarderEvent() = default; + + public: + NS_INLINE_DECL_REFCOUNTING_INHERITED(LogForwarderEvent, Runnable) + + explicit LogForwarderEvent(const nsCString& aMessage) + : mozilla::Runnable("LogForwarderEvent"), mMessage(aMessage) {} + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread() && + (XRE_IsContentProcess() || XRE_IsGPUProcess())); + + if (XRE_IsContentProcess()) { + dom::ContentChild* cc = dom::ContentChild::GetSingleton(); + Unused << cc->SendGraphicsError(mMessage); + } else if (XRE_IsGPUProcess()) { + GPUParent* gp = GPUParent::GetSingleton(); + Unused << gp->SendGraphicsError(mMessage); + } + + return NS_OK; + } + + protected: + nsCString mMessage; +}; + +void CrashStatsLogForwarder::Log(const std::string& aString) { + MutexAutoLock lock(mMutex); + + if (UpdateStringsVector(aString)) { + UpdateCrashReport(); + } + + // Add it to the parent strings + if (!XRE_IsParentProcess()) { + nsCString stringToSend(aString.c_str()); + if (NS_IsMainThread()) { + if (XRE_IsContentProcess()) { + dom::ContentChild* cc = dom::ContentChild::GetSingleton(); + Unused << cc->SendGraphicsError(stringToSend); + } else if (XRE_IsGPUProcess()) { + GPUParent* gp = GPUParent::GetSingleton(); + Unused << gp->SendGraphicsError(stringToSend); + } + } else { + nsCOMPtr<nsIRunnable> r1 = new LogForwarderEvent(stringToSend); + NS_DispatchToMainThread(r1); + } + } +} + +class CrashTelemetryEvent : public Runnable { + virtual ~CrashTelemetryEvent() = default; + + public: + NS_INLINE_DECL_REFCOUNTING_INHERITED(CrashTelemetryEvent, Runnable) + + explicit CrashTelemetryEvent(uint32_t aReason) + : mozilla::Runnable("CrashTelemetryEvent"), mReason(aReason) {} + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread()); + Telemetry::Accumulate(Telemetry::GFX_CRASH, mReason); + return NS_OK; + } + + protected: + uint32_t mReason; +}; + +void CrashStatsLogForwarder::CrashAction(LogReason aReason) { +#ifndef RELEASE_OR_BETA + // Non-release builds crash by default, but will use telemetry + // if this environment variable is present. + static bool useTelemetry = gfxEnv::MOZ_GFX_CRASH_TELEMETRY(); +#else + // Release builds use telemetry by default, but will crash instead + // if this environment variable is present. + static bool useTelemetry = !gfxEnv::MOZ_GFX_CRASH_MOZ_CRASH(); +#endif + + if (useTelemetry) { + // The callers need to assure that aReason is in the range + // that the telemetry call below supports. + if (NS_IsMainThread()) { + Telemetry::Accumulate(Telemetry::GFX_CRASH, (uint32_t)aReason); + } else { + nsCOMPtr<nsIRunnable> r1 = new CrashTelemetryEvent((uint32_t)aReason); + NS_DispatchToMainThread(r1); + } + } else { + // ignoring aReason, we can get the information we need from the stack + MOZ_CRASH("GFX_CRASH"); + } +} + +#define GFX_DOWNLOADABLE_FONTS_ENABLED "gfx.downloadable_fonts.enabled" + +#define GFX_PREF_FALLBACK_USE_CMAPS \ + "gfx.font_rendering.fallback.always_use_cmaps" + +#define GFX_PREF_OPENTYPE_SVG "gfx.font_rendering.opentype_svg.enabled" + +#define GFX_PREF_WORD_CACHE_CHARLIMIT "gfx.font_rendering.wordcache.charlimit" +#define GFX_PREF_WORD_CACHE_MAXENTRIES "gfx.font_rendering.wordcache.maxentries" + +#define GFX_PREF_GRAPHITE_SHAPING "gfx.font_rendering.graphite.enabled" +#if defined(XP_MACOSX) +# define GFX_PREF_CORETEXT_SHAPING "gfx.font_rendering.coretext.enabled" +#endif + +#define FONT_VARIATIONS_PREF "layout.css.font-variations.enabled" + +static const char* kObservedPrefs[] = {"gfx.downloadable_fonts.", + "gfx.font_rendering.", nullptr}; + +static void FontPrefChanged(const char* aPref, void* aData) { + MOZ_ASSERT(aPref); + NS_ASSERTION(gfxPlatform::GetPlatform(), "the singleton instance has gone"); + gfxPlatform::GetPlatform()->FontsPrefsChanged(aPref); +} + +void gfxPlatform::OnMemoryPressure(layers::MemoryPressureReason aWhy) { + Factory::PurgeAllCaches(); + gfxGradientCache::PurgeAllCaches(); + gfxFontMissingGlyphs::Purge(); + PurgeSkiaFontCache(); + if (XRE_IsParentProcess()) { + layers::CompositorManagerChild* manager = + CompositorManagerChild::GetInstance(); + if (manager) { + manager->SendNotifyMemoryPressure(); + } + } +} + +gfxPlatform::gfxPlatform() + : mAzureCanvasBackendCollector(this, &gfxPlatform::GetAzureBackendInfo), + mApzSupportCollector(this, &gfxPlatform::GetApzSupportInfo), + mFrameStatsCollector(this, &gfxPlatform::GetFrameStats), + mCMSInfoCollector(this, &gfxPlatform::GetCMSSupportInfo), + mDisplayInfoCollector(this, &gfxPlatform::GetDisplayInfo), + mOverlayInfoCollector(this, &gfxPlatform::GetOverlayInfo), + mSwapChainInfoCollector(this, &gfxPlatform::GetSwapChainInfo), + mCompositorBackend(layers::LayersBackend::LAYERS_NONE), + mScreenDepth(0) { + mAllowDownloadableFonts = UNINITIALIZED_VALUE; + + InitBackendPrefs(GetBackendPrefs()); + VRManager::ManagerInit(); +} + +gfxPlatform* gfxPlatform::GetPlatform() { + if (!gPlatform) { + MOZ_RELEASE_ASSERT(!XRE_IsContentProcess(), + "Content Process should have called InitChild() before " + "first GetPlatform()"); + Init(); + } + return gPlatform; +} + +bool gfxPlatform::Initialized() { return !!gPlatform; } + +/* static */ +void gfxPlatform::InitChild(const ContentDeviceData& aData) { + MOZ_ASSERT(XRE_IsContentProcess()); + MOZ_ASSERT(!gPlatform, + "InitChild() should be called before first GetPlatform()"); + // Make the provided initial ContentDeviceData available to the init + // routines. + gContentDeviceInitData = &aData; + Init(); + gContentDeviceInitData = nullptr; +} + +#define WR_DEBUG_PREF "gfx.webrender.debug" + +static void SwapIntervalPrefChangeCallback(const char* aPrefName, void*) { + bool egl = Preferences::GetBool("gfx.swap-interval.egl", false); + bool glx = Preferences::GetBool("gfx.swap-interval.glx", false); + gfxVars::SetSwapIntervalEGL(egl); + gfxVars::SetSwapIntervalGLX(glx); +} + +static void WebRendeProfilerUIPrefChangeCallback(const char* aPrefName, void*) { + nsCString uiString; + if (NS_SUCCEEDED(Preferences::GetCString("gfx.webrender.debug.profiler-ui", + uiString))) { + gfxVars::SetWebRenderProfilerUI(uiString); + } +} + +// List of boolean dynamic parameter for WebRender. +// +// The parameters in this list are: +// - The pref name. +// - The BoolParameter enum variant (see webrender_api/src/lib.rs) +// - A default value. +#define WR_BOOL_PARAMETER_LIST(_) \ + _("gfx.webrender.batched-texture-uploads", \ + wr::BoolParameter::BatchedUploads, true) \ + _("gfx.webrender.draw-calls-for-texture-copy", \ + wr::BoolParameter::DrawCallsForTextureCopy, true) \ + _("gfx.webrender.pbo-uploads", wr::BoolParameter::PboUploads, true) \ + _("gfx.webrender.multithreading", wr::BoolParameter::Multithreading, true) + +static void WebRenderBoolParameterChangeCallback(const char*, void*) { + uint32_t bits = 0; + +#define WR_BOOL_PARAMETER(name, key, default_val) \ + if (Preferences::GetBool(name, default_val)) { \ + bits |= 1 << (uint32_t)key; \ + } + + WR_BOOL_PARAMETER_LIST(WR_BOOL_PARAMETER) +#undef WR_BOOL_PARAMETER + + gfx::gfxVars::SetWebRenderBoolParameters(bits); +} + +static void RegisterWebRenderBoolParamCallback() { +#define WR_BOOL_PARAMETER(name, _key, _default_val) \ + Preferences::RegisterCallback(WebRenderBoolParameterChangeCallback, name); + + WR_BOOL_PARAMETER_LIST(WR_BOOL_PARAMETER) +#undef WR_BOOL_PARAMETER + + WebRenderBoolParameterChangeCallback(nullptr, nullptr); +} + +static void WebRenderDebugPrefChangeCallback(const char* aPrefName, void*) { + wr::DebugFlags flags{0}; +#define GFX_WEBRENDER_DEBUG(suffix, bit) \ + if (Preferences::GetBool(WR_DEBUG_PREF suffix, false)) { \ + flags |= (bit); \ + } + + GFX_WEBRENDER_DEBUG(".profiler", wr::DebugFlags::PROFILER_DBG) + GFX_WEBRENDER_DEBUG(".render-targets", wr::DebugFlags::RENDER_TARGET_DBG) + GFX_WEBRENDER_DEBUG(".texture-cache", wr::DebugFlags::TEXTURE_CACHE_DBG) + GFX_WEBRENDER_DEBUG(".gpu-time-queries", wr::DebugFlags::GPU_TIME_QUERIES) + GFX_WEBRENDER_DEBUG(".gpu-sample-queries", wr::DebugFlags::GPU_SAMPLE_QUERIES) + GFX_WEBRENDER_DEBUG(".disable-batching", wr::DebugFlags::DISABLE_BATCHING) + GFX_WEBRENDER_DEBUG(".epochs", wr::DebugFlags::EPOCHS) + GFX_WEBRENDER_DEBUG(".smart-profiler", wr::DebugFlags::SMART_PROFILER) + GFX_WEBRENDER_DEBUG(".echo-driver-messages", + wr::DebugFlags::ECHO_DRIVER_MESSAGES) + GFX_WEBRENDER_DEBUG(".show-overdraw", wr::DebugFlags::SHOW_OVERDRAW) + GFX_WEBRENDER_DEBUG(".gpu-cache", wr::DebugFlags::GPU_CACHE_DBG) + GFX_WEBRENDER_DEBUG(".texture-cache.clear-evicted", + wr::DebugFlags::TEXTURE_CACHE_DBG_CLEAR_EVICTED) + GFX_WEBRENDER_DEBUG(".picture-caching", wr::DebugFlags::PICTURE_CACHING_DBG) + GFX_WEBRENDER_DEBUG(".force-picture-invalidation", + wr::DebugFlags::FORCE_PICTURE_INVALIDATION) + GFX_WEBRENDER_DEBUG(".primitives", wr::DebugFlags::PRIMITIVE_DBG) + // Bit 18 is for the zoom display, which requires the mouse position and thus + // currently only works in wrench. + GFX_WEBRENDER_DEBUG(".small-screen", wr::DebugFlags::SMALL_SCREEN) + GFX_WEBRENDER_DEBUG(".disable-opaque-pass", + wr::DebugFlags::DISABLE_OPAQUE_PASS) + GFX_WEBRENDER_DEBUG(".disable-alpha-pass", wr::DebugFlags::DISABLE_ALPHA_PASS) + GFX_WEBRENDER_DEBUG(".disable-clip-masks", wr::DebugFlags::DISABLE_CLIP_MASKS) + GFX_WEBRENDER_DEBUG(".disable-text-prims", wr::DebugFlags::DISABLE_TEXT_PRIMS) + GFX_WEBRENDER_DEBUG(".disable-gradient-prims", + wr::DebugFlags::DISABLE_GRADIENT_PRIMS) + GFX_WEBRENDER_DEBUG(".obscure-images", wr::DebugFlags::OBSCURE_IMAGES) + GFX_WEBRENDER_DEBUG(".glyph-flashing", wr::DebugFlags::GLYPH_FLASHING) + GFX_WEBRENDER_DEBUG(".capture-profiler", wr::DebugFlags::PROFILER_CAPTURE) + GFX_WEBRENDER_DEBUG(".window-visibility", + wr::DebugFlags::WINDOW_VISIBILITY_DBG) + GFX_WEBRENDER_DEBUG(".restrict-blob-size", wr::DebugFlags::RESTRICT_BLOB_SIZE) +#undef GFX_WEBRENDER_DEBUG + gfx::gfxVars::SetWebRenderDebugFlags(flags._0); +} + +static void WebRenderQualityPrefChangeCallback(const char* aPref, void*) { + gfxPlatform::GetPlatform()->UpdateForceSubpixelAAWherePossible(); +} + +static void WebRenderBatchingPrefChangeCallback(const char* aPrefName, void*) { + uint32_t count = Preferences::GetUint( + StaticPrefs::GetPrefName_gfx_webrender_batching_lookback(), 10); + + gfx::gfxVars::SetWebRenderBatchingLookback(count); +} + +static void WebRenderBlobTileSizePrefChangeCallback(const char* aPrefName, + void*) { + uint32_t tileSize = Preferences::GetUint( + StaticPrefs::GetPrefName_gfx_webrender_blob_tile_size(), 256); + gfx::gfxVars::SetWebRenderBlobTileSize(tileSize); +} + +static void WebRenderUploadThresholdPrefChangeCallback(const char* aPrefName, + void*) { + int value = Preferences::GetInt( + StaticPrefs::GetPrefName_gfx_webrender_batched_upload_threshold(), + 512 * 512); + + gfxVars::SetWebRenderBatchedUploadThreshold(value); +} + +static uint32_t GetSkiaGlyphCacheSize() { + // Only increase font cache size on non-android to save memory. +#if !defined(MOZ_WIDGET_ANDROID) + // 10mb as the default pref cache size on desktop due to talos perf tweaking. + // Chromium uses 20mb and skia default uses 2mb. + // We don't need to change the font cache count since we usually + // cache thrash due to asian character sets in talos. + // Only increase memory on the content process + uint32_t cacheSize = + StaticPrefs::gfx_content_skia_font_cache_size_AtStartup() * 1024 * 1024; + if (mozilla::BrowserTabsRemoteAutostart()) { + return XRE_IsContentProcess() ? cacheSize : kDefaultGlyphCacheSize; + } + + return cacheSize; +#else + return kDefaultGlyphCacheSize; +#endif // MOZ_WIDGET_ANDROID +} + +class WebRenderMemoryReporter final : public nsIMemoryReporter { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMEMORYREPORTER + + private: + ~WebRenderMemoryReporter() = default; +}; + +// Memory reporter for WebRender. +// +// The reporting within WebRender is manual and incomplete. We could do a much +// more thorough job by depending on the malloc_size_of crate, but integrating +// that into WebRender is tricky [1]. +// +// So the idea is to start with manual reporting for the large allocations +// detected by DMD, and see how much that can cover in practice (which may +// require a few rounds of iteration). If that approach turns out to be +// fundamentally insufficient, we can either duplicate more of the +// malloc_size_of functionality in WebRender, or deal with the complexity of a +// gecko-only crate dependency. +// +// [1] See https://bugzilla.mozilla.org/show_bug.cgi?id=1480293#c1 +struct WebRenderMemoryReporterHelper { + WebRenderMemoryReporterHelper(nsIHandleReportCallback* aCallback, + nsISupports* aData) + : mCallback(aCallback), mData(aData) {} + nsCOMPtr<nsIHandleReportCallback> mCallback; + nsCOMPtr<nsISupports> mData; + + void Report(size_t aBytes, const char* aName) const { + nsPrintfCString path("explicit/gfx/webrender/%s", aName); + nsCString desc("CPU heap memory used by WebRender"_ns); + ReportInternal(aBytes, path, desc, nsIMemoryReporter::KIND_HEAP); + } + + void ReportTexture(size_t aBytes, const char* aName) const { + nsPrintfCString path("gfx/webrender/textures/%s", aName); + nsCString desc("GPU texture memory used by WebRender"_ns); + ReportInternal(aBytes, path, desc, nsIMemoryReporter::KIND_OTHER); + } + + void ReportTotalGPUBytes(size_t aBytes) const { + nsCString path("gfx/webrender/total-gpu-bytes"_ns); + nsCString desc(nsLiteralCString( + "Total GPU bytes used by WebRender (should match textures/ sum)")); + ReportInternal(aBytes, path, desc, nsIMemoryReporter::KIND_OTHER); + } + + void ReportInternal(size_t aBytes, nsACString& aPath, nsACString& aDesc, + int32_t aKind) const { + // Generally, memory reporters pass the empty string as the process name to + // indicate "current process". However, if we're using a GPU process, the + // measurements will actually take place in that process, and it's easier to + // just note that here rather than trying to invoke the memory reporter in + // the GPU process. + nsAutoCString processName; + if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { + GPUParent::GetGPUProcessName(processName); + } + + mCallback->Callback(processName, aPath, aKind, + nsIMemoryReporter::UNITS_BYTES, aBytes, aDesc, mData); + } +}; + +static void FinishAsyncMemoryReport() { + nsCOMPtr<nsIMemoryReporterManager> imgr = + do_GetService("@mozilla.org/memory-reporter-manager;1"); + if (imgr) { + imgr->EndReport(); + } +} + +// clang-format off +// (For some reason, clang-format gets the second macro right, but totally mangles the first). +#define REPORT_INTERNER(id) \ + helper.Report(aReport.interning.interners.id, \ + "interning/" #id "/interners"); +// clang-format on + +#define REPORT_DATA_STORE(id) \ + helper.Report(aReport.interning.data_stores.id, \ + "interning/" #id "/data-stores"); + +NS_IMPL_ISUPPORTS(WebRenderMemoryReporter, nsIMemoryReporter) + +NS_IMETHODIMP +WebRenderMemoryReporter::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + layers::CompositorManagerChild* manager = + CompositorManagerChild::GetInstance(); + if (!manager) { + FinishAsyncMemoryReport(); + return NS_OK; + } + + WebRenderMemoryReporterHelper helper(aHandleReport, aData); + manager->SendReportMemory( + [=](wr::MemoryReport aReport) { + // CPU Memory. + helper.Report(aReport.clip_stores, "clip-stores"); + helper.Report(aReport.gpu_cache_metadata, "gpu-cache/metadata"); + helper.Report(aReport.gpu_cache_cpu_mirror, "gpu-cache/cpu-mirror"); + helper.Report(aReport.render_tasks, "render-tasks"); + helper.Report(aReport.hit_testers, "hit-testers"); + helper.Report(aReport.fonts, "resource-cache/fonts"); + helper.Report(aReport.weak_fonts, "resource-cache/weak-fonts"); + helper.Report(aReport.images, "resource-cache/images"); + helper.Report(aReport.rasterized_blobs, + "resource-cache/rasterized-blobs"); + helper.Report(aReport.texture_cache_structures, + "texture-cache/structures"); + helper.Report(aReport.shader_cache, "shader-cache"); + helper.Report(aReport.display_list, "display-list"); + helper.Report(aReport.swgl, "swgl"); + helper.Report(aReport.upload_staging_memory, "upload-stagin-memory"); + + WEBRENDER_FOR_EACH_INTERNER(REPORT_INTERNER); + WEBRENDER_FOR_EACH_INTERNER(REPORT_DATA_STORE); + + // GPU Memory. + helper.ReportTexture(aReport.gpu_cache_textures, "gpu-cache"); + helper.ReportTexture(aReport.vertex_data_textures, "vertex-data"); + helper.ReportTexture(aReport.render_target_textures, "render-targets"); + helper.ReportTexture(aReport.depth_target_textures, "depth-targets"); + helper.ReportTexture(aReport.picture_tile_textures, "picture-tiles"); + helper.ReportTexture(aReport.atlas_textures, "texture-cache/atlas"); + helper.ReportTexture(aReport.standalone_textures, + "texture-cache/standalone"); + helper.ReportTexture(aReport.texture_upload_pbos, + "texture-upload-pbos"); + helper.ReportTexture(aReport.swap_chain, "swap-chains"); + helper.ReportTexture(aReport.render_texture_hosts, + "render-texture-hosts"); + helper.ReportTexture(aReport.upload_staging_textures, + "upload-staging-textures"); + + FinishAsyncMemoryReport(); + }, + [](mozilla::ipc::ResponseRejectReason&& aReason) { + FinishAsyncMemoryReport(); + }); + + return NS_OK; +} + +#undef REPORT_INTERNER +#undef REPORT_DATA_STORE + +std::atomic<int8_t> gfxPlatform::sHasVariationFontSupport = -1; + +bool gfxPlatform::HasVariationFontSupport() { + // We record the status here: 0 for not supported, 1 for supported. + if (sHasVariationFontSupport < 0) { + // It doesn't actually matter if we race with another thread setting this, + // as any thread will set it to the same value. +#if defined(XP_WIN) + sHasVariationFontSupport = gfxWindowsPlatform::CheckVariationFontSupport(); +#elif defined(XP_MACOSX) + sHasVariationFontSupport = gfxPlatformMac::CheckVariationFontSupport(); +#elif defined(MOZ_WIDGET_GTK) + sHasVariationFontSupport = gfxPlatformGtk::CheckVariationFontSupport(); +#elif defined(ANDROID) + sHasVariationFontSupport = gfxAndroidPlatform::CheckVariationFontSupport(); +#else +# error "No gfxPlatform implementation available" +#endif + } + return sHasVariationFontSupport > 0; +} + +void gfxPlatform::Init() { + MOZ_RELEASE_ASSERT(!XRE_IsGPUProcess(), "GFX: Not allowed in GPU process."); + MOZ_RELEASE_ASSERT(!XRE_IsRDDProcess(), "GFX: Not allowed in RDD process."); + MOZ_RELEASE_ASSERT(NS_IsMainThread(), "GFX: Not in main thread."); + + if (gEverInitialized) { + MOZ_CRASH("Already started???"); + } + gEverInitialized = true; + + gfxVars::Initialize(); + + gfxConfig::Init(); + + if (XRE_IsParentProcess()) { + GPUProcessManager::Initialize(); + RDDProcessManager::Initialize(); + + nsCOMPtr<nsIFile> file; + nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(file)); + if (NS_FAILED(rv)) { + gfxVars::SetGREDirectory(nsString()); + } else { + nsAutoString path; + file->GetPath(path); + gfxVars::SetGREDirectory(nsString(path)); + } + } + + if (XRE_IsParentProcess()) { + nsCOMPtr<nsIFile> profDir; + nsresult rv = NS_GetSpecialDirectory(NS_APP_PROFILE_DIR_STARTUP, + getter_AddRefs(profDir)); + if (NS_FAILED(rv)) { + gfxVars::SetProfDirectory(nsString()); + } else { + nsAutoString path; + profDir->GetPath(path); + gfxVars::SetProfDirectory(nsString(path)); + } + + nsAutoCString path; + Preferences::GetCString("layers.windowrecording.path", path); + gfxVars::SetLayersWindowRecordingPath(path); + + if (gFxREmbedded) { + gfxVars::SetFxREmbedded(true); + } + } + + // Drop a note in the crash report if we end up forcing an option that could + // destabilize things. New items should be appended at the end (of an + // existing or in a new section), so that we don't have to know the version to + // interpret these cryptic strings. + { + nsAutoCString forcedPrefs; + // D2D prefs + forcedPrefs.AppendPrintf( + "FP(D%d%d", StaticPrefs::gfx_direct2d_disabled_AtStartup(), + StaticPrefs::gfx_direct2d_force_enabled_AtStartup()); + // Layers prefs + forcedPrefs.AppendPrintf( + "-L%d%d%d%d", + StaticPrefs::layers_amd_switchable_gfx_enabled_AtStartup(), + StaticPrefs::layers_acceleration_disabled_AtStartup_DoNotUseDirectly(), + StaticPrefs:: + layers_acceleration_force_enabled_AtStartup_DoNotUseDirectly(), + StaticPrefs::layers_d3d11_force_warp_AtStartup()); + // WebGL prefs + forcedPrefs.AppendPrintf( + "-W%d%d%d%d%d%d%d", StaticPrefs::webgl_angle_force_d3d11(), + StaticPrefs::webgl_angle_force_warp(), StaticPrefs::webgl_disabled(), + StaticPrefs::webgl_disable_angle(), StaticPrefs::webgl_dxgl_enabled(), + StaticPrefs::webgl_force_enabled(), StaticPrefs::webgl_msaa_force()); + // Prefs that don't fit into any of the other sections + forcedPrefs.AppendPrintf("-T%d%d%d) ", + StaticPrefs::gfx_android_rgb16_force_AtStartup(), + StaticPrefs::gfx_canvas_accelerated(), + StaticPrefs::layers_force_shmem_tiles_AtStartup()); + ScopedGfxFeatureReporter::AppNote(forcedPrefs); + } + + InitMoz2DLogging(); + + /* Initialize the GfxInfo service. + * Note: we can't call functions on GfxInfo that depend + * on gPlatform until after it has been initialized + * below. GfxInfo initialization annotates our + * crash reports so we want to do it before + * we try to load any drivers and do device detection + * incase that code crashes. See bug #591561. */ + nsCOMPtr<nsIGfxInfo> gfxInfo; + /* this currently will only succeed on Windows */ + gfxInfo = components::GfxInfo::Service(); + + if (XRE_IsParentProcess()) { + // Some gfxVars must be initialized prior gPlatform for coherent results. + gfxVars::SetDXInterop2Blocked(IsDXInterop2Blocked()); + gfxVars::SetDXNV12Blocked(IsDXNV12Blocked()); + gfxVars::SetDXP010Blocked(IsDXP010Blocked()); + gfxVars::SetDXP016Blocked(IsDXP016Blocked()); + } + +#if defined(XP_WIN) + gPlatform = new gfxWindowsPlatform; +#elif defined(XP_MACOSX) + gPlatform = new gfxPlatformMac; +#elif defined(MOZ_WIDGET_GTK) + gPlatform = new gfxPlatformGtk; +#elif defined(ANDROID) + gPlatform = new gfxAndroidPlatform; +#else +# error "No gfxPlatform implementation available" +#endif + gPlatform->PopulateScreenInfo(); + gPlatform->InitAcceleration(); + gPlatform->InitWebRenderConfig(); + + gPlatform->InitHardwareVideoConfig(); + gPlatform->InitWebGLConfig(); + gPlatform->InitWebGPUConfig(); + gPlatform->InitWindowOcclusionConfig(); + gPlatform->InitBackdropFilterConfig(); + gPlatform->InitAcceleratedCanvas2DConfig(); + +#if defined(XP_WIN) + // When using WebRender, we defer initialization of the D3D11 devices until + // the (rare) cases where they're used. Note that the GPU process where + // WebRender runs doesn't initialize gfxPlatform and performs explicit + // initialization of the bits it needs. + if (XRE_IsParentProcess() && !gfxConfig::IsEnabled(Feature::GPU_PROCESS) && + StaticPrefs:: + gfx_webrender_enabled_no_gpu_process_with_angle_win_AtStartup()) { + gPlatform->EnsureDevicesInitialized(); + } +#endif + + if (XRE_IsParentProcess()) { + mozilla::glean::gpu_process::feature_status.Set( + gfxConfig::GetFeature(Feature::GPU_PROCESS) + .GetStatusAndFailureIdString()); + } + + if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { + GPUProcessManager* gpu = GPUProcessManager::Get(); + Unused << gpu->LaunchGPUProcess(); + } + + if (XRE_IsParentProcess()) { + // Create the global vsync source and dispatcher. + RefPtr<VsyncSource> vsyncSource = + gfxPlatform::ForceSoftwareVsync() + ? gPlatform->GetSoftwareVsyncSource() + : gPlatform->GetGlobalHardwareVsyncSource(); + gPlatform->mVsyncDispatcher = new VsyncDispatcher(vsyncSource); + + // Listen for layout.frame_rate pref changes. + Preferences::RegisterCallback( + gfxPlatform::ReInitFrameRate, + nsDependentCString(StaticPrefs::GetPrefName_layout_frame_rate())); + Preferences::RegisterCallback( + gfxPlatform::ReInitFrameRate, + nsDependentCString( + StaticPrefs::GetPrefName_privacy_resistFingerprinting())); + } + + // Create the sRGB to output display profile transforms. They can be accessed + // off the main thread so we want to avoid a race condition. + InitializeCMS(); + + SkGraphics::Init(); +#ifdef MOZ_ENABLE_FREETYPE + SkInitCairoFT(gPlatform->FontHintingEnabled()); +#endif + gfxGradientCache::Init(); + + InitLayersIPC(); + + // This *create* the platform font list instance, but may not *initialize* it + // yet if the gfx.font-list.lazy-init.enabled pref is set. The first *use* + // of the list will ensure it is initialized. + if (!gPlatform->CreatePlatformFontList()) { + MOZ_CRASH("Could not initialize gfxPlatformFontList"); + } + + gPlatform->mScreenReferenceDrawTarget = + gPlatform->CreateOffscreenContentDrawTarget(IntSize(1, 1), + SurfaceFormat::B8G8R8A8); + if (!gPlatform->mScreenReferenceDrawTarget || + !gPlatform->mScreenReferenceDrawTarget->IsValid()) { + // If TDR is detected, create a draw target with software backend + // and it should be replaced later when the process gets the device + // reset notification. + if (!gPlatform->DidRenderingDeviceReset()) { + gfxCriticalError() << "Could not initialize mScreenReferenceDrawTarget"; + } + } + + if (NS_FAILED(gfxFontCache::Init())) { + MOZ_CRASH("Could not initialize gfxFontCache"); + } + + Preferences::RegisterPrefixCallbacks(FontPrefChanged, kObservedPrefs); + + GLContext::PlatformStartup(); + + // Listen to memory pressure event so we can purge DrawTarget caches + gPlatform->mMemoryPressureObserver = + layers::MemoryPressureObserver::Create(gPlatform); + + // Request the imgITools service, implicitly initializing ImageLib. + nsCOMPtr<imgITools> imgTools = do_GetService("@mozilla.org/image/tools;1"); + if (!imgTools) { + MOZ_CRASH("Could not initialize ImageLib"); + } + + RegisterStrongMemoryReporter(new GfxMemoryImageReporter()); + if (XRE_IsParentProcess()) { + RegisterStrongAsyncMemoryReporter(new WebRenderMemoryReporter()); + } + + RegisterStrongMemoryReporter(new SkMemoryReporter()); + + uint32_t skiaCacheSize = GetSkiaGlyphCacheSize(); + if (skiaCacheSize != kDefaultGlyphCacheSize) { + SkGraphics::SetFontCacheLimit(skiaCacheSize); + } + + InitNullMetadata(); + InitOpenGLConfig(); + + if (XRE_IsParentProcess()) { + Preferences::Unlock(FONT_VARIATIONS_PREF); + if (!gfxPlatform::HasVariationFontSupport()) { + // Ensure variation fonts are disabled and the pref is locked. + Preferences::SetBool(FONT_VARIATIONS_PREF, false, PrefValueKind::Default); + Preferences::SetBool(FONT_VARIATIONS_PREF, false); + Preferences::Lock(FONT_VARIATIONS_PREF); + } + } + + if (XRE_IsParentProcess()) { + ReportTelemetry(); + } + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "gfx-features-ready", nullptr); + } +} + +void gfxPlatform::ReportTelemetry() { + MOZ_RELEASE_ASSERT(XRE_IsParentProcess(), + "GFX: Only allowed to be called from parent process."); + + nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); + + { + auto& screenManager = widget::ScreenManager::GetSingleton(); + const uint32_t screenCount = screenManager.CurrentScreenList().Length(); + RefPtr<widget::Screen> primaryScreen = screenManager.GetPrimaryScreen(); + const LayoutDeviceIntRect rect = primaryScreen->GetRect(); + + mozilla::glean::gfx_display::count.Set(screenCount); + mozilla::glean::gfx_display::primary_height.Set(rect.Height()); + mozilla::glean::gfx_display::primary_width.Set(rect.Width()); + } + + nsString adapterDesc; + gfxInfo->GetAdapterDescription(adapterDesc); + mozilla::glean::gfx_adapter_primary::description.Set( + NS_ConvertUTF16toUTF8(adapterDesc)); + + nsString adapterVendorId; + gfxInfo->GetAdapterVendorID(adapterVendorId); + mozilla::glean::gfx_adapter_primary::vendor_id.Set( + NS_ConvertUTF16toUTF8(adapterVendorId)); + + nsString adapterDeviceId; + gfxInfo->GetAdapterDeviceID(adapterDeviceId); + mozilla::glean::gfx_adapter_primary::device_id.Set( + NS_ConvertUTF16toUTF8(adapterDeviceId)); + + nsString adapterSubsystemId; + gfxInfo->GetAdapterSubsysID(adapterSubsystemId); + mozilla::glean::gfx_adapter_primary::subsystem_id.Set( + NS_ConvertUTF16toUTF8(adapterSubsystemId)); + + uint32_t adapterRam = 0; + gfxInfo->GetAdapterRAM(&adapterRam); + mozilla::glean::gfx_adapter_primary::ram.Set(adapterRam); + + nsString adapterDriver; + gfxInfo->GetAdapterDriver(adapterDriver); + mozilla::glean::gfx_adapter_primary::driver_files.Set( + NS_ConvertUTF16toUTF8(adapterDriver)); + + nsString adapterDriverVendor; + gfxInfo->GetAdapterDriverVendor(adapterDriverVendor); + mozilla::glean::gfx_adapter_primary::driver_vendor.Set( + NS_ConvertUTF16toUTF8(adapterDriverVendor)); + + nsString adapterDriverVersion; + gfxInfo->GetAdapterDriverVersion(adapterDriverVersion); + mozilla::glean::gfx_adapter_primary::driver_version.Set( + NS_ConvertUTF16toUTF8(adapterDriverVersion)); + + nsString adapterDriverDate; + gfxInfo->GetAdapterDriverDate(adapterDriverDate); + mozilla::glean::gfx_adapter_primary::driver_date.Set( + NS_ConvertUTF16toUTF8(adapterDriverDate)); + + mozilla::glean::gfx_status::headless.Set(IsHeadless()); + + MOZ_ASSERT(gPlatform, "Need gPlatform to generate some telemetry."); + Telemetry::ScalarSet(Telemetry::ScalarID::GFX_SUPPORTS_HDR, + gPlatform->SupportsHDR()); +} + +static bool IsFeatureSupported(long aFeature, bool aDefault) { + nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); + nsCString blockId; + int32_t status; + if (!NS_SUCCEEDED(gfxInfo->GetFeatureStatus(aFeature, blockId, &status))) { + return aDefault; + } + return status == nsIGfxInfo::FEATURE_STATUS_OK; +} + +/* static*/ +bool gfxPlatform::IsDXInterop2Blocked() { + return !IsFeatureSupported(nsIGfxInfo::FEATURE_DX_INTEROP2, false); +} + +/* static*/ +bool gfxPlatform::IsDXNV12Blocked() { + return !IsFeatureSupported(nsIGfxInfo::FEATURE_DX_NV12, false); +} + +/* static*/ +bool gfxPlatform::IsDXP010Blocked() { + return !IsFeatureSupported(nsIGfxInfo::FEATURE_DX_P010, false); +} + +/* static*/ +bool gfxPlatform::IsDXP016Blocked() { + return !IsFeatureSupported(nsIGfxInfo::FEATURE_DX_P016, false); +} + +/* static */ +int32_t gfxPlatform::MaxTextureSize() { + // Make sure we don't completely break rendering because of a typo in the + // pref or whatnot. + const int32_t kMinSizePref = 2048; + return std::max( + kMinSizePref, + StaticPrefs::gfx_max_texture_size_AtStartup_DoNotUseDirectly()); +} + +/* static */ +int32_t gfxPlatform::MaxAllocSize() { + // Make sure we don't completely break rendering because of a typo in the + // pref or whatnot. + const int32_t kMinAllocPref = 10000000; + return std::max(kMinAllocPref, + StaticPrefs::gfx_max_alloc_size_AtStartup_DoNotUseDirectly()); +} + +/* static */ +void gfxPlatform::InitMoz2DLogging() { + auto fwd = new CrashStatsLogForwarder( + CrashReporter::Annotation::GraphicsCriticalError); + fwd->SetCircularBufferSize(StaticPrefs::gfx_logging_crash_length_AtStartup()); + + mozilla::gfx::Config cfg; + cfg.mLogForwarder = fwd; + cfg.mMaxTextureSize = gfxPlatform::MaxTextureSize(); + cfg.mMaxAllocSize = gfxPlatform::MaxAllocSize(); + + gfx::Factory::Init(cfg); +} + +/* static */ +bool gfxPlatform::IsHeadless() { + static bool initialized = false; + static bool headless = false; + if (!initialized) { + initialized = true; + headless = PR_GetEnv("MOZ_HEADLESS"); + } + return headless; +} + +/* static */ +bool gfxPlatform::UseRemoteCanvas() { + return XRE_IsContentProcess() && (gfx::gfxVars::RemoteCanvasEnabled() || + gfx::gfxVars::UseAcceleratedCanvas2D()); +} + +/* static */ +bool gfxPlatform::IsBackendAccelerated( + const mozilla::gfx::BackendType aBackendType) { + return aBackendType == BackendType::DIRECT2D || + aBackendType == BackendType::DIRECT2D1_1; +} + +/* static */ +bool gfxPlatform::CanMigrateMacGPUs() { + int32_t pMigration = StaticPrefs::gfx_compositor_gpu_migration(); + + bool forceDisable = pMigration == 0; + bool forceEnable = pMigration == 2; + + return forceEnable || !forceDisable; +} + +static bool sLayersIPCIsUp = false; + +/* static */ +void gfxPlatform::InitNullMetadata() { + ScrollMetadata::sNullMetadata = new ScrollMetadata(); + ClearOnShutdown(&ScrollMetadata::sNullMetadata); +} + +void gfxPlatform::Shutdown() { + // In some cases, gPlatform may not be created but Shutdown() called, + // e.g., during xpcshell tests. + if (!gPlatform) { + return; + } + + MOZ_ASSERT(!sLayersIPCIsUp); + + // These may be called before the corresponding subsystems have actually + // started up. That's OK, they can handle it. + gfxFontCache::Shutdown(); + gfxGradientCache::Shutdown(); + gfxAlphaBoxBlur::ShutdownBlurCache(); + gfxGraphiteShaper::Shutdown(); + gfxPlatformFontList::Shutdown(); + gfxFontMissingGlyphs::Shutdown(); + + // Free the various non-null transforms and loaded profiles + ShutdownCMS(); + + Preferences::UnregisterPrefixCallbacks(FontPrefChanged, kObservedPrefs); + + NS_ASSERTION(gPlatform->mMemoryPressureObserver, + "mMemoryPressureObserver has already gone"); + if (gPlatform->mMemoryPressureObserver) { + gPlatform->mMemoryPressureObserver->Unregister(); + gPlatform->mMemoryPressureObserver = nullptr; + } + + if (XRE_IsParentProcess()) { + if (gPlatform->mGlobalHardwareVsyncSource) { + gPlatform->mGlobalHardwareVsyncSource->Shutdown(); + } + if (gPlatform->mSoftwareVsyncSource && + gPlatform->mSoftwareVsyncSource != + gPlatform->mGlobalHardwareVsyncSource) { + gPlatform->mSoftwareVsyncSource->Shutdown(); + } + } + + gPlatform->mGlobalHardwareVsyncSource = nullptr; + gPlatform->mSoftwareVsyncSource = nullptr; + gPlatform->mVsyncDispatcher = nullptr; + + // Shut down the default GL context provider. + GLContextProvider::Shutdown(); + +#if defined(XP_WIN) + // The above shutdown calls operate on the available context providers on + // most platforms. Windows is a "special snowflake", though, and has three + // context providers available, so we have to shut all of them down. + // We should only support the default GL provider on Windows; then, this + // could go away. Unfortunately, we currently support WGL (the default) for + // WebGL on Optimus. + GLContextProviderEGL::Shutdown(); +#endif + + if (XRE_IsParentProcess()) { + GPUProcessManager::Shutdown(); + VRProcessManager::Shutdown(); + RDDProcessManager::Shutdown(); + } + + gfx::Factory::ShutDown(); + gfxVars::Shutdown(); + gfxFont::DestroySingletons(); + + gfxConfig::Shutdown(); + + gPlatform->WillShutdown(); + + delete gPlatform; + gPlatform = nullptr; +} + +/* static */ +void gfxPlatform::InitLayersIPC() { + if (sLayersIPCIsUp) { + return; + } + sLayersIPCIsUp = true; + + if (XRE_IsParentProcess()) { +#if defined(XP_WIN) + if (gfxConfig::IsEnabled(gfx::Feature::WINDOW_OCCLUSION)) { + widget::WinWindowOcclusionTracker::Ensure(); + } +#endif + if (!gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { + RemoteTextureMap::Init(); + wr::RenderThread::Start(GPUProcessManager::Get()->AllocateNamespace()); + image::ImageMemoryReporter::InitForWebRender(); + } + + layers::CompositorThreadHolder::Start(); + + if (!gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { + gfx::CanvasRenderThread::Start(); + } + } +} + +/* static */ +void gfxPlatform::ShutdownLayersIPC() { + if (!sLayersIPCIsUp) { + return; + } + sLayersIPCIsUp = false; + + if (XRE_IsContentProcess()) { + gfx::VRManagerChild::ShutDown(); + gfx::CanvasManagerChild::Shutdown(); + // cf bug 1215265. + if (StaticPrefs::layers_child_process_shutdown()) { + layers::CompositorManagerChild::Shutdown(); + layers::ImageBridgeChild::ShutDown(); + } + + } else if (XRE_IsParentProcess()) { + VideoBridgeParent::Shutdown(); + RDDProcessManager::RDDProcessShutdown(); + gfx::VRManagerChild::ShutDown(); + gfx::CanvasManagerChild::Shutdown(); + layers::CompositorManagerChild::Shutdown(); + layers::ImageBridgeChild::ShutDown(); + // This could be running on either the Compositor thread, the Renderer + // thread, or the dedicated CanvasRender thread, so we need to shutdown + // before the former two. + gfx::CanvasRenderThread::Shutdown(); + // This has to happen after shutting down the child protocols. + layers::CompositorThreadHolder::Shutdown(); + RemoteTextureMap::Shutdown(); + image::ImageMemoryReporter::ShutdownForWebRender(); + // There is a case that RenderThread exists when UseWebRender() is + // false. This could happen when WebRender was fallbacked to compositor. + if (wr::RenderThread::Get()) { + wr::RenderThread::ShutDown(); + + Preferences::UnregisterCallback(WebRenderDebugPrefChangeCallback, + WR_DEBUG_PREF); + Preferences::UnregisterCallback(WebRendeProfilerUIPrefChangeCallback, + "gfx.webrender.debug.profiler-ui"); + Preferences::UnregisterCallback( + WebRenderBlobTileSizePrefChangeCallback, + nsDependentCString( + StaticPrefs::GetPrefName_gfx_webrender_blob_tile_size())); + } +#if defined(XP_WIN) + widget::WinWindowOcclusionTracker::ShutDown(); +#endif + } else { + // TODO: There are other kind of processes and we should make sure gfx + // stuff is either not created there or shut down properly. + } +} + +void gfxPlatform::WillShutdown() { + // Destoy these first in case they depend on backend-specific resources. + // Otherwise, the backend's destructor would be called before the + // base gfxPlatform destructor. + mScreenReferenceSurface = nullptr; + mScreenReferenceDrawTarget = nullptr; + + // Always clear out the Skia font cache here, in case it is referencing any + // SharedFTFaces that would otherwise outlive destruction of the FT_Library + // that owns them. + SkGraphics::PurgeFontCache(); + + // The cairo folks think we should only clean up in debug builds, + // but we're generally in the habit of trying to shut down as + // cleanly as possible even in production code, so call this + // cairo_debug_* function unconditionally. + // + // because cairo can assert and thus crash on shutdown, don't do this in + // release builds +#ifdef NS_FREE_PERMANENT_DATA + cairo_debug_reset_static_data(); +#endif +} + +gfxPlatform::~gfxPlatform() = default; + +/* static */ +already_AddRefed<DrawTarget> gfxPlatform::CreateDrawTargetForSurface( + gfxASurface* aSurface, const IntSize& aSize) { + SurfaceFormat format = aSurface->GetSurfaceFormat(); + RefPtr<DrawTarget> drawTarget = Factory::CreateDrawTargetForCairoSurface( + aSurface->CairoSurface(), aSize, &format); + if (!drawTarget) { + gfxWarning() << "gfxPlatform::CreateDrawTargetForSurface failed in " + "CreateDrawTargetForCairoSurface"; + return nullptr; + } + return drawTarget.forget(); +} + +cairo_user_data_key_t kSourceSurface; + +/** + * Record the backend that was used to construct the SourceSurface. + * When getting the cached SourceSurface for a gfxASurface/DrawTarget pair, + * we check to make sure the DrawTarget's backend matches the backend + * for the cached SourceSurface, and only use it if they match. This + * can avoid expensive and unnecessary readbacks. + */ +struct SourceSurfaceUserData { + RefPtr<SourceSurface> mSrcSurface; + BackendType mBackendType; +}; + +static void SourceBufferDestroy(void* srcSurfUD) { + delete static_cast<SourceSurfaceUserData*>(srcSurfUD); +} + +UserDataKey kThebesSurface; + +struct DependentSourceSurfaceUserData { + RefPtr<gfxASurface> mSurface; +}; + +static void SourceSurfaceDestroyed(void* aData) { + delete static_cast<DependentSourceSurfaceUserData*>(aData); +} + +void gfxPlatform::ClearSourceSurfaceForSurface(gfxASurface* aSurface) { + aSurface->SetData(&kSourceSurface, nullptr, nullptr); +} + +/* static */ +already_AddRefed<SourceSurface> gfxPlatform::GetSourceSurfaceForSurface( + RefPtr<DrawTarget> aTarget, gfxASurface* aSurface, bool aIsPlugin) { + if (!aSurface->CairoSurface() || aSurface->CairoStatus()) { + return nullptr; + } + + if (!aTarget) { + aTarget = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + } + + void* userData = aSurface->GetData(&kSourceSurface); + + if (userData) { + SourceSurfaceUserData* surf = static_cast<SourceSurfaceUserData*>(userData); + + if (surf->mSrcSurface->IsValid() && + surf->mBackendType == aTarget->GetBackendType()) { + RefPtr<SourceSurface> srcSurface(surf->mSrcSurface); + return srcSurface.forget(); + } + // We can just continue here as when setting new user data the destroy + // function will be called for the old user data. + } + + SurfaceFormat format = aSurface->GetSurfaceFormat(); + + if (aTarget->GetBackendType() == BackendType::CAIRO) { + // If we're going to be used with a CAIRO DrawTarget, then just create a + // SourceSurfaceCairo since we don't know the underlying type of the CAIRO + // DrawTarget and can't pick a better surface type. Doing this also avoids + // readback of aSurface's surface into memory if, for example, aSurface + // wraps an xlib cairo surface (which can be important to avoid a major + // slowdown). + // + // We return here regardless of whether CreateSourceSurfaceFromNativeSurface + // succeeds or not since we don't expect to be able to do any better below + // if it fails. + // + // Note that the returned SourceSurfaceCairo holds a strong reference to + // the cairo_surface_t* that it wraps, which essencially means it holds a + // strong reference to aSurface since aSurface shares its + // cairo_surface_t*'s reference count variable. As a result we can't cache + // srcBuffer on aSurface (see below) since aSurface would then hold a + // strong reference back to srcBuffer, creating a reference loop and a + // memory leak. Not caching is fine since wrapping is cheap enough (no + // copying) so we can just wrap again next time we're called. + return Factory::CreateSourceSurfaceForCairoSurface( + aSurface->CairoSurface(), aSurface->GetSize(), format); + } + + RefPtr<SourceSurface> srcBuffer; + + // Currently no other DrawTarget types implement + // CreateSourceSurfaceFromNativeSurface + + if (!srcBuffer) { + // If aSurface wraps data, we can create a SourceSurfaceRawData that wraps + // the same data, then optimize it for aTarget: + RefPtr<DataSourceSurface> surf = GetWrappedDataSourceSurface(aSurface); + if (surf) { + srcBuffer = aIsPlugin + ? aTarget->OptimizeSourceSurfaceForUnknownAlpha(surf) + : aTarget->OptimizeSourceSurface(surf); + + if (srcBuffer == surf) { + // GetWrappedDataSourceSurface returns a SourceSurface that holds a + // strong reference to aSurface since it wraps aSurface's data and + // needs it to stay alive. As a result we can't cache srcBuffer on + // aSurface (below) since aSurface would then hold a strong reference + // back to srcBuffer, creating a reference loop and a memory leak. Not + // caching is fine since wrapping is cheap enough (no copying) so we + // can just wrap again next time we're called. + // + // Note that the check below doesn't catch this since srcBuffer will be + // a SourceSurfaceRawData object (even if aSurface is not a + // gfxImageSurface object), which is why we need this separate check. + return srcBuffer.forget(); + } + } + } + + if (!srcBuffer) { + MOZ_ASSERT(aTarget->GetBackendType() != BackendType::CAIRO, + "We already tried CreateSourceSurfaceFromNativeSurface with a " + "DrawTargetCairo above"); + // We've run out of performant options. We now try creating a SourceSurface + // using a temporary DrawTargetCairo and then optimizing it to aTarget's + // actual type. The CreateSourceSurfaceFromNativeSurface() call will + // likely create a DataSourceSurface (possibly involving copying and/or + // readback), and the OptimizeSourceSurface may well copy again and upload + // to the GPU. So, while this code path is rarely hit, hitting it may be + // very slow. + srcBuffer = Factory::CreateSourceSurfaceForCairoSurface( + aSurface->CairoSurface(), aSurface->GetSize(), format); + if (srcBuffer) { + srcBuffer = aTarget->OptimizeSourceSurface(srcBuffer); + } + } + + if (!srcBuffer) { + return nullptr; + } + + if ((srcBuffer->GetType() == SurfaceType::CAIRO && + static_cast<SourceSurfaceCairo*>(srcBuffer.get())->GetSurface() == + aSurface->CairoSurface()) || + (srcBuffer->GetType() == SurfaceType::CAIRO_IMAGE && + static_cast<DataSourceSurfaceCairo*>(srcBuffer.get())->GetSurface() == + aSurface->CairoSurface())) { + // See the "Note that the returned SourceSurfaceCairo..." comment above. + return srcBuffer.forget(); + } + + // Add user data to aSurface so we can cache lookups in the future. + auto* srcSurfUD = new SourceSurfaceUserData; + srcSurfUD->mBackendType = aTarget->GetBackendType(); + srcSurfUD->mSrcSurface = srcBuffer; + aSurface->SetData(&kSourceSurface, srcSurfUD, SourceBufferDestroy); + + return srcBuffer.forget(); +} + +already_AddRefed<DataSourceSurface> gfxPlatform::GetWrappedDataSourceSurface( + gfxASurface* aSurface) { + RefPtr<gfxImageSurface> image = aSurface->GetAsImageSurface(); + if (!image) { + return nullptr; + } + RefPtr<DataSourceSurface> result = Factory::CreateWrappingDataSourceSurface( + image->Data(), image->Stride(), image->GetSize(), + ImageFormatToSurfaceFormat(image->Format())); + + if (!result) { + return nullptr; + } + + // If we wrapped the underlying data of aSurface, then we need to add user + // data to make sure aSurface stays alive until we are done with the data. + auto* srcSurfUD = new DependentSourceSurfaceUserData; + srcSurfUD->mSurface = aSurface; + result->AddUserData(&kThebesSurface, srcSurfUD, SourceSurfaceDestroyed); + + return result.forget(); +} + +void gfxPlatform::PopulateScreenInfo() { + nsCOMPtr<nsIScreenManager> manager = + do_GetService("@mozilla.org/gfx/screenmanager;1"); + MOZ_ASSERT(manager, "failed to get nsIScreenManager"); + + nsCOMPtr<nsIScreen> screen; + manager->GetPrimaryScreen(getter_AddRefs(screen)); + if (!screen) { + // This can happen in xpcshell, for instance + return; + } + + screen->GetColorDepth(&mScreenDepth); + if (XRE_IsParentProcess()) { + gfxVars::SetScreenDepth(mScreenDepth); + } + + int left, top; + screen->GetRect(&left, &top, &mScreenSize.width, &mScreenSize.height); +} + +bool gfxPlatform::SupportsAzureContentForDrawTarget(DrawTarget* aTarget) { + if (!aTarget || !aTarget->IsValid()) { + return false; + } + + return SupportsAzureContentForType(aTarget->GetBackendType()); +} + +void gfxPlatform::PurgeSkiaFontCache() { + if (gfxPlatform::GetPlatform()->GetDefaultContentBackend() == + BackendType::SKIA) { + SkGraphics::PurgeFontCache(); + } +} + +already_AddRefed<DrawTarget> gfxPlatform::CreateDrawTargetForBackend( + BackendType aBackend, const IntSize& aSize, SurfaceFormat aFormat) { + // There is a bunch of knowledge in the gfxPlatform heirarchy about how to + // create the best offscreen surface for the current system and situation. We + // can easily take advantage of this for the Cairo backend, so that's what we + // do. + // mozilla::gfx::Factory can get away without having all this knowledge for + // now, but this might need to change in the future (using + // CreateOffscreenSurface() and CreateDrawTargetForSurface() for all + // backends). + if (aBackend == BackendType::CAIRO) { + RefPtr<gfxASurface> surf = + CreateOffscreenSurface(aSize, SurfaceFormatToImageFormat(aFormat)); + if (!surf || surf->CairoStatus()) { + return nullptr; + } + return CreateDrawTargetForSurface(surf, aSize); + } + return Factory::CreateDrawTarget(aBackend, aSize, aFormat); +} + +already_AddRefed<DrawTarget> gfxPlatform::CreateOffscreenCanvasDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat) { + NS_ASSERTION(mPreferredCanvasBackend != BackendType::NONE, "No backend."); + + // If we are using remote canvas we don't want to use acceleration in + // canvas DrawTargets we are not remoting, so we always use the fallback + // software one. + if (!gfxPlatform::UseRemoteCanvas() || + !gfxPlatform::IsBackendAccelerated(mPreferredCanvasBackend)) { + RefPtr<DrawTarget> target = + CreateDrawTargetForBackend(mPreferredCanvasBackend, aSize, aFormat); + if (target || mFallbackCanvasBackend == BackendType::NONE) { + return target.forget(); + } + } + +#ifdef XP_WIN + // On Windows, the fallback backend (Cairo) should use its image backend. + return Factory::CreateDrawTarget(mFallbackCanvasBackend, aSize, aFormat); +#else + return CreateDrawTargetForBackend(mFallbackCanvasBackend, aSize, aFormat); +#endif +} + +already_AddRefed<DrawTarget> gfxPlatform::CreateOffscreenContentDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat, bool aFallback) { + BackendType backend = (aFallback) ? mSoftwareBackend : mContentBackend; + NS_ASSERTION(backend != BackendType::NONE, "No backend."); + RefPtr<DrawTarget> dt = CreateDrawTargetForBackend(backend, aSize, aFormat); + + if (!dt) { + return nullptr; + } + + // We'd prefer this to take proper care and return a CaptureDT, but for the + // moment since we can't and this means we're going to be drawing on the main + // thread force it's initialization. See bug 1526045 and bug 1521368. + dt->ClearRect(gfx::Rect()); + if (!dt->IsValid()) { + return nullptr; + } + return dt.forget(); +} + +already_AddRefed<DrawTarget> gfxPlatform::CreateSimilarSoftwareDrawTarget( + DrawTarget* aDT, const IntSize& aSize, SurfaceFormat aFormat) { + RefPtr<DrawTarget> dt; + + if (Factory::DoesBackendSupportDataDrawtarget(aDT->GetBackendType())) { + dt = aDT->CreateSimilarDrawTarget(aSize, aFormat); + } else { + BackendType backendType = BackendType::SKIA; + dt = Factory::CreateDrawTarget(backendType, aSize, aFormat); + } + + return dt.forget(); +} + +/* static */ +already_AddRefed<DrawTarget> gfxPlatform::CreateDrawTargetForData( + unsigned char* aData, const IntSize& aSize, int32_t aStride, + SurfaceFormat aFormat, bool aUninitialized) { + BackendType backendType = gfxVars::ContentBackend(); + NS_ASSERTION(backendType != BackendType::NONE, "No backend."); + + if (!Factory::DoesBackendSupportDataDrawtarget(backendType)) { + backendType = BackendType::SKIA; + } + + RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData( + backendType, aData, aSize, aStride, aFormat, aUninitialized); + + return dt.forget(); +} + +/* static */ +BackendType gfxPlatform::BackendTypeForName(const nsCString& aName) { + if (aName.EqualsLiteral("cairo")) return BackendType::CAIRO; + if (aName.EqualsLiteral("skia")) return BackendType::SKIA; + if (aName.EqualsLiteral("direct2d")) return BackendType::DIRECT2D; + if (aName.EqualsLiteral("direct2d1.1")) return BackendType::DIRECT2D1_1; + return BackendType::NONE; +} + +nsresult gfxPlatform::GetFontList(nsAtom* aLangGroup, + const nsACString& aGenericFamily, + nsTArray<nsString>& aListOfFonts) { + gfxPlatformFontList::PlatformFontList()->GetFontList( + aLangGroup, aGenericFamily, aListOfFonts); + return NS_OK; +} + +nsresult gfxPlatform::UpdateFontList(bool aFullRebuild) { + gfxPlatformFontList::PlatformFontList()->UpdateFontList(aFullRebuild); + return NS_OK; +} + +void gfxPlatform::GetStandardFamilyName(const nsCString& aFontName, + nsACString& aFamilyName) { + gfxPlatformFontList::PlatformFontList()->GetStandardFamilyName(aFontName, + aFamilyName); +} + +nsAutoCString gfxPlatform::GetDefaultFontName( + const nsACString& aLangGroup, const nsACString& aGenericFamily) { + // To benefit from Return Value Optimization, all paths here must return + // this one variable: + nsAutoCString result; + + auto* pfl = gfxPlatformFontList::PlatformFontList(); + FamilyAndGeneric fam = pfl->GetDefaultFontFamily(aLangGroup, aGenericFamily); + if (!pfl->GetLocalizedFamilyName(fam.mFamily, result)) { + NS_WARNING("missing default font-family name"); + } + + return result; +} + +bool gfxPlatform::DownloadableFontsEnabled() { + if (mAllowDownloadableFonts == UNINITIALIZED_VALUE) { + mAllowDownloadableFonts = + Preferences::GetBool(GFX_DOWNLOADABLE_FONTS_ENABLED, false); + } + + return mAllowDownloadableFonts; +} + +bool gfxPlatform::UseCmapsDuringSystemFallback() { + return StaticPrefs::gfx_font_rendering_fallback_always_use_cmaps(); +} + +bool gfxPlatform::OpenTypeSVGEnabled() { + return StaticPrefs::gfx_font_rendering_opentype_svg_enabled(); +} + +uint32_t gfxPlatform::WordCacheCharLimit() { + return StaticPrefs::gfx_font_rendering_wordcache_charlimit(); +} + +uint32_t gfxPlatform::WordCacheMaxEntries() { + return StaticPrefs::gfx_font_rendering_wordcache_maxentries(); +} + +bool gfxPlatform::UseGraphiteShaping() { + return StaticPrefs::gfx_font_rendering_graphite_enabled(); +} + +bool gfxPlatform::IsFontFormatSupported( + StyleFontFaceSourceFormatKeyword aFormatHint, + StyleFontFaceSourceTechFlags aTechFlags) { + // By default, font resources are assumed to be supported; but if the format + // hint or technology flags explicitly indicate something we don't support, + // then return false. + switch (aFormatHint) { + case StyleFontFaceSourceFormatKeyword::None: + break; + case StyleFontFaceSourceFormatKeyword::Collection: + return false; + case StyleFontFaceSourceFormatKeyword::Opentype: + case StyleFontFaceSourceFormatKeyword::Truetype: + break; + case StyleFontFaceSourceFormatKeyword::EmbeddedOpentype: + return false; + case StyleFontFaceSourceFormatKeyword::Svg: + return false; + case StyleFontFaceSourceFormatKeyword::Woff: + break; + case StyleFontFaceSourceFormatKeyword::Woff2: + break; + case StyleFontFaceSourceFormatKeyword::Unknown: + return false; + default: + MOZ_ASSERT_UNREACHABLE("bad format hint!"); + return false; + } + StyleFontFaceSourceTechFlags unsupportedTechnologies = + StyleFontFaceSourceTechFlags::INCREMENTAL | + StyleFontFaceSourceTechFlags::COLOR_SBIX; + if (!StaticPrefs::gfx_downloadable_fonts_keep_color_bitmaps()) { + unsupportedTechnologies |= StyleFontFaceSourceTechFlags::COLOR_CBDT; + } + if (!StaticPrefs::gfx_font_rendering_colr_v1_enabled()) { + unsupportedTechnologies |= StyleFontFaceSourceTechFlags::COLOR_COLRV1; + } + if (!StaticPrefs::layout_css_font_palette_enabled()) { + unsupportedTechnologies |= StyleFontFaceSourceTechFlags::PALETTES; + } + if (!StaticPrefs::layout_css_font_variations_enabled()) { + unsupportedTechnologies |= StyleFontFaceSourceTechFlags::VARIATIONS; + } + if (aTechFlags & unsupportedTechnologies) { + return false; + } + return true; +} + +bool gfxPlatform::IsKnownIconFontFamily(const nsAtom* aFamilyName) const { + return gfxPlatformFontList::PlatformFontList()->IsKnownIconFontFamily( + aFamilyName); +} + +gfxFontEntry* gfxPlatform::LookupLocalFont(nsPresContext* aPresContext, + const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) { + return gfxPlatformFontList::PlatformFontList()->LookupLocalFont( + aPresContext, aFontName, aWeightForEntry, aStretchForEntry, + aStyleForEntry); +} + +gfxFontEntry* gfxPlatform::MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, + uint32_t aLength) { + return gfxPlatformFontList::PlatformFontList()->MakePlatformFont( + aFontName, aWeightForEntry, aStretchForEntry, aStyleForEntry, aFontData, + aLength); +} + +BackendPrefsData gfxPlatform::GetBackendPrefs() const { + BackendPrefsData data; + + data.mCanvasBitmask = BackendTypeBit(BackendType::SKIA); + data.mContentBitmask = BackendTypeBit(BackendType::SKIA); + +#ifdef MOZ_WIDGET_GTK + data.mCanvasBitmask |= BackendTypeBit(BackendType::CAIRO); + data.mContentBitmask |= BackendTypeBit(BackendType::CAIRO); +#endif + + data.mCanvasDefault = BackendType::SKIA; + data.mContentDefault = BackendType::SKIA; + + return data; +} + +void gfxPlatform::InitBackendPrefs(BackendPrefsData&& aPrefsData) { + mPreferredCanvasBackend = GetCanvasBackendPref(aPrefsData.mCanvasBitmask); + if (mPreferredCanvasBackend == BackendType::NONE) { + mPreferredCanvasBackend = aPrefsData.mCanvasDefault; + } + + if (mPreferredCanvasBackend == BackendType::DIRECT2D1_1) { + // Falling back to D2D 1.0 won't help us here. When D2D 1.1 DT creation + // fails it means the surface was too big or there's something wrong with + // the device. D2D 1.0 will encounter a similar situation. + mFallbackCanvasBackend = GetCanvasBackendPref( + aPrefsData.mCanvasBitmask & ~(BackendTypeBit(mPreferredCanvasBackend) | + BackendTypeBit(BackendType::DIRECT2D))); + } else { + mFallbackCanvasBackend = GetCanvasBackendPref( + aPrefsData.mCanvasBitmask & ~BackendTypeBit(mPreferredCanvasBackend)); + } + + mContentBackendBitmask = aPrefsData.mContentBitmask; + mContentBackend = GetContentBackendPref(mContentBackendBitmask); + if (mContentBackend == BackendType::NONE) { + mContentBackend = aPrefsData.mContentDefault; + // mContentBackendBitmask is our canonical reference for supported + // backends so we need to add the default if we are using it and + // overriding the prefs. + mContentBackendBitmask |= BackendTypeBit(aPrefsData.mContentDefault); + } + + uint32_t swBackendBits = BackendTypeBit(BackendType::SKIA); +#ifdef MOZ_WIDGET_GTK + swBackendBits |= BackendTypeBit(BackendType::CAIRO); +#endif + mSoftwareBackend = GetContentBackendPref(swBackendBits); + if (mSoftwareBackend == BackendType::NONE) { + mSoftwareBackend = BackendType::SKIA; + } + + // If we don't have a fallback canvas backend then use the same software + // fallback as content. + if (mFallbackCanvasBackend == BackendType::NONE) { + mFallbackCanvasBackend = mSoftwareBackend; + } + + if (XRE_IsParentProcess()) { + gfxVars::SetContentBackend(mContentBackend); + gfxVars::SetSoftwareBackend(mSoftwareBackend); + } +} + +/* static */ +BackendType gfxPlatform::GetCanvasBackendPref(uint32_t aBackendBitmask) { + return GetBackendPref("gfx.canvas.azure.backends", aBackendBitmask); +} + +/* static */ +BackendType gfxPlatform::GetContentBackendPref(uint32_t& aBackendBitmask) { + return GetBackendPref("gfx.content.azure.backends", aBackendBitmask); +} + +/* static */ +BackendType gfxPlatform::GetBackendPref(const char* aBackendPrefName, + uint32_t& aBackendBitmask) { + nsTArray<nsCString> backendList; + nsAutoCString prefString; + if (NS_SUCCEEDED(Preferences::GetCString(aBackendPrefName, prefString))) { + ParseString(prefString, ',', backendList); + } + + uint32_t allowedBackends = 0; + BackendType result = BackendType::NONE; + for (uint32_t i = 0; i < backendList.Length(); ++i) { + BackendType type = BackendTypeForName(backendList[i]); + if (BackendTypeBit(type) & aBackendBitmask) { + allowedBackends |= BackendTypeBit(type); + if (result == BackendType::NONE) { + result = type; + } + } + } + + aBackendBitmask = allowedBackends; + return result; +} + +bool gfxPlatform::InSafeMode() { + static bool sSafeModeInitialized = false; + static bool sInSafeMode = false; + + if (!sSafeModeInitialized) { + sSafeModeInitialized = true; + nsCOMPtr<nsIXULRuntime> xr = do_GetService("@mozilla.org/xre/runtime;1"); + if (xr) { + xr->GetInSafeMode(&sInSafeMode); + } + } + return sInSafeMode; +} + +bool gfxPlatform::OffMainThreadCompositingEnabled() { + return UsesOffMainThreadCompositing(); +} + +void gfxPlatform::SetCMSModeOverride(CMSMode aMode) { + MOZ_ASSERT(gCMSInitialized); + gCMSMode = aMode; +} + +int gfxPlatform::GetRenderingIntent() { + // StaticPrefList.yaml is using 0 as the default for the rendering + // intent preference, based on that being the value for + // QCMS_INTENT_DEFAULT. Assert here to catch if that ever + // changes and we can then figure out what to do about it. + MOZ_ASSERT(QCMS_INTENT_DEFAULT == 0); + + /* Try to query the pref system for a rendering intent. */ + int32_t pIntent = StaticPrefs::gfx_color_management_rendering_intent(); + if ((pIntent < QCMS_INTENT_MIN) || (pIntent > QCMS_INTENT_MAX)) { + /* If the pref is out of range, use embedded profile. */ + pIntent = -1; + } + return pIntent; +} + +DeviceColor gfxPlatform::TransformPixel(const sRGBColor& in, + qcms_transform* transform) { + if (transform) { + /* we want the bytes in RGB order */ +#ifdef IS_LITTLE_ENDIAN + /* ABGR puts the bytes in |RGBA| order on little endian */ + uint32_t packed = in.ToABGR(); + qcms_transform_data(transform, (uint8_t*)&packed, (uint8_t*)&packed, 1); + auto out = DeviceColor::FromABGR(packed); +#else + /* ARGB puts the bytes in |ARGB| order on big endian */ + uint32_t packed = in.UnusualToARGB(); + /* add one to move past the alpha byte */ + qcms_transform_data(transform, (uint8_t*)&packed + 1, (uint8_t*)&packed + 1, + 1); + auto out = DeviceColor::UnusualFromARGB(packed); +#endif + out.a = in.a; + return out; + } + return DeviceColor(in.r, in.g, in.b, in.a); +} + +nsTArray<uint8_t> gfxPlatform::GetPrefCMSOutputProfileData() { + const auto mirror = StaticPrefs::gfx_color_management_display_profile(); + const auto fname = *mirror; + if (fname == "") { + return nsTArray<uint8_t>(); + } + + void* mem = nullptr; + size_t size = 0; + qcms_data_from_path(fname.get(), &mem, &size); + + nsTArray<uint8_t> result; + + if (mem) { + result.AppendElements(static_cast<uint8_t*>(mem), size); + free(mem); + } + + return result; +} + +const mozilla::gfx::ContentDeviceData* gfxPlatform::GetInitContentDeviceData() { + return gContentDeviceInitData; +} + +Maybe<nsTArray<uint8_t>>& gfxPlatform::GetCMSOutputProfileData() { + return gCMSOutputProfileData; +} + +CMSMode GfxColorManagementMode() { + const auto mode = StaticPrefs::gfx_color_management_mode(); + if (mode >= 0 && mode < UnderlyingValue(CMSMode::AllCount)) { + return CMSMode(mode); + } + return CMSMode::Off; +} + +void gfxPlatform::InitializeCMS() { + if (gCMSInitialized) { + return; + } + + if (XRE_IsGPUProcess()) { + // Colors in the GPU process should already be managed, so we don't need to + // perform color management there. + gCMSInitialized = true; + return; + } + + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(), + "CMS should be initialized on the main thread"); + if (MOZ_UNLIKELY(!NS_IsMainThread())) { + return; + } + + gCMSMode = GfxColorManagementMode(); + + gCMSsRGBProfile = qcms_profile_sRGB(); + + /* Determine if we're using the internal override to force sRGB as + an output profile for reftests. See Bug 452125. + + Note that we don't normally (outside of tests) set a default value + of this preference, which means nsIPrefBranch::GetBoolPref will + typically throw (and leave its out-param untouched). + */ + if (StaticPrefs::gfx_color_management_force_srgb() || + StaticPrefs::gfx_color_management_native_srgb()) { + gCMSOutputProfile = gCMSsRGBProfile; + } + + if (!gCMSOutputProfile) { + nsTArray<uint8_t> outputProfileData = + gfxPlatform::GetPlatform()->GetPlatformCMSOutputProfileData(); + if (!outputProfileData.IsEmpty()) { + gCMSOutputProfile = qcms_profile_from_memory_curves_only( + outputProfileData.Elements(), outputProfileData.Length()); + } + } + + /* Determine if the profile looks bogus. If so, close the profile + * and use sRGB instead. See bug 460629, */ + if (gCMSOutputProfile && qcms_profile_is_bogus(gCMSOutputProfile)) { + NS_ASSERTION(gCMSOutputProfile != gCMSsRGBProfile, + "Builtin sRGB profile tagged as bogus!!!"); + qcms_profile_release(gCMSOutputProfile); + gCMSOutputProfile = nullptr; + } + + if (!gCMSOutputProfile) { + gCMSOutputProfile = gCMSsRGBProfile; + } + + /* Precache the LUT16 Interpolations for the output profile. See + bug 444661 for details. */ + qcms_profile_precache_output_transform(gCMSOutputProfile); + + // Create the RGB transform. + gCMSRGBTransform = + qcms_transform_create(gCMSsRGBProfile, QCMS_DATA_RGB_8, gCMSOutputProfile, + QCMS_DATA_RGB_8, QCMS_INTENT_PERCEPTUAL); + + // And the inverse. + gCMSInverseRGBTransform = + qcms_transform_create(gCMSOutputProfile, QCMS_DATA_RGB_8, gCMSsRGBProfile, + QCMS_DATA_RGB_8, QCMS_INTENT_PERCEPTUAL); + + // The RGBA transform. + gCMSRGBATransform = qcms_transform_create(gCMSsRGBProfile, QCMS_DATA_RGBA_8, + gCMSOutputProfile, QCMS_DATA_RGBA_8, + QCMS_INTENT_PERCEPTUAL); + + // And the BGRA one. + gCMSBGRATransform = qcms_transform_create(gCMSsRGBProfile, QCMS_DATA_BGRA_8, + gCMSOutputProfile, QCMS_DATA_BGRA_8, + QCMS_INTENT_PERCEPTUAL); + + // FIXME: We only enable iccv4 after we create the platform profile, to + // wallpaper over bug 1697787. + // + // This should happen ideally right after setting gCMSMode. + if (StaticPrefs::gfx_color_management_enablev4()) { + qcms_enable_iccv4(); + } + + gCMSInitialized = true; +} + +qcms_transform* gfxPlatform::GetCMSOSRGBATransform() { + switch (SurfaceFormat::OS_RGBA) { + case SurfaceFormat::B8G8R8A8: + return GetCMSBGRATransform(); + case SurfaceFormat::R8G8B8A8: + return GetCMSRGBATransform(); + default: + // We do not support color management with big endian. + return nullptr; + } +} + +qcms_data_type gfxPlatform::GetCMSOSRGBAType() { + switch (SurfaceFormat::OS_RGBA) { + case SurfaceFormat::B8G8R8A8: + return QCMS_DATA_BGRA_8; + case SurfaceFormat::R8G8B8A8: + return QCMS_DATA_RGBA_8; + default: + // We do not support color management with big endian. + return QCMS_DATA_RGBA_8; + } +} + +/* Shuts down various transforms and profiles for CMS. */ +void gfxPlatform::ShutdownCMS() { + if (gCMSRGBTransform) { + qcms_transform_release(gCMSRGBTransform); + gCMSRGBTransform = nullptr; + } + if (gCMSInverseRGBTransform) { + qcms_transform_release(gCMSInverseRGBTransform); + gCMSInverseRGBTransform = nullptr; + } + if (gCMSRGBATransform) { + qcms_transform_release(gCMSRGBATransform); + gCMSRGBATransform = nullptr; + } + if (gCMSBGRATransform) { + qcms_transform_release(gCMSBGRATransform); + gCMSBGRATransform = nullptr; + } + if (gCMSOutputProfile) { + qcms_profile_release(gCMSOutputProfile); + + // handle the aliased case + if (gCMSsRGBProfile == gCMSOutputProfile) { + gCMSsRGBProfile = nullptr; + } + gCMSOutputProfile = nullptr; + } + if (gCMSsRGBProfile) { + qcms_profile_release(gCMSsRGBProfile); + gCMSsRGBProfile = nullptr; + } + + // Reset the state variables + gCMSMode = CMSMode::Off; + gCMSInitialized = false; +} + +uint32_t gfxPlatform::GetBidiNumeralOption() { + return StaticPrefs::bidi_numeral(); +} + +/* static */ +void gfxPlatform::FlushFontAndWordCaches() { + gfxFontCache* fontCache = gfxFontCache::GetCache(); + if (fontCache) { + fontCache->Flush(); + } + + gfxPlatform::PurgeSkiaFontCache(); +} + +/* static */ +void gfxPlatform::ForceGlobalReflow(NeedsReframe aNeedsReframe, + BroadcastToChildren aBroadcastToChildren) { + MOZ_ASSERT(NS_IsMainThread()); + const bool reframe = aNeedsReframe == NeedsReframe::Yes; + // Send a notification that will be observed by PresShells in this process + // only. + if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) { + char16_t needsReframe[] = {char16_t(reframe), 0}; + obs->NotifyObservers(nullptr, "font-info-updated", needsReframe); + } + if (XRE_IsParentProcess() && + aBroadcastToChildren == BroadcastToChildren::Yes) { + // Propagate the change to child processes. + for (auto* process : + dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) { + Unused << process->SendForceGlobalReflow(reframe); + } + } +} + +void gfxPlatform::FontsPrefsChanged(const char* aPref) { + NS_ASSERTION(aPref != nullptr, "null preference"); + if (!strcmp(GFX_DOWNLOADABLE_FONTS_ENABLED, aPref)) { + mAllowDownloadableFonts = UNINITIALIZED_VALUE; + } else if (!strcmp(GFX_PREF_WORD_CACHE_CHARLIMIT, aPref) || + !strcmp(GFX_PREF_WORD_CACHE_MAXENTRIES, aPref) || + !strcmp(GFX_PREF_GRAPHITE_SHAPING, aPref)) { + FlushFontAndWordCaches(); + } else if ( +#if defined(XP_MACOSX) + !strcmp(GFX_PREF_CORETEXT_SHAPING, aPref) || +#endif + !strcmp("gfx.font_rendering.ahem_antialias_none", aPref)) { + FlushFontAndWordCaches(); + } else if (!strcmp(GFX_PREF_OPENTYPE_SVG, aPref)) { + gfxFontCache::GetCache()->Flush(); + gfxFontCache::GetCache()->NotifyGlyphsChanged(); + } +} + +mozilla::LogModule* gfxPlatform::GetLog(eGfxLog aWhichLog) { + // logs shared across gfx + static LazyLogModule sFontlistLog("fontlist"); + static LazyLogModule sFontInitLog("fontinit"); + static LazyLogModule sTextrunLog("textrun"); + static LazyLogModule sTextrunuiLog("textrunui"); + static LazyLogModule sCmapDataLog("cmapdata"); + static LazyLogModule sTextPerfLog("textperf"); + + switch (aWhichLog) { + case eGfxLog_fontlist: + return sFontlistLog; + case eGfxLog_fontinit: + return sFontInitLog; + case eGfxLog_textrun: + return sTextrunLog; + case eGfxLog_textrunui: + return sTextrunuiLog; + case eGfxLog_cmapdata: + return sCmapDataLog; + case eGfxLog_textperf: + return sTextPerfLog; + } + + MOZ_ASSERT_UNREACHABLE("Unexpected log type"); + return nullptr; +} + +RefPtr<mozilla::gfx::DrawTarget> gfxPlatform::ScreenReferenceDrawTarget() { + MOZ_ASSERT_IF(XRE_IsContentProcess(), NS_IsMainThread()); + return (mScreenReferenceDrawTarget) + ? mScreenReferenceDrawTarget + : gPlatform->CreateOffscreenContentDrawTarget( + IntSize(1, 1), SurfaceFormat::B8G8R8A8, true); +} + +/* static */ RefPtr<mozilla::gfx::DrawTarget> +gfxPlatform::ThreadLocalScreenReferenceDrawTarget() { + if (NS_IsMainThread() && gPlatform) { + return gPlatform->ScreenReferenceDrawTarget(); + } + + gfxPlatformWorker* platformWorker = gfxPlatformWorker::Get(); + if (platformWorker) { + return platformWorker->ScreenReferenceDrawTarget(); + } + + return Factory::CreateDrawTarget(BackendType::SKIA, IntSize(1, 1), + SurfaceFormat::B8G8R8A8); +} + +mozilla::gfx::SurfaceFormat gfxPlatform::Optimal2DFormatForContent( + gfxContentType aContent) { + switch (aContent) { + case gfxContentType::COLOR: + switch (GetOffscreenFormat()) { + case SurfaceFormat::A8R8G8B8_UINT32: + return mozilla::gfx::SurfaceFormat::B8G8R8A8; + case SurfaceFormat::X8R8G8B8_UINT32: + return mozilla::gfx::SurfaceFormat::B8G8R8X8; + case SurfaceFormat::R5G6B5_UINT16: + return mozilla::gfx::SurfaceFormat::R5G6B5_UINT16; + default: + MOZ_ASSERT_UNREACHABLE( + "unknown gfxImageFormat for " + "gfxContentType::COLOR"); + return mozilla::gfx::SurfaceFormat::B8G8R8A8; + } + case gfxContentType::ALPHA: + return mozilla::gfx::SurfaceFormat::A8; + case gfxContentType::COLOR_ALPHA: + return mozilla::gfx::SurfaceFormat::B8G8R8A8; + default: + MOZ_ASSERT_UNREACHABLE("unknown gfxContentType"); + return mozilla::gfx::SurfaceFormat::B8G8R8A8; + } +} + +gfxImageFormat gfxPlatform::OptimalFormatForContent(gfxContentType aContent) { + switch (aContent) { + case gfxContentType::COLOR: + return GetOffscreenFormat(); + case gfxContentType::ALPHA: + return SurfaceFormat::A8; + case gfxContentType::COLOR_ALPHA: + return SurfaceFormat::A8R8G8B8_UINT32; + default: + MOZ_ASSERT_UNREACHABLE("unknown gfxContentType"); + return SurfaceFormat::A8R8G8B8_UINT32; + } +} + +/** + * There are a number of layers acceleration (or layers in general) preferences + * that should be consistent for the lifetime of the application (bug 840967). + * As such, we will evaluate them all as soon as one of them is evaluated + * and remember the values. Changing these preferences during the run will + * not have any effect until we restart. + */ +static mozilla::Atomic<bool> sLayersSupportsHardwareVideoDecoding(false); +static bool sLayersHardwareVideoDecodingFailed = false; + +static mozilla::Atomic<bool> sLayersAccelerationPrefsInitialized(false); + +static void VideoDecodingFailedChangedCallback(const char* aPref, void*) { + sLayersHardwareVideoDecodingFailed = Preferences::GetBool(aPref, false); + gfxPlatform::GetPlatform()->UpdateCanUseHardwareVideoDecoding(); +} + +void gfxPlatform::UpdateCanUseHardwareVideoDecoding() { + if (XRE_IsParentProcess()) { + gfxVars::SetCanUseHardwareVideoDecoding(CanUseHardwareVideoDecoding()); + } +} + +void gfxPlatform::UpdateForceSubpixelAAWherePossible() { + bool forceSubpixelAAWherePossible = + StaticPrefs::gfx_webrender_quality_force_subpixel_aa_where_possible(); + gfxVars::SetForceSubpixelAAWherePossible(forceSubpixelAAWherePossible); +} + +void gfxPlatform::InitAcceleration() { + if (sLayersAccelerationPrefsInitialized) { + return; + } + + InitCompositorAccelerationPrefs(); + + // If this is called for the first time on a non-main thread, we're screwed. + // At the moment there's no explicit guarantee that the main thread calls + // this before the compositor thread, but let's at least make the assumption + // explicit. + MOZ_ASSERT(NS_IsMainThread(), "can only initialize prefs on the main thread"); + +#ifndef MOZ_WIDGET_GTK + nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); + nsCString discardFailureId; + int32_t status; +#endif + + if (XRE_IsParentProcess()) { + gfxVars::SetBrowserTabsRemoteAutostart(BrowserTabsRemoteAutostart()); + gfxVars::SetOffscreenFormat(GetOffscreenFormat()); + gfxVars::SetRequiresAcceleratedGLContextForCompositorOGL( + RequiresAcceleratedGLContextForCompositorOGL()); +#ifdef XP_WIN + if (NS_SUCCEEDED( + gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_D3D11_KEYED_MUTEX, + discardFailureId, &status))) { + gfxVars::SetAllowD3D11KeyedMutex(status == nsIGfxInfo::FEATURE_STATUS_OK); + } else { + // If we couldn't properly evaluate the status, err on the side + // of caution and give this functionality to the user. + gfxCriticalNote << "Cannot evaluate keyed mutex feature status"; + gfxVars::SetAllowD3D11KeyedMutex(true); + } + if (StaticPrefs::gfx_direct3d11_use_double_buffering()) { + gfxVars::SetUseDoubleBufferingWithCompositor(true); + } +#endif + } + + if (StaticPrefs::media_hardware_video_decoding_enabled_AtStartup()) { +#ifdef MOZ_WIDGET_GTK + sLayersSupportsHardwareVideoDecoding = + gfxPlatformGtk::GetPlatform()->InitVAAPIConfig( + StaticPrefs:: + media_hardware_video_decoding_force_enabled_AtStartup() || + StaticPrefs::media_ffmpeg_vaapi_enabled_AtStartup()); +#else + if ( +# ifdef XP_WIN + Preferences::GetBool("media.wmf.dxva.enabled", true) && +# endif + NS_SUCCEEDED(gfxInfo->GetFeatureStatus( + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, discardFailureId, + &status))) { + if (status == nsIGfxInfo::FEATURE_STATUS_OK || + StaticPrefs:: + media_hardware_video_decoding_force_enabled_AtStartup()) { + sLayersSupportsHardwareVideoDecoding = true; + } + } +#endif + } else if (XRE_IsParentProcess()) { + FeatureState& feature = + gfxConfig::GetFeature(Feature::HARDWARE_VIDEO_DECODING); + feature.EnableByDefault(); + feature.UserDisable("User disabled via pref", + "FEATURE_HARDWARE_VIDEO_DECODING_PREF_DISABLED"_ns); + } + + sLayersAccelerationPrefsInitialized = true; + + if (XRE_IsParentProcess()) { + Preferences::RegisterCallbackAndCall( + VideoDecodingFailedChangedCallback, + "media.hardware-video-decoding.failed"); + InitGPUProcessPrefs(); + + FeatureState& feature = gfxConfig::GetFeature(Feature::REMOTE_CANVAS); + feature.SetDefault(StaticPrefs::gfx_canvas_remote_AtStartup(), + FeatureStatus::Disabled, "Disabled via pref"); + + if (!gfxConfig::IsEnabled(Feature::GPU_PROCESS) && + !StaticPrefs::gfx_canvas_remote_allow_in_parent_AtStartup()) { + feature.Disable(FeatureStatus::UnavailableNoGpuProcess, + "Disabled without GPU process", + "FEATURE_REMOTE_CANVAS_NO_GPU_PROCESS"_ns); + } + +#ifndef XP_WIN + gfxConfig::ForceDisable(Feature::REMOTE_CANVAS, FeatureStatus::Blocked, + "Platform not supported", + "FEATURE_REMOTE_CANVAS_NOT_WINDOWS"_ns); +#endif + + gfxVars::SetRemoteCanvasEnabled(feature.IsEnabled()); + } +} + +void gfxPlatform::InitGPUProcessPrefs() { + // We want to hide this from about:support, so only set a default if the + // pref is known to be true. + if (!StaticPrefs::layers_gpu_process_enabled_AtStartup() && + !StaticPrefs::layers_gpu_process_force_enabled_AtStartup()) { + return; + } + + FeatureState& gpuProc = gfxConfig::GetFeature(Feature::GPU_PROCESS); + + // We require E10S - otherwise, there is very little benefit to the GPU + // process, since the UI process must still use acceleration for + // performance. + if (!BrowserTabsRemoteAutostart()) { + gpuProc.DisableByDefault(FeatureStatus::Unavailable, + "Multi-process mode is not enabled", + "FEATURE_FAILURE_NO_E10S"_ns); + } else { + gpuProc.SetDefaultFromPref( + StaticPrefs::GetPrefName_layers_gpu_process_enabled(), true, + StaticPrefs::GetPrefDefault_layers_gpu_process_enabled()); + } + + if (StaticPrefs::layers_gpu_process_force_enabled_AtStartup()) { + gpuProc.UserForceEnable("User force-enabled via pref"); + } + + nsCString message; + nsCString failureId; + if (!gfxPlatform::IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_GPU_PROCESS, + &message, failureId)) { + gpuProc.Disable(FeatureStatus::Blocklisted, message.get(), failureId); + return; + } + + if (IsHeadless()) { + gpuProc.ForceDisable(FeatureStatus::Blocked, "Headless mode is enabled", + "FEATURE_FAILURE_HEADLESS_MODE"_ns); + return; + } + + InitPlatformGPUProcessPrefs(); +} + +void gfxPlatform::InitCompositorAccelerationPrefs() { + const char* acceleratedEnv = PR_GetEnv("MOZ_ACCELERATED"); + + FeatureState& feature = gfxConfig::GetFeature(Feature::HW_COMPOSITING); + + // Base value - does the platform allow acceleration? + if (feature.SetDefault(AccelerateLayersByDefault(), FeatureStatus::Blocked, + "Acceleration blocked by platform")) { + if (StaticPrefs:: + layers_acceleration_disabled_AtStartup_DoNotUseDirectly()) { + feature.UserDisable("Disabled by layers.acceleration.disabled=true", + "FEATURE_FAILURE_COMP_PREF"_ns); + } else if (acceleratedEnv && *acceleratedEnv == '0') { + feature.UserDisable("Disabled by envvar", "FEATURE_FAILURE_COMP_ENV"_ns); + } + } else { + if (acceleratedEnv && *acceleratedEnv == '1') { + feature.UserEnable("Enabled by envvar"); + } + } + + // This has specific meaning elsewhere, so we always record it. + if (StaticPrefs:: + layers_acceleration_force_enabled_AtStartup_DoNotUseDirectly()) { + feature.UserForceEnable("Force-enabled by pref"); + } + + // Safe, headless, and record/replay modes override everything. + if (InSafeMode()) { + feature.ForceDisable(FeatureStatus::Blocked, + "Acceleration blocked by safe-mode", + "FEATURE_FAILURE_COMP_SAFEMODE"_ns); + } + if (IsHeadless()) { + feature.ForceDisable(FeatureStatus::Blocked, + "Acceleration blocked by headless mode", + "FEATURE_FAILURE_COMP_HEADLESSMODE"_ns); + } +} + +/*static*/ +bool gfxPlatform::WebRenderPrefEnabled() { + return StaticPrefs::gfx_webrender_all_AtStartup(); +} + +/*static*/ +bool gfxPlatform::WebRenderEnvvarEnabled() { + const char* env = PR_GetEnv("MOZ_WEBRENDER"); + return (env && *env == '1'); +} + +/* static */ const char* gfxPlatform::WebRenderResourcePathOverride() { + const char* resourcePath = PR_GetEnv("WR_RESOURCE_PATH"); + if (!resourcePath || resourcePath[0] == '\0') { + return nullptr; + } + return resourcePath; +} + +void gfxPlatform::InitWebRenderConfig() { + bool prefEnabled = WebRenderPrefEnabled(); + bool envvarEnabled = WebRenderEnvvarEnabled(); + + // WR? WR+ => means WR was enabled on qualified hardware + // WR! WR+ => means WR was enabled via gfx.webrender.{all,enabled} or + // envvar, possibly on unqualified hardware + // In all cases WR- means WR was not enabled, for one of many possible + // reasons. Prior to bug 1523788 landing the gfx.webrender.{all,enabled} + // prefs only worked on Nightly so keep that in mind when looking at older + // crash reports. + ScopedGfxFeatureReporter reporter("WR", prefEnabled || envvarEnabled); + if (!XRE_IsParentProcess()) { + // The parent process runs through all the real decision-making code + // later in this function. For other processes we still want to report + // the state of the feature for crash reports. + reporter.SetSuccessful(); + return; + } + + // Update the gfxConfig feature states. + gfxConfigManager manager; + manager.Init(); + manager.ConfigureWebRender(); + + bool hasHardware = gfxConfig::IsEnabled(Feature::WEBRENDER); + +#ifdef MOZ_WIDGET_GTK + // We require a hardware driver to back the GL context unless the user forced + // on WebRender. + if (!gfxConfig::IsForcedOnByUser(Feature::WEBRENDER) && + StaticPrefs::gfx_webrender_reject_software_driver_AtStartup()) { + gfxVars::SetWebRenderRequiresHardwareDriver(true); + } +#endif + +#ifdef XP_WIN + if (gfxConfig::IsEnabled(Feature::WEBRENDER_ANGLE)) { + gfxVars::SetUseWebRenderANGLE(true); + } +#endif + + if (gfxConfig::IsEnabled(Feature::WEBRENDER_SHADER_CACHE)) { + gfxVars::SetUseWebRenderProgramBinaryDisk(true); + } + + gfxVars::SetUseWebRenderOptimizedShaders( + gfxConfig::IsEnabled(Feature::WEBRENDER_OPTIMIZED_SHADERS)); + + gfxVars::SetUseSoftwareWebRender(!hasHardware); + + Preferences::RegisterPrefixCallbackAndCall(SwapIntervalPrefChangeCallback, + "gfx.swap-interval"); + + reporter.SetSuccessful(); + + Preferences::RegisterPrefixCallbackAndCall(WebRenderDebugPrefChangeCallback, + WR_DEBUG_PREF); + + RegisterWebRenderBoolParamCallback(); + + Preferences::RegisterPrefixCallbackAndCall( + WebRendeProfilerUIPrefChangeCallback, "gfx.webrender.debug.profiler-ui"); + Preferences::RegisterCallback( + WebRenderQualityPrefChangeCallback, + nsDependentCString( + StaticPrefs:: + GetPrefName_gfx_webrender_quality_force_subpixel_aa_where_possible())); + + Preferences::RegisterCallback( + WebRenderBatchingPrefChangeCallback, + nsDependentCString( + StaticPrefs::GetPrefName_gfx_webrender_batching_lookback())); + + Preferences::RegisterCallbackAndCall( + WebRenderBlobTileSizePrefChangeCallback, + nsDependentCString( + StaticPrefs::GetPrefName_gfx_webrender_blob_tile_size())); + + Preferences::RegisterCallbackAndCall( + WebRenderUploadThresholdPrefChangeCallback, + nsDependentCString( + StaticPrefs::GetPrefName_gfx_webrender_batched_upload_threshold())); + + if (WebRenderResourcePathOverride()) { + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::IsWebRenderResourcePathOverridden, true); + } + + UpdateForceSubpixelAAWherePossible(); + +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GTK) + if (StaticPrefs::gfx_webrender_software_opengl_AtStartup()) { + gfxVars::SetAllowSoftwareWebRenderOGL(true); + } +#endif + +#ifdef XP_WIN + if (gfxConfig::IsEnabled(Feature::WEBRENDER_DCOMP_PRESENT)) { + gfxVars::SetUseWebRenderDCompWin(true); + } + if (StaticPrefs::gfx_webrender_software_d3d11_AtStartup()) { + gfxVars::SetAllowSoftwareWebRenderD3D11(true); + } + + const bool overlaySupported = + IsWin10AnniversaryUpdateOrLater() && + gfxConfig::IsEnabled(Feature::WEBRENDER_COMPOSITOR); + MOZ_ASSERT_IF(overlaySupported, + gfxConfig::IsEnabled(Feature::WEBRENDER_DCOMP_PRESENT)); + + bool useVideoHwOverlay = false; + if (StaticPrefs::gfx_webrender_dcomp_video_hw_overlay_win_AtStartup()) { + if (overlaySupported) { + useVideoHwOverlay = true; + } + + if (useVideoHwOverlay && + !StaticPrefs:: + gfx_webrender_dcomp_video_hw_overlay_win_force_enabled_AtStartup()) { + nsCString failureId; + int32_t status; + const nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); + if (NS_FAILED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_VIDEO_OVERLAY, + failureId, &status))) { + FeatureState& feature = + gfxConfig::GetFeature(Feature::VIDEO_HARDWARE_OVERLAY); + feature.DisableByDefault(FeatureStatus::BlockedNoGfxInfo, + "gfxInfo is broken", + "FEATURE_FAILURE_WR_NO_GFX_INFO"_ns); + useVideoHwOverlay = false; + } else { + if (status != nsIGfxInfo::FEATURE_ALLOW_ALWAYS) { + FeatureState& feature = + gfxConfig::GetFeature(Feature::VIDEO_HARDWARE_OVERLAY); + feature.DisableByDefault(FeatureStatus::Blocked, + "Blocklisted by gfxInfo", failureId); + useVideoHwOverlay = false; + } + } + } + } else if (overlaySupported) { + FeatureState& feature = + gfxConfig::GetFeature(Feature::VIDEO_HARDWARE_OVERLAY); + feature.DisableByDefault(FeatureStatus::Blocked, "Disabled by pref", + "FEATURE_FAILURE_DISABLED_BY_PREF"_ns); + } + + if (useVideoHwOverlay) { + FeatureState& feature = + gfxConfig::GetFeature(Feature::VIDEO_HARDWARE_OVERLAY); + feature.EnableByDefault(); + gfxVars::SetUseWebRenderDCompVideoHwOverlayWin(true); + } + + bool useVideoSwOverlay = false; + if (overlaySupported && + StaticPrefs::gfx_webrender_dcomp_video_sw_overlay_win_AtStartup()) { + useVideoSwOverlay = true; + + if (useVideoSwOverlay && + !StaticPrefs:: + gfx_webrender_dcomp_video_sw_overlay_win_force_enabled_AtStartup()) { + nsCString failureId; + int32_t status; + const nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); + if (NS_FAILED(gfxInfo->GetFeatureStatus( + nsIGfxInfo::FEATURE_VIDEO_SOFTWARE_OVERLAY, failureId, + &status))) { + FeatureState& feature = + gfxConfig::GetFeature(Feature::VIDEO_SOFTWARE_OVERLAY); + feature.DisableByDefault(FeatureStatus::BlockedNoGfxInfo, + "gfxInfo is broken", + "FEATURE_FAILURE_WR_NO_GFX_INFO"_ns); + useVideoSwOverlay = false; + } else { + if (status != nsIGfxInfo::FEATURE_STATUS_OK) { + FeatureState& feature = + gfxConfig::GetFeature(Feature::VIDEO_SOFTWARE_OVERLAY); + feature.DisableByDefault(FeatureStatus::Blocked, + "Blocklisted by gfxInfo", failureId); + useVideoSwOverlay = false; + } + } + } + } else if (overlaySupported) { + FeatureState& feature = + gfxConfig::GetFeature(Feature::VIDEO_SOFTWARE_OVERLAY); + feature.DisableByDefault(FeatureStatus::Blocked, "Disabled by pref", + "FEATURE_FAILURE_DISABLED_BY_PREF"_ns); + } + + if (useVideoSwOverlay) { + FeatureState& feature = + gfxConfig::GetFeature(Feature::VIDEO_SOFTWARE_OVERLAY); + feature.EnableByDefault(); + gfxVars::SetUseWebRenderDCompVideoSwOverlayWin(true); + } + + bool useHwVideoZeroCopy = false; + if (StaticPrefs::media_wmf_zero_copy_nv12_textures_AtStartup()) { + if (hasHardware) { + useHwVideoZeroCopy = true; + } + + if (useHwVideoZeroCopy && + !StaticPrefs:: + media_wmf_zero_copy_nv12_textures_force_enabled_AtStartup()) { + nsCString failureId; + int32_t status; + const nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); + if (NS_FAILED(gfxInfo->GetFeatureStatus( + nsIGfxInfo::FEATURE_HW_DECODED_VIDEO_ZERO_COPY, failureId, + &status))) { + FeatureState& feature = + gfxConfig::GetFeature(Feature::HW_DECODED_VIDEO_ZERO_COPY); + feature.DisableByDefault(FeatureStatus::BlockedNoGfxInfo, + "gfxInfo is broken", + "FEATURE_FAILURE_WR_NO_GFX_INFO"_ns); + useHwVideoZeroCopy = false; + } else { + if (status != nsIGfxInfo::FEATURE_ALLOW_ALWAYS) { + FeatureState& feature = + gfxConfig::GetFeature(Feature::HW_DECODED_VIDEO_ZERO_COPY); + feature.DisableByDefault(FeatureStatus::Blocked, + "Blocklisted by gfxInfo", failureId); + useHwVideoZeroCopy = false; + } + } + } + } + + if (useHwVideoZeroCopy) { + FeatureState& feature = + gfxConfig::GetFeature(Feature::HW_DECODED_VIDEO_ZERO_COPY); + feature.EnableByDefault(); + gfxVars::SetHwDecodedVideoZeroCopy(true); + } + + bool reuseDecoderDevice = false; + if (StaticPrefs::gfx_direct3d11_reuse_decoder_device_AtStartup()) { + reuseDecoderDevice = true; + + if (reuseDecoderDevice && + !StaticPrefs:: + gfx_direct3d11_reuse_decoder_device_force_enabled_AtStartup()) { + nsCString failureId; + int32_t status; + const nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); + if (NS_FAILED(gfxInfo->GetFeatureStatus( + nsIGfxInfo::FEATURE_REUSE_DECODER_DEVICE, failureId, &status))) { + FeatureState& feature = + gfxConfig::GetFeature(Feature::REUSE_DECODER_DEVICE); + feature.DisableByDefault(FeatureStatus::BlockedNoGfxInfo, + "gfxInfo is broken", + "FEATURE_FAILURE_WR_NO_GFX_INFO"_ns); + reuseDecoderDevice = false; + } else { + if (status != nsIGfxInfo::FEATURE_STATUS_OK) { + FeatureState& feature = + gfxConfig::GetFeature(Feature::REUSE_DECODER_DEVICE); + feature.DisableByDefault(FeatureStatus::Blocked, + "Blocklisted by gfxInfo", failureId); + reuseDecoderDevice = false; + } + } + } + } + + if (reuseDecoderDevice) { + FeatureState& feature = + gfxConfig::GetFeature(Feature::REUSE_DECODER_DEVICE); + feature.EnableByDefault(); + gfxVars::SetReuseDecoderDevice(true); + } + + if (Preferences::GetBool("gfx.webrender.flip-sequential", false)) { + if (gfxVars::UseWebRenderANGLE()) { + gfxVars::SetUseWebRenderFlipSequentialWin(true); + } + } + if (Preferences::GetBool("gfx.webrender.triple-buffering.enabled", false)) { + if (gfxVars::UseWebRenderDCompWin() || + gfxVars::UseWebRenderFlipSequentialWin()) { + gfxVars::SetUseWebRenderTripleBufferingWin(true); + } + } +#endif + + if (gfxConfig::IsEnabled(Feature::WEBRENDER_COMPOSITOR)) { + gfxVars::SetUseWebRenderCompositor(true); + } + + Telemetry::ScalarSet( + Telemetry::ScalarID::GFX_OS_COMPOSITOR, + gfx::gfxConfig::IsEnabled(gfx::Feature::WEBRENDER_COMPOSITOR)); + + if (gfxConfig::IsEnabled(Feature::WEBRENDER_PARTIAL)) { + gfxVars::SetWebRenderMaxPartialPresentRects( + StaticPrefs::gfx_webrender_max_partial_present_rects_AtStartup()); + } + + // Set features that affect WR's RendererOptions + gfxVars::SetUseGLSwizzle( + IsFeatureSupported(nsIGfxInfo::FEATURE_GL_SWIZZLE, true)); + gfxVars::SetUseWebRenderScissoredCacheClears(gfx::gfxConfig::IsEnabled( + gfx::Feature::WEBRENDER_SCISSORED_CACHE_CLEARS)); + + // The RemoveShaderCacheFromDiskIfNecessary() needs to be called after + // WebRenderConfig initialization. + gfxUtils::RemoveShaderCacheFromDiskIfNecessary(); +} + +void gfxPlatform::InitHardwareVideoConfig() { + if (!XRE_IsParentProcess()) { + return; + } + +#ifdef MOZ_WIDGET_GTK + // We don't want to expose codec info if whole HW decoding is disabled. + if (!sLayersSupportsHardwareVideoDecoding) { + return; + } +#endif + + nsCString message; + nsCString failureId; + + FeatureState& featureVP8 = gfxConfig::GetFeature(Feature::VP8_HW_DECODE); + featureVP8.EnableByDefault(); + + if (!IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_VP8_HW_DECODE, &message, + failureId)) { + featureVP8.Disable(FeatureStatus::Blocklisted, message.get(), failureId); + } + gfxVars::SetUseVP8HwDecode(featureVP8.IsEnabled()); + + FeatureState& featureVP9 = gfxConfig::GetFeature(Feature::VP9_HW_DECODE); + featureVP9.EnableByDefault(); + + if (!IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_VP9_HW_DECODE, &message, + failureId)) { + featureVP9.Disable(FeatureStatus::Blocklisted, message.get(), failureId); + } + gfxVars::SetUseVP9HwDecode(featureVP9.IsEnabled()); + + // H264_HW_DECODE/AV1_HW_DECODE is used on Linux only right now. +#ifdef MOZ_WIDGET_GTK + FeatureState& featureH264 = gfxConfig::GetFeature(Feature::H264_HW_DECODE); + featureH264.EnableByDefault(); + + if (!IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_H264_HW_DECODE, &message, + failureId)) { + featureH264.Disable(FeatureStatus::Blocklisted, message.get(), failureId); + } + gfxVars::SetUseH264HwDecode(featureH264.IsEnabled()); + + FeatureState& featureAV1 = gfxConfig::GetFeature(Feature::AV1_HW_DECODE); + featureAV1.EnableByDefault(); + + if (!IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_AV1_HW_DECODE, &message, + failureId)) { + featureAV1.Disable(FeatureStatus::Blocklisted, message.get(), failureId); + } + gfxVars::SetUseAV1HwDecode(featureAV1.IsEnabled()); +#endif +} + +void gfxPlatform::InitWebGLConfig() { + if (!XRE_IsParentProcess()) return; + + const nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); + + const auto IsFeatureOk = [&](const int32_t feature) { + nsCString discardFailureId; + int32_t status; + MOZ_RELEASE_ASSERT(NS_SUCCEEDED( + gfxInfo->GetFeatureStatus(feature, discardFailureId, &status))); + return (status == nsIGfxInfo::FEATURE_STATUS_OK); + }; + + gfxVars::SetAllowWebgl2(IsFeatureOk(nsIGfxInfo::FEATURE_WEBGL2)); + gfxVars::SetWebglAllowWindowsNativeGl( + IsFeatureOk(nsIGfxInfo::FEATURE_WEBGL_OPENGL)); + gfxVars::SetAllowWebglAccelAngle( + IsFeatureOk(nsIGfxInfo::FEATURE_WEBGL_ANGLE)); + gfxVars::SetWebglUseHardware( + IsFeatureOk(nsIGfxInfo::FEATURE_WEBGL_USE_HARDWARE)); + + if (kIsMacOS) { + // Avoid crash for Intel HD Graphics 3000 on OSX. (Bug 1413269) + nsString vendorID, deviceID; + gfxInfo->GetAdapterVendorID(vendorID); + gfxInfo->GetAdapterDeviceID(deviceID); + if (vendorID.EqualsLiteral("0x8086") && + (deviceID.EqualsLiteral("0x0116") || + deviceID.EqualsLiteral("0x0126"))) { + gfxVars::SetWebglAllowCoreProfile(false); + } + } + + bool allowWebGLOop = + IsFeatureOk(nsIGfxInfo::FEATURE_ALLOW_WEBGL_OUT_OF_PROCESS); + if (!kIsAndroid) { + gfxVars::SetAllowWebglOop(allowWebGLOop); + } else { + // On android, enable out-of-process WebGL only when GPU process exists. + gfxVars::SetAllowWebglOop(allowWebGLOop && + gfxConfig::IsEnabled(Feature::GPU_PROCESS)); + // Enable gl::SharedSurface of AndroidHardwareBuffer when API version is 26+ + // and out-of-process WebGL is enabled. +#ifdef MOZ_WIDGET_ANDROID + if (gfxVars::AllowWebglOop() && jni::GetAPIVersion() >= 26 && + StaticPrefs::webgl_out_of_process_enable_ahardwarebuffer_AtStartup()) { + gfxVars::SetUseAHardwareBufferSharedSurfaceWebglOop(true); + } +#endif + } + + bool threadsafeGL = IsFeatureOk(nsIGfxInfo::FEATURE_THREADSAFE_GL); + threadsafeGL |= StaticPrefs::webgl_threadsafe_gl_force_enabled_AtStartup(); + threadsafeGL &= !StaticPrefs::webgl_threadsafe_gl_force_disabled_AtStartup(); + gfxVars::SetSupportsThreadsafeGL(threadsafeGL); + + FeatureState& feature = + gfxConfig::GetFeature(Feature::CANVAS_RENDERER_THREAD); + if (!threadsafeGL) { + feature.DisableByDefault(FeatureStatus::Blocked, "Thread unsafe GL", + "FEATURE_FAILURE_THREAD_UNSAFE_GL"_ns); + } else if (!StaticPrefs::webgl_use_canvas_render_thread_AtStartup()) { + feature.DisableByDefault(FeatureStatus::Blocked, "Disabled by pref", + "FEATURE_FAILURE_DISABLED_BY_PREF"_ns); + } else { + feature.EnableByDefault(); + } + gfxVars::SetUseCanvasRenderThread(feature.IsEnabled()); + + bool webglOopAsyncPresentForceSync = + (threadsafeGL && !gfxVars::UseCanvasRenderThread()) || + StaticPrefs::webgl_out_of_process_async_present_force_sync(); + gfxVars::SetWebglOopAsyncPresentForceSync(webglOopAsyncPresentForceSync); + + if (kIsAndroid) { + // Don't enable robust buffer access on Adreno 620 and 630 devices. + // It causes the linking of some shaders to fail. See bug 1485441 and + // bug 1810693. + nsAutoString renderer; + gfxInfo->GetAdapterDeviceID(renderer); + if ((renderer.Find(u"Adreno (TM) 620") != -1) || + (renderer.Find(u"Adreno (TM) 630") != -1)) { + gfxVars::SetAllowEglRbab(false); + } + } + + if (kIsLinux) { + nsCString discardFailureId; + int32_t status; + FeatureState& feature = + gfxConfig::GetFeature(Feature::DMABUF_SURFACE_EXPORT); + if (NS_FAILED( + gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DMABUF_SURFACE_EXPORT, + discardFailureId, &status)) || + status != nsIGfxInfo::FEATURE_STATUS_OK) { + feature.DisableByDefault(FeatureStatus::Blocked, "Blocklisted by gfxInfo", + discardFailureId); + gfxVars::SetUseDMABufSurfaceExport(false); + } else { + feature.EnableByDefault(); + } + } +} + +void gfxPlatform::InitWebGPUConfig() { + if (!XRE_IsParentProcess()) { + return; + } + + FeatureState& feature = gfxConfig::GetFeature(Feature::WEBGPU); + feature.EnableByDefault(); + + nsCString message; + nsCString failureId; + if (!IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_WEBGPU, &message, failureId)) { + if (StaticPrefs::gfx_webgpu_ignore_blocklist_AtStartup()) { + feature.UserForceEnable( + "Ignoring blocklist entry because gfx.webgpu.ignore-blocklist is " + "true."); + } + + feature.Disable(FeatureStatus::Blocklisted, message.get(), failureId); + } + +#ifdef RELEASE_OR_BETA + feature.ForceDisable(FeatureStatus::Blocked, + "WebGPU cannot be enabled in release or beta", + "WEBGPU_DISABLE_RELEASE_OR_BETA"_ns); +#endif + + gfxVars::SetAllowWebGPU(feature.IsEnabled()); +} + +#ifdef XP_WIN +static void WindowOcclusionPrefChangeCallback(const char* aPref, void*) { + const char* env = PR_GetEnv("MOZ_WINDOW_OCCLUSION"); + if (env) { + // env has a higher priority than pref. + return; + } + + FeatureState& feature = gfxConfig::GetFeature(Feature::WINDOW_OCCLUSION); + bool enabled = + StaticPrefs::widget_windows_window_occlusion_tracking_enabled(); + + printf_stderr("Dynamically enable window occlusion %d\n", enabled); + + // Update feature before calling WinUtils::EnableWindowOcclusion() + if (enabled) { + feature.UserEnable("User enabled by pref"); + } else { + feature.UserDisable("User disabled via pref", + "FEATURE_FAILURE_PREF_DISABLED"_ns); + } + widget::WinUtils::EnableWindowOcclusion(enabled); +} +#endif + +void gfxPlatform::InitWindowOcclusionConfig() { + if (!XRE_IsParentProcess()) { + return; + } +#ifdef XP_WIN + FeatureState& feature = gfxConfig::GetFeature(Feature::WINDOW_OCCLUSION); + feature.SetDefaultFromPref( + StaticPrefs:: + GetPrefName_widget_windows_window_occlusion_tracking_enabled(), + true, + StaticPrefs:: + GetPrefDefault_widget_windows_window_occlusion_tracking_enabled()); + + const char* env = PR_GetEnv("MOZ_WINDOW_OCCLUSION"); + if (env) { + if (*env == '1') { + feature.UserForceEnable("Force enabled by envvar"); + } else { + feature.UserDisable("Force disabled by envvar", + "FEATURE_FAILURE_OCCL_ENV"_ns); + } + } + + Preferences::RegisterCallback( + WindowOcclusionPrefChangeCallback, + nsDependentCString( + StaticPrefs:: + GetPrefName_widget_windows_window_occlusion_tracking_enabled())); +#endif +} + +static void BackdropFilterPrefChangeCallback(const char*, void*) { + FeatureState& feature = gfxConfig::GetFeature(Feature::BACKDROP_FILTER); + + // We need to reset because the user status needs to be set before the + // environment status, but the environment status comes from the blocklist, + // and the user status can be updated after the fact. + feature.Reset(); + feature.EnableByDefault(); + + if (StaticPrefs::layout_css_backdrop_filter_force_enabled()) { + feature.UserForceEnable("Force enabled by pref"); + } + + nsCString message; + nsCString failureId; + if (!gfxPlatform::IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_BACKDROP_FILTER, + &message, failureId)) { + feature.Disable(FeatureStatus::Blocklisted, message.get(), failureId); + } + + // This may still be gated by the layout.css.backdrop-filter.enabled pref but + // the test infrastructure is very sensitive to how changes to that pref + // propagate, so we don't include them in the gfxVars/gfxFeature. + gfxVars::SetAllowBackdropFilter(feature.IsEnabled()); +} + +void gfxPlatform::InitBackdropFilterConfig() { + // This would ideally be in the nsCSSProps code + // but nsCSSProps is initialized before gfxPlatform + // so it has to be done here. + gfxVars::AddReceiver(&nsCSSProps::GfxVarReceiver()); + + if (!XRE_IsParentProcess()) { + // gfxVars doesn't notify receivers when initialized on content processes + // we need to explicitly recompute backdrop-filter's enabled state here. + nsCSSProps::RecomputeEnabledState( + StaticPrefs::GetPrefName_layout_css_backdrop_filter_enabled()); + return; + } + + BackdropFilterPrefChangeCallback(nullptr, nullptr); + + Preferences::RegisterCallback( + BackdropFilterPrefChangeCallback, + nsDependentCString( + StaticPrefs::GetPrefName_layout_css_backdrop_filter_force_enabled())); +} + +static void AcceleratedCanvas2DPrefChangeCallback(const char*, void*) { + FeatureState& feature = gfxConfig::GetFeature(Feature::ACCELERATED_CANVAS2D); + + // Reset to track toggling prefs and ensure force-enable does not happen + // after blocklist. + feature.Reset(); + + // gfx.canvas.accelerated pref controls whether platform enables the feature, + // but it still allows blocklisting to override it later. + feature.SetDefaultFromPref( + StaticPrefs::GetPrefName_gfx_canvas_accelerated(), true, + StaticPrefs::GetPrefDefault_gfx_canvas_accelerated()); + + // gfx.canvas.accelerated.force-enabled overrides the blocklist. + if (StaticPrefs::gfx_canvas_accelerated_force_enabled()) { + feature.UserForceEnable("Force-enabled by pref"); + } + + if (kIsAndroid && !gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { + feature.Disable(FeatureStatus::Blocked, "Disabled by GPU Process disabled", + "FEATURE_FAILURE_DISABLED_BY_GPU_PROCESS_DISABLED"_ns); + } else if (!gfxConfig::IsEnabled(Feature::WEBRENDER)) { + // There isn't much benefit to accelerating Canvas2D if we can't accelerate + // WebRender itself. + feature.Disable(FeatureStatus::Blocked, "Disabled by Software WebRender", + "FEATURE_FAILURE_DISABLED_BY_SOFTWARE_WEBRENDER"_ns); + } + + // Check if blocklisted despite the default pref. + nsCString message; + nsCString failureId; + if (!gfxPlatform::IsGfxInfoStatusOkay( + nsIGfxInfo::FEATURE_ACCELERATED_CANVAS2D, &message, failureId)) { + feature.Disable(FeatureStatus::Blocklisted, message.get(), failureId); + } + + if (StaticPrefs::gfx_canvas_remote_worker_threads_AtStartup() != 0) { + feature.ForceDisable(FeatureStatus::Failed, + "Disabled with non-zero canvas worker threads", + "FEATURE_FAILURE_DISABLE_BY_CANVAS_WORKER_THREADS"_ns); + } + + gfxVars::SetUseAcceleratedCanvas2D(feature.IsEnabled()); +} + +void gfxPlatform::InitAcceleratedCanvas2DConfig() { + if (!XRE_IsParentProcess()) { + return; + } + + // Decide during pref changes whether or not to enable acceleration. This + // allows easily toggling acceleration on and off to test performance. + AcceleratedCanvas2DPrefChangeCallback(nullptr, nullptr); + + Preferences::RegisterCallback( + AcceleratedCanvas2DPrefChangeCallback, + nsDependentCString(StaticPrefs::GetPrefName_gfx_canvas_accelerated())); + Preferences::RegisterCallback( + AcceleratedCanvas2DPrefChangeCallback, + nsDependentCString( + StaticPrefs::GetPrefName_gfx_canvas_accelerated_force_enabled())); +} + +bool gfxPlatform::CanUseHardwareVideoDecoding() { + // this function is called from the compositor thread, so it is not + // safe to init the prefs etc. from here. + MOZ_ASSERT(sLayersAccelerationPrefsInitialized); + return sLayersSupportsHardwareVideoDecoding && + !sLayersHardwareVideoDecodingFailed; +} + +bool gfxPlatform::AccelerateLayersByDefault() { +#if defined(MOZ_GL_PROVIDER) || defined(MOZ_WIDGET_UIKIT) + return true; +#else + return false; +#endif +} + +/* static */ +bool gfxPlatform::UsesOffMainThreadCompositing() { + if (XRE_GetProcessType() == GeckoProcessType_GPU) { + return true; + } + + static bool firstTime = true; + static bool result = false; + + if (firstTime) { + MOZ_ASSERT(sLayersAccelerationPrefsInitialized); + result = gfxVars::BrowserTabsRemoteAutostart() || + !StaticPrefs:: + layers_offmainthreadcomposition_force_disabled_AtStartup(); +#if defined(MOZ_WIDGET_GTK) + // Linux users who chose OpenGL are being included in OMTC + result |= StaticPrefs:: + layers_acceleration_force_enabled_AtStartup_DoNotUseDirectly(); + +#endif + firstTime = false; + } + + return result; +} + +RefPtr<mozilla::VsyncDispatcher> gfxPlatform::GetGlobalVsyncDispatcher() { + MOZ_ASSERT(mVsyncDispatcher, + "mVsyncDispatcher should have been initialized by ReInitFrameRate " + "during gfxPlatform init"); + MOZ_ASSERT(XRE_IsParentProcess()); + return mVsyncDispatcher; +} + +already_AddRefed<mozilla::gfx::VsyncSource> +gfxPlatform::GetGlobalHardwareVsyncSource() { + if (!mGlobalHardwareVsyncSource) { + mGlobalHardwareVsyncSource = CreateGlobalHardwareVsyncSource(); + } + return do_AddRef(mGlobalHardwareVsyncSource); +} + +/*** + * The preference "layout.frame_rate" has 3 meanings depending on the value: + * + * -1 = Auto (default), use hardware vsync or software vsync @ 60 hz if hw + * vsync fails. + * 0 = ASAP mode - used during talos testing. + * X = Software vsync at a rate of X times per second. + */ +already_AddRefed<mozilla::gfx::VsyncSource> +gfxPlatform::GetSoftwareVsyncSource() { + if (!mSoftwareVsyncSource) { + double rateInMS = 1000.0 / (double)gfxPlatform::GetSoftwareVsyncRate(); + mSoftwareVsyncSource = new mozilla::gfx::SoftwareVsyncSource( + TimeDuration::FromMilliseconds(rateInMS)); + } + return do_AddRef(mSoftwareVsyncSource); +} + +/* static */ +bool gfxPlatform::IsInLayoutAsapMode() { + // There are 2 modes of ASAP mode. + // 1 is that the refresh driver and compositor are in lock step + // the second is that the compositor goes ASAP and the refresh driver + // goes at whatever the configurated rate is. This only checks the version + // talos uses, which is the refresh driver and compositor are in lockstep. + // Ignore privacy_resistFingerprinting to preserve ASAP mode there. + return StaticPrefs::layout_frame_rate() == 0; +} + +static int LayoutFrameRateFromPrefs() { + auto val = StaticPrefs::layout_frame_rate(); + if (nsContentUtils::ShouldResistFingerprinting( + "The frame rate is a global property.", RFPTarget::FrameRate)) { + val = 60; + } + return val; +} + +/* static */ +bool gfxPlatform::ForceSoftwareVsync() { + return LayoutFrameRateFromPrefs() > 0; +} + +/* static */ +int gfxPlatform::GetSoftwareVsyncRate() { + int preferenceRate = LayoutFrameRateFromPrefs(); + if (preferenceRate <= 0) { + return gfxPlatform::GetDefaultFrameRate(); + } + return preferenceRate; +} + +/* static */ +int gfxPlatform::GetDefaultFrameRate() { return 60; } + +/* static */ +void gfxPlatform::ReInitFrameRate(const char* aPrefIgnored, + void* aDataIgnored) { + MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); + + if (gPlatform->mSoftwareVsyncSource) { + // Update the rate of the existing software vsync source. + double rateInMS = 1000.0 / (double)gfxPlatform::GetSoftwareVsyncRate(); + gPlatform->mSoftwareVsyncSource->SetVsyncRate( + TimeDuration::FromMilliseconds(rateInMS)); + } + + // Swap out the dispatcher's underlying source. + RefPtr<VsyncSource> vsyncSource = + gfxPlatform::ForceSoftwareVsync() + ? gPlatform->GetSoftwareVsyncSource() + : gPlatform->GetGlobalHardwareVsyncSource(); + gPlatform->mVsyncDispatcher->SetVsyncSource(vsyncSource); +} + +const char* gfxPlatform::GetAzureCanvasBackend() const { + BackendType backend{}; + + if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { + // Assume content process' backend prefs. + BackendPrefsData data = GetBackendPrefs(); + backend = GetCanvasBackendPref(data.mCanvasBitmask); + if (backend == BackendType::NONE) { + backend = data.mCanvasDefault; + } + } else { + backend = mPreferredCanvasBackend; + } + + return GetBackendName(backend); +} + +const char* gfxPlatform::GetAzureContentBackend() const { + BackendType backend{}; + + if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { + // Assume content process' backend prefs. + BackendPrefsData data = GetBackendPrefs(); + backend = GetContentBackendPref(data.mContentBitmask); + if (backend == BackendType::NONE) { + backend = data.mContentDefault; + } + } else { + backend = mContentBackend; + } + + return GetBackendName(backend); +} + +void gfxPlatform::GetAzureBackendInfo(mozilla::widget::InfoObject& aObj) { + if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { + aObj.DefineProperty("AzureCanvasBackend (UI Process)", + GetBackendName(mPreferredCanvasBackend)); + aObj.DefineProperty("AzureFallbackCanvasBackend (UI Process)", + GetBackendName(mFallbackCanvasBackend)); + aObj.DefineProperty("AzureContentBackend (UI Process)", + GetBackendName(mContentBackend)); + } else { + aObj.DefineProperty("AzureFallbackCanvasBackend", + GetBackendName(mFallbackCanvasBackend)); + } + + aObj.DefineProperty("AzureCanvasBackend", GetAzureCanvasBackend()); + aObj.DefineProperty("AzureContentBackend", GetAzureContentBackend()); +} + +void gfxPlatform::GetApzSupportInfo(mozilla::widget::InfoObject& aObj) { + if (!gfxPlatform::AsyncPanZoomEnabled()) { + return; + } + + if (SupportsApzWheelInput()) { + aObj.DefineProperty("ApzWheelInput", 1); + } + + if (SupportsApzTouchInput()) { + aObj.DefineProperty("ApzTouchInput", 1); + } + + if (SupportsApzDragInput()) { + aObj.DefineProperty("ApzDragInput", 1); + } + + if (SupportsApzKeyboardInput() && + !StaticPrefs::accessibility_browsewithcaret()) { + aObj.DefineProperty("ApzKeyboardInput", 1); + } + + if (SupportsApzAutoscrolling()) { + aObj.DefineProperty("ApzAutoscrollInput", 1); + } + + if (SupportsApzZooming()) { + aObj.DefineProperty("ApzZoomingInput", 1); + } +} + +void gfxPlatform::GetFrameStats(mozilla::widget::InfoObject& aObj) { + uint32_t i = 0; + for (FrameStats& f : mFrameStats) { + nsPrintfCString name("Slow Frame #%02u", ++i); + + nsPrintfCString value( + "Frame %" PRIu64 + "(%s) CONTENT_FRAME_TIME %d - Transaction start %f, main-thread time " + "%f, full paint time %f, Skipped composites %u, Composite start %f, " + "Resource upload time %f, GPU cache upload time %f, Render time %f, " + "Composite time %f", + f.id().mId, f.url().get(), f.contentFrameTime(), + (f.transactionStart() - f.refreshStart()).ToMilliseconds(), + (f.fwdTime() - f.transactionStart()).ToMilliseconds(), + f.sceneBuiltTime() + ? (f.sceneBuiltTime() - f.transactionStart()).ToMilliseconds() + : 0.0, + f.skippedComposites(), + (f.compositeStart() - f.refreshStart()).ToMilliseconds(), + f.resourceUploadTime(), f.gpuCacheUploadTime(), + (f.compositeEnd() - f.renderStart()).ToMilliseconds(), + (f.compositeEnd() - f.compositeStart()).ToMilliseconds()); + aObj.DefineProperty(name.get(), value.get()); + } +} + +void gfxPlatform::GetCMSSupportInfo(mozilla::widget::InfoObject& aObj) { + nsTArray<uint8_t> outputProfileData = + gfxPlatform::GetPlatform()->GetPlatformCMSOutputProfileData(); + if (outputProfileData.IsEmpty()) { + nsPrintfCString msg("Empty profile data"); + aObj.DefineProperty("CMSOutputProfile", msg.get()); + return; + } + + // Some profiles can be quite large. We don't want to include giant profiles + // by default in about:support. For now, we only accept less than 8kiB. + const size_t kMaxProfileSize = 8192; + if (outputProfileData.Length() >= kMaxProfileSize) { + nsPrintfCString msg("%zu bytes, too large", outputProfileData.Length()); + aObj.DefineProperty("CMSOutputProfile", msg.get()); + return; + } + + nsString encodedProfile; + nsresult rv = + Base64Encode(reinterpret_cast<const char*>(outputProfileData.Elements()), + outputProfileData.Length(), encodedProfile); + if (!NS_SUCCEEDED(rv)) { + nsPrintfCString msg("base64 encode failed 0x%08x", + static_cast<uint32_t>(rv)); + aObj.DefineProperty("CMSOutputProfile", msg.get()); + return; + } + + aObj.DefineProperty("CMSOutputProfile", encodedProfile); +} + +void gfxPlatform::GetDisplayInfo(mozilla::widget::InfoObject& aObj) { + auto& screens = widget::ScreenManager::GetSingleton().CurrentScreenList(); + aObj.DefineProperty("DisplayCount", screens.Length()); + + size_t i = 0; + for (auto& screen : screens) { + const LayoutDeviceIntRect rect = screen->GetRect(); + nsPrintfCString value("%dx%d@%dHz scales:%f|%f", rect.width, rect.height, + screen->GetRefreshRate(), + screen->GetContentsScaleFactor(), + screen->GetDefaultCSSScaleFactor()); + + aObj.DefineProperty(nsPrintfCString("Display%zu", i++).get(), + NS_ConvertUTF8toUTF16(value)); + } + + // Platform display info is only currently used for about:support and getting + // it might fail in a child process anyway. + if (XRE_IsParentProcess()) { + GetPlatformDisplayInfo(aObj); + } +} + +void gfxPlatform::GetOverlayInfo(mozilla::widget::InfoObject& aObj) { + if (mOverlayInfo.isNothing()) { + return; + } + + auto toString = [](mozilla::layers::OverlaySupportType aType) -> const char* { + switch (aType) { + case mozilla::layers::OverlaySupportType::None: + return "None"; + case mozilla::layers::OverlaySupportType::Software: + return "Software"; + case mozilla::layers::OverlaySupportType::Direct: + return "Direct"; + case mozilla::layers::OverlaySupportType::Scaling: + return "Scaling"; + default: + MOZ_ASSERT_UNREACHABLE("Unexpected to be called"); + } + MOZ_CRASH("Incomplete switch"); + }; + + nsPrintfCString value("NV12=%s YUV2=%s BGRA8=%s RGB10A2=%s", + toString(mOverlayInfo.ref().mNv12Overlay), + toString(mOverlayInfo.ref().mYuy2Overlay), + toString(mOverlayInfo.ref().mBgra8Overlay), + toString(mOverlayInfo.ref().mRgb10a2Overlay)); + + aObj.DefineProperty("OverlaySupport", NS_ConvertUTF8toUTF16(value)); +} + +void gfxPlatform::GetSwapChainInfo(mozilla::widget::InfoObject& aObj) { + if (mSwapChainInfo.isNothing()) { + return; + } + + auto toString = [](bool aTearingSupported) -> const char* { + if (aTearingSupported) { + return "Supported"; + } + return "Not Supported"; + }; + + nsPrintfCString value("%s", toString(mSwapChainInfo.ref().mTearingSupported)); + + aObj.DefineProperty("SwapChainTearingSupport", NS_ConvertUTF8toUTF16(value)); +} + +class FrameStatsComparator { + public: + bool Equals(const FrameStats& aA, const FrameStats& aB) const { + return aA.contentFrameTime() == aB.contentFrameTime(); + } + // Reverse the condition here since we want the array sorted largest to + // smallest. + bool LessThan(const FrameStats& aA, const FrameStats& aB) const { + return aA.contentFrameTime() > aB.contentFrameTime(); + } +}; + +void gfxPlatform::NotifyFrameStats(nsTArray<FrameStats>&& aFrameStats) { + if (!StaticPrefs::gfx_logging_slow_frames_enabled_AtStartup()) { + return; + } + + FrameStatsComparator comp; + for (FrameStats& f : aFrameStats) { + mFrameStats.InsertElementSorted(f, comp); + } + if (mFrameStats.Length() > 10) { + mFrameStats.SetLength(10); + } +} + +/*static*/ +uint32_t gfxPlatform::TargetFrameRate() { + if (gPlatform && gPlatform->mVsyncDispatcher) { + return round(1000.0 / + gPlatform->mVsyncDispatcher->GetVsyncRate().ToMilliseconds()); + } + return 0; +} + +/* static */ +bool gfxPlatform::UseDesktopZoomingScrollbars() { + return StaticPrefs::apz_allow_zooming() && + !StaticPrefs::apz_force_disable_desktop_zooming_scrollbars(); +} + +/*static*/ +bool gfxPlatform::AsyncPanZoomEnabled() { +#if !defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_WIDGET_UIKIT) + // For XUL applications (everything but Firefox on Android) + // we only want to use APZ when E10S is enabled. If + // we ever get input events off the main thread we can consider relaxing + // this requirement. + if (!BrowserTabsRemoteAutostart()) { + return false; + } +#endif +#ifdef MOZ_WIDGET_ANDROID + return true; +#else + // If Fission is enabled, OOP iframes require APZ for hittest. So, we + // need to forcibly enable APZ in that case for avoiding users confused. + if (FissionAutostart()) { + return true; + } + return StaticPrefs:: + layers_async_pan_zoom_enabled_AtStartup_DoNotUseDirectly(); +#endif +} + +/*static*/ +bool gfxPlatform::PerfWarnings() { + return StaticPrefs::gfx_perf_warnings_enabled(); +} + +void gfxPlatform::NotifyCompositorCreated(LayersBackend aBackend) { + if (mCompositorBackend == aBackend) { + return; + } + + if (mCompositorBackend != LayersBackend::LAYERS_NONE) { + gfxCriticalNote << "Compositors might be mixed (" << int(mCompositorBackend) + << "," << int(aBackend) << ")"; + } + + // Set the backend before we notify so it's available immediately. + mCompositorBackend = aBackend; + + if (XRE_IsParentProcess()) { + nsDependentCString compositor(GetLayersBackendName(mCompositorBackend)); + mozilla::glean::gfx_status::compositor.Set(compositor); + + nsCString geckoVersion; + nsCOMPtr<nsIXULAppInfo> app = do_GetService("@mozilla.org/xre/app-info;1"); + if (app) { + app->GetVersion(geckoVersion); + } + mozilla::glean::gfx_status::last_compositor_gecko_version.Set(geckoVersion); + + mozilla::glean::gfx_feature::webrender.Set( + gfxConfig::GetFeature(gfx::Feature::WEBRENDER) + .GetStatusAndFailureIdString()); + } + + // Notify that we created a compositor, so telemetry can update. + NS_DispatchToMainThread( + NS_NewRunnableFunction("gfxPlatform::NotifyCompositorCreated", [] { + if (nsCOMPtr<nsIObserverService> obsvc = + services::GetObserverService()) { + obsvc->NotifyObservers(nullptr, "compositor:created", nullptr); + } + })); +} + +/* static */ +bool gfxPlatform::FallbackFromAcceleration(FeatureStatus aStatus, + const char* aMessage, + const nsACString& aFailureId, + bool aCrashAfterFinalFallback) { + // We always want to ensure (Hardware) WebRender is disabled. + if (gfxConfig::IsEnabled(Feature::WEBRENDER)) { + gfxConfig::GetFeature(Feature::WEBRENDER) + .ForceDisable(aStatus, aMessage, aFailureId); + } + + // Determine whether or not we are allowed to use Software WebRender in + // fallback without the GPU process. Either the pref is false, or the feature + // is enabled and we are currently still using it. + bool swglFallbackAllowed = + !StaticPrefs:: + gfx_webrender_fallback_software_requires_gpu_process_AtStartup() || + gfxConfig::IsEnabled(Feature::GPU_PROCESS); + +#ifdef XP_WIN + // Before we disable D3D11 and HW_COMPOSITING, we should check if we can + // fallback from WebRender to Software WebRender + D3D11 compositing. + if (swglFallbackAllowed && gfxVars::AllowSoftwareWebRenderD3D11() && + gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING) && + !gfxVars::UseSoftwareWebRender()) { + // Fallback to Software WebRender + D3D11 compositing. + gfxCriticalNote << "Fallback WR to SW-WR + D3D11"; + gfxVars::SetUseSoftwareWebRender(true); + return true; + } + + if (swglFallbackAllowed && gfxVars::AllowSoftwareWebRenderD3D11() && + gfxVars::UseSoftwareWebRender()) { + // Fallback from Software WebRender + D3D11 to Software WebRender. + gfxCriticalNote << "Fallback SW-WR + D3D11 to SW-WR"; + gfxVars::SetAllowSoftwareWebRenderD3D11(false); + return true; + } + + // We aren't using Software WebRender + D3D11 compositing, so turn off the + // D3D11 and D2D. + if (gfxConfig::IsEnabled(Feature::DIRECT2D)) { + gfxConfig::GetFeature(Feature::DIRECT2D) + .ForceDisable(aStatus, aMessage, aFailureId); + } + if (gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + gfxConfig::GetFeature(Feature::D3D11_COMPOSITING) + .ForceDisable(aStatus, aMessage, aFailureId); + } +#endif + +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GTK) + // Before we disable OpenGL and HW_COMPOSITING, we should check if we can + // fallback from WebRender to Software WebRender + OpenGL compositing. + if (swglFallbackAllowed && gfxVars::AllowSoftwareWebRenderOGL() && + gfxConfig::IsEnabled(Feature::OPENGL_COMPOSITING) && + !gfxVars::UseSoftwareWebRender()) { + // Fallback to Software WebRender + OpenGL compositing. + gfxCriticalNote << "Fallback WR to SW-WR + OpenGL"; + gfxVars::SetUseSoftwareWebRender(true); + return true; + } +#endif + // Android does not want to fallback to SW-WR. +#ifdef MOZ_WIDGET_GTK + if (swglFallbackAllowed && gfxVars::AllowSoftwareWebRenderOGL() && + gfxVars::UseSoftwareWebRender()) { + // Fallback from Software WebRender + OpenGL to Software WebRender. + gfxCriticalNote << "Fallback SW-WR + OpenGL to SW-WR"; + gfxVars::SetAllowSoftwareWebRenderOGL(false); + return true; + } +#endif + +#ifndef MOZ_WIDGET_ANDROID + // Non-Android wants to fallback to Software WebRender or Basic. Android wants + // to fallback to OpenGL. + if (gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) { + gfxConfig::GetFeature(Feature::HW_COMPOSITING) + .ForceDisable(aStatus, aMessage, aFailureId); + } +#endif + + if (StaticPrefs::gfx_webrender_fallback_software_AtStartup() && + swglFallbackAllowed && !gfxVars::UseSoftwareWebRender()) { + // Fallback from WebRender to Software WebRender. + gfxCriticalNote << "Fallback WR to SW-WR"; + gfxVars::SetUseSoftwareWebRender(true); + return true; + } + + if (!gfxVars::UseSoftwareWebRender()) { + // Software WebRender may be disabled due to a startup issue with the + // blocklist, despite it being our only fallback option based on the prefs. + // If WebRender is unable to be initialized, this means that user would + // otherwise get stuck with WebRender. As such, force a switch to Software + // WebRender in this case. + gfxCriticalNoteOnce << "Fallback WR to SW-WR, forced"; + gfxVars::SetUseSoftwareWebRender(true); + return true; + } + + if (aCrashAfterFinalFallback) { + MOZ_CRASH("Fallback configurations exhausted"); + } + + // Continue using Software WebRender (disabled fallback to Basic). + gfxCriticalNoteOnce << "Fallback remains SW-WR"; + return false; +} + +/* static */ +void gfxPlatform::DisableGPUProcess() { + if (gfxVars::RemoteCanvasEnabled() && + !StaticPrefs::gfx_canvas_remote_allow_in_parent_AtStartup()) { + gfxConfig::Disable( + Feature::REMOTE_CANVAS, FeatureStatus::UnavailableNoGpuProcess, + "Disabled by GPU process disabled", + "FEATURE_REMOTE_CANVAS_DISABLED_BY_GPU_PROCESS_DISABLED"_ns); + gfxVars::SetRemoteCanvasEnabled(false); + } + + if (kIsAndroid) { + // On android, enable out-of-process WebGL only when GPU process exists. + gfxVars::SetAllowWebglOop(false); + // On android, enable accelerated canvas only when GPU process exists. + gfxVars::SetUseAcceleratedCanvas2D(false); + gfxConfig::Disable(Feature::ACCELERATED_CANVAS2D, FeatureStatus::Blocked, + "Disabled by GPU Process disabled", + "FEATURE_FAILURE_DISABLED_BY_GPU_PROCESS_DISABLED"_ns); + } + + RemoteTextureMap::Init(); + // We need to initialize the parent process to prepare for WebRender if we + // did not end up disabling it, despite losing the GPU process. + wr::RenderThread::Start(GPUProcessManager::Get()->AllocateNamespace()); + gfx::CanvasRenderThread::Start(); + image::ImageMemoryReporter::InitForWebRender(); +} + +/* static */ void gfxPlatform::DisableRemoteCanvas() { + if (gfxVars::RemoteCanvasEnabled()) { + gfxConfig::ForceDisable(Feature::REMOTE_CANVAS, FeatureStatus::Failed, + "Disabled by runtime error", + "FEATURE_REMOTE_CANVAS_RUNTIME_ERROR"_ns); + gfxVars::SetRemoteCanvasEnabled(false); + } + if (gfxVars::UseAcceleratedCanvas2D()) { + gfxConfig::ForceDisable(Feature::ACCELERATED_CANVAS2D, + FeatureStatus::Failed, "Disabled by runtime error", + "FEATURE_ACCELERATED_CANVAS2D_RUNTIME_ERROR"_ns); + gfxVars::SetUseAcceleratedCanvas2D(false); + } +} + +void gfxPlatform::ImportCachedContentDeviceData() { + MOZ_ASSERT(XRE_IsContentProcess()); + + // Import the content device data if we've got some waiting. + if (!gContentDeviceInitData) { + return; + } + + ImportContentDeviceData(*gContentDeviceInitData); + gContentDeviceInitData = nullptr; +} + +void gfxPlatform::ImportContentDeviceData( + const mozilla::gfx::ContentDeviceData& aData) { + MOZ_ASSERT(XRE_IsContentProcess()); + + const DevicePrefs& prefs = aData.prefs(); + gfxConfig::Inherit(Feature::HW_COMPOSITING, prefs.hwCompositing()); + + // We don't inherit Feature::OPENGL_COMPOSITING here, because platforms + // will handle that (without imported data from the parent) in + // InitOpenGLConfig. + + gCMSOutputProfileData = Some(aData.cmsOutputProfileData().Clone()); +} + +void gfxPlatform::BuildContentDeviceData( + mozilla::gfx::ContentDeviceData* aOut) { + MOZ_ASSERT(XRE_IsParentProcess()); + + // Make sure our settings are synchronized from the GPU process. + DebugOnly<nsresult> rv = GPUProcessManager::Get()->EnsureGPUReady(); + MOZ_ASSERT(rv != NS_ERROR_ILLEGAL_DURING_SHUTDOWN); + + aOut->prefs().hwCompositing() = gfxConfig::GetValue(Feature::HW_COMPOSITING); + aOut->prefs().oglCompositing() = + gfxConfig::GetValue(Feature::OPENGL_COMPOSITING); +} + +void gfxPlatform::ImportGPUDeviceData( + const mozilla::gfx::GPUDeviceData& aData) { + MOZ_ASSERT(XRE_IsParentProcess()); + + gfxConfig::ImportChange(Feature::OPENGL_COMPOSITING, aData.oglCompositing()); +} + +bool gfxPlatform::SupportsApzTouchInput() const { + return dom::TouchEvent::PrefEnabled(nullptr); +} + +bool gfxPlatform::SupportsApzDragInput() const { + return StaticPrefs::apz_drag_enabled(); +} + +bool gfxPlatform::SupportsApzKeyboardInput() const { + return StaticPrefs::apz_keyboard_enabled_AtStartup(); +} + +bool gfxPlatform::SupportsApzAutoscrolling() const { + return StaticPrefs::apz_autoscroll_enabled(); +} + +bool gfxPlatform::SupportsApzZooming() const { + return StaticPrefs::apz_allow_zooming(); +} + +void gfxPlatform::InitOpenGLConfig() { +#ifdef XP_WIN + // Don't enable by default on Windows, since it could show up in about:support + // even though it'll never get used. Only attempt if user enables the pref + if (!Preferences::GetBool("layers.prefer-opengl")) { + return; + } +#endif + + FeatureState& openGLFeature = + gfxConfig::GetFeature(Feature::OPENGL_COMPOSITING); + + // Check to see hw comp supported + if (!gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) { + openGLFeature.DisableByDefault(FeatureStatus::Unavailable, + "Hardware compositing is disabled", + "FEATURE_FAILURE_OPENGL_NEED_HWCOMP"_ns); + return; + } + +#ifdef XP_WIN + openGLFeature.SetDefaultFromPref( + StaticPrefs::GetPrefName_layers_prefer_opengl(), true, + StaticPrefs::GetPrefDefault_layers_prefer_opengl()); +#else + openGLFeature.EnableByDefault(); +#endif + + // When layers acceleration is force-enabled, enable it even for blocklisted + // devices. + if (StaticPrefs:: + layers_acceleration_force_enabled_AtStartup_DoNotUseDirectly()) { + openGLFeature.UserForceEnable("Force-enabled by pref"); + return; + } + + nsCString message; + nsCString failureId; + if (!IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_OPENGL_LAYERS, &message, + failureId)) { + openGLFeature.Disable(FeatureStatus::Blocklisted, message.get(), failureId); + } +} + +bool gfxPlatform::IsGfxInfoStatusOkay(int32_t aFeature, nsCString* aOutMessage, + nsCString& aFailureId) { + nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); + if (!gfxInfo) { + return true; + } + + int32_t status; + if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(aFeature, aFailureId, &status)) && + status != nsIGfxInfo::FEATURE_STATUS_OK) { + aOutMessage->AssignLiteral("#BLOCKLIST_"); + aOutMessage->AppendASCII(aFailureId.get()); + return false; + } + + return true; +} diff --git a/gfx/thebes/gfxPlatform.h b/gfx/thebes/gfxPlatform.h new file mode 100644 index 0000000000..5af6d77345 --- /dev/null +++ b/gfx/thebes/gfxPlatform.h @@ -0,0 +1,1043 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_PLATFORM_H +#define GFX_PLATFORM_H + +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/intl/UnicodeScriptCodes.h" +#include "nsTArray.h" +#include "nsString.h" +#include "nsCOMPtr.h" + +#include "gfxTelemetry.h" +#include "gfxTypes.h" +#include "gfxSkipChars.h" + +#include "qcms.h" + +#include "mozilla/RefPtr.h" +#include "GfxInfoCollector.h" + +#include "mozilla/Maybe.h" +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/layers/MemoryPressureObserver.h" +#include "mozilla/layers/OverlayInfo.h" + +class gfxASurface; +class gfxFont; +class gfxFontGroup; +struct gfxFontStyle; +class gfxUserFontSet; +class gfxFontEntry; +class gfxPlatformFontList; +class gfxTextRun; +class nsIURI; +class nsAtom; +class nsIObserver; +class nsPresContext; +class SRGBOverrideObserver; +class gfxTextPerfMetrics; +typedef struct FT_LibraryRec_* FT_Library; + +namespace mozilla { +struct StyleFontFamilyList; +class LogModule; +class VsyncDispatcher; +namespace layers { +class FrameStats; +} +namespace gfx { +class DrawTarget; +class SourceSurface; +class DataSourceSurface; +class ScaledFont; +class VsyncSource; +class SoftwareVsyncSource; +class ContentDeviceData; +class GPUDeviceData; +class FeatureState; + +inline uint32_t BackendTypeBit(BackendType b) { return 1 << uint8_t(b); } + +} // namespace gfx +namespace dom { +class SystemFontListEntry; +class SystemFontList; +} // namespace dom +} // namespace mozilla + +#define MOZ_PERFORMANCE_WARNING(module, ...) \ + do { \ + if (gfxPlatform::PerfWarnings()) { \ + printf_stderr("[" module "] " __VA_ARGS__); \ + } \ + } while (0) + +enum class CMSMode : int32_t { + Off = 0, // No color management + All = 1, // Color manage everything + TaggedOnly = 2, // Color manage tagged Images Only + AllCount = 3 +}; + +enum eGfxLog { + // all font enumerations, localized names, fullname/psnames, cmap loads + eGfxLog_fontlist = 0, + // timing info on font initialization + eGfxLog_fontinit = 1, + // dump text runs, font matching, system fallback for content + eGfxLog_textrun = 2, + // dump text runs, font matching, system fallback for chrome + eGfxLog_textrunui = 3, + // dump cmap coverage data as they are loaded + eGfxLog_cmapdata = 4, + // text perf data + eGfxLog_textperf = 5 +}; + +// Used during font matching to express a preference, if any, for whether +// to use a font that will present a color or monochrome glyph. +enum class eFontPresentation : uint8_t { + // Character does not have the emoji property, so no special heuristics + // apply during font selection. + Any = 0, + // Character is potentially emoji, but Text-style presentation has been + // explicitly requested using VS15. + Text = 1, + // Character has Emoji-style presentation by default (but an author- + // provided webfont will be used even if it is not color). + EmojiDefault = 2, + // Character explicitly requires Emoji-style presentation due to VS16 or + // skin-tone codepoint. + EmojiExplicit = 3 +}; + +inline bool PrefersColor(eFontPresentation aPresentation) { + return aPresentation >= eFontPresentation::EmojiDefault; +} + +// when searching through pref langs, max number of pref langs +const uint32_t kMaxLenPrefLangList = 32; + +#define UNINITIALIZED_VALUE (-1) + +inline const char* GetBackendName(mozilla::gfx::BackendType aBackend) { + switch (aBackend) { + case mozilla::gfx::BackendType::DIRECT2D: + return "direct2d"; + case mozilla::gfx::BackendType::CAIRO: + return "cairo"; + case mozilla::gfx::BackendType::SKIA: + return "skia"; + case mozilla::gfx::BackendType::RECORDING: + return "recording"; + case mozilla::gfx::BackendType::DIRECT2D1_1: + return "direct2d 1.1"; + case mozilla::gfx::BackendType::WEBRENDER_TEXT: + return "webrender text"; + case mozilla::gfx::BackendType::NONE: + return "none"; + case mozilla::gfx::BackendType::WEBGL: + return "webgl"; + case mozilla::gfx::BackendType::BACKEND_LAST: + return "invalid"; + } + MOZ_CRASH("Incomplete switch"); +} + +enum class DeviceResetReason { + OK = 0, // No reset. + HUNG, // Windows specific, guilty device reset. + REMOVED, // Windows specific, device removed or driver upgraded. + RESET, // Guilty device reset. + DRIVER_ERROR, // Innocent device reset. + INVALID_CALL, // Windows specific, guilty device reset. + OUT_OF_MEMORY, + FORCED_RESET, // Simulated device reset. + OTHER, // Unrecognized reason for device reset. + D3D9_RESET, // Windows specific, not used. + NVIDIA_VIDEO, // Linux specific, NVIDIA video memory was reset. + UNKNOWN, // GL specific, unknown if guilty or innocent. +}; + +enum class ForcedDeviceResetReason { + OPENSHAREDHANDLE = 0, + COMPOSITOR_UPDATED, +}; + +struct BackendPrefsData { + uint32_t mCanvasBitmask = 0; + mozilla::gfx::BackendType mCanvasDefault = mozilla::gfx::BackendType::NONE; + uint32_t mContentBitmask = 0; + mozilla::gfx::BackendType mContentDefault = mozilla::gfx::BackendType::NONE; +}; + +class gfxPlatform : public mozilla::layers::MemoryPressureListener { + friend class SRGBOverrideObserver; + + public: + typedef mozilla::StretchRange StretchRange; + typedef mozilla::SlantStyleRange SlantStyleRange; + typedef mozilla::WeightRange WeightRange; + typedef mozilla::gfx::sRGBColor sRGBColor; + typedef mozilla::gfx::DeviceColor DeviceColor; + typedef mozilla::gfx::DataSourceSurface DataSourceSurface; + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::IntSize IntSize; + typedef mozilla::gfx::SourceSurface SourceSurface; + typedef mozilla::intl::Script Script; + + /** + * Return a pointer to the current active platform. + * This is a singleton; it contains mostly convenience + * functions to obtain platform-specific objects. + */ + static gfxPlatform* GetPlatform(); + + /** + * Returns whether or not graphics has been initialized yet. This is + * intended for Telemetry where we don't necessarily want to initialize + * graphics just to observe its state. + */ + static bool Initialized(); + + /** + * Shut down Thebes. + * Init() arranges for this to be called at an appropriate time. + */ + static void Shutdown(); + + /** + * Initialize gfxPlatform (if not already done) in a child process, with + * the provided ContentDeviceData. + */ + static void InitChild(const mozilla::gfx::ContentDeviceData& aData); + + static void InitLayersIPC(); + static void ShutdownLayersIPC(); + + /** + * Initialize ScrollMetadata statics. Does not depend on gfxPlatform. + */ + static void InitNullMetadata(); + + static int32_t MaxTextureSize(); + static int32_t MaxAllocSize(); + static void InitMoz2DLogging(); + + static bool IsHeadless(); + + static bool UseRemoteCanvas(); + + static bool IsBackendAccelerated( + const mozilla::gfx::BackendType aBackendType); + + static bool CanMigrateMacGPUs(); + + /** + * Create an offscreen surface of the given dimensions + * and image format. + */ + virtual already_AddRefed<gfxASurface> CreateOffscreenSurface( + const IntSize& aSize, gfxImageFormat aFormat) = 0; + + /** + * Beware that this method may return DrawTargets which are not fully + * supported on the current platform and might fail silently in subtle ways. + * This is a massive potential footgun. You should only use these methods for + * canvas drawing really. Use extreme caution if you use them for content + * where you are not 100% sure we support the DrawTarget we get back. See + * SupportsAzureContentForDrawTarget. + */ + static already_AddRefed<DrawTarget> CreateDrawTargetForSurface( + gfxASurface* aSurface, const mozilla::gfx::IntSize& aSize); + + /* + * Creates a SourceSurface for a gfxASurface. This function does no caching, + * so the caller should cache the gfxASurface if it will be used frequently. + * The returned surface keeps a reference to aTarget, so it is OK to keep the + * surface, even if aTarget changes. + * aTarget should not keep a reference to the returned surface because that + * will cause a cycle. + * + * This function is static so that it can be accessed from outside the main + * process. + * + * aIsPlugin is used to tell the backend that they can optimize this surface + * specifically because it's used for a plugin. This is mostly for Skia. + */ + static already_AddRefed<SourceSurface> GetSourceSurfaceForSurface( + RefPtr<mozilla::gfx::DrawTarget> aTarget, gfxASurface* aSurface, + bool aIsPlugin = false); + + static void ClearSourceSurfaceForSurface(gfxASurface* aSurface); + + static already_AddRefed<DataSourceSurface> GetWrappedDataSourceSurface( + gfxASurface* aSurface); + + already_AddRefed<DrawTarget> CreateOffscreenContentDrawTarget( + const mozilla::gfx::IntSize& aSize, mozilla::gfx::SurfaceFormat aFormat, + bool aFallback = false); + + already_AddRefed<DrawTarget> CreateOffscreenCanvasDrawTarget( + const mozilla::gfx::IntSize& aSize, mozilla::gfx::SurfaceFormat aFormat); + + already_AddRefed<DrawTarget> CreateSimilarSoftwareDrawTarget( + DrawTarget* aDT, const IntSize& aSize, + mozilla::gfx::SurfaceFormat aFormat); + + static already_AddRefed<DrawTarget> CreateDrawTargetForData( + unsigned char* aData, const mozilla::gfx::IntSize& aSize, int32_t aStride, + mozilla::gfx::SurfaceFormat aFormat, bool aUninitialized = false); + + /** + * Returns true if we should use Azure to render content with aTarget. For + * example, it is possible that we are using Direct2D for rendering and thus + * using Azure. But we want to render to a CairoDrawTarget, in which case + * SupportsAzureContent will return true but SupportsAzureContentForDrawTarget + * will return false. + */ + bool SupportsAzureContentForDrawTarget(mozilla::gfx::DrawTarget* aTarget); + + bool SupportsAzureContentForType(mozilla::gfx::BackendType aType) { + return BackendTypeBit(aType) & mContentBackendBitmask; + } + + static bool AsyncPanZoomEnabled(); + + const char* GetAzureCanvasBackend() const; + const char* GetAzureContentBackend() const; + + void GetAzureBackendInfo(mozilla::widget::InfoObject& aObj); + void GetApzSupportInfo(mozilla::widget::InfoObject& aObj); + void GetFrameStats(mozilla::widget::InfoObject& aObj); + void GetCMSSupportInfo(mozilla::widget::InfoObject& aObj); + void GetDisplayInfo(mozilla::widget::InfoObject& aObj); + void GetOverlayInfo(mozilla::widget::InfoObject& aObj); + void GetSwapChainInfo(mozilla::widget::InfoObject& aObj); + + // Get the default content backend that will be used with the default + // compositor. If the compositor is known when calling this function, + // GetContentBackendFor() should be called instead. + mozilla::gfx::BackendType GetDefaultContentBackend() const { + return mContentBackend; + } + + /// Return the software backend to use by default. + mozilla::gfx::BackendType GetSoftwareBackend() { return mSoftwareBackend; } + + // Return the best content backend available that is compatible with the + // given layers backend. + virtual mozilla::gfx::BackendType GetContentBackendFor( + mozilla::layers::LayersBackend aLayers) { + return mContentBackend; + } + + virtual mozilla::gfx::BackendType GetPreferredCanvasBackend() { + return mPreferredCanvasBackend; + } + mozilla::gfx::BackendType GetFallbackCanvasBackend() { + return mFallbackCanvasBackend; + } + + /* + * Font bits + */ + + /** + * Fill aListOfFonts with the results of querying the list of font names + * that correspond to the given language group or generic font family + * (or both, or neither). + */ + virtual nsresult GetFontList(nsAtom* aLangGroup, + const nsACString& aGenericFamily, + nsTArray<nsString>& aListOfFonts); + + /** + * Fill aFontList with a list of SystemFontListEntry records for the + * available fonts on the platform; used to pass the list from chrome to + * content process. Currently implemented only on MacOSX and Linux. + */ + virtual void ReadSystemFontList(mozilla::dom::SystemFontList*){}; + + /** + * Rebuilds the system font lists (if aFullRebuild is true), or just notifies + * content that the list has changed but existing memory mappings are still + * valid (aFullRebuild is false). + */ + nsresult UpdateFontList(bool aFullRebuild = true); + + /** + * Create the platform font-list object (gfxPlatformFontList concrete + * subclass). This function is responsible to create the appropriate subclass + * of gfxPlatformFontList *and* to call its InitFontList() method. + */ + virtual bool CreatePlatformFontList() = 0; + + /** + * Resolving a font name to family name. The result MUST be in the result of + * GetFontList(). If the name doesn't in the system, aFamilyName will be empty + * string, but not failed. + */ + void GetStandardFamilyName(const nsCString& aFontName, + nsACString& aFamilyName); + + /** + * Returns default font name (localized family name) for aLangGroup and + * aGenericFamily. The result is typically the first font in + * font.name-list.<aGenericFamily>.<aLangGroup>. However, if it's not + * available in the system, this may return second or later font in the + * pref. If there are no available fonts in the pref, returns empty string. + */ + nsAutoCString GetDefaultFontName(const nsACString& aLangGroup, + const nsACString& aGenericFamily); + + /** + * Look up a local platform font using the full font face name. + * (Needed to support @font-face src local().) + * Ownership of the returned gfxFontEntry is passed to the caller, + * who must either AddRef() or delete. + */ + gfxFontEntry* LookupLocalFont(nsPresContext* aPresContext, + const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry); + + /** + * Activate a platform font. (Needed to support @font-face src url().) + * aFontData is a NS_Malloc'ed block that must be freed by this function + * (or responsibility passed on) when it is no longer needed; the caller + * will NOT free it. + * Ownership of the returned gfxFontEntry is passed to the caller, + * who must either AddRef() or delete. + */ + gfxFontEntry* MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, uint32_t aLength); + + /** + * Whether to allow downloadable fonts via @font-face rules + */ + bool DownloadableFontsEnabled(); + + /** + * True when hinting should be enabled. This setting shouldn't + * change per gecko process, while the process is live. If so the + * results are not defined. + * + * NB: this bit is only honored by the FT2 backend, currently. + */ + virtual bool FontHintingEnabled() { return true; } + + /** + * True when zooming should not require reflow, so glyph metrics and + * positioning should not be adjusted for device pixels. + * If this is TRUE, then FontHintingEnabled() should be FALSE, + * but the converse is not necessarily required; + * + * Like FontHintingEnabled (above), this setting shouldn't + * change per gecko process, while the process is live. If so the + * results are not defined. + * + * NB: this bit is only honored by the FT2 backend, currently. + */ + virtual bool RequiresLinearZoom() { return false; } + + /** + * Whether the frame->StyleFont().mFont.smoothing field is respected by + * text rendering on this platform. + */ + virtual bool RespectsFontStyleSmoothing() const { return false; } + + /** + * Whether to check all font cmaps during system font fallback + */ + bool UseCmapsDuringSystemFallback(); + + /** + * Whether to render SVG glyphs within an OpenType font wrapper + */ + bool OpenTypeSVGEnabled(); + + /** + * Max character length of words in the word cache + */ + uint32_t WordCacheCharLimit(); + + /** + * Max number of entries in word cache + */ + uint32_t WordCacheMaxEntries(); + + /** + * Whether to use the SIL Graphite rendering engine + * (for fonts that include Graphite tables) + */ + bool UseGraphiteShaping(); + + // Check whether format is supported on a platform (if unclear, returns true). + // Default implementation checks for "common" formats that we support across + // all platforms, but individual platform implementations may override. + virtual bool IsFontFormatSupported( + mozilla::StyleFontFaceSourceFormatKeyword aFormatHint, + mozilla::StyleFontFaceSourceTechFlags aTechFlags); + + bool IsKnownIconFontFamily(const nsAtom* aFamilyName) const; + + virtual bool DidRenderingDeviceReset( + DeviceResetReason* aResetReason = nullptr) { + return false; + } + + // returns a list of commonly used fonts for a given character + // these are *possible* matches, no cmap-checking is done at this level + virtual void GetCommonFallbackFonts(uint32_t /*aCh*/, Script /*aRunScript*/, + eFontPresentation /*aPresentation*/, + nsTArray<const char*>& /*aFontList*/) { + // platform-specific override, by default do nothing + } + + // Are we in safe mode? + static bool InSafeMode(); + + static bool OffMainThreadCompositingEnabled(); + + void UpdateCanUseHardwareVideoDecoding(); + + /** + * Are we going to try color management? + */ + static CMSMode GetCMSMode() { + EnsureCMSInitialized(); + return gCMSMode; + } + + /** + * Used only for testing. Override the pref setting. + */ + static void SetCMSModeOverride(CMSMode aMode); + + /** + * Determines the rendering intent for color management. + * + * If the value in the pref gfx.color_management.rendering_intent is a + * valid rendering intent as defined in gfx/qcms/qcms.h, that + * value is returned. Otherwise, -1 is returned and the embedded intent + * should be used. + * + * See bug 444014 for details. + */ + static int GetRenderingIntent(); + + /** + * Convert a pixel using a cms transform in an endian-aware manner. + */ + static DeviceColor TransformPixel(const sRGBColor& in, + qcms_transform* transform); + + /** + * Return the output device ICC profile. + */ + static qcms_profile* GetCMSOutputProfile() { + EnsureCMSInitialized(); + return gCMSOutputProfile; + } + + /** + * Return the sRGB ICC profile. + */ + static qcms_profile* GetCMSsRGBProfile() { + EnsureCMSInitialized(); + return gCMSsRGBProfile; + } + + /** + * Return sRGB -> output device transform. + */ + static qcms_transform* GetCMSRGBTransform() { + EnsureCMSInitialized(); + return gCMSRGBTransform; + } + + /** + * Return output -> sRGB device transform. + */ + static qcms_transform* GetCMSInverseRGBTransform() { + MOZ_ASSERT(gCMSInitialized); + return gCMSInverseRGBTransform; + } + + /** + * Return sRGBA -> output device transform. + */ + static qcms_transform* GetCMSRGBATransform() { + MOZ_ASSERT(gCMSInitialized); + return gCMSRGBATransform; + } + + /** + * Return sBGRA -> output device transform. + */ + static qcms_transform* GetCMSBGRATransform() { + MOZ_ASSERT(gCMSInitialized); + return gCMSBGRATransform; + } + + /** + * Return OS RGBA -> output device transform. + */ + static qcms_transform* GetCMSOSRGBATransform(); + + /** + * Return OS RGBA QCMS type. + */ + static qcms_data_type GetCMSOSRGBAType(); + + virtual void FontsPrefsChanged(const char* aPref); + + uint32_t GetBidiNumeralOption(); + + /** + * Force all presContexts to reflow (and reframe if needed). + * + * This is used when something about platform settings changes that might have + * an effect on layout, such as font rendering settings that influence + * metrics, or installed fonts. + * + * By default it also broadcast it to child processes, but some callers might + * not need it if they implement their own notification. + */ + enum class NeedsReframe : bool { No, Yes }; + enum class BroadcastToChildren : bool { No, Yes }; + static void ForceGlobalReflow(NeedsReframe, + BroadcastToChildren = BroadcastToChildren::Yes); + + static void FlushFontAndWordCaches(); + + /** + * Returns a 1x1 DrawTarget that can be used for measuring text etc. as + * it would measure if rendered on-screen. Guaranteed to return a + * non-null and valid DrawTarget. + */ + RefPtr<mozilla::gfx::DrawTarget> ScreenReferenceDrawTarget(); + + static RefPtr<mozilla::gfx::DrawTarget> + ThreadLocalScreenReferenceDrawTarget(); + + virtual mozilla::gfx::SurfaceFormat Optimal2DFormatForContent( + gfxContentType aContent); + + virtual gfxImageFormat OptimalFormatForContent(gfxContentType aContent); + + virtual gfxImageFormat GetOffscreenFormat() { + return mozilla::gfx::SurfaceFormat::X8R8G8B8_UINT32; + } + + /** + * Returns a logger if one is available and logging is enabled + */ + static mozilla::LogModule* GetLog(eGfxLog aWhichLog); + + int GetScreenDepth() const { return mScreenDepth; } + mozilla::gfx::IntSize GetScreenSize() const { return mScreenSize; } + + static void PurgeSkiaFontCache(); + + static bool UsesOffMainThreadCompositing(); + + /** + * Returns the global vsync dispatcher. There is only one global vsync + * dispatcher and it stays around for the entire lifetime of the process. + * Must only be called in the parent process. + */ + RefPtr<mozilla::VsyncDispatcher> GetGlobalVsyncDispatcher(); + + /** + * True if layout rendering should use ASAP mode, which means + * the refresh driver and compositor should render ASAP. + * Used for talos testing purposes + */ + static bool IsInLayoutAsapMode(); + + /** + * Returns whether or not a custom vsync rate is set. + */ + static bool ForceSoftwareVsync(); + + /** + * Returns the software vsync rate to use. + */ + static int GetSoftwareVsyncRate(); + + /** + * Returns the default frame rate for the refresh driver / software vsync. + */ + static int GetDefaultFrameRate(); + + /** + * Update the frame rate (called e.g. after pref changes). + */ + static void ReInitFrameRate(const char* aPrefIgnored, void* aDataIgnored); + + /** + * Update force subpixel AA quality setting (called after pref + * changes). + */ + void UpdateForceSubpixelAAWherePossible(); + + /** + * Used to test which input types are handled via APZ. + */ + virtual bool SupportsApzWheelInput() const { return false; } + bool SupportsApzTouchInput() const; + bool SupportsApzDragInput() const; + bool SupportsApzKeyboardInput() const; + bool SupportsApzAutoscrolling() const; + bool SupportsApzZooming() const; + + // If a device reset has occurred, schedule any necessary paints in the + // widget. This should only be used within nsRefreshDriver. + virtual void SchedulePaintIfDeviceReset() {} + + /** + * Helper method, creates a draw target for a specific Azure backend. + * Used by CreateOffscreenDrawTarget. + */ + already_AddRefed<DrawTarget> CreateDrawTargetForBackend( + mozilla::gfx::BackendType aBackend, const mozilla::gfx::IntSize& aSize, + mozilla::gfx::SurfaceFormat aFormat); + + /** + * Wrapper around StaticPrefs::gfx_perf_warnings_enabled(). + * Extracted into a function to avoid including StaticPrefs_gfx.h from this + * file. + */ + static bool PerfWarnings(); + + static void DisableGPUProcess(); + + void NotifyCompositorCreated(mozilla::layers::LayersBackend aBackend); + mozilla::layers::LayersBackend GetCompositorBackend() const { + return mCompositorBackend; + } + + virtual void CompositorUpdated() {} + + // Plugin async drawing support. + virtual bool SupportsPluginDirectBitmapDrawing() { return false; } + + // Some platforms don't support CompositorOGL in an unaccelerated OpenGL + // context. These platforms should return true here. + virtual bool RequiresAcceleratedGLContextForCompositorOGL() const { + return false; + } + + /** + * Check the blocklist for a feature. Returns false if the feature is blocked + * with an appropriate message and failure ID. + * */ + static bool IsGfxInfoStatusOkay(int32_t aFeature, nsCString* aOutMessage, + nsCString& aFailureId); + + const gfxSkipChars& EmptySkipChars() const { return kEmptySkipChars; } + + /** + * Returns a buffer containing the CMS output profile data. The way this + * is obtained is platform-specific. + */ + virtual nsTArray<uint8_t> GetPlatformCMSOutputProfileData() { + return GetPrefCMSOutputProfileData(); + } + + /** + * Return information on how child processes should initialize graphics + * devices. + */ + virtual void BuildContentDeviceData(mozilla::gfx::ContentDeviceData* aOut); + + /** + * Imports settings from the GPU process. This should only be called through + * GPUProcessManager, in the UI process. + */ + virtual void ImportGPUDeviceData(const mozilla::gfx::GPUDeviceData& aData); + + void SetOverlayInfo(const mozilla::layers::OverlayInfo& aInfo) { + mOverlayInfo = mozilla::Some(aInfo); + } + + void SetSwapChainInfo(const mozilla::layers::SwapChainInfo& aInfo) { + mSwapChainInfo = mozilla::Some(aInfo); + } + + static void DisableRemoteCanvas(); + + static bool HasVariationFontSupport(); + + // you probably want to use gfxVars::UseWebRender() instead of this + static bool WebRenderPrefEnabled(); + // you probably want to use gfxVars::UseWebRender() instead of this + static bool WebRenderEnvvarEnabled(); + + static const char* WebRenderResourcePathOverride(); + + // Returns true if we would like to keep the GPU process if possible. + // If aCrashAfterFinalFallback is true then crash if we have already + // exhausted all of our fallback options. Otherwise we remain on the final + // fallback configuration. + static bool FallbackFromAcceleration(mozilla::gfx::FeatureStatus aStatus, + const char* aMessage, + const nsACString& aFailureId, + bool aCrashAfterFinalFallback = false); + + void NotifyFrameStats(nsTArray<mozilla::layers::FrameStats>&& aFrameStats); + + virtual void OnMemoryPressure( + mozilla::layers::MemoryPressureReason aWhy) override; + + virtual void EnsureDevicesInitialized(){}; + virtual bool DevicesInitialized() { return true; }; + + virtual bool IsWaylandDisplay() { return false; } + + static uint32_t TargetFrameRate(); + + static bool UseDesktopZoomingScrollbars(); + + virtual bool SupportsHDR() { return false; } + + protected: + gfxPlatform(); + virtual ~gfxPlatform(); + + virtual void InitAcceleration(); + virtual void InitWebRenderConfig(); + void InitHardwareVideoConfig(); + virtual void InitWebGLConfig(); + virtual void InitWebGPUConfig(); + virtual void InitWindowOcclusionConfig(); + void InitBackdropFilterConfig(); + void InitAcceleratedCanvas2DConfig(); + + virtual void GetPlatformDisplayInfo(mozilla::widget::InfoObject& aObj) {} + + /** + * Called immediately before deleting the gfxPlatform object. + */ + virtual void WillShutdown(); + + // Return a hardware vsync source for this platform. + already_AddRefed<mozilla::gfx::VsyncSource> GetGlobalHardwareVsyncSource(); + + // Return a software vsync source (which uses a timer internally). + // Can be used as a fallback for platforms without hardware vsync, + // and when the layout.frame_rate pref is set to a non-negative value. + already_AddRefed<mozilla::gfx::VsyncSource> GetSoftwareVsyncSource(); + + // Create the platform-specific global vsync source. Can fall back to + // GetSoftwareVsyncSource(). + virtual already_AddRefed<mozilla::gfx::VsyncSource> + CreateGlobalHardwareVsyncSource() = 0; + + // Returns whether or not layers should be accelerated by default on this + // platform. + virtual bool AccelerateLayersByDefault(); + + // Returns preferences of canvas and content backends. + virtual BackendPrefsData GetBackendPrefs() const; + + /** + * Initialise the preferred and fallback canvas backends + * aBackendBitmask specifies the backends which are acceptable to the caller. + * The backend used is determined by aBackendBitmask and the order specified + * by the gfx.canvas.azure.backends pref. + */ + void InitBackendPrefs(BackendPrefsData&& aPrefsData); + + /** + * Content-process only. Updates device preferences from the parent process, + * if we've received any. + */ + void ImportCachedContentDeviceData(); + virtual void ImportContentDeviceData( + const mozilla::gfx::ContentDeviceData& aData); + + public: + /** + * Returns the contents of the file pointed to by the + * gfx.color_management.display_profile pref, if set. + * Returns an empty array if not set, or if an error occurs + */ + static nsTArray<uint8_t> GetPrefCMSOutputProfileData(); + + protected: + /** + * If inside a child process and currently being initialized by the + * SetXPCOMProcessAttributes message, this can be used by subclasses to + * retrieve the ContentDeviceData passed by the message + * + * If not currently being initialized, will return nullptr. In this case, + * child should send a sync message to ask parent for color profile + */ + const mozilla::gfx::ContentDeviceData* GetInitContentDeviceData(); + + /** + * If inside a child process and have ever received a + * SetXPCOMProcessAttributes message, this contains the cmsOutputProfileData + * from that message. + */ + mozilla::Maybe<nsTArray<uint8_t>>& GetCMSOutputProfileData(); + + /** + * Increase the global device counter after a device has been removed/reset. + */ + void BumpDeviceCounter(); + + /** + * returns the first backend named in the pref gfx.canvas.azure.backends + * which is a component of aBackendBitmask, a bitmask of backend types + */ + static mozilla::gfx::BackendType GetCanvasBackendPref( + uint32_t aBackendBitmask); + + /** + * returns the first backend named in the pref gfx.content.azure.backend + * which is a component of aBackendBitmask, a bitmask of backend types + */ + static mozilla::gfx::BackendType GetContentBackendPref( + uint32_t& aBackendBitmask); + + /** + * Will return the first backend named in aBackendPrefName + * allowed by aBackendBitmask, a bitmask of backend types. + * It also modifies aBackendBitmask to only include backends that are + * allowed given the prefs. + */ + static mozilla::gfx::BackendType GetBackendPref(const char* aBackendPrefName, + uint32_t& aBackendBitmask); + /** + * Decode the backend enumberation from a string. + */ + static mozilla::gfx::BackendType BackendTypeForName(const nsCString& aName); + + virtual bool CanUseHardwareVideoDecoding(); + + int8_t mAllowDownloadableFonts; + + // Whether the platform supports rendering OpenType font variations + static std::atomic<int8_t> sHasVariationFontSupport; + + // The global vsync dispatcher. Only non-null in the parent process. + // Its underlying VsyncSource is either mGlobalHardwareVsyncSource + // or mSoftwareVsyncSource. + RefPtr<mozilla::VsyncDispatcher> mVsyncDispatcher; + + // Cached software vsync source. Only non-null in the parent process, + // and only after the first time GetHardwareVsyncSource has been called. + RefPtr<mozilla::gfx::VsyncSource> mGlobalHardwareVsyncSource; + + // Cached software vsync source. Only non-null in the parent process, + // and only after the first time GetSoftwareVsyncSource has been called. + // Used as a fallback source if hardware vsync is not available, + // or when the layout.frame_rate pref is set. + RefPtr<mozilla::gfx::SoftwareVsyncSource> mSoftwareVsyncSource; + + RefPtr<mozilla::gfx::DrawTarget> mScreenReferenceDrawTarget; + + private: + /** + * Start up Thebes. + */ + static void Init(); + + static void InitOpenGLConfig(); + + static mozilla::Atomic<bool, mozilla::MemoryOrdering::ReleaseAcquire> + gCMSInitialized; + static CMSMode gCMSMode; + + // These two may point to the same profile + static qcms_profile* gCMSOutputProfile; + static qcms_profile* gCMSsRGBProfile; + + static qcms_transform* gCMSRGBTransform; + static qcms_transform* gCMSInverseRGBTransform; + static qcms_transform* gCMSRGBATransform; + static qcms_transform* gCMSBGRATransform; + + inline static void EnsureCMSInitialized() { + if (MOZ_UNLIKELY(!gCMSInitialized)) { + InitializeCMS(); + } + } + + static void InitializeCMS(); + static void ShutdownCMS(); + + /** + * This uses nsIScreenManager to determine the screen size and color depth + */ + void PopulateScreenInfo(); + + void InitCompositorAccelerationPrefs(); + void InitGPUProcessPrefs(); + virtual void InitPlatformGPUProcessPrefs() {} + + // Gather telemetry data about the Gfx Platform and send it + static void ReportTelemetry(); + + static bool IsDXInterop2Blocked(); + static bool IsDXNV12Blocked(); + static bool IsDXP010Blocked(); + static bool IsDXP016Blocked(); + + RefPtr<gfxASurface> mScreenReferenceSurface; + RefPtr<mozilla::layers::MemoryPressureObserver> mMemoryPressureObserver; + + // The preferred draw target backend to use for canvas + mozilla::gfx::BackendType mPreferredCanvasBackend; + // The fallback draw target backend to use for canvas, if the preferred + // backend fails + mozilla::gfx::BackendType mFallbackCanvasBackend; + // The backend to use for content + mozilla::gfx::BackendType mContentBackend; + // The backend to use when we need it not to be accelerated. + mozilla::gfx::BackendType mSoftwareBackend; + // Bitmask of backend types we can use to render content + uint32_t mContentBackendBitmask; + + mozilla::widget::GfxInfoCollector<gfxPlatform> mAzureCanvasBackendCollector; + mozilla::widget::GfxInfoCollector<gfxPlatform> mApzSupportCollector; + mozilla::widget::GfxInfoCollector<gfxPlatform> mFrameStatsCollector; + mozilla::widget::GfxInfoCollector<gfxPlatform> mCMSInfoCollector; + mozilla::widget::GfxInfoCollector<gfxPlatform> mDisplayInfoCollector; + mozilla::widget::GfxInfoCollector<gfxPlatform> mOverlayInfoCollector; + mozilla::widget::GfxInfoCollector<gfxPlatform> mSwapChainInfoCollector; + + nsTArray<mozilla::layers::FrameStats> mFrameStats; + + // Backend that we are compositing with. NONE, if no compositor has been + // created yet. + mozilla::layers::LayersBackend mCompositorBackend; + + int32_t mScreenDepth; + mozilla::gfx::IntSize mScreenSize; + + mozilla::Maybe<mozilla::layers::OverlayInfo> mOverlayInfo; + mozilla::Maybe<mozilla::layers::SwapChainInfo> mSwapChainInfo; + + // An instance of gfxSkipChars which is empty. It is used as the + // basis for error-case iterators. + const gfxSkipChars kEmptySkipChars; +}; + +CMSMode GfxColorManagementMode(); + +#endif /* GFX_PLATFORM_H */ diff --git a/gfx/thebes/gfxPlatformFontList.cpp b/gfx/thebes/gfxPlatformFontList.cpp new file mode 100644 index 0000000000..709a0f3c27 --- /dev/null +++ b/gfx/thebes/gfxPlatformFontList.cpp @@ -0,0 +1,3210 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Logging.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/intl/Locale.h" +#include "mozilla/intl/LocaleService.h" +#include "mozilla/intl/OSPreferences.h" + +#include "gfxPlatformFontList.h" +#include "gfxTextRun.h" +#include "gfxUserFontSet.h" +#include "SharedFontList-impl.h" + +#include "GeckoProfiler.h" +#include "nsCRT.h" +#include "nsGkAtoms.h" +#include "nsPresContext.h" +#include "nsServiceManagerUtils.h" +#include "nsUnicharUtils.h" +#include "nsUnicodeProperties.h" +#include "nsXULAppAPI.h" + +#include "mozilla/AppShutdown.h" +#include "mozilla/Attributes.h" +#include "mozilla/BinarySearch.h" +#include "mozilla/Likely.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/dom/BlobImpl.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ContentProcessMessageManager.h" +#include "mozilla/dom/Document.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/ipc/FileDescriptorUtils.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/TextUtils.h" +#include "mozilla/Unused.h" + +#include "base/eintr_wrapper.h" + +#include <locale.h> +#include <numeric> + +using namespace mozilla; +using mozilla::intl::Locale; +using mozilla::intl::LocaleParser; +using mozilla::intl::LocaleService; +using mozilla::intl::OSPreferences; + +#define LOG_FONTLIST(args) \ + MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug, args) +#define LOG_FONTLIST_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug) +#define LOG_FONTINIT(args) \ + MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug, args) +#define LOG_FONTINIT_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug) + +gfxPlatformFontList* gfxPlatformFontList::sPlatformFontList = nullptr; + +// Character ranges that require complex-script shaping support in the font, +// and so should be masked out by ReadCMAP if the necessary layout tables +// are not present. +// Currently used by the Mac and FT2 implementations only, but probably should +// be supported on Windows as well. +const gfxFontEntry::ScriptRange gfxPlatformFontList::sComplexScriptRanges[] = { + // Actually, now that harfbuzz supports presentation-forms shaping for + // Arabic, we can render it without layout tables. So maybe we don't + // want to mask the basic Arabic block here? + // This affects the arabic-fallback-*.html reftests, which rely on + // loading a font that *doesn't* have any GSUB table. + {0x0600, 0x060B, 1, {TRUETYPE_TAG('a', 'r', 'a', 'b'), 0, 0}}, + // skip 060C Arabic comma, also used by N'Ko etc + {0x060D, 0x061A, 1, {TRUETYPE_TAG('a', 'r', 'a', 'b'), 0, 0}}, + // skip 061B Arabic semicolon, also used by N'Ko etc + {0x061C, 0x061E, 1, {TRUETYPE_TAG('a', 'r', 'a', 'b'), 0, 0}}, + // skip 061F Arabic question mark, also used by N'Ko etc + {0x0620, 0x063F, 1, {TRUETYPE_TAG('a', 'r', 'a', 'b'), 0, 0}}, + // skip 0640 Arabic tatweel (for syriac, adlam, etc) + {0x0641, 0x06D3, 1, {TRUETYPE_TAG('a', 'r', 'a', 'b'), 0, 0}}, + // skip 06D4 Arabic full stop (for hanifi rohingya) + {0x06D5, 0x06FF, 1, {TRUETYPE_TAG('a', 'r', 'a', 'b'), 0, 0}}, + {0x0700, 0x074F, 1, {TRUETYPE_TAG('s', 'y', 'r', 'c'), 0, 0}}, + {0x0750, 0x077F, 1, {TRUETYPE_TAG('a', 'r', 'a', 'b'), 0, 0}}, + {0x08A0, 0x08FF, 1, {TRUETYPE_TAG('a', 'r', 'a', 'b'), 0, 0}}, + {0x0900, + 0x0963, + 2, + {TRUETYPE_TAG('d', 'e', 'v', '2'), TRUETYPE_TAG('d', 'e', 'v', 'a'), 0}}, + // skip 0964 DEVANAGARI DANDA and 0965 DEVANAGARI DOUBLE DANDA, shared by + // various other Indic writing systems + {0x0966, + 0x097F, + 2, + {TRUETYPE_TAG('d', 'e', 'v', '2'), TRUETYPE_TAG('d', 'e', 'v', 'a'), 0}}, + {0x0980, + 0x09FF, + 2, + {TRUETYPE_TAG('b', 'n', 'g', '2'), TRUETYPE_TAG('b', 'e', 'n', 'g'), 0}}, + {0x0A00, + 0x0A7F, + 2, + {TRUETYPE_TAG('g', 'u', 'r', '2'), TRUETYPE_TAG('g', 'u', 'r', 'u'), 0}}, + {0x0A80, + 0x0AFF, + 2, + {TRUETYPE_TAG('g', 'j', 'r', '2'), TRUETYPE_TAG('g', 'u', 'j', 'r'), 0}}, + {0x0B00, + 0x0B7F, + 2, + {TRUETYPE_TAG('o', 'r', 'y', '2'), TRUETYPE_TAG('o', 'r', 'y', 'a'), 0}}, + {0x0B80, + 0x0BFF, + 2, + {TRUETYPE_TAG('t', 'm', 'l', '2'), TRUETYPE_TAG('t', 'a', 'm', 'l'), 0}}, + {0x0C00, + 0x0C7F, + 2, + {TRUETYPE_TAG('t', 'e', 'l', '2'), TRUETYPE_TAG('t', 'e', 'l', 'u'), 0}}, + {0x0C80, + 0x0CFF, + 2, + {TRUETYPE_TAG('k', 'n', 'd', '2'), TRUETYPE_TAG('k', 'n', 'd', 'a'), 0}}, + {0x0D00, + 0x0D7F, + 2, + {TRUETYPE_TAG('m', 'l', 'm', '2'), TRUETYPE_TAG('m', 'l', 'y', 'm'), 0}}, + {0x0D80, 0x0DFF, 1, {TRUETYPE_TAG('s', 'i', 'n', 'h'), 0, 0}}, + {0x0E80, 0x0EFF, 1, {TRUETYPE_TAG('l', 'a', 'o', ' '), 0, 0}}, + {0x0F00, 0x0FFF, 1, {TRUETYPE_TAG('t', 'i', 'b', 't'), 0, 0}}, + {0x1000, + 0x109f, + 2, + {TRUETYPE_TAG('m', 'y', 'm', 'r'), TRUETYPE_TAG('m', 'y', 'm', '2'), 0}}, + {0x1780, 0x17ff, 1, {TRUETYPE_TAG('k', 'h', 'm', 'r'), 0, 0}}, + // Khmer Symbols (19e0..19ff) don't seem to need any special shaping + {0xaa60, + 0xaa7f, + 2, + {TRUETYPE_TAG('m', 'y', 'm', 'r'), TRUETYPE_TAG('m', 'y', 'm', '2'), 0}}, + // Thai seems to be "renderable" without AAT morphing tables + {0, 0, 0, {0, 0, 0}} // terminator +}; + +static const char* kObservedPrefs[] = { + "font.", "font.name-list.", "intl.accept_languages", // hmmmm... + "browser.display.use_document_fonts.icon_font_allowlist", nullptr}; + +static const char kFontSystemWhitelistPref[] = "font.system.whitelist"; + +static const char kCJKFallbackOrderPref[] = "font.cjk_pref_fallback_order"; + +// Pref for the list of icon font families that still get to override the +// default font from prefs, even when use_document_fonts is disabled. +// (This is to enable ligature-based icon fonts to keep working.) +static const char kIconFontsPref[] = + "browser.display.use_document_fonts.icon_font_allowlist"; + +// xxx - this can probably be eliminated by reworking pref font handling code +static const char* gPrefLangNames[] = { +#define FONT_PREF_LANG(enum_id_, str_, atom_id_) str_ +#include "gfxFontPrefLangList.h" +#undef FONT_PREF_LANG +}; + +static_assert(MOZ_ARRAY_LENGTH(gPrefLangNames) == uint32_t(eFontPrefLang_Count), + "size of pref lang name array doesn't match pref lang enum size"); + +class gfxFontListPrefObserver final : public nsIObserver { + ~gfxFontListPrefObserver() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER +}; + +static void FontListPrefChanged(const char* aPref, void* aData = nullptr) { + // XXX this could be made to only clear out the cache for the prefs that were + // changed but it probably isn't that big a deal. + gfxPlatformFontList::PlatformFontList()->ClearLangGroupPrefFonts(); + gfxPlatformFontList::PlatformFontList()->LoadIconFontOverrideList(); + gfxFontCache::GetCache()->Flush(); +} + +static gfxFontListPrefObserver* gFontListPrefObserver = nullptr; + +NS_IMPL_ISUPPORTS(gfxFontListPrefObserver, nsIObserver) + +#define LOCALES_CHANGED_TOPIC "intl:system-locales-changed" + +NS_IMETHODIMP +gfxFontListPrefObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + NS_ASSERTION(!strcmp(aTopic, LOCALES_CHANGED_TOPIC), "invalid topic"); + FontListPrefChanged(nullptr); + + if (XRE_IsParentProcess()) { + gfxPlatform::ForceGlobalReflow(gfxPlatform::NeedsReframe::No); + } + return NS_OK; +} + +MOZ_DEFINE_MALLOC_SIZE_OF(FontListMallocSizeOf) + +NS_IMPL_ISUPPORTS(gfxPlatformFontList::MemoryReporter, nsIMemoryReporter) + +NS_IMETHODIMP +gfxPlatformFontList::MemoryReporter::CollectReports( + nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) { + FontListSizes sizes; + sizes.mFontListSize = 0; + sizes.mFontTableCacheSize = 0; + sizes.mCharMapsSize = 0; + sizes.mLoaderSize = 0; + sizes.mSharedSize = 0; + + gfxPlatformFontList::PlatformFontList()->AddSizeOfIncludingThis( + &FontListMallocSizeOf, &sizes); + + MOZ_COLLECT_REPORT( + "explicit/gfx/font-list", KIND_HEAP, UNITS_BYTES, sizes.mFontListSize, + "Memory used to manage the list of font families and faces."); + + MOZ_COLLECT_REPORT( + "explicit/gfx/font-charmaps", KIND_HEAP, UNITS_BYTES, sizes.mCharMapsSize, + "Memory used to record the character coverage of individual fonts."); + + if (sizes.mFontTableCacheSize) { + MOZ_COLLECT_REPORT( + "explicit/gfx/font-tables", KIND_HEAP, UNITS_BYTES, + sizes.mFontTableCacheSize, + "Memory used for cached font metrics and layout tables."); + } + + if (sizes.mLoaderSize) { + MOZ_COLLECT_REPORT("explicit/gfx/font-loader", KIND_HEAP, UNITS_BYTES, + sizes.mLoaderSize, + "Memory used for (platform-specific) font loader."); + } + + if (sizes.mSharedSize) { + MOZ_COLLECT_REPORT( + "font-list-shmem", KIND_NONHEAP, UNITS_BYTES, sizes.mSharedSize, + "Shared memory for system font list and character coverage data."); + } + + return NS_OK; +} + +PRThread* gfxPlatformFontList::sInitFontListThread = nullptr; + +static void InitFontListCallback(void* aFontList) { + AUTO_PROFILER_REGISTER_THREAD("InitFontList"); + PR_SetCurrentThreadName("InitFontList"); + + if (!static_cast<gfxPlatformFontList*>(aFontList)->InitFontList()) { + gfxPlatformFontList::Shutdown(); + } +} + +/* static */ +bool gfxPlatformFontList::Initialize(gfxPlatformFontList* aList) { + sPlatformFontList = aList; + if (XRE_IsParentProcess() && + StaticPrefs::gfx_font_list_omt_enabled_AtStartup() && + StaticPrefs::gfx_e10s_font_list_shared_AtStartup() && + !gfxPlatform::InSafeMode()) { + sInitFontListThread = PR_CreateThread( + PR_USER_THREAD, InitFontListCallback, aList, PR_PRIORITY_NORMAL, + PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0); + return true; + } + if (aList->InitFontList()) { + return true; + } + Shutdown(); + return false; +} + +gfxPlatformFontList::gfxPlatformFontList(bool aNeedFullnamePostscriptNames) + : mLock("gfxPlatformFontList lock"), + mFontFamilies(64), + mOtherFamilyNames(16), + mSharedCmaps(8) { + if (aNeedFullnamePostscriptNames) { + mExtraNames = MakeUnique<ExtraNames>(); + } + + mLangService = nsLanguageAtomService::GetService(); + + LoadBadUnderlineList(); + LoadIconFontOverrideList(); + + mFontPrefs = MakeUnique<FontPrefs>(); + + gfxFontUtils::GetPrefsFontList(kFontSystemWhitelistPref, mEnabledFontsList); + mFontFamilyWhitelistActive = !mEnabledFontsList.IsEmpty(); + + // pref changes notification setup + NS_ASSERTION(!gFontListPrefObserver, + "There has been font list pref observer already"); + gFontListPrefObserver = new gfxFontListPrefObserver(); + NS_ADDREF(gFontListPrefObserver); + + Preferences::RegisterPrefixCallbacks(FontListPrefChanged, kObservedPrefs); + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->AddObserver(gFontListPrefObserver, LOCALES_CHANGED_TOPIC, false); + } + + // Only the parent process listens for whitelist changes; it will then + // notify its children to rebuild their font lists. + if (XRE_IsParentProcess()) { + Preferences::RegisterCallback(FontWhitelistPrefChanged, + kFontSystemWhitelistPref); + } + + RegisterStrongMemoryReporter(new MemoryReporter()); + + // initialize lang group pref font defaults (i.e. serif/sans-serif) + mDefaultGenericsLangGroup.AppendElements(ArrayLength(gPrefLangNames)); + for (uint32_t i = 0; i < ArrayLength(gPrefLangNames); i++) { + nsAutoCString prefDefaultFontType("font.default."); + prefDefaultFontType.Append(GetPrefLangName(eFontPrefLang(i))); + nsAutoCString serifOrSans; + Preferences::GetCString(prefDefaultFontType.get(), serifOrSans); + if (serifOrSans.EqualsLiteral("sans-serif")) { + mDefaultGenericsLangGroup[i] = StyleGenericFontFamily::SansSerif; + } else { + mDefaultGenericsLangGroup[i] = StyleGenericFontFamily::Serif; + } + } +} + +gfxPlatformFontList::~gfxPlatformFontList() { + // Note that gfxPlatformFontList::Shutdown() ensures that the init-font-list + // thread is finished before we come here. + + AutoLock lock(mLock); + + // We can't just do mSharedCmaps.Clear() here because removing each item from + // the table would drop its last reference, and its Release() method would + // then call back to MaybeRemoveCmap to search for it, which we can't do + // while in the middle of clearing the table. + // So we first clear the "shared" flag in each entry, so Release() won't try + // to re-find them in the table. + for (auto iter = mSharedCmaps.ConstIter(); !iter.Done(); iter.Next()) { + iter.Get()->mCharMap->ClearSharedFlag(); + } + mSharedCmaps.Clear(); + + ClearLangGroupPrefFontsLocked(); + + NS_ASSERTION(gFontListPrefObserver, "There is no font list pref observer"); + + Preferences::UnregisterPrefixCallbacks(FontListPrefChanged, kObservedPrefs); + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->RemoveObserver(gFontListPrefObserver, LOCALES_CHANGED_TOPIC); + } + + if (XRE_IsParentProcess()) { + Preferences::UnregisterCallback(FontWhitelistPrefChanged, + kFontSystemWhitelistPref); + } + NS_RELEASE(gFontListPrefObserver); +} + +/* static */ +void gfxPlatformFontList::FontWhitelistPrefChanged(const char* aPref, + void* aClosure) { + MOZ_ASSERT(XRE_IsParentProcess()); + auto* pfl = gfxPlatformFontList::PlatformFontList(); + pfl->UpdateFontList(true); + dom::ContentParent::NotifyUpdatedFonts(true); +} + +void gfxPlatformFontList::ApplyWhitelist() { + uint32_t numFonts = mEnabledFontsList.Length(); + if (!mFontFamilyWhitelistActive) { + return; + } + nsTHashSet<nsCString> familyNamesWhitelist; + for (uint32_t i = 0; i < numFonts; i++) { + nsAutoCString key; + ToLowerCase(mEnabledFontsList[i], key); + familyNamesWhitelist.Insert(key); + } + AutoTArray<RefPtr<gfxFontFamily>, 128> accepted; + bool whitelistedFontFound = false; + for (const auto& entry : mFontFamilies) { + if (entry.GetData()->IsHidden()) { + // Hidden system fonts are exempt from whitelisting, but don't count + // towards determining whether we "kept" any (user-visible) fonts + accepted.AppendElement(entry.GetData()); + continue; + } + nsAutoCString fontFamilyName(entry.GetKey()); + ToLowerCase(fontFamilyName); + if (familyNamesWhitelist.Contains(fontFamilyName)) { + accepted.AppendElement(entry.GetData()); + whitelistedFontFound = true; + } + } + if (!whitelistedFontFound) { + // No whitelisted fonts found! Ignore the whitelist. + return; + } + // Replace the original full list with the accepted subset. + mFontFamilies.Clear(); + for (auto& f : accepted) { + nsAutoCString fontFamilyName(f->Name()); + ToLowerCase(fontFamilyName); + mFontFamilies.InsertOrUpdate(fontFamilyName, std::move(f)); + } +} + +void gfxPlatformFontList::ApplyWhitelist( + nsTArray<fontlist::Family::InitData>& aFamilies) { + mLock.AssertCurrentThreadIn(); + if (!mFontFamilyWhitelistActive) { + return; + } + nsTHashSet<nsCString> familyNamesWhitelist; + for (const auto& item : mEnabledFontsList) { + nsAutoCString key; + ToLowerCase(item, key); + familyNamesWhitelist.Insert(key); + } + AutoTArray<fontlist::Family::InitData, 128> accepted; + bool keptNonHidden = false; + for (auto& f : aFamilies) { + if (f.mVisibility == FontVisibility::Hidden || + familyNamesWhitelist.Contains(f.mKey)) { + accepted.AppendElement(f); + if (f.mVisibility != FontVisibility::Hidden) { + keptNonHidden = true; + } + } + } + if (!keptNonHidden) { + // No (visible) families were whitelisted: ignore the whitelist + // and just leave the fontlist unchanged. + return; + } + aFamilies = std::move(accepted); +} + +bool gfxPlatformFontList::FamilyInList(const nsACString& aName, + const char* aList[], size_t aCount) { + size_t result; + return BinarySearchIf( + aList, 0, aCount, + [&](const char* const aVal) -> int { + return nsCaseInsensitiveUTF8StringComparator( + aName.BeginReading(), aVal, aName.Length(), strlen(aVal)); + }, + &result); +} + +void gfxPlatformFontList::CheckFamilyList(const char* aList[], size_t aCount) { +#ifdef DEBUG + MOZ_ASSERT(aCount > 0, "empty font family list?"); + const char* a = aList[0]; + uint32_t aLen = strlen(a); + for (size_t i = 1; i < aCount; ++i) { + const char* b = aList[i]; + uint32_t bLen = strlen(b); + if (nsCaseInsensitiveUTF8StringComparator(a, b, aLen, bLen) >= 0) { + MOZ_CRASH_UNSAFE_PRINTF("incorrectly sorted font family list: %s >= %s", + a, b); + } + a = b; + aLen = bLen; + } +#endif +} + +bool gfxPlatformFontList::AddWithLegacyFamilyName(const nsACString& aLegacyName, + gfxFontEntry* aFontEntry, + FontVisibility aVisibility) { + mLock.AssertCurrentThreadIn(); + bool added = false; + nsAutoCString key; + ToLowerCase(aLegacyName, key); + mOtherFamilyNames + .LookupOrInsertWith(key, + [&] { + RefPtr<gfxFontFamily> family = + CreateFontFamily(aLegacyName, aVisibility); + // We don't want the family to search for faces, + // we're adding them directly here. + family->SetHasStyles(true); + // And we don't want it to attempt to search for + // legacy names, because we've already done that + // (and this is the result). + family->SetCheckedForLegacyFamilyNames(true); + added = true; + return family; + }) + ->AddFontEntry(aFontEntry->Clone()); + return added; +} + +bool gfxPlatformFontList::InitFontList() { + // If the startup font-list-init thread is still running, we need to wait + // for it to finish before trying to reinitialize here. + if (sInitFontListThread && !IsInitFontListThread()) { + PR_JoinThread(sInitFontListThread); + sInitFontListThread = nullptr; + } + + AutoLock lock(mLock); + + if (LOG_FONTINIT_ENABLED()) { + LOG_FONTINIT(("(fontinit) system fontlist initialization\n")); + } + + if (IsInitialized()) { + // Font-list reinitialization always occurs on the main thread, in response + // to a change notification; it's only the initial creation during startup + // that may be on another thread. + MOZ_ASSERT(NS_IsMainThread()); + + // Rebuilding fontlist so clear out font/word caches. + gfxFontCache* fontCache = gfxFontCache::GetCache(); + if (fontCache) { + fontCache->FlushShapedWordCaches(); + fontCache->Flush(); + } + + gfxPlatform::PurgeSkiaFontCache(); + + // There's no need to broadcast this reflow request to child processes, as + // ContentParent::NotifyUpdatedFonts deals with it by re-entering into this + // function on child processes. + ForceGlobalReflowLocked(gfxPlatform::NeedsReframe::Yes, + gfxPlatform::BroadcastToChildren::No); + + mAliasTable.Clear(); + mLocalNameTable.Clear(); + mIconFontsSet.Clear(); + + CancelLoadCmapsTask(); + mStartedLoadingCmapsFrom = 0xffffffffu; + + CancelInitOtherFamilyNamesTask(); + mFontFamilies.Clear(); + mOtherFamilyNames.Clear(); + mOtherFamilyNamesInitialized = false; + + if (mExtraNames) { + mExtraNames->mFullnames.Clear(); + mExtraNames->mPostscriptNames.Clear(); + } + mFaceNameListsInitialized = false; + ClearLangGroupPrefFontsLocked(); + CancelLoader(); + + // Clear cached family records that will no longer be valid. + for (auto& f : mReplacementCharFallbackFamily) { + f = FontFamily(); + } + + gfxFontUtils::GetPrefsFontList(kFontSystemWhitelistPref, mEnabledFontsList); + mFontFamilyWhitelistActive = !mEnabledFontsList.IsEmpty(); + + LoadIconFontOverrideList(); + } + + // From here, gfxPlatformFontList::IsInitialized will return true, + // unless InitFontListForPlatform() fails and we reset it below. + mFontlistInitCount++; + + InitializeCodepointsWithNoFonts(); + + // Try to initialize the cross-process shared font list if enabled by prefs, + // but not if we're running in Safe Mode. + if (StaticPrefs::gfx_e10s_font_list_shared_AtStartup() && + !gfxPlatform::InSafeMode()) { + for (const auto& entry : mFontEntries.Values()) { + if (!entry) { + continue; + } + AutoWriteLock lock(entry->mLock); + entry->mShmemCharacterMap = nullptr; + entry->mShmemFace = nullptr; + entry->mFamilyName.Truncate(); + } + mFontEntries.Clear(); + mShmemCharMaps.Clear(); + bool oldSharedList = mSharedFontList != nullptr; + mSharedFontList.reset(new fontlist::FontList(mFontlistInitCount)); + InitSharedFontListForPlatform(); + if (mSharedFontList && mSharedFontList->Initialized()) { + if (mLocalNameTable.Count()) { + SharedFontList()->SetLocalNames(mLocalNameTable); + mLocalNameTable.Clear(); + } + } else { + // something went wrong, fall back to in-process list + gfxCriticalNote << "Failed to initialize shared font list, " + "falling back to in-process list."; + mSharedFontList.reset(nullptr); + } + if (oldSharedList && XRE_IsParentProcess()) { + // notify all children of the change + if (NS_IsMainThread()) { + dom::ContentParent::NotifyUpdatedFonts(true); + } else { + NS_DispatchToMainThread(NS_NewRunnableFunction( + "NotifyUpdatedFonts callback", + [] { dom::ContentParent::NotifyUpdatedFonts(true); })); + } + } + } + + if (!SharedFontList()) { + if (NS_FAILED(InitFontListForPlatform())) { + mFontlistInitCount = 0; + return false; + } + ApplyWhitelist(); + } + + // Set up mDefaultFontEntry as a "last resort" default that we can use + // to avoid crashing if the font list is otherwise unusable. + gfxFontStyle defStyle; + FontFamily fam = GetDefaultFontLocked(nullptr, &defStyle); + gfxFontEntry* fe; + if (fam.mShared) { + auto face = fam.mShared->FindFaceForStyle(SharedFontList(), defStyle); + fe = face ? GetOrCreateFontEntryLocked(face, fam.mShared) : nullptr; + } else { + fe = fam.mUnshared->FindFontForStyle(defStyle); + } + mDefaultFontEntry = fe; + + return true; +} + +void gfxPlatformFontList::LoadIconFontOverrideList() { + mIconFontsSet.Clear(); + AutoTArray<nsCString, 20> iconFontsList; + gfxFontUtils::GetPrefsFontList(kIconFontsPref, iconFontsList); + for (auto& name : iconFontsList) { + ToLowerCase(name); + mIconFontsSet.Insert(name); + } +} + +void gfxPlatformFontList::InitializeCodepointsWithNoFonts() { + auto& first = mCodepointsWithNoFonts[FontVisibility(0)]; + for (auto& bitset : mCodepointsWithNoFonts) { + if (&bitset == &first) { + bitset.reset(); + bitset.SetRange(0, 0x1f); // C0 controls + bitset.SetRange(0x7f, 0x9f); // C1 controls + bitset.SetRange(0xE000, 0xF8FF); // PUA + bitset.SetRange(0xF0000, 0x10FFFD); // Supplementary PUA + bitset.SetRange(0xfdd0, 0xfdef); // noncharacters + for (unsigned i = 0; i <= 0x100000; i += 0x10000) { + bitset.SetRange(i + 0xfffe, i + 0xffff); // noncharacters + } + bitset.Compact(); + } else { + bitset = first; + } + } +} + +void gfxPlatformFontList::FontListChanged() { + MOZ_ASSERT(!XRE_IsParentProcess()); + AutoLock lock(mLock); + InitializeCodepointsWithNoFonts(); + if (SharedFontList()) { + // If we're using a shared local face-name list, this may have changed + // such that existing font entries held by user font sets are no longer + // safe to use: ensure they all get flushed. + RebuildLocalFonts(/*aForgetLocalFaces*/ true); + } + ForceGlobalReflowLocked(gfxPlatform::NeedsReframe::Yes); +} + +void gfxPlatformFontList::GenerateFontListKey(const nsACString& aKeyName, + nsACString& aResult) { + aResult = aKeyName; + ToLowerCase(aResult); +} + +// Used if a stylo thread wants to trigger InitOtherFamilyNames in the main +// process: we can't do IPC from the stylo thread so we post this to the main +// thread instead. +class InitOtherFamilyNamesForStylo : public mozilla::Runnable { + public: + explicit InitOtherFamilyNamesForStylo(bool aDeferOtherFamilyNamesLoading) + : Runnable("gfxPlatformFontList::InitOtherFamilyNamesForStylo"), + mDefer(aDeferOtherFamilyNamesLoading) {} + + NS_IMETHOD Run() override { + auto pfl = gfxPlatformFontList::PlatformFontList(); + auto list = pfl->SharedFontList(); + if (!list) { + return NS_OK; + } + bool initialized = false; + dom::ContentChild::GetSingleton()->SendInitOtherFamilyNames( + list->GetGeneration(), mDefer, &initialized); + pfl->mOtherFamilyNamesInitialized.compareExchange(false, initialized); + return NS_OK; + } + + private: + bool mDefer; +}; + +#define OTHERNAMES_TIMEOUT 200 + +bool gfxPlatformFontList::InitOtherFamilyNames( + bool aDeferOtherFamilyNamesLoading) { + if (mOtherFamilyNamesInitialized) { + return true; + } + + if (SharedFontList() && !XRE_IsParentProcess()) { + if (NS_IsMainThread()) { + bool initialized; + dom::ContentChild::GetSingleton()->SendInitOtherFamilyNames( + SharedFontList()->GetGeneration(), aDeferOtherFamilyNamesLoading, + &initialized); + mOtherFamilyNamesInitialized.compareExchange(false, initialized); + } else { + NS_DispatchToMainThread( + new InitOtherFamilyNamesForStylo(aDeferOtherFamilyNamesLoading)); + } + return mOtherFamilyNamesInitialized; + } + + // If the font loader delay has been set to zero, we don't defer loading + // additional family names (regardless of the aDefer... parameter), as we + // take this to mean availability of font info is to be prioritized over + // potential startup perf or main-thread jank. + // (This is used so we can reliably run reftests that depend on localized + // font-family names being available.) + if (aDeferOtherFamilyNamesLoading && + StaticPrefs::gfx_font_loader_delay_AtStartup() > 0) { + if (!mPendingOtherFamilyNameTask) { + RefPtr<mozilla::CancelableRunnable> task = + new InitOtherFamilyNamesRunnable(); + mPendingOtherFamilyNameTask = task; + NS_DispatchToMainThreadQueue(task.forget(), EventQueuePriority::Idle); + } + } else { + InitOtherFamilyNamesInternal(false); + } + return mOtherFamilyNamesInitialized; +} + +// time limit for loading facename lists (ms) +#define NAMELIST_TIMEOUT 200 + +gfxFontEntry* gfxPlatformFontList::SearchFamiliesForFaceName( + const nsACString& aFaceName) { + TimeStamp start = TimeStamp::Now(); + bool timedOut = false; + // if mFirstChar is not 0, only load facenames for families + // that start with this character + char16_t firstChar = 0; + gfxFontEntry* lookup = nullptr; + + // iterate over familes starting with the same letter + firstChar = ToLowerCase(aFaceName.CharAt(0)); + + for (const auto& entry : mFontFamilies) { + nsCStringHashKey::KeyType key = entry.GetKey(); + const RefPtr<gfxFontFamily>& family = entry.GetData(); + + // when filtering, skip names that don't start with the filter character + if (firstChar && ToLowerCase(key.CharAt(0)) != firstChar) { + continue; + } + + family->ReadFaceNames(this, NeedFullnamePostscriptNames()); + + TimeDuration elapsed = TimeStamp::Now() - start; + if (elapsed.ToMilliseconds() > NAMELIST_TIMEOUT) { + timedOut = true; + break; + } + } + + lookup = FindFaceName(aFaceName); + + TimeStamp end = TimeStamp::Now(); + Telemetry::AccumulateTimeDelta(Telemetry::FONTLIST_INITFACENAMELISTS, start, + end); + if (LOG_FONTINIT_ENABLED()) { + TimeDuration elapsed = end - start; + LOG_FONTINIT(("(fontinit) SearchFamiliesForFaceName took %8.2f ms %s %s", + elapsed.ToMilliseconds(), (lookup ? "found name" : ""), + (timedOut ? "timeout" : ""))); + } + + return lookup; +} + +gfxFontEntry* gfxPlatformFontList::FindFaceName(const nsACString& aFaceName) { + gfxFontEntry* lookup; + + // lookup in name lookup tables, return null if not found + if (mExtraNames && + ((lookup = mExtraNames->mPostscriptNames.GetWeak(aFaceName)) || + (lookup = mExtraNames->mFullnames.GetWeak(aFaceName)))) { + return lookup; + } + + return nullptr; +} + +gfxFontEntry* gfxPlatformFontList::LookupInFaceNameLists( + const nsACString& aFaceName) { + gfxFontEntry* lookup = nullptr; + + // initialize facename lookup tables if needed + // note: this can terminate early or time out, in which case + // mFaceNameListsInitialized remains false + if (!mFaceNameListsInitialized) { + lookup = SearchFamiliesForFaceName(aFaceName); + if (lookup) { + return lookup; + } + } + + // lookup in name lookup tables, return null if not found + if (!(lookup = FindFaceName(aFaceName))) { + // names not completely initialized, so keep track of lookup misses + if (!mFaceNameListsInitialized) { + if (!mFaceNamesMissed) { + mFaceNamesMissed = MakeUnique<nsTHashSet<nsCString>>(2); + } + mFaceNamesMissed->Insert(aFaceName); + } + } + + return lookup; +} + +gfxFontEntry* gfxPlatformFontList::LookupInSharedFaceNameList( + nsPresContext* aPresContext, const nsACString& aFaceName, + WeightRange aWeightForEntry, StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) { + nsAutoCString keyName(aFaceName); + ToLowerCase(keyName); + fontlist::FontList* list = SharedFontList(); + fontlist::Family* family = nullptr; + fontlist::Face* face = nullptr; + if (list->NumLocalFaces()) { + fontlist::LocalFaceRec* rec = list->FindLocalFace(keyName); + if (rec) { + auto* families = list->Families(); + if (families) { + family = &families[rec->mFamilyIndex]; + face = family->Faces(list)[rec->mFaceIndex].ToPtr<fontlist::Face>(list); + } + } + } else { + list->SearchForLocalFace(keyName, &family, &face); + } + if (!face || !family) { + return nullptr; + } + FontVisibility level = + aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User; + if (!IsVisibleToCSS(*family, level)) { + if (aPresContext) { + aPresContext->ReportBlockedFontFamily(*family); + } + return nullptr; + } + gfxFontEntry* fe = CreateFontEntry(face, family); + if (fe) { + fe->mIsLocalUserFont = true; + fe->mWeightRange = aWeightForEntry; + fe->mStretchRange = aStretchForEntry; + fe->mStyleRange = aStyleForEntry; + } + return fe; +} + +void gfxPlatformFontList::LoadBadUnderlineList() { + gfxFontUtils::GetPrefsFontList("font.blacklist.underline_offset", + mBadUnderlineFamilyNames); + for (auto& fam : mBadUnderlineFamilyNames) { + ToLowerCase(fam); + } + mBadUnderlineFamilyNames.Compact(); + mBadUnderlineFamilyNames.Sort(); +} + +void gfxPlatformFontList::UpdateFontList(bool aFullRebuild) { + MOZ_ASSERT(NS_IsMainThread()); + if (aFullRebuild) { + InitFontList(); + AutoLock lock(mLock); + RebuildLocalFonts(); + } else { + // The font list isn't being fully rebuilt, we're just being notified that + // character maps have been updated and so font fallback needs to be re- + // done. We only care about this if we have previously encountered a + // fallback that required cmaps that were not yet available, and so we + // asked for the async cmap loader to run. + AutoLock lock(mLock); + if (mStartedLoadingCmapsFrom != 0xffffffffu) { + InitializeCodepointsWithNoFonts(); + mStartedLoadingCmapsFrom = 0xffffffffu; + ForceGlobalReflowLocked(gfxPlatform::NeedsReframe::No); + } + } +} + +bool gfxPlatformFontList::IsVisibleToCSS(const gfxFontFamily& aFamily, + FontVisibility aVisibility) const { + return aFamily.Visibility() <= aVisibility || IsFontFamilyWhitelistActive(); +} + +bool gfxPlatformFontList::IsVisibleToCSS(const fontlist::Family& aFamily, + FontVisibility aVisibility) const { + return aFamily.Visibility() <= aVisibility || IsFontFamilyWhitelistActive(); +} + +void gfxPlatformFontList::GetFontList(nsAtom* aLangGroup, + const nsACString& aGenericFamily, + nsTArray<nsString>& aListOfFonts) { + AutoLock lock(mLock); + + if (SharedFontList()) { + fontlist::FontList* list = SharedFontList(); + const fontlist::Family* families = list->Families(); + if (families) { + for (uint32_t i = 0; i < list->NumFamilies(); i++) { + auto& f = families[i]; + if (!IsVisibleToCSS(f, FontVisibility::User) || f.IsAltLocaleFamily()) { + continue; + } + // XXX TODO: filter families for aGenericFamily, if supported by + // platform + aListOfFonts.AppendElement( + NS_ConvertUTF8toUTF16(list->LocalizedFamilyName(&f))); + } + } + return; + } + + for (const RefPtr<gfxFontFamily>& family : mFontFamilies.Values()) { + if (!IsVisibleToCSS(*family, FontVisibility::User)) { + continue; + } + if (family->FilterForFontList(aLangGroup, aGenericFamily)) { + nsAutoCString localizedFamilyName; + family->LocalizedName(localizedFamilyName); + aListOfFonts.AppendElement(NS_ConvertUTF8toUTF16(localizedFamilyName)); + } + } + + aListOfFonts.Sort(); + aListOfFonts.Compact(); +} + +void gfxPlatformFontList::GetFontFamilyList( + nsTArray<RefPtr<gfxFontFamily>>& aFamilyArray) { + AutoLock lock(mLock); + MOZ_ASSERT(aFamilyArray.IsEmpty()); + // This doesn't use ToArray, because the caller passes an AutoTArray. + aFamilyArray.SetCapacity(mFontFamilies.Count()); + for (const auto& family : mFontFamilies.Values()) { + aFamilyArray.AppendElement(family); + } +} + +already_AddRefed<gfxFont> gfxPlatformFontList::SystemFindFontForChar( + nsPresContext* aPresContext, uint32_t aCh, uint32_t aNextCh, + Script aRunScript, eFontPresentation aPresentation, + const gfxFontStyle* aStyle, FontVisibility* aVisibility) { + AutoLock lock(mLock); + FontVisibility level = + aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User; + MOZ_ASSERT(!mCodepointsWithNoFonts[level].test(aCh), + "don't call for codepoints already known to be unsupported"); + + // Try to short-circuit font fallback for U+FFFD, used to represent + // encoding errors: just use cached family from last time U+FFFD was seen. + // This helps speed up pages with lots of encoding errors, binary-as-text, + // etc. + if (aCh == 0xFFFD) { + gfxFontEntry* fontEntry = nullptr; + auto& fallbackFamily = mReplacementCharFallbackFamily[level]; + if (fallbackFamily.mShared) { + fontlist::Face* face = + fallbackFamily.mShared->FindFaceForStyle(SharedFontList(), *aStyle); + if (face) { + fontEntry = GetOrCreateFontEntryLocked(face, fallbackFamily.mShared); + *aVisibility = fallbackFamily.mShared->Visibility(); + } + } else if (fallbackFamily.mUnshared) { + fontEntry = fallbackFamily.mUnshared->FindFontForStyle(*aStyle); + *aVisibility = fallbackFamily.mUnshared->Visibility(); + } + + // this should never fail, as we must have found U+FFFD in order to set + // mReplacementCharFallbackFamily[...] at all, but better play it safe + if (fontEntry && fontEntry->HasCharacter(aCh)) { + return fontEntry->FindOrMakeFont(aStyle); + } + } + + TimeStamp start = TimeStamp::Now(); + + // search commonly available fonts + bool common = true; + FontFamily fallbackFamily; + RefPtr<gfxFont> candidate = + CommonFontFallback(aPresContext, aCh, aNextCh, aRunScript, aPresentation, + aStyle, fallbackFamily); + RefPtr<gfxFont> font; + if (candidate) { + if (aPresentation == eFontPresentation::Any) { + font = std::move(candidate); + } else { + bool hasColorGlyph = candidate->HasColorGlyphFor(aCh, aNextCh); + if (hasColorGlyph == PrefersColor(aPresentation)) { + font = std::move(candidate); + } + } + } + + // If we didn't find a common font, or it was not the preferred type (color + // or monochrome), do system-wide fallback (except for specials). + uint32_t cmapCount = 0; + if (!font) { + common = false; + font = GlobalFontFallback(aPresContext, aCh, aNextCh, aRunScript, + aPresentation, aStyle, cmapCount, fallbackFamily); + // If the font we found doesn't match the requested type, and we also found + // a candidate above, prefer that one. + if (font && aPresentation != eFontPresentation::Any && candidate) { + bool hasColorGlyph = font->HasColorGlyphFor(aCh, aNextCh); + if (hasColorGlyph != PrefersColor(aPresentation)) { + font = std::move(candidate); + } + } + } + TimeDuration elapsed = TimeStamp::Now() - start; + + LogModule* log = gfxPlatform::GetLog(eGfxLog_textrun); + + if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Warning))) { + Script script = intl::UnicodeProperties::GetScriptCode(aCh); + MOZ_LOG(log, LogLevel::Warning, + ("(textrun-systemfallback-%s) char: u+%6.6x " + "script: %d match: [%s]" + " time: %dus cmaps: %d\n", + (common ? "common" : "global"), aCh, static_cast<int>(script), + (font ? font->GetFontEntry()->Name().get() : "<none>"), + int32_t(elapsed.ToMicroseconds()), cmapCount)); + } + + // no match? add to set of non-matching codepoints + if (!font) { + mCodepointsWithNoFonts[level].set(aCh); + } else { + *aVisibility = fallbackFamily.mShared + ? fallbackFamily.mShared->Visibility() + : fallbackFamily.mUnshared->Visibility(); + if (aCh == 0xFFFD) { + mReplacementCharFallbackFamily[level] = fallbackFamily; + } + } + + // track system fallback time + static bool first = true; + int32_t intElapsed = + int32_t(first ? elapsed.ToMilliseconds() : elapsed.ToMicroseconds()); + Telemetry::Accumulate((first ? Telemetry::SYSTEM_FONT_FALLBACK_FIRST + : Telemetry::SYSTEM_FONT_FALLBACK), + intElapsed); + first = false; + + // track the script for which fallback occurred (incremented one make it + // 1-based) + Telemetry::Accumulate(Telemetry::SYSTEM_FONT_FALLBACK_SCRIPT, + int(aRunScript) + 1); + + return font.forget(); +} + +#define NUM_FALLBACK_FONTS 8 + +already_AddRefed<gfxFont> gfxPlatformFontList::CommonFontFallback( + nsPresContext* aPresContext, uint32_t aCh, uint32_t aNextCh, + Script aRunScript, eFontPresentation aPresentation, + const gfxFontStyle* aMatchStyle, FontFamily& aMatchedFamily) { + AutoTArray<const char*, NUM_FALLBACK_FONTS> defaultFallbacks; + gfxPlatform::GetPlatform()->GetCommonFallbackFonts( + aCh, aRunScript, aPresentation, defaultFallbacks); + GlobalFontMatch data(aCh, aNextCh, *aMatchStyle, aPresentation); + FontVisibility level = + aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User; + + // If a color-emoji presentation is requested, we will check any font found + // to see if it can provide this; if not, we'll remember it as a possible + // candidate but search the remainder of the list for a better choice. + RefPtr<gfxFont> candidateFont; + FontFamily candidateFamily; + auto check = [&](gfxFontEntry* aFontEntry, + FontFamily aFamily) -> already_AddRefed<gfxFont> { + RefPtr<gfxFont> font = aFontEntry->FindOrMakeFont(aMatchStyle); + if (aPresentation < eFontPresentation::EmojiDefault || + font->HasColorGlyphFor(aCh, aNextCh)) { + aMatchedFamily = aFamily; + return font.forget(); + } + // We want a color glyph but this font only has monochrome; remember it + // (unless we already have a candidate) but continue to search. + if (!candidateFont) { + candidateFont = std::move(font); + candidateFamily = aFamily; + } + return nullptr; + }; + + if (SharedFontList()) { + for (const auto name : defaultFallbacks) { + fontlist::Family* family = + FindSharedFamily(aPresContext, nsDependentCString(name)); + if (!family || !IsVisibleToCSS(*family, level)) { + continue; + } + // XXX(jfkthame) Should we fire the async cmap-loader here, or let it + // always do a potential sync initialization of the family? + family->SearchAllFontsForChar(SharedFontList(), &data); + if (data.mBestMatch) { + RefPtr<gfxFont> font = check(data.mBestMatch, FontFamily(family)); + if (font) { + return font.forget(); + } + } + } + } else { + for (const auto name : defaultFallbacks) { + gfxFontFamily* fallback = + FindFamilyByCanonicalName(nsDependentCString(name)); + if (!fallback || !IsVisibleToCSS(*fallback, level)) { + continue; + } + fallback->FindFontForChar(&data); + if (data.mBestMatch) { + RefPtr<gfxFont> font = check(data.mBestMatch, FontFamily(fallback)); + if (font) { + return font.forget(); + } + } + } + } + + // If we had a candidate that supports the character, but doesn't have the + // desired emoji-style glyph, we'll return it anyhow as nothing better was + // found. + if (candidateFont) { + aMatchedFamily = candidateFamily; + return candidateFont.forget(); + } + + return nullptr; +} + +already_AddRefed<gfxFont> gfxPlatformFontList::GlobalFontFallback( + nsPresContext* aPresContext, uint32_t aCh, uint32_t aNextCh, + Script aRunScript, eFontPresentation aPresentation, + const gfxFontStyle* aMatchStyle, uint32_t& aCmapCount, + FontFamily& aMatchedFamily) { + bool useCmaps = IsFontFamilyWhitelistActive() || + gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback(); + FontVisibility level = + aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User; + if (!useCmaps) { + // Allow platform-specific fallback code to try and find a usable font + gfxFontEntry* fe = PlatformGlobalFontFallback(aPresContext, aCh, aRunScript, + aMatchStyle, aMatchedFamily); + if (fe) { + if (aMatchedFamily.mShared) { + if (IsVisibleToCSS(*aMatchedFamily.mShared, level)) { + RefPtr<gfxFont> font = fe->FindOrMakeFont(aMatchStyle); + if (font) { + if (aPresentation == eFontPresentation::Any) { + return font.forget(); + } + bool hasColorGlyph = font->HasColorGlyphFor(aCh, aNextCh); + if (hasColorGlyph == PrefersColor(aPresentation)) { + return font.forget(); + } + } + } + } else { + if (IsVisibleToCSS(*aMatchedFamily.mUnshared, level)) { + RefPtr<gfxFont> font = fe->FindOrMakeFont(aMatchStyle); + if (font) { + if (aPresentation == eFontPresentation::Any) { + return font.forget(); + } + bool hasColorGlyph = font->HasColorGlyphFor(aCh, aNextCh); + if (hasColorGlyph == PrefersColor(aPresentation)) { + return font.forget(); + } + } + } + } + } + } + + // otherwise, try to find it among local fonts + GlobalFontMatch data(aCh, aNextCh, *aMatchStyle, aPresentation); + if (SharedFontList()) { + fontlist::Family* families = SharedFontList()->Families(); + if (families) { + for (uint32_t i = 0; i < SharedFontList()->NumFamilies(); i++) { + fontlist::Family& family = families[i]; + if (!IsVisibleToCSS(family, level)) { + continue; + } + if (!family.IsFullyInitialized() && + StaticPrefs::gfx_font_rendering_fallback_async() && + !XRE_IsParentProcess()) { + // Start loading all the missing charmaps; but this is async, + // so for now we just continue, ignoring this family. + StartCmapLoadingFromFamily(i); + } else { + family.SearchAllFontsForChar(SharedFontList(), &data); + if (data.mMatchDistance == 0.0) { + // no better style match is possible, so stop searching + break; + } + } + } + if (data.mBestMatch) { + aMatchedFamily = FontFamily(data.mMatchedSharedFamily); + return data.mBestMatch->FindOrMakeFont(aMatchStyle); + } + } + } else { + // iterate over all font families to find a font that support the + // character + for (const RefPtr<gfxFontFamily>& family : mFontFamilies.Values()) { + if (!IsVisibleToCSS(*family, level)) { + continue; + } + // evaluate all fonts in this family for a match + family->FindFontForChar(&data); + if (data.mMatchDistance == 0.0) { + // no better style match is possible, so stop searching + break; + } + } + + aCmapCount = data.mCmapsTested; + if (data.mBestMatch) { + aMatchedFamily = FontFamily(data.mMatchedFamily); + return data.mBestMatch->FindOrMakeFont(aMatchStyle); + } + } + + return nullptr; +} + +class StartCmapLoadingRunnable : public mozilla::Runnable { + public: + explicit StartCmapLoadingRunnable(uint32_t aStartIndex) + : Runnable("gfxPlatformFontList::StartCmapLoadingRunnable"), + mStartIndex(aStartIndex) {} + + NS_IMETHOD Run() override { + auto* pfl = gfxPlatformFontList::PlatformFontList(); + auto* list = pfl->SharedFontList(); + if (!list) { + return NS_OK; + } + if (mStartIndex >= list->NumFamilies()) { + return NS_OK; + } + if (XRE_IsParentProcess()) { + pfl->StartCmapLoading(list->GetGeneration(), mStartIndex); + } else { + dom::ContentChild::GetSingleton()->SendStartCmapLoading( + list->GetGeneration(), mStartIndex); + } + return NS_OK; + } + + private: + uint32_t mStartIndex; +}; + +void gfxPlatformFontList::StartCmapLoadingFromFamily(uint32_t aStartIndex) { + AutoLock lock(mLock); + if (aStartIndex >= mStartedLoadingCmapsFrom) { + // We already initiated cmap-loading from here or earlier in the list; + // no need to do it again here. + return; + } + mStartedLoadingCmapsFrom = aStartIndex; + + // If we're already on the main thread, don't bother dispatching a runnable + // here to kick off the loading process, just do it directly. + if (NS_IsMainThread()) { + auto* list = SharedFontList(); + if (XRE_IsParentProcess()) { + StartCmapLoading(list->GetGeneration(), aStartIndex); + } else { + dom::ContentChild::GetSingleton()->SendStartCmapLoading( + list->GetGeneration(), aStartIndex); + } + } else { + NS_DispatchToMainThread(new StartCmapLoadingRunnable(aStartIndex)); + } +} + +class LoadCmapsRunnable : public CancelableRunnable { + class WillShutdownObserver : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + explicit WillShutdownObserver(LoadCmapsRunnable* aRunnable) + : mRunnable(aRunnable) {} + + void Remove() { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID); + } + mRunnable = nullptr; + } + + protected: + virtual ~WillShutdownObserver() = default; + + LoadCmapsRunnable* mRunnable; + }; + + public: + explicit LoadCmapsRunnable(uint32_t aGeneration, uint32_t aFamilyIndex) + : CancelableRunnable("gfxPlatformFontList::LoadCmapsRunnable"), + mGeneration(aGeneration), + mStartIndex(aFamilyIndex), + mIndex(aFamilyIndex) { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + mObserver = new WillShutdownObserver(this); + obs->AddObserver(mObserver, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false); + } + } + + virtual ~LoadCmapsRunnable() { + if (mObserver) { + mObserver->Remove(); + } + } + + // Reset the current family index, if the value passed is earlier than our + // original starting position. We don't "reset" if it would move the current + // position forward, or back into the already-scanned range. + // We could optimize further by remembering the current position reached, + // and then skipping ahead from the original start, but it doesn't seem worth + // extra complexity for a task that usually only happens once, and already- + // processed families will be skipped pretty quickly in Run() anyhow. + void MaybeResetIndex(uint32_t aFamilyIndex) { + if (aFamilyIndex < mStartIndex) { + mStartIndex = aFamilyIndex; + mIndex = aFamilyIndex; + } + } + + nsresult Cancel() override { + mIsCanceled = true; + return NS_OK; + } + + NS_IMETHOD Run() override { + if (mIsCanceled) { + return NS_OK; + } + auto* pfl = gfxPlatformFontList::PlatformFontList(); + auto* list = pfl->SharedFontList(); + MOZ_ASSERT(list); + if (!list) { + return NS_OK; + } + if (mGeneration != list->GetGeneration()) { + return NS_OK; + } + uint32_t numFamilies = list->NumFamilies(); + if (mIndex >= numFamilies) { + return NS_OK; + } + auto* families = list->Families(); + // Skip any families that are already initialized. + while (mIndex < numFamilies && families[mIndex].IsFullyInitialized()) { + ++mIndex; + } + // Fully process one family, and advance index. + if (mIndex < numFamilies) { + Unused << pfl->InitializeFamily(&families[mIndex], true); + ++mIndex; + } + // If there are more families to initialize, post ourselves back to the + // idle queue to handle the next one; otherwise we're finished and we need + // to notify content processes to update their rendering. + if (mIndex < numFamilies) { + RefPtr<CancelableRunnable> task = this; + NS_DispatchToMainThreadQueue(task.forget(), EventQueuePriority::Idle); + } else { + pfl->Lock(); + pfl->CancelLoadCmapsTask(); + pfl->InitializeCodepointsWithNoFonts(); + dom::ContentParent::NotifyUpdatedFonts(false); + pfl->Unlock(); + } + return NS_OK; + } + + private: + uint32_t mGeneration; + uint32_t mStartIndex; + uint32_t mIndex; + bool mIsCanceled = false; + + RefPtr<WillShutdownObserver> mObserver; +}; + +NS_IMPL_ISUPPORTS(LoadCmapsRunnable::WillShutdownObserver, nsIObserver) + +NS_IMETHODIMP +LoadCmapsRunnable::WillShutdownObserver::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + if (!nsCRT::strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID)) { + if (mRunnable) { + mRunnable->Cancel(); + } + } else { + MOZ_ASSERT_UNREACHABLE("unexpected notification topic"); + } + return NS_OK; +} + +void gfxPlatformFontList::StartCmapLoading(uint32_t aGeneration, + uint32_t aStartIndex) { + MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); + if (aGeneration != SharedFontList()->GetGeneration()) { + return; + } + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + return; + } + if (mLoadCmapsRunnable) { + // We already have a runnable; just make sure it covers the full range of + // families needed. + static_cast<LoadCmapsRunnable*>(mLoadCmapsRunnable.get()) + ->MaybeResetIndex(aStartIndex); + return; + } + mLoadCmapsRunnable = new LoadCmapsRunnable(aGeneration, aStartIndex); + RefPtr<CancelableRunnable> task = mLoadCmapsRunnable; + NS_DispatchToMainThreadQueue(task.forget(), EventQueuePriority::Idle); +} + +gfxFontFamily* gfxPlatformFontList::CheckFamily(gfxFontFamily* aFamily) { + if (aFamily && !aFamily->HasStyles()) { + aFamily->FindStyleVariations(); + } + + if (aFamily && aFamily->FontListLength() == 0) { + // Failed to load any faces for this family, so discard it. + nsAutoCString key; + GenerateFontListKey(aFamily->Name(), key); + mFontFamilies.Remove(key); + return nullptr; + } + + return aFamily; +} + +bool gfxPlatformFontList::FindAndAddFamilies( + nsPresContext* aPresContext, StyleGenericFontFamily aGeneric, + const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput, + FindFamiliesFlags aFlags, gfxFontStyle* aStyle, nsAtom* aLanguage, + gfxFloat aDevToCssSize) { + AutoLock lock(mLock); + +#ifdef DEBUG + auto initialLength = aOutput->Length(); +#endif + + bool didFind = + FindAndAddFamiliesLocked(aPresContext, aGeneric, aFamily, aOutput, aFlags, + aStyle, aLanguage, aDevToCssSize); +#ifdef DEBUG + auto finalLength = aOutput->Length(); + // Validate the expectation that the output-array grows if we return true, + // or remains the same (probably empty) if we return false. + MOZ_ASSERT_IF(didFind, finalLength > initialLength); + MOZ_ASSERT_IF(!didFind, finalLength == initialLength); +#endif + + return didFind; +} + +bool gfxPlatformFontList::FindAndAddFamiliesLocked( + nsPresContext* aPresContext, StyleGenericFontFamily aGeneric, + const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput, + FindFamiliesFlags aFlags, gfxFontStyle* aStyle, nsAtom* aLanguage, + gfxFloat aDevToCssSize) { + nsAutoCString key; + GenerateFontListKey(aFamily, key); + + bool allowHidden = bool(aFlags & FindFamiliesFlags::eSearchHiddenFamilies); + FontVisibility visibilityLevel = + aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User; + + // If this font lookup is the result of resolving a CSS generic (not a direct + // font-family request by the page), and RFP settings allow generics to be + // unrestricted, bump the effective visibility level applied here so as to + // allow user-installed fonts to be used. + if (visibilityLevel < FontVisibility::User && + aGeneric != StyleGenericFontFamily::None && + !aPresContext->Document()->ShouldResistFingerprinting( + RFPTarget::FontVisibilityRestrictGenerics)) { + visibilityLevel = FontVisibility::User; + } + + if (SharedFontList()) { + fontlist::Family* family = SharedFontList()->FindFamily(key); + // If not found, and other family names have not yet been initialized, + // initialize the rest of the list and try again. This is done lazily + // since reading name table entries is expensive. + // Although ASCII localized family names are possible they don't occur + // in practice, so avoid pulling in names at startup. + if (!family && !mOtherFamilyNamesInitialized) { + bool triggerLoading = true; + bool mayDefer = + !(aFlags & FindFamiliesFlags::eForceOtherFamilyNamesLoading); + if (IsAscii(key)) { + // If `key` is an ASCII name, only trigger loading if it includes a + // space, and the "base" name (up to the last space) exists as a known + // family, so that this might be a legacy styled-family name. + const char* data = key.BeginReading(); + int32_t index = key.Length(); + while (--index > 0) { + if (data[index] == ' ') { + break; + } + } + if (index <= 0 || + !SharedFontList()->FindFamily(nsAutoCString(key.get(), index))) { + triggerLoading = false; + } + } + if (triggerLoading) { + if (InitOtherFamilyNames(mayDefer)) { + family = SharedFontList()->FindFamily(key); + } + } + if (!family && !mOtherFamilyNamesInitialized && + !(aFlags & FindFamiliesFlags::eNoAddToNamesMissedWhenSearching)) { + AddToMissedNames(key); + } + } + // Check whether the family we found is actually allowed to be looked up, + // according to current font-visibility prefs. + if (family) { + bool visible = IsVisibleToCSS(*family, visibilityLevel); + if (visible || (allowHidden && family->IsHidden())) { + aOutput->AppendElement(FamilyAndGeneric(family, aGeneric)); + return true; + } + if (aPresContext) { + aPresContext->ReportBlockedFontFamily(*family); + } + } + return false; + } + + NS_ASSERTION(mFontFamilies.Count() != 0, + "system font list was not initialized correctly"); + + auto isBlockedByVisibilityLevel = [=](gfxFontFamily* aFamily) -> bool { + bool visible = IsVisibleToCSS(*aFamily, visibilityLevel); + if (visible || (allowHidden && aFamily->IsHidden())) { + return false; + } + if (aPresContext) { + aPresContext->ReportBlockedFontFamily(*aFamily); + } + return true; + }; + + // lookup in canonical (i.e. English) family name list + gfxFontFamily* familyEntry = mFontFamilies.GetWeak(key); + if (familyEntry) { + if (isBlockedByVisibilityLevel(familyEntry)) { + return false; + } + } + + // if not found, lookup in other family names list (mostly localized names) + if (!familyEntry) { + familyEntry = mOtherFamilyNames.GetWeak(key); + } + if (familyEntry) { + if (isBlockedByVisibilityLevel(familyEntry)) { + return false; + } + } + + // if still not found and other family names not yet fully initialized, + // initialize the rest of the list and try again. this is done lazily + // since reading name table entries is expensive. + // although ASCII localized family names are possible they don't occur + // in practice so avoid pulling in names at startup + if (!familyEntry && !mOtherFamilyNamesInitialized && !IsAscii(aFamily)) { + InitOtherFamilyNames( + !(aFlags & FindFamiliesFlags::eForceOtherFamilyNamesLoading)); + familyEntry = mOtherFamilyNames.GetWeak(key); + if (!familyEntry && !mOtherFamilyNamesInitialized && + !(aFlags & FindFamiliesFlags::eNoAddToNamesMissedWhenSearching)) { + // localized family names load timed out, add name to list of + // names to check after localized names are loaded + AddToMissedNames(key); + } + if (familyEntry) { + if (isBlockedByVisibilityLevel(familyEntry)) { + return false; + } + } + } + + familyEntry = CheckFamily(familyEntry); + + // If we failed to find the requested family, check for a space in the + // name; if found, and if the "base" name (up to the last space) exists + // as a family, then this might be a legacy GDI-style family name for + // an additional weight/width. Try searching the faces of the base family + // and create any corresponding legacy families. + if (!familyEntry && + !(aFlags & FindFamiliesFlags::eNoSearchForLegacyFamilyNames)) { + // We don't have nsAString::RFindChar, so look for a space manually + const char* data = aFamily.BeginReading(); + int32_t index = aFamily.Length(); + while (--index > 0) { + if (data[index] == ' ') { + break; + } + } + if (index > 0) { + gfxFontFamily* base = + FindUnsharedFamily(aPresContext, Substring(aFamily, 0, index), + FindFamiliesFlags::eNoSearchForLegacyFamilyNames); + // If we found the "base" family name, and if it has members with + // legacy names, this will add corresponding font-family entries to + // the mOtherFamilyNames list; then retry the legacy-family search. + if (base && base->CheckForLegacyFamilyNames(this)) { + familyEntry = mOtherFamilyNames.GetWeak(key); + } + if (familyEntry) { + if (isBlockedByVisibilityLevel(familyEntry)) { + return false; + } + } + } + } + + if (familyEntry) { + aOutput->AppendElement(FamilyAndGeneric(familyEntry, aGeneric)); + return true; + } + + return false; +} + +void gfxPlatformFontList::AddToMissedNames(const nsCString& aKey) { + if (!mOtherNamesMissed) { + mOtherNamesMissed = MakeUnique<nsTHashSet<nsCString>>(2); + } + mOtherNamesMissed->Insert(aKey); +} + +fontlist::Family* gfxPlatformFontList::FindSharedFamily( + nsPresContext* aPresContext, const nsACString& aFamily, + FindFamiliesFlags aFlags, gfxFontStyle* aStyle, nsAtom* aLanguage, + gfxFloat aDevToCss) { + if (!SharedFontList()) { + return nullptr; + } + AutoTArray<FamilyAndGeneric, 1> families; + if (!FindAndAddFamiliesLocked(aPresContext, StyleGenericFontFamily::None, + aFamily, &families, aFlags, aStyle, aLanguage, + aDevToCss) || + !families[0].mFamily.mShared) { + return nullptr; + } + fontlist::Family* family = families[0].mFamily.mShared; + if (!family->IsInitialized()) { + if (!InitializeFamily(family)) { + return nullptr; + } + } + return family; +} + +class InitializeFamilyRunnable : public mozilla::Runnable { + public: + explicit InitializeFamilyRunnable(uint32_t aFamilyIndex, bool aLoadCmaps) + : Runnable("gfxPlatformFontList::InitializeFamilyRunnable"), + mIndex(aFamilyIndex), + mLoadCmaps(aLoadCmaps) {} + + NS_IMETHOD Run() override { + auto list = gfxPlatformFontList::PlatformFontList()->SharedFontList(); + if (!list) { + return NS_OK; + } + if (mIndex >= list->NumFamilies()) { + // Out of range? Maybe the list got reinitialized since this request + // was posted - just ignore it. + return NS_OK; + } + dom::ContentChild::GetSingleton()->SendInitializeFamily( + list->GetGeneration(), mIndex, mLoadCmaps); + return NS_OK; + } + + private: + uint32_t mIndex; + bool mLoadCmaps; +}; + +bool gfxPlatformFontList::InitializeFamily(fontlist::Family* aFamily, + bool aLoadCmaps) { + MOZ_ASSERT(SharedFontList()); + auto list = SharedFontList(); + if (!XRE_IsParentProcess()) { + auto* families = list->Families(); + if (!families) { + return false; + } + uint32_t index = aFamily - families; + if (index >= list->NumFamilies()) { + return false; + } + if (NS_IsMainThread()) { + dom::ContentChild::GetSingleton()->SendInitializeFamily( + list->GetGeneration(), index, aLoadCmaps); + } else { + NS_DispatchToMainThread(new InitializeFamilyRunnable(index, aLoadCmaps)); + } + return aFamily->IsInitialized(); + } + + if (!aFamily->IsInitialized()) { + // The usual case: we're being asked to populate the face list. + AutoTArray<fontlist::Face::InitData, 16> faceList; + GetFacesInitDataForFamily(aFamily, faceList, aLoadCmaps); + aFamily->AddFaces(list, faceList); + } else { + // The family's face list was already initialized, but if aLoadCmaps is + // true we also want to eagerly load character maps. This is used when a + // child process is doing SearchAllFontsForChar, to have the parent load + // all the cmaps at once and reduce IPC traffic (and content-process file + // access overhead, which is crippling for DirectWrite on Windows). + if (aLoadCmaps) { + auto* faces = aFamily->Faces(list); + if (faces) { + for (size_t i = 0; i < aFamily->NumFaces(); i++) { + auto* face = faces[i].ToPtr<fontlist::Face>(list); + if (face && face->mCharacterMap.IsNull()) { + // We don't want to cache this font entry, as the parent will most + // likely never use it again; it's just to populate the charmap for + // the benefit of the child process. + RefPtr<gfxFontEntry> fe = CreateFontEntry(face, aFamily); + if (fe) { + fe->ReadCMAP(); + } + } + } + } + } + } + + if (aLoadCmaps && aFamily->IsInitialized()) { + aFamily->SetupFamilyCharMap(list); + } + + return aFamily->IsInitialized(); +} + +gfxFontEntry* gfxPlatformFontList::FindFontForFamily( + nsPresContext* aPresContext, const nsACString& aFamily, + const gfxFontStyle* aStyle) { + AutoLock lock(mLock); + + nsAutoCString key; + GenerateFontListKey(aFamily, key); + + FontFamily family = FindFamily(aPresContext, key); + if (family.IsNull()) { + return nullptr; + } + if (family.mShared) { + auto face = family.mShared->FindFaceForStyle(SharedFontList(), *aStyle); + if (!face) { + return nullptr; + } + return GetOrCreateFontEntryLocked(face, family.mShared); + } + return family.mUnshared->FindFontForStyle(*aStyle); +} + +gfxFontEntry* gfxPlatformFontList::GetOrCreateFontEntryLocked( + fontlist::Face* aFace, const fontlist::Family* aFamily) { + return mFontEntries + .LookupOrInsertWith(aFace, + [=] { return CreateFontEntry(aFace, aFamily); }) + .get(); +} + +void gfxPlatformFontList::AddOtherFamilyNames( + gfxFontFamily* aFamilyEntry, const nsTArray<nsCString>& aOtherFamilyNames) { + AutoLock lock(mLock); + + for (const auto& name : aOtherFamilyNames) { + nsAutoCString key; + GenerateFontListKey(name, key); + + mOtherFamilyNames.LookupOrInsertWith(key, [&] { + LOG_FONTLIST( + ("(fontlist-otherfamily) canonical family: %s, other family: %s\n", + aFamilyEntry->Name().get(), name.get())); + if (mBadUnderlineFamilyNames.ContainsSorted(key)) { + aFamilyEntry->SetBadUnderlineFamily(); + } + return RefPtr{aFamilyEntry}; + }); + } +} + +void gfxPlatformFontList::AddFullnameLocked(gfxFontEntry* aFontEntry, + const nsCString& aFullname) { + mExtraNames->mFullnames.LookupOrInsertWith(aFullname, [&] { + LOG_FONTLIST(("(fontlist-fullname) name: %s, fullname: %s\n", + aFontEntry->Name().get(), aFullname.get())); + return RefPtr{aFontEntry}; + }); +} + +void gfxPlatformFontList::AddPostscriptNameLocked( + gfxFontEntry* aFontEntry, const nsCString& aPostscriptName) { + mExtraNames->mPostscriptNames.LookupOrInsertWith(aPostscriptName, [&] { + LOG_FONTLIST(("(fontlist-postscript) name: %s, psname: %s\n", + aFontEntry->Name().get(), aPostscriptName.get())); + return RefPtr{aFontEntry}; + }); +} + +bool gfxPlatformFontList::GetStandardFamilyName(const nsCString& aFontName, + nsACString& aFamilyName) { + AutoLock lock(mLock); + FontFamily family = FindFamily(nullptr, aFontName); + if (family.IsNull()) { + return false; + } + return GetLocalizedFamilyName(family, aFamilyName); +} + +bool gfxPlatformFontList::GetLocalizedFamilyName(const FontFamily& aFamily, + nsACString& aFamilyName) { + if (aFamily.mShared) { + aFamilyName = SharedFontList()->LocalizedFamilyName(aFamily.mShared); + return true; + } + if (aFamily.mUnshared) { + aFamily.mUnshared->LocalizedName(aFamilyName); + return true; + } + return false; // leaving the aFamilyName outparam untouched +} + +FamilyAndGeneric gfxPlatformFontList::GetDefaultFontFamily( + const nsACString& aLangGroup, const nsACString& aGenericFamily) { + if (NS_WARN_IF(aLangGroup.IsEmpty()) || + NS_WARN_IF(aGenericFamily.IsEmpty())) { + return FamilyAndGeneric(); + } + + AutoLock lock(mLock); + + nsAutoCString value; + AutoTArray<nsCString, 4> names; + if (mFontPrefs->LookupNameList(PrefName(aGenericFamily, aLangGroup), value)) { + gfxFontUtils::ParseFontList(value, names); + } + + for (const nsCString& name : names) { + FontFamily family = FindFamily(nullptr, name); + if (!family.IsNull()) { + return FamilyAndGeneric(family); + } + } + + return FamilyAndGeneric(); +} + +ShmemCharMapHashEntry::ShmemCharMapHashEntry(const gfxSparseBitSet* aCharMap) + : mList(gfxPlatformFontList::PlatformFontList()->SharedFontList()), + mHash(aCharMap->GetChecksum()) { + size_t len = SharedBitSet::RequiredSize(*aCharMap); + mCharMap = mList->Alloc(len); + SharedBitSet::Create(mCharMap.ToPtr(mList, len), len, *aCharMap); +} + +fontlist::Pointer gfxPlatformFontList::GetShmemCharMapLocked( + const gfxSparseBitSet* aCmap) { + auto* entry = mShmemCharMaps.GetEntry(aCmap); + if (!entry) { + entry = mShmemCharMaps.PutEntry(aCmap); + } + return entry->GetCharMap(); +} + +// Lookup aCmap in the shared cmap set, adding if not already present. +// This is the only way for a reference to a gfxCharacterMap to be acquired +// by another thread than its original creator. +already_AddRefed<gfxCharacterMap> gfxPlatformFontList::FindCharMap( + gfxCharacterMap* aCmap) { + // Lock to prevent potentially racing against MaybeRemoveCmap. + AutoLock lock(mLock); + + // Find existing entry or insert a new one (which will add a reference). + aCmap->CalcHash(); + aCmap->mShared = true; // Set the shared flag in preparation for adding + // to the global table. + RefPtr cmap = mSharedCmaps.PutEntry(aCmap)->GetKey(); + + // If we ended up finding a different, pre-existing entry, clear the + // shared flag on this one so that it'll get deleted on Release(). + if (cmap.get() != aCmap) { + aCmap->mShared = false; + } + + return cmap.forget(); +} + +// Potentially remove the charmap from the shared cmap set. This is called +// when a user of the charmap drops a reference and the refcount goes to 1; +// in that case, it is possible our shared set is the only remaining user +// of the object, and we should remove it. +// Note that aCharMap might have already been freed, so we must not try to +// dereference it until we have checked that it's still present in our table. +void gfxPlatformFontList::MaybeRemoveCmap(gfxCharacterMap* aCharMap) { + // Lock so that nobody else can get a reference via FindCharMap while we're + // checking here. + AutoLock lock(mLock); + + // Skip lookups during teardown. + if (!mSharedCmaps.Count()) { + return; + } + + // aCharMap needs to match the entry and be the same ptr and still have a + // refcount of exactly 1 (i.e. we hold the only reference) before removing. + // If we're racing another thread, it might already have been removed, in + // which case GetEntry will not find it and we won't try to dereference the + // already-freed pointer. + CharMapHashKey* found = + mSharedCmaps.GetEntry(const_cast<gfxCharacterMap*>(aCharMap)); + if (found && found->GetKey() == aCharMap && aCharMap->RefCount() == 1) { + // Forget our reference to the object that's being deleted, without + // calling Release() on it. + Unused << found->mCharMap.forget(); + + // Do the deletion. + delete aCharMap; + + // Log this as a "Release" to keep leak-checking correct. + NS_LOG_RELEASE(aCharMap, 0, "gfxCharacterMap"); + + mSharedCmaps.RemoveEntry(found); + } +} + +static void GetSystemUIFontFamilies([[maybe_unused]] nsAtom* aLangGroup, + nsTArray<nsCString>& aFamilies) { + // TODO: On macOS, use CTCreateUIFontForLanguage or such thing (though the + // code below ends up using [NSFont systemFontOfSize: 0.0]. + nsFont systemFont; + gfxFontStyle fontStyle; + nsAutoString systemFontName; + if (!LookAndFeel::GetFont(StyleSystemFont::Menu, systemFontName, fontStyle)) { + return; + } + systemFontName.Trim("\"'"); + CopyUTF16toUTF8(systemFontName, *aFamilies.AppendElement()); +} + +void gfxPlatformFontList::ResolveGenericFontNames( + nsPresContext* aPresContext, StyleGenericFontFamily aGenericType, + eFontPrefLang aPrefLang, PrefFontList* aGenericFamilies) { + const char* langGroupStr = GetPrefLangName(aPrefLang); + const char* generic = GetGenericName(aGenericType); + + if (!generic) { + return; + } + + AutoTArray<nsCString, 4> genericFamilies; + + // load family for "font.name.generic.lang" + PrefName prefName(generic, langGroupStr); + nsAutoCString value; + if (mFontPrefs->LookupName(prefName, value)) { + gfxFontUtils::ParseFontList(value, genericFamilies); + } + + // load fonts for "font.name-list.generic.lang" + if (mFontPrefs->LookupNameList(prefName, value)) { + gfxFontUtils::ParseFontList(value, genericFamilies); + } + + nsAtom* langGroup = GetLangGroupForPrefLang(aPrefLang); + MOZ_ASSERT(langGroup, "null lang group for pref lang"); + + if (aGenericType == StyleGenericFontFamily::SystemUi) { + GetSystemUIFontFamilies(langGroup, genericFamilies); + } + + GetFontFamiliesFromGenericFamilies( + aPresContext, aGenericType, genericFamilies, langGroup, aGenericFamilies); + +#if 0 // dump out generic mappings + printf("%s ===> ", NamePref(generic, langGroupStr).get()); + for (uint32_t k = 0; k < aGenericFamilies->Length(); k++) { + if (k > 0) printf(", "); + printf("%s", (*aGenericFamilies)[k].mIsShared + ? (*aGenericFamilies)[k].mShared->DisplayName().AsString(SharedFontList()).get() + : (*aGenericFamilies)[k].mUnshared->Name().get()); + } + printf("\n"); +#endif +} + +void gfxPlatformFontList::ResolveEmojiFontNames( + nsPresContext* aPresContext, PrefFontList* aGenericFamilies) { + // emoji preference has no lang name + AutoTArray<nsCString, 4> genericFamilies; + + nsAutoCString value; + if (mFontPrefs->LookupNameList(PrefName("emoji", ""), value)) { + gfxFontUtils::ParseFontList(value, genericFamilies); + } + + GetFontFamiliesFromGenericFamilies( + aPresContext, StyleGenericFontFamily::MozEmoji, genericFamilies, nullptr, + aGenericFamilies); +} + +void gfxPlatformFontList::GetFontFamiliesFromGenericFamilies( + nsPresContext* aPresContext, StyleGenericFontFamily aGenericType, + nsTArray<nsCString>& aGenericNameFamilies, nsAtom* aLangGroup, + PrefFontList* aGenericFamilies) { + // lookup and add platform fonts uniquely + for (const nsCString& genericFamily : aGenericNameFamilies) { + AutoTArray<FamilyAndGeneric, 10> families; + FindAndAddFamiliesLocked(aPresContext, aGenericType, genericFamily, + &families, FindFamiliesFlags(0), nullptr, + aLangGroup); + for (const FamilyAndGeneric& f : families) { + if (!aGenericFamilies->Contains(f.mFamily)) { + aGenericFamilies->AppendElement(f.mFamily); + } + } + } +} + +gfxPlatformFontList::PrefFontList* +gfxPlatformFontList::GetPrefFontsLangGroupLocked( + nsPresContext* aPresContext, StyleGenericFontFamily aGenericType, + eFontPrefLang aPrefLang) { + if (aGenericType == StyleGenericFontFamily::MozEmoji || + aPrefLang == eFontPrefLang_Emoji) { + // Emoji font has no lang + PrefFontList* prefFonts = mEmojiPrefFont.get(); + if (MOZ_UNLIKELY(!prefFonts)) { + prefFonts = new PrefFontList; + ResolveEmojiFontNames(aPresContext, prefFonts); + mEmojiPrefFont.reset(prefFonts); + } + return prefFonts; + } + + auto index = static_cast<size_t>(aGenericType); + PrefFontList* prefFonts = mLangGroupPrefFonts[aPrefLang][index].get(); + if (MOZ_UNLIKELY(!prefFonts)) { + prefFonts = new PrefFontList; + ResolveGenericFontNames(aPresContext, aGenericType, aPrefLang, prefFonts); + mLangGroupPrefFonts[aPrefLang][index].reset(prefFonts); + } + return prefFonts; +} + +void gfxPlatformFontList::AddGenericFonts( + nsPresContext* aPresContext, StyleGenericFontFamily aGenericType, + nsAtom* aLanguage, nsTArray<FamilyAndGeneric>& aFamilyList) { + AutoLock lock(mLock); + + // map lang ==> langGroup + nsAtom* langGroup = GetLangGroup(aLanguage); + + // langGroup ==> prefLang + eFontPrefLang prefLang = GetFontPrefLangFor(langGroup); + + // lookup pref fonts + PrefFontList* prefFonts = + GetPrefFontsLangGroupLocked(aPresContext, aGenericType, prefLang); + + if (!prefFonts->IsEmpty()) { + aFamilyList.SetCapacity(aFamilyList.Length() + prefFonts->Length()); + for (auto& f : *prefFonts) { + aFamilyList.AppendElement(FamilyAndGeneric(f, aGenericType)); + } + } +} + +static nsAtom* PrefLangToLangGroups(uint32_t aIndex) { + // static array here avoids static constructor + static nsAtom* gPrefLangToLangGroups[] = { +#define FONT_PREF_LANG(enum_id_, str_, atom_id_) nsGkAtoms::atom_id_ +#include "gfxFontPrefLangList.h" +#undef FONT_PREF_LANG + }; + + return aIndex < ArrayLength(gPrefLangToLangGroups) + ? gPrefLangToLangGroups[aIndex] + : nsGkAtoms::Unicode; +} + +eFontPrefLang gfxPlatformFontList::GetFontPrefLangFor(const char* aLang) { + if (!aLang || !aLang[0]) { + return eFontPrefLang_Others; + } + for (uint32_t i = 0; i < ArrayLength(gPrefLangNames); ++i) { + if (!nsCRT::strcasecmp(gPrefLangNames[i], aLang)) { + return eFontPrefLang(i); + } + } + return eFontPrefLang_Others; +} + +eFontPrefLang gfxPlatformFontList::GetFontPrefLangFor(nsAtom* aLang) { + if (!aLang) return eFontPrefLang_Others; + nsAutoCString lang; + aLang->ToUTF8String(lang); + return GetFontPrefLangFor(lang.get()); +} + +nsAtom* gfxPlatformFontList::GetLangGroupForPrefLang(eFontPrefLang aLang) { + // the special CJK set pref lang should be resolved into separate + // calls to individual CJK pref langs before getting here + NS_ASSERTION(aLang != eFontPrefLang_CJKSet, "unresolved CJK set pref lang"); + + return PrefLangToLangGroups(uint32_t(aLang)); +} + +const char* gfxPlatformFontList::GetPrefLangName(eFontPrefLang aLang) { + if (uint32_t(aLang) < ArrayLength(gPrefLangNames)) { + return gPrefLangNames[uint32_t(aLang)]; + } + return nullptr; +} + +eFontPrefLang gfxPlatformFontList::GetFontPrefLangFor(uint32_t aCh) { + switch (ublock_getCode(aCh)) { + case UBLOCK_BASIC_LATIN: + case UBLOCK_LATIN_1_SUPPLEMENT: + case UBLOCK_LATIN_EXTENDED_A: + case UBLOCK_LATIN_EXTENDED_B: + case UBLOCK_IPA_EXTENSIONS: + case UBLOCK_SPACING_MODIFIER_LETTERS: + case UBLOCK_LATIN_EXTENDED_ADDITIONAL: + case UBLOCK_LATIN_EXTENDED_C: + case UBLOCK_LATIN_EXTENDED_D: + case UBLOCK_LATIN_EXTENDED_E: + case UBLOCK_PHONETIC_EXTENSIONS: + return eFontPrefLang_Western; + case UBLOCK_GREEK: + case UBLOCK_GREEK_EXTENDED: + return eFontPrefLang_Greek; + case UBLOCK_CYRILLIC: + case UBLOCK_CYRILLIC_SUPPLEMENT: + case UBLOCK_CYRILLIC_EXTENDED_A: + case UBLOCK_CYRILLIC_EXTENDED_B: + case UBLOCK_CYRILLIC_EXTENDED_C: + return eFontPrefLang_Cyrillic; + case UBLOCK_ARMENIAN: + return eFontPrefLang_Armenian; + case UBLOCK_HEBREW: + return eFontPrefLang_Hebrew; + case UBLOCK_ARABIC: + case UBLOCK_ARABIC_PRESENTATION_FORMS_A: + case UBLOCK_ARABIC_PRESENTATION_FORMS_B: + case UBLOCK_ARABIC_SUPPLEMENT: + case UBLOCK_ARABIC_EXTENDED_A: + case UBLOCK_ARABIC_MATHEMATICAL_ALPHABETIC_SYMBOLS: + return eFontPrefLang_Arabic; + case UBLOCK_DEVANAGARI: + case UBLOCK_DEVANAGARI_EXTENDED: + return eFontPrefLang_Devanagari; + case UBLOCK_BENGALI: + return eFontPrefLang_Bengali; + case UBLOCK_GURMUKHI: + return eFontPrefLang_Gurmukhi; + case UBLOCK_GUJARATI: + return eFontPrefLang_Gujarati; + case UBLOCK_ORIYA: + return eFontPrefLang_Oriya; + case UBLOCK_TAMIL: + return eFontPrefLang_Tamil; + case UBLOCK_TELUGU: + return eFontPrefLang_Telugu; + case UBLOCK_KANNADA: + return eFontPrefLang_Kannada; + case UBLOCK_MALAYALAM: + return eFontPrefLang_Malayalam; + case UBLOCK_SINHALA: + case UBLOCK_SINHALA_ARCHAIC_NUMBERS: + return eFontPrefLang_Sinhala; + case UBLOCK_THAI: + return eFontPrefLang_Thai; + case UBLOCK_TIBETAN: + return eFontPrefLang_Tibetan; + case UBLOCK_GEORGIAN: + case UBLOCK_GEORGIAN_SUPPLEMENT: + case UBLOCK_GEORGIAN_EXTENDED: + return eFontPrefLang_Georgian; + case UBLOCK_HANGUL_JAMO: + case UBLOCK_HANGUL_COMPATIBILITY_JAMO: + case UBLOCK_HANGUL_SYLLABLES: + case UBLOCK_HANGUL_JAMO_EXTENDED_A: + case UBLOCK_HANGUL_JAMO_EXTENDED_B: + return eFontPrefLang_Korean; + case UBLOCK_ETHIOPIC: + case UBLOCK_ETHIOPIC_EXTENDED: + case UBLOCK_ETHIOPIC_SUPPLEMENT: + case UBLOCK_ETHIOPIC_EXTENDED_A: + return eFontPrefLang_Ethiopic; + case UBLOCK_UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS: + case UBLOCK_UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_EXTENDED: + return eFontPrefLang_Canadian; + case UBLOCK_KHMER: + case UBLOCK_KHMER_SYMBOLS: + return eFontPrefLang_Khmer; + case UBLOCK_CJK_RADICALS_SUPPLEMENT: + case UBLOCK_KANGXI_RADICALS: + case UBLOCK_IDEOGRAPHIC_DESCRIPTION_CHARACTERS: + case UBLOCK_CJK_SYMBOLS_AND_PUNCTUATION: + case UBLOCK_HIRAGANA: + case UBLOCK_KATAKANA: + case UBLOCK_BOPOMOFO: + case UBLOCK_KANBUN: + case UBLOCK_BOPOMOFO_EXTENDED: + case UBLOCK_ENCLOSED_CJK_LETTERS_AND_MONTHS: + case UBLOCK_CJK_COMPATIBILITY: + case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A: + case UBLOCK_CJK_UNIFIED_IDEOGRAPHS: + case UBLOCK_CJK_COMPATIBILITY_IDEOGRAPHS: + case UBLOCK_CJK_COMPATIBILITY_FORMS: + case UBLOCK_SMALL_FORM_VARIANTS: + case UBLOCK_HALFWIDTH_AND_FULLWIDTH_FORMS: + case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B: + case UBLOCK_CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT: + case UBLOCK_KATAKANA_PHONETIC_EXTENSIONS: + case UBLOCK_CJK_STROKES: + case UBLOCK_VERTICAL_FORMS: + case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_C: + case UBLOCK_KANA_SUPPLEMENT: + case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_D: + case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_E: + case UBLOCK_IDEOGRAPHIC_SYMBOLS_AND_PUNCTUATION: + case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_F: + case UBLOCK_KANA_EXTENDED_A: + return eFontPrefLang_CJKSet; + case UBLOCK_MATHEMATICAL_OPERATORS: + case UBLOCK_MATHEMATICAL_ALPHANUMERIC_SYMBOLS: + case UBLOCK_MISCELLANEOUS_MATHEMATICAL_SYMBOLS_A: + case UBLOCK_MISCELLANEOUS_MATHEMATICAL_SYMBOLS_B: + case UBLOCK_SUPPLEMENTAL_MATHEMATICAL_OPERATORS: + return eFontPrefLang_Mathematics; + default: + return eFontPrefLang_Others; + } +} + +bool gfxPlatformFontList::IsLangCJK(eFontPrefLang aLang) { + switch (aLang) { + case eFontPrefLang_Japanese: + case eFontPrefLang_ChineseTW: + case eFontPrefLang_ChineseCN: + case eFontPrefLang_ChineseHK: + case eFontPrefLang_Korean: + case eFontPrefLang_CJKSet: + return true; + default: + return false; + } +} + +void gfxPlatformFontList::GetLangPrefs(eFontPrefLang aPrefLangs[], + uint32_t& aLen, eFontPrefLang aCharLang, + eFontPrefLang aPageLang) { + AutoLock lock(mLock); + if (IsLangCJK(aCharLang)) { + AppendCJKPrefLangs(aPrefLangs, aLen, aCharLang, aPageLang); + } else { + AppendPrefLang(aPrefLangs, aLen, aCharLang); + } + + AppendPrefLang(aPrefLangs, aLen, eFontPrefLang_Others); +} + +void gfxPlatformFontList::AppendCJKPrefLangs(eFontPrefLang aPrefLangs[], + uint32_t& aLen, + eFontPrefLang aCharLang, + eFontPrefLang aPageLang) { + // prefer the lang specified by the page *if* CJK + if (IsLangCJK(aPageLang)) { + AppendPrefLang(aPrefLangs, aLen, aPageLang); + } + + // if not set up, set up the default CJK order, based on accept lang + // settings and locale + if (mCJKPrefLangs.Length() == 0) { + // temp array + eFontPrefLang tempPrefLangs[kMaxLenPrefLangList]; + uint32_t tempLen = 0; + + // Add the CJK pref fonts from accept languages, the order should be same + // order. We use gfxFontUtils::GetPrefsFontList to read the list even + // though it's not actually a list of fonts but of lang codes; the format + // is the same. + AutoTArray<nsCString, 5> list; + gfxFontUtils::GetPrefsFontList("intl.accept_languages", list, true); + for (const auto& lang : list) { + eFontPrefLang fpl = GetFontPrefLangFor(lang.get()); + switch (fpl) { + case eFontPrefLang_Japanese: + case eFontPrefLang_Korean: + case eFontPrefLang_ChineseCN: + case eFontPrefLang_ChineseHK: + case eFontPrefLang_ChineseTW: + AppendPrefLang(tempPrefLangs, tempLen, fpl); + break; + default: + break; + } + } + + // Try using app's locale + nsAutoCString localeStr; + LocaleService::GetInstance()->GetAppLocaleAsBCP47(localeStr); + + { + Locale locale; + if (LocaleParser::TryParse(localeStr, locale).isOk() && + locale.Canonicalize().isOk()) { + if (locale.Language().EqualTo("ja")) { + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Japanese); + } else if (locale.Language().EqualTo("zh")) { + if (locale.Region().EqualTo("CN")) { + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseCN); + } else if (locale.Region().EqualTo("TW")) { + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseTW); + } else if (locale.Region().EqualTo("HK")) { + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseHK); + } + } else if (locale.Language().EqualTo("ko")) { + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Korean); + } + } + } + + // Then add the known CJK prefs in order of system preferred locales + AutoTArray<nsCString, 5> prefLocales; + prefLocales.AppendElement("ja"_ns); + prefLocales.AppendElement("zh-CN"_ns); + prefLocales.AppendElement("zh-TW"_ns); + prefLocales.AppendElement("zh-HK"_ns); + prefLocales.AppendElement("ko"_ns); + + AutoTArray<nsCString, 16> sysLocales; + AutoTArray<nsCString, 16> negLocales; + if (NS_SUCCEEDED( + OSPreferences::GetInstance()->GetSystemLocales(sysLocales))) { + LocaleService::GetInstance()->NegotiateLanguages( + sysLocales, prefLocales, ""_ns, + LocaleService::kLangNegStrategyFiltering, negLocales); + for (const auto& localeStr : negLocales) { + Locale locale; + if (LocaleParser::TryParse(localeStr, locale).isOk() && + locale.Canonicalize().isOk()) { + if (locale.Language().EqualTo("ja")) { + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Japanese); + } else if (locale.Language().EqualTo("zh")) { + if (locale.Region().EqualTo("CN")) { + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseCN); + } else if (locale.Region().EqualTo("TW")) { + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseTW); + } else if (locale.Region().EqualTo("HK")) { + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseHK); + } + } else if (locale.Language().EqualTo("ko")) { + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Korean); + } + } + } + } + + // Last resort... set up CJK font prefs in the order listed by the user- + // configurable ordering pref. + gfxFontUtils::GetPrefsFontList(kCJKFallbackOrderPref, list); + for (const auto& item : list) { + eFontPrefLang fpl = GetFontPrefLangFor(item.get()); + switch (fpl) { + case eFontPrefLang_Japanese: + case eFontPrefLang_Korean: + case eFontPrefLang_ChineseCN: + case eFontPrefLang_ChineseHK: + case eFontPrefLang_ChineseTW: + AppendPrefLang(tempPrefLangs, tempLen, fpl); + break; + default: + break; + } + } + + // Truly-last resort... try Chinese font prefs before Japanese because + // they tend to have more complete character coverage, and therefore less + // risk of "ransom-note" effects. + // (If the kCJKFallbackOrderPref was fully populated, as it is by default, + // this will do nothing as all these values are already present.) + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseCN); + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseHK); + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseTW); + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Japanese); + AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Korean); + + // copy into the cached array + for (const auto lang : Span<eFontPrefLang>(tempPrefLangs, tempLen)) { + mCJKPrefLangs.AppendElement(lang); + } + } + + // append in cached CJK langs + for (const auto lang : mCJKPrefLangs) { + AppendPrefLang(aPrefLangs, aLen, eFontPrefLang(lang)); + } +} + +void gfxPlatformFontList::AppendPrefLang(eFontPrefLang aPrefLangs[], + uint32_t& aLen, + eFontPrefLang aAddLang) { + if (aLen >= kMaxLenPrefLangList) { + return; + } + + // If the lang is already present, just ignore the addition. + for (const auto lang : Span<eFontPrefLang>(aPrefLangs, aLen)) { + if (lang == aAddLang) { + return; + } + } + + aPrefLangs[aLen++] = aAddLang; +} + +StyleGenericFontFamily gfxPlatformFontList::GetDefaultGeneric( + eFontPrefLang aLang) { + if (aLang == eFontPrefLang_Emoji) { + return StyleGenericFontFamily::MozEmoji; + } + + AutoLock lock(mLock); + + if (uint32_t(aLang) < ArrayLength(gPrefLangNames)) { + return mDefaultGenericsLangGroup[uint32_t(aLang)]; + } + return StyleGenericFontFamily::Serif; +} + +FontFamily gfxPlatformFontList::GetDefaultFont(nsPresContext* aPresContext, + const gfxFontStyle* aStyle) { + AutoLock lock(mLock); + return GetDefaultFontLocked(aPresContext, aStyle); +} + +FontFamily gfxPlatformFontList::GetDefaultFontLocked( + nsPresContext* aPresContext, const gfxFontStyle* aStyle) { + FontFamily family = GetDefaultFontForPlatform(aPresContext, aStyle); + if (!family.IsNull()) { + return family; + } + // Something has gone wrong and we were unable to retrieve a default font + // from the platform. (Likely the whitelist has blocked all potential + // default fonts.) As a last resort, we return the first font in our list. + if (SharedFontList()) { + MOZ_RELEASE_ASSERT(SharedFontList()->NumFamilies() > 0); + return FontFamily(SharedFontList()->Families()); + } + MOZ_RELEASE_ASSERT(mFontFamilies.Count() > 0); + return FontFamily(mFontFamilies.ConstIter().Data()); +} + +void gfxPlatformFontList::GetFontFamilyNames( + nsTArray<nsCString>& aFontFamilyNames) { + if (SharedFontList()) { + fontlist::FontList* list = SharedFontList(); + const fontlist::Family* families = list->Families(); + if (families) { + for (uint32_t i = 0, n = list->NumFamilies(); i < n; i++) { + const fontlist::Family& family = families[i]; + if (!family.IsHidden()) { + aFontFamilyNames.AppendElement(family.DisplayName().AsString(list)); + } + } + } + } else { + for (const RefPtr<gfxFontFamily>& family : mFontFamilies.Values()) { + if (!family->IsHidden()) { + aFontFamilyNames.AppendElement(family->Name()); + } + } + } +} + +nsAtom* gfxPlatformFontList::GetLangGroup(nsAtom* aLanguage) { + // map lang ==> langGroup + nsAtom* langGroup = nullptr; + if (aLanguage) { + langGroup = mLangService->GetLanguageGroup(aLanguage); + } + if (!langGroup) { + langGroup = nsGkAtoms::Unicode; + } + return langGroup; +} + +/* static */ const char* gfxPlatformFontList::GetGenericName( + StyleGenericFontFamily aGenericType) { + // type should be standard generic type at this point + // map generic type to string + switch (aGenericType) { + case StyleGenericFontFamily::Serif: + return "serif"; + case StyleGenericFontFamily::SansSerif: + return "sans-serif"; + case StyleGenericFontFamily::Monospace: + return "monospace"; + case StyleGenericFontFamily::Cursive: + return "cursive"; + case StyleGenericFontFamily::Fantasy: + return "fantasy"; + case StyleGenericFontFamily::SystemUi: + return "system-ui"; + case StyleGenericFontFamily::MozEmoji: + return "-moz-emoji"; + case StyleGenericFontFamily::None: + break; + } + MOZ_ASSERT_UNREACHABLE("Unknown generic"); + return nullptr; +} + +void gfxPlatformFontList::InitLoader() { + GetFontFamilyNames(mFontInfo->mFontFamiliesToLoad); + mStartIndex = 0; + mNumFamilies = mFontInfo->mFontFamiliesToLoad.Length(); + memset(&(mFontInfo->mLoadStats), 0, sizeof(mFontInfo->mLoadStats)); +} + +#define FONT_LOADER_MAX_TIMESLICE \ + 20 // max time for one pass through RunLoader = 20ms + +bool gfxPlatformFontList::LoadFontInfo() { + AutoLock lock(mLock); + TimeStamp start = TimeStamp::Now(); + uint32_t i, endIndex = mNumFamilies; + fontlist::FontList* list = SharedFontList(); + bool loadCmaps = + !list && (!UsesSystemFallback() || + gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback()); + + // for each font family, load in various font info + for (i = mStartIndex; i < endIndex; i++) { + nsAutoCString key; + GenerateFontListKey(mFontInfo->mFontFamiliesToLoad[i], key); + + if (list) { + fontlist::Family* family = list->FindFamily(key); + if (!family) { + continue; + } + ReadFaceNamesForFamily(family, NeedFullnamePostscriptNames()); + } else { + // lookup in canonical (i.e. English) family name list + gfxFontFamily* familyEntry = mFontFamilies.GetWeak(key); + if (!familyEntry) { + continue; + } + + // read in face names + familyEntry->ReadFaceNames(this, NeedFullnamePostscriptNames(), + mFontInfo); + + // load the cmaps if needed + if (loadCmaps) { + familyEntry->ReadAllCMAPs(mFontInfo); + } + } + + // Limit the time spent reading fonts in one pass, unless the font-loader + // delay was set to zero, in which case we run to completion even if it + // causes some jank. + if (StaticPrefs::gfx_font_loader_delay_AtStartup() > 0) { + TimeDuration elapsed = TimeStamp::Now() - start; + if (elapsed.ToMilliseconds() > FONT_LOADER_MAX_TIMESLICE && + i + 1 != endIndex) { + endIndex = i + 1; + break; + } + } + } + + mStartIndex = endIndex; + bool done = mStartIndex >= mNumFamilies; + + if (LOG_FONTINIT_ENABLED()) { + TimeDuration elapsed = TimeStamp::Now() - start; + LOG_FONTINIT(("(fontinit) fontloader load pass %8.2f ms done %s\n", + elapsed.ToMilliseconds(), (done ? "true" : "false"))); + } + + if (done) { + mOtherFamilyNamesInitialized = true; + CancelInitOtherFamilyNamesTask(); + mFaceNameListsInitialized = true; + } + + return done; +} + +void gfxPlatformFontList::CleanupLoader() { + AutoLock lock(mLock); + + mFontFamiliesToLoad.Clear(); + mNumFamilies = 0; + bool rebuilt = false, forceReflow = false; + + // if had missed face names that are now available, force reflow all + if (mFaceNamesMissed) { + rebuilt = std::any_of(mFaceNamesMissed->cbegin(), mFaceNamesMissed->cend(), + [&](const auto& key) { + mLock.AssertCurrentThreadIn(); + return FindFaceName(key); + }); + if (rebuilt) { + RebuildLocalFonts(); + } + + mFaceNamesMissed = nullptr; + } + + if (mOtherNamesMissed) { + forceReflow = std::any_of( + mOtherNamesMissed->cbegin(), mOtherNamesMissed->cend(), + [&](const auto& key) { + mLock.AssertCurrentThreadIn(); + return FindUnsharedFamily( + nullptr, key, + (FindFamiliesFlags::eForceOtherFamilyNamesLoading | + FindFamiliesFlags::eNoAddToNamesMissedWhenSearching)); + }); + if (forceReflow) { + ForceGlobalReflowLocked(gfxPlatform::NeedsReframe::No); + } + + mOtherNamesMissed = nullptr; + } + + if (LOG_FONTINIT_ENABLED() && mFontInfo) { + LOG_FONTINIT( + ("(fontinit) fontloader load thread took %8.2f ms " + "%d families %d fonts %d cmaps " + "%d facenames %d othernames %s %s", + mLoadTime.ToMilliseconds(), mFontInfo->mLoadStats.families, + mFontInfo->mLoadStats.fonts, mFontInfo->mLoadStats.cmaps, + mFontInfo->mLoadStats.facenames, mFontInfo->mLoadStats.othernames, + (rebuilt ? "(userfont sets rebuilt)" : ""), + (forceReflow ? "(global reflow)" : ""))); + } + + gfxFontInfoLoader::CleanupLoader(); +} + +void gfxPlatformFontList::ForceGlobalReflowLocked( + gfxPlatform::NeedsReframe aNeedsReframe, + gfxPlatform::BroadcastToChildren aBroadcastToChildren) { + if (!NS_IsMainThread()) { + NS_DispatchToMainThread(NS_NewRunnableFunction( + "gfxPlatformFontList::ForceGlobalReflowLocked", + [aNeedsReframe, aBroadcastToChildren] { + gfxPlatform::ForceGlobalReflow(aNeedsReframe, aBroadcastToChildren); + })); + return; + } + + AutoUnlock unlock(mLock); + gfxPlatform::ForceGlobalReflow(aNeedsReframe, aBroadcastToChildren); +} + +void gfxPlatformFontList::GetPrefsAndStartLoader() { + // If we're already in shutdown, there's no point in starting this, and it + // could trigger an assertion if we try to use the Thread Manager too late. + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + return; + } + uint32_t delay = std::max(1u, StaticPrefs::gfx_font_loader_delay_AtStartup()); + if (NS_IsMainThread()) { + StartLoader(delay); + } else { + NS_DispatchToMainThread(NS_NewRunnableFunction( + "StartLoader callback", [delay, fontList = this] { + fontList->Lock(); + fontList->StartLoader(delay); + fontList->Unlock(); + })); + } +} + +void gfxPlatformFontList::RebuildLocalFonts(bool aForgetLocalFaces) { + for (auto* fontset : mUserFontSetList) { + if (aForgetLocalFaces) { + fontset->ForgetLocalFaces(); + } + fontset->RebuildLocalRules(); + } +} + +void gfxPlatformFontList::ClearLangGroupPrefFontsLocked() { + for (uint32_t i = eFontPrefLang_First; + i < eFontPrefLang_First + eFontPrefLang_Count; i++) { + auto& prefFontsLangGroup = mLangGroupPrefFonts[i]; + for (auto& pref : prefFontsLangGroup) { + pref = nullptr; + } + } + mCJKPrefLangs.Clear(); + mEmojiPrefFont = nullptr; + + // Create a new FontPrefs and replace the existing one. + mFontPrefs = MakeUnique<FontPrefs>(); +} + +// Support for memory reporting + +// this is also used by subclasses that hold additional font tables +/*static*/ +size_t gfxPlatformFontList::SizeOfFontFamilyTableExcludingThis( + const FontFamilyTable& aTable, MallocSizeOf aMallocSizeOf) { + return std::accumulate( + aTable.Keys().cbegin(), aTable.Keys().cend(), + aTable.ShallowSizeOfExcludingThis(aMallocSizeOf), + [&](size_t oldValue, const nsACString& key) { + // We don't count the size of the family here, because this is an + // *extra* reference to a family that will have already been counted in + // the main list. + return oldValue + key.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + }); +} + +/*static*/ +size_t gfxPlatformFontList::SizeOfFontEntryTableExcludingThis( + const FontEntryTable& aTable, MallocSizeOf aMallocSizeOf) { + return std::accumulate( + aTable.Keys().cbegin(), aTable.Keys().cend(), + aTable.ShallowSizeOfExcludingThis(aMallocSizeOf), + [&](size_t oldValue, const nsACString& key) { + // The font itself is counted by its owning family; here we only care + // about the names stored in the hashtable keys. + + return oldValue + key.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + }); +} + +void gfxPlatformFontList::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + AutoLock lock(mLock); + + aSizes->mFontListSize += + mFontFamilies.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& entry : mFontFamilies) { + aSizes->mFontListSize += + entry.GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf); + entry.GetData()->AddSizeOfIncludingThis(aMallocSizeOf, aSizes); + } + + aSizes->mFontListSize += + SizeOfFontFamilyTableExcludingThis(mOtherFamilyNames, aMallocSizeOf); + + if (mExtraNames) { + aSizes->mFontListSize += SizeOfFontEntryTableExcludingThis( + mExtraNames->mFullnames, aMallocSizeOf); + aSizes->mFontListSize += SizeOfFontEntryTableExcludingThis( + mExtraNames->mPostscriptNames, aMallocSizeOf); + } + + for (uint32_t i = eFontPrefLang_First; + i < eFontPrefLang_First + eFontPrefLang_Count; i++) { + auto& prefFontsLangGroup = mLangGroupPrefFonts[i]; + for (const UniquePtr<PrefFontList>& pf : prefFontsLangGroup) { + if (pf) { + aSizes->mFontListSize += pf->ShallowSizeOfExcludingThis(aMallocSizeOf); + } + } + } + + for (const auto& bitset : mCodepointsWithNoFonts) { + aSizes->mFontListSize += bitset.SizeOfExcludingThis(aMallocSizeOf); + } + aSizes->mFontListSize += + mFontFamiliesToLoad.ShallowSizeOfExcludingThis(aMallocSizeOf); + + aSizes->mFontListSize += + mBadUnderlineFamilyNames.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& i : mBadUnderlineFamilyNames) { + aSizes->mFontListSize += i.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + + aSizes->mFontListSize += + mSharedCmaps.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& entry : mSharedCmaps) { + aSizes->mCharMapsSize += entry.GetKey()->SizeOfIncludingThis(aMallocSizeOf); + } + + aSizes->mFontListSize += + mFontEntries.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& entry : mFontEntries.Values()) { + if (entry) { + entry->AddSizeOfIncludingThis(aMallocSizeOf, aSizes); + } + } + + if (SharedFontList()) { + aSizes->mFontListSize += + SharedFontList()->SizeOfIncludingThis(aMallocSizeOf); + if (XRE_IsParentProcess()) { + aSizes->mSharedSize += SharedFontList()->AllocatedShmemSize(); + } + } +} + +void gfxPlatformFontList::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +void gfxPlatformFontList::InitOtherFamilyNamesInternal( + bool aDeferOtherFamilyNamesLoading) { + if (mOtherFamilyNamesInitialized) { + return; + } + + AutoLock lock(mLock); + + if (aDeferOtherFamilyNamesLoading) { + TimeStamp start = TimeStamp::Now(); + bool timedOut = false; + + auto list = SharedFontList(); + if (list) { + // If the gfxFontInfoLoader task is not yet running, kick it off now so + // that it will load remaining names etc as soon as idle time permits. + if (mState == stateInitial || mState == stateTimerOnDelay) { + StartLoader(0); + timedOut = true; + } + } else { + for (const RefPtr<gfxFontFamily>& family : mFontFamilies.Values()) { + family->ReadOtherFamilyNames(this); + TimeDuration elapsed = TimeStamp::Now() - start; + if (elapsed.ToMilliseconds() > OTHERNAMES_TIMEOUT) { + timedOut = true; + break; + } + } + } + + if (!timedOut) { + mOtherFamilyNamesInitialized = true; + CancelInitOtherFamilyNamesTask(); + } + TimeStamp end = TimeStamp::Now(); + Telemetry::AccumulateTimeDelta(Telemetry::FONTLIST_INITOTHERFAMILYNAMES, + start, end); + + if (LOG_FONTINIT_ENABLED()) { + TimeDuration elapsed = end - start; + LOG_FONTINIT(("(fontinit) InitOtherFamilyNames took %8.2f ms %s", + elapsed.ToMilliseconds(), (timedOut ? "timeout" : ""))); + } + } else { + TimeStamp start = TimeStamp::Now(); + + auto list = SharedFontList(); + if (list) { + for (auto& f : mozilla::Range<fontlist::Family>(list->Families(), + list->NumFamilies())) { + ReadFaceNamesForFamily(&f, false); + } + } else { + for (const RefPtr<gfxFontFamily>& family : mFontFamilies.Values()) { + family->ReadOtherFamilyNames(this); + } + } + + mOtherFamilyNamesInitialized = true; + CancelInitOtherFamilyNamesTask(); + + TimeStamp end = TimeStamp::Now(); + Telemetry::AccumulateTimeDelta( + Telemetry::FONTLIST_INITOTHERFAMILYNAMES_NO_DEFERRING, start, end); + + if (LOG_FONTINIT_ENABLED()) { + TimeDuration elapsed = end - start; + LOG_FONTINIT( + ("(fontinit) InitOtherFamilyNames without deferring took %8.2f ms", + elapsed.ToMilliseconds())); + } + } +} + +void gfxPlatformFontList::CancelInitOtherFamilyNamesTask() { + if (mPendingOtherFamilyNameTask) { + mPendingOtherFamilyNameTask->Cancel(); + mPendingOtherFamilyNameTask = nullptr; + } + auto list = SharedFontList(); + if (list && XRE_IsParentProcess()) { + bool forceReflow = false; + if (!mAliasTable.IsEmpty()) { + list->SetAliases(mAliasTable); + mAliasTable.Clear(); + forceReflow = true; + } + if (mLocalNameTable.Count()) { + list->SetLocalNames(mLocalNameTable); + mLocalNameTable.Clear(); + forceReflow = true; + } + if (forceReflow) { + dom::ContentParent::BroadcastFontListChanged(); + } + } +} + +void gfxPlatformFontList::ShareFontListShmBlockToProcess( + uint32_t aGeneration, uint32_t aIndex, base::ProcessId aPid, + base::SharedMemoryHandle* aOut) { + auto list = SharedFontList(); + if (!list) { + return; + } + if (!aGeneration || list->GetGeneration() == aGeneration) { + list->ShareShmBlockToProcess(aIndex, aPid, aOut); + } else { + *aOut = base::SharedMemory::NULLHandle(); + } +} + +void gfxPlatformFontList::ShareFontListToProcess( + nsTArray<base::SharedMemoryHandle>* aBlocks, base::ProcessId aPid) { + auto list = SharedFontList(); + if (list) { + list->ShareBlocksToProcess(aBlocks, aPid); + } +} + +base::SharedMemoryHandle gfxPlatformFontList::ShareShmBlockToProcess( + uint32_t aIndex, base::ProcessId aPid) { + MOZ_RELEASE_ASSERT(SharedFontList()); + return SharedFontList()->ShareBlockToProcess(aIndex, aPid); +} + +void gfxPlatformFontList::ShmBlockAdded(uint32_t aGeneration, uint32_t aIndex, + base::SharedMemoryHandle aHandle) { + if (SharedFontList()) { + AutoLock lock(mLock); + SharedFontList()->ShmBlockAdded(aGeneration, aIndex, std::move(aHandle)); + } +} + +void gfxPlatformFontList::InitializeFamily(uint32_t aGeneration, + uint32_t aFamilyIndex, + bool aLoadCmaps) { + auto list = SharedFontList(); + MOZ_ASSERT(list); + if (!list) { + return; + } + if (list->GetGeneration() != aGeneration) { + return; + } + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + return; + } + if (aFamilyIndex >= list->NumFamilies()) { + return; + } + fontlist::Family* family = list->Families() + aFamilyIndex; + if (!family->IsInitialized() || aLoadCmaps) { + Unused << InitializeFamily(family, aLoadCmaps); + } +} + +void gfxPlatformFontList::SetCharacterMap(uint32_t aGeneration, + uint32_t aFamilyIndex, bool aAlias, + uint32_t aFaceIndex, + const gfxSparseBitSet& aMap) { + MOZ_ASSERT(XRE_IsParentProcess()); + auto list = SharedFontList(); + MOZ_ASSERT(list); + if (!list) { + return; + } + if (list->GetGeneration() != aGeneration) { + return; + } + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + return; + } + + const fontlist::Family* family; + if (aAlias) { + if (aFamilyIndex >= list->NumAliases()) { + MOZ_ASSERT(false, "AliasFamily index out of range"); + return; + } + family = list->AliasFamilies() + aFamilyIndex; + } else { + if (aFamilyIndex >= list->NumFamilies()) { + MOZ_ASSERT(false, "Family index out of range"); + return; + } + family = list->Families() + aFamilyIndex; + } + + if (aFaceIndex >= family->NumFaces()) { + MOZ_ASSERT(false, "Face index out of range"); + return; + } + + if (auto* face = + family->Faces(list)[aFaceIndex].ToPtr<fontlist::Face>(list)) { + face->mCharacterMap = GetShmemCharMap(&aMap); + } +} + +void gfxPlatformFontList::SetupFamilyCharMap(uint32_t aGeneration, + uint32_t aIndex, bool aAlias) { + MOZ_ASSERT(XRE_IsParentProcess()); + auto list = SharedFontList(); + MOZ_ASSERT(list); + if (!list) { + return; + } + if (list->GetGeneration() != aGeneration) { + return; + } + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + return; + } + + if (aAlias) { + if (aIndex >= list->NumAliases()) { + MOZ_ASSERT(false, "AliasFamily index out of range"); + return; + } + list->AliasFamilies()[aIndex].SetupFamilyCharMap(list); + return; + } + + if (aIndex >= list->NumFamilies()) { + MOZ_ASSERT(false, "Family index out of range"); + return; + } + list->Families()[aIndex].SetupFamilyCharMap(list); +} + +bool gfxPlatformFontList::InitOtherFamilyNames(uint32_t aGeneration, + bool aDefer) { + auto list = SharedFontList(); + MOZ_ASSERT(list); + if (!list) { + return false; + } + if (list->GetGeneration() != aGeneration) { + return false; + } + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + return false; + } + return InitOtherFamilyNames(aDefer); +} + +uint32_t gfxPlatformFontList::GetGeneration() const { + return SharedFontList() ? SharedFontList()->GetGeneration() : 0; +} + +gfxPlatformFontList::FontPrefs::FontPrefs() { + // This must be created on the main thread, so that we can safely use the + // Preferences service. Once created, it can be read from any thread. + MOZ_ASSERT(NS_IsMainThread()); + Init(); +} + +void gfxPlatformFontList::FontPrefs::Init() { + if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal)) { + return; + } + nsIPrefBranch* prefRootBranch = Preferences::GetRootBranch(); + if (!prefRootBranch) { + return; + } + nsTArray<nsCString> prefNames; + if (NS_SUCCEEDED(prefRootBranch->GetChildList(kNamePrefix, prefNames))) { + for (auto& prefName : prefNames) { + nsAutoCString value; + if (NS_SUCCEEDED(Preferences::GetCString(prefName.get(), value))) { + nsAutoCString pref(Substring(prefName, sizeof(kNamePrefix) - 1)); + mFontName.InsertOrUpdate(pref, value); + } + } + } + if (NS_SUCCEEDED(prefRootBranch->GetChildList(kNameListPrefix, prefNames))) { + for (auto& prefName : prefNames) { + nsAutoCString value; + if (NS_SUCCEEDED(Preferences::GetCString(prefName.get(), value))) { + nsAutoCString pref(Substring(prefName, sizeof(kNameListPrefix) - 1)); + mFontNameList.InsertOrUpdate(pref, value); + } + } + } + mEmojiHasUserValue = Preferences::HasUserValue("font.name-list.emoji"); +} + +bool gfxPlatformFontList::FontPrefs::LookupName(const nsACString& aPref, + nsACString& aValue) const { + if (const auto& value = mFontName.Lookup(aPref)) { + aValue = *value; + return true; + } + return false; +} + +bool gfxPlatformFontList::FontPrefs::LookupNameList(const nsACString& aPref, + nsACString& aValue) const { + if (const auto& value = mFontNameList.Lookup(aPref)) { + aValue = *value; + return true; + } + return false; +} + +bool gfxPlatformFontList::IsKnownIconFontFamily( + const nsAtom* aFamilyName) const { + nsAtomCString fam(aFamilyName); + ToLowerCase(fam); + return mIconFontsSet.Contains(fam); +} + +#undef LOG +#undef LOG_ENABLED diff --git a/gfx/thebes/gfxPlatformFontList.h b/gfx/thebes/gfxPlatformFontList.h new file mode 100644 index 0000000000..4d5b9e4015 --- /dev/null +++ b/gfx/thebes/gfxPlatformFontList.h @@ -0,0 +1,1074 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFXPLATFORMFONTLIST_H_ +#define GFXPLATFORMFONTLIST_H_ + +#include "nsClassHashtable.h" +#include "nsTHashMap.h" +#include "nsTHashSet.h" +#include "nsRefPtrHashtable.h" +#include "nsTHashtable.h" + +#include "gfxFontUtils.h" +#include "gfxFontInfoLoader.h" +#include "gfxFont.h" +#include "gfxFontConstants.h" +#include "gfxPlatform.h" +#include "SharedFontList.h" + +#include "nsIMemoryReporter.h" +#include "mozilla/Attributes.h" +#include "mozilla/EnumeratedArray.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RangedArray.h" +#include "mozilla/RecursiveMutex.h" +#include "nsLanguageAtomService.h" + +#include "base/shared_memory.h" + +namespace mozilla { +namespace fontlist { +struct AliasData; +} +} // namespace mozilla + +class CharMapHashKey : public PLDHashEntryHdr { + public: + typedef gfxCharacterMap* KeyType; + typedef const gfxCharacterMap* KeyTypePointer; + + explicit CharMapHashKey(const gfxCharacterMap* aCharMap) + : mCharMap(const_cast<gfxCharacterMap*>(aCharMap)) { + MOZ_COUNT_CTOR(CharMapHashKey); + } + CharMapHashKey(const CharMapHashKey& toCopy) : mCharMap(toCopy.mCharMap) { + MOZ_COUNT_CTOR(CharMapHashKey); + } + MOZ_COUNTED_DTOR(CharMapHashKey) + + gfxCharacterMap* GetKey() const { return mCharMap.get(); } + + bool KeyEquals(const gfxCharacterMap* aCharMap) const { + MOZ_ASSERT(!aCharMap->mBuildOnTheFly && !mCharMap->mBuildOnTheFly, + "custom cmap used in shared cmap hashtable"); + // cmaps built on the fly never match + if (aCharMap->mHash != mCharMap->mHash) { + return false; + } + return mCharMap->Equals(aCharMap); + } + + static const gfxCharacterMap* KeyToPointer(gfxCharacterMap* aCharMap) { + return aCharMap; + } + static PLDHashNumber HashKey(const gfxCharacterMap* aCharMap) { + return aCharMap->mHash; + } + + enum { ALLOW_MEMMOVE = true }; + + protected: + friend class gfxPlatformFontList; + + // gfxCharacterMap::Release() will notify us when the refcount of a + // charmap drops to 1; at that point, we'll lock the cache, check if + // the charmap is owned by the cache and this is still the only ref, + // and if so, delete it. + RefPtr<gfxCharacterMap> mCharMap; +}; + +/** + * A helper class used to create a SharedBitSet instance in a FontList's shared + * memory, while ensuring that we avoid bloating memory by avoiding creating + * duplicate instances. + */ +class ShmemCharMapHashEntry final : public PLDHashEntryHdr { + public: + typedef const gfxSparseBitSet* KeyType; + typedef const gfxSparseBitSet* KeyTypePointer; + + /** + * Creation from a gfxSparseBitSet creates not only the ShmemCharMapHashEntry + * itself, but also a SharedBitSet in shared memory. + * Only the parent process creates and manages these entries. + */ + explicit ShmemCharMapHashEntry(const gfxSparseBitSet* aCharMap); + + ShmemCharMapHashEntry(ShmemCharMapHashEntry&&) = default; + ShmemCharMapHashEntry& operator=(ShmemCharMapHashEntry&&) = default; + + /** + * Return a shared-memory Pointer that refers to the wrapped SharedBitSet. + * This can be passed to content processes to give them access to the same + * SharedBitSet as the parent stored. + */ + mozilla::fontlist::Pointer GetCharMap() const { return mCharMap; } + + bool KeyEquals(KeyType aCharMap) const { + // mHash is a 32-bit Adler checksum of the bitset; if it doesn't match we + // can immediately reject it as non-matching, but if it is equal we still + // need to do a full equality check below. + if (mHash != aCharMap->GetChecksum()) { + return false; + } + + return mCharMap.ToPtr<const SharedBitSet>(mList)->Equals(aCharMap); + } + + static KeyTypePointer KeyToPointer(KeyType aCharMap) { return aCharMap; } + static PLDHashNumber HashKey(KeyType aCharMap) { + return aCharMap->GetChecksum(); + } + + enum { ALLOW_MEMMOVE = true }; + + private: + // charMaps are stored in the shared memory that FontList objects point to, + // and are never deleted until the FontList (all referencing font lists, + // actually) have gone away. + mozilla::fontlist::FontList* mList; + mozilla::fontlist::Pointer mCharMap; + uint32_t mHash; +}; + +// gfxPlatformFontList is an abstract class for the global font list on the +// system; concrete subclasses for each platform implement the actual interface +// to the system fonts. This class exists because we cannot rely on the platform +// font-finding APIs to behave in sensible/similar ways, particularly with rich, +// complex OpenType families, so we do our own font family/style management here +// instead. + +// Much of this is based on the old gfxQuartzFontCache, but adapted for use on +// all platforms. + +struct FontListSizes { + uint32_t mFontListSize; // size of the font list and dependent objects + // (font family and face names, etc), but NOT + // including the font table cache and the cmaps + uint32_t + mFontTableCacheSize; // memory used for the gfxFontEntry table caches + uint32_t mCharMapsSize; // memory used for cmap coverage info + uint32_t mLoaderSize; // memory used for (platform-specific) loader + uint32_t mSharedSize; // shared-memory use (reported by parent only) +}; + +class gfxUserFontSet; + +class gfxPlatformFontList : public gfxFontInfoLoader { + friend class InitOtherFamilyNamesRunnable; + + public: + typedef mozilla::StretchRange StretchRange; + typedef mozilla::SlantStyleRange SlantStyleRange; + typedef mozilla::WeightRange WeightRange; + typedef mozilla::intl::Script Script; + + using AutoLock = mozilla::RecursiveMutexAutoLock; + using AutoUnlock = mozilla::RecursiveMutexAutoUnlock; + + // Class used to hold cached copies of the font-name prefs, so that they can + // be accessed from non-main-thread callers who are not allowed to touch the + // Preferences service. + class FontPrefs final { + public: + using HashMap = nsTHashMap<nsCStringHashKey, nsCString>; + + FontPrefs(); + ~FontPrefs() = default; + + FontPrefs(const FontPrefs& aOther) = delete; + FontPrefs& operator=(const FontPrefs& aOther) = delete; + + // Lookup the font.name.<foo> or font.name-list.<foo> pref for a given + // generic+langgroup pair. + bool LookupName(const nsACString& aPref, nsACString& aValue) const; + bool LookupNameList(const nsACString& aPref, nsACString& aValue) const; + + // Does the font.name-list.emoji pref have a user-set value? + bool EmojiHasUserValue() const { return mEmojiHasUserValue; } + + // Expose iterators over all the defined prefs of each type. + HashMap::ConstIterator NameIter() const { return mFontName.ConstIter(); } + HashMap::ConstIterator NameListIter() const { + return mFontNameList.ConstIter(); + } + + private: + static constexpr char kNamePrefix[] = "font.name."; + static constexpr char kNameListPrefix[] = "font.name-list."; + + void Init(); + + HashMap mFontName; + HashMap mFontNameList; + bool mEmojiHasUserValue = false; + }; + + // For font family lists loaded from user preferences (prefs such as + // font.name-list.<generic>.<langGroup>) that map CSS generics to + // platform-specific font families. + typedef nsTArray<FontFamily> PrefFontList; + + // Return the global font-list singleton, or NULL if aMustInitialize is false + // and it has not yet been fully initialized. + static gfxPlatformFontList* PlatformFontList(bool aMustInitialize = true) { + if (!aMustInitialize && + !(sPlatformFontList && sPlatformFontList->IsInitialized())) { + return nullptr; + } + // If there is a font-list initialization thread, we need to let it run + // to completion before the font list can be used for anything else. + if (sInitFontListThread) { + // If we're currently on the initialization thread, just continue; + // otherwise wait for it to finish. + if (IsInitFontListThread()) { + return sPlatformFontList; + } + PR_JoinThread(sInitFontListThread); + sInitFontListThread = nullptr; + // If font-list initialization failed, the thread will have cleared + // the static sPlatformFontList pointer; we cannot proceed without any + // usable fonts. + if (!sPlatformFontList) { + MOZ_CRASH("Could not initialize gfxPlatformFontList"); + } + } + if (!sPlatformFontList->IsInitialized()) { + if (!sPlatformFontList->InitFontList()) { + MOZ_CRASH("Could not initialize gfxPlatformFontList"); + } + } + return sPlatformFontList; + } + + static bool Initialize(gfxPlatformFontList* aList); + + static void Shutdown() { + // Ensure any font-list initialization thread is finished before we delete + // the platform fontlist singleton, which that thread may try to use. + if (sInitFontListThread && !IsInitFontListThread()) { + PR_JoinThread(sInitFontListThread); + sInitFontListThread = nullptr; + } + delete sPlatformFontList; + sPlatformFontList = nullptr; + } + + bool IsInitialized() const { return mFontlistInitCount; } + + virtual ~gfxPlatformFontList(); + + // Initialize font lists; return true on success, false if something fails. + bool InitFontList(); + + void FontListChanged(); + + /** + * Gathers (from a platform's underlying font system) the information needed + * to initialize a fontlist::Family with its Face members. + */ + virtual void GetFacesInitDataForFamily( + const mozilla::fontlist::Family* aFamily, + nsTArray<mozilla::fontlist::Face::InitData>& aFaces, + bool aLoadCmaps) const {} + + virtual void GetFontList(nsAtom* aLangGroup, const nsACString& aGenericFamily, + nsTArray<nsString>& aListOfFonts); + + // Pass false to notify content that the shared font list has been modified + // but not completely invalidated. + void UpdateFontList(bool aFullRebuild = true); + + void ClearLangGroupPrefFonts() { + AutoLock lock(mLock); + ClearLangGroupPrefFontsLocked(); + } + virtual void ClearLangGroupPrefFontsLocked() MOZ_REQUIRES(mLock); + + void GetFontFamilyList(nsTArray<RefPtr<gfxFontFamily>>& aFamilyArray); + + already_AddRefed<gfxFont> SystemFindFontForChar( + nsPresContext* aPresContext, uint32_t aCh, uint32_t aNextCh, + Script aRunScript, eFontPresentation aPresentation, + const gfxFontStyle* aStyle, FontVisibility* aVisibility); + + // Flags to control optional behaviors in FindAndAddFamilies. The sense + // of the bit flags have been chosen such that the default parameter of + // FindFamiliesFlags(0) in FindFamily will give the most commonly-desired + // behavior, and only a few callsites need to explicitly pass other values. + enum class FindFamiliesFlags { + // If set, "other" (e.g. localized) family names should be loaded + // immediately; if clear, InitOtherFamilyNames is allowed to defer + // loading to avoid blocking. + eForceOtherFamilyNamesLoading = 1 << 0, + + // If set, FindAndAddFamilies should not check for legacy "styled + // family" names to add to the font list. This is used to avoid + // a recursive search when using FindFamily to find a potential base + // family name for a styled variant. + eNoSearchForLegacyFamilyNames = 1 << 1, + + // If set, FindAndAddFamilies will not add a missing entry to + // mOtherNamesMissed + eNoAddToNamesMissedWhenSearching = 1 << 2, + + // If set, the family name was quoted and so must not be treated as a CSS + // generic. + eQuotedFamilyName = 1 << 3, + + // If set, "hidden" font families (like ".SF NS Text" on macOS) are + // searched in addition to standard user-visible families. + eSearchHiddenFamilies = 1 << 4, + }; + + // Find family(ies) matching aFamily and append to the aOutput array + // (there may be multiple results in the case of fontconfig aliases, etc). + // Return true if any match was found and appended, false if none. + bool FindAndAddFamilies( + nsPresContext* aPresContext, mozilla::StyleGenericFontFamily aGeneric, + const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput, + FindFamiliesFlags aFlags, gfxFontStyle* aStyle = nullptr, + nsAtom* aLanguage = nullptr, gfxFloat aDevToCssSize = 1.0); + + virtual bool FindAndAddFamiliesLocked( + nsPresContext* aPresContext, mozilla::StyleGenericFontFamily aGeneric, + const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput, + FindFamiliesFlags aFlags, gfxFontStyle* aStyle = nullptr, + nsAtom* aLanguage = nullptr, gfxFloat aDevToCssSize = 1.0) + MOZ_REQUIRES(mLock); + + gfxFontEntry* FindFontForFamily(nsPresContext* aPresContext, + const nsACString& aFamily, + const gfxFontStyle* aStyle); + + mozilla::fontlist::FontList* SharedFontList() const { + return mSharedFontList.get(); + } + + // Create a handle for a single shmem block (identified by index) ready to + // be shared to the given processId. + void ShareFontListShmBlockToProcess(uint32_t aGeneration, uint32_t aIndex, + base::ProcessId aPid, + base::SharedMemoryHandle* aOut); + + // Populate the array aBlocks with the complete list of shmem handles ready + // to be shared to the given processId. + void ShareFontListToProcess(nsTArray<base::SharedMemoryHandle>* aBlocks, + base::ProcessId aPid); + + void ShmBlockAdded(uint32_t aGeneration, uint32_t aIndex, + base::SharedMemoryHandle aHandle); + + base::SharedMemoryHandle ShareShmBlockToProcess(uint32_t aIndex, + base::ProcessId aPid); + + void SetCharacterMap(uint32_t aGeneration, uint32_t aFamilyIndex, bool aAlias, + uint32_t aFaceIndex, const gfxSparseBitSet& aMap); + + void SetupFamilyCharMap(uint32_t aGeneration, uint32_t aIndex, bool aAlias); + + // Start the async cmap loading process, if not already under way, from the + // given family index. (For use in any process that needs font lookups.) + void StartCmapLoadingFromFamily(uint32_t aStartIndex); + + // [Parent] Handle request from content process to start cmap loading. + void StartCmapLoading(uint32_t aGeneration, uint32_t aStartIndex); + + void CancelLoadCmapsTask() { + if (mLoadCmapsRunnable) { + mLoadCmapsRunnable->Cancel(); + mLoadCmapsRunnable = nullptr; + } + } + + // Populate aFamily with face records, and if aLoadCmaps is true, also load + // their character maps (rather than leaving this to be done lazily). + // Note that even when aFamily->IsInitialized() is true, it can make sense + // to call InitializeFamily again if passing aLoadCmaps=true, in order to + // ensure cmaps are loaded. + [[nodiscard]] bool InitializeFamily(mozilla::fontlist::Family* aFamily, + bool aLoadCmaps = false); + void InitializeFamily(uint32_t aGeneration, uint32_t aFamilyIndex, + bool aLoadCmaps); + + // name lookup table methods + + void AddOtherFamilyNames(gfxFontFamily* aFamilyEntry, + const nsTArray<nsCString>& aOtherFamilyNames); + + void AddFullname(gfxFontEntry* aFontEntry, const nsCString& aFullname) { + AutoLock lock(mLock); + AddFullnameLocked(aFontEntry, aFullname); + } + void AddFullnameLocked(gfxFontEntry* aFontEntry, const nsCString& aFullname) + MOZ_REQUIRES(mLock); + + void AddPostscriptName(gfxFontEntry* aFontEntry, + const nsCString& aPostscriptName) { + AutoLock lock(mLock); + AddPostscriptNameLocked(aFontEntry, aPostscriptName); + } + void AddPostscriptNameLocked(gfxFontEntry* aFontEntry, + const nsCString& aPostscriptName) + MOZ_REQUIRES(mLock); + + bool NeedFullnamePostscriptNames() { return mExtraNames != nullptr; } + + /** + * Read PSName and FullName of the given face, for src:local lookup, + * returning true if actually implemented and succeeded. + */ + virtual bool ReadFaceNames(const mozilla::fontlist::Family* aFamily, + const mozilla::fontlist::Face* aFace, + nsCString& aPSName, nsCString& aFullName) { + return false; + } + + // initialize localized family names + bool InitOtherFamilyNames(bool aDeferOtherFamilyNamesLoading); + bool InitOtherFamilyNames(uint32_t aGeneration, bool aDefer); + + // pure virtual functions, to be provided by concrete subclasses + + // get the system default font family + FontFamily GetDefaultFont(nsPresContext* aPresContext, + const gfxFontStyle* aStyle); + FontFamily GetDefaultFontLocked(nsPresContext* aPresContext, + const gfxFontStyle* aStyle) + MOZ_REQUIRES(mLock); + + // get the "ultimate" default font, for use if the font list is otherwise + // unusable (e.g. in the middle of being updated) + gfxFontEntry* GetDefaultFontEntry() { + AutoLock lock(mLock); + return mDefaultFontEntry.get(); + } + + /** + * Look up a font by name on the host platform. + * + * Note that the style attributes (weight, stretch, style) are NOT used in + * selecting the platform font, which is looked up by name only; these are + * values to be recorded in the new font entry. + */ + virtual gfxFontEntry* LookupLocalFont(nsPresContext* aPresContext, + const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) = 0; + + /** + * Create a new platform font from downloaded data (@font-face). + * + * Note that the style attributes (weight, stretch, style) are NOT related + * (necessarily) to any values within the font resource itself; these are + * values to be recorded in the new font entry and used for face selection, + * in place of whatever inherent style attributes the resource may have. + * + * This method takes ownership of the data block passed in as aFontData, + * and must ensure it is free()'d when no longer required. + */ + virtual gfxFontEntry* MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, + uint32_t aLength) = 0; + + // get the standard family name on the platform for a given font name + // (platforms may override, eg Mac) + virtual bool GetStandardFamilyName(const nsCString& aFontName, + nsACString& aFamilyName); + + // Get the localized family name for a given font family. + bool GetLocalizedFamilyName(const FontFamily& aFamily, + nsACString& aFamilyName); + + // get the default font name which is available on the system from + // font.name-list.*. if there are no available fonts in the pref, + // returns an empty FamilyAndGeneric record. + FamilyAndGeneric GetDefaultFontFamily(const nsACString& aLangGroup, + const nsACString& aGenericFamily); + + virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const; + virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const; + + mozilla::fontlist::Pointer GetShmemCharMap(const gfxSparseBitSet* aCmap) { + AutoLock lock(mLock); + return GetShmemCharMapLocked(aCmap); + } + mozilla::fontlist::Pointer GetShmemCharMapLocked(const gfxSparseBitSet* aCmap) + MOZ_REQUIRES(mLock); + + // Search for existing cmap that matches the input; return the input if no + // match is found. + already_AddRefed<gfxCharacterMap> FindCharMap(gfxCharacterMap* aCmap); + + // Remove the cmap from the shared cmap set if it holds the only remaining + // reference to the object. + void MaybeRemoveCmap(gfxCharacterMap* aCharMap); + + // Keep track of userfont sets to notify when global fontlist changes occur. + void AddUserFontSet(gfxUserFontSet* aUserFontSet) { + AutoLock lock(mLock); + mUserFontSetList.Insert(aUserFontSet); + } + + void RemoveUserFontSet(gfxUserFontSet* aUserFontSet) { + AutoLock lock(mLock); + mUserFontSetList.Remove(aUserFontSet); + } + + static const gfxFontEntry::ScriptRange sComplexScriptRanges[]; + + void GetFontlistInitInfo(uint32_t& aNumInits, uint32_t& aLoaderState) { + aNumInits = mFontlistInitCount; + aLoaderState = (uint32_t)mState; + } + + virtual void AddGenericFonts(nsPresContext* aPresContext, + mozilla::StyleGenericFontFamily aGenericType, + nsAtom* aLanguage, + nsTArray<FamilyAndGeneric>& aFamilyList); + + /** + * Given a Face from the shared font list, return a gfxFontEntry usable + * by the current process. This returns a cached entry if available, + * otherwise it calls the (platform-specific) CreateFontEntry method to + * make one, and adds it to the cache. + */ + gfxFontEntry* GetOrCreateFontEntry(mozilla::fontlist::Face* aFace, + const mozilla::fontlist::Family* aFamily) { + AutoLock lock(mLock); + return GetOrCreateFontEntryLocked(aFace, aFamily); + } + gfxFontEntry* GetOrCreateFontEntryLocked( + mozilla::fontlist::Face* aFace, const mozilla::fontlist::Family* aFamily) + MOZ_REQUIRES(mLock); + + const FontPrefs* GetFontPrefs() const MOZ_REQUIRES(mLock) { + return mFontPrefs.get(); + } + + bool EmojiPrefHasUserValue() const { + AutoLock lock(mLock); + return mFontPrefs->EmojiHasUserValue(); + } + + PrefFontList* GetPrefFontsLangGroup( + nsPresContext* aPresContext, mozilla::StyleGenericFontFamily aGenericType, + eFontPrefLang aPrefLang) { + AutoLock lock(mLock); + return GetPrefFontsLangGroupLocked(aPresContext, aGenericType, aPrefLang); + } + PrefFontList* GetPrefFontsLangGroupLocked( + nsPresContext* aPresContext, mozilla::StyleGenericFontFamily aGenericType, + eFontPrefLang aPrefLang) MOZ_REQUIRES(mLock); + + // in some situations, need to make decisions about ambiguous characters, may + // need to look at multiple pref langs + void GetLangPrefs(eFontPrefLang aPrefLangs[], uint32_t& aLen, + eFontPrefLang aCharLang, eFontPrefLang aPageLang); + + // convert a lang group to enum constant (i.e. "zh-TW" ==> + // eFontPrefLang_ChineseTW) + static eFontPrefLang GetFontPrefLangFor(const char* aLang); + + // convert a lang group atom to enum constant + static eFontPrefLang GetFontPrefLangFor(nsAtom* aLang); + + // convert an enum constant to a lang group atom + static nsAtom* GetLangGroupForPrefLang(eFontPrefLang aLang); + + // convert a enum constant to lang group string (i.e. eFontPrefLang_ChineseTW + // ==> "zh-TW") + static const char* GetPrefLangName(eFontPrefLang aLang); + + // map a char code to a font language for Preferences + static eFontPrefLang GetFontPrefLangFor(uint32_t aCh); + + // returns true if a pref lang is CJK + static bool IsLangCJK(eFontPrefLang aLang); + + // helper method to add a pref lang to an array, if not already in array + static void AppendPrefLang(eFontPrefLang aPrefLangs[], uint32_t& aLen, + eFontPrefLang aAddLang); + + // default serif/sans-serif choice based on font.default.xxx prefs + mozilla::StyleGenericFontFamily GetDefaultGeneric(eFontPrefLang aLang); + + // Returns true if the font family whitelist is not empty. In this case we + // ignore the "CSS visibility level"; only the given fonts are present in + // the browser's font list. + bool IsFontFamilyWhitelistActive() const { + return mFontFamilyWhitelistActive; + }; + + static void FontWhitelistPrefChanged(const char* aPref, void* aClosure); + + bool AddWithLegacyFamilyName(const nsACString& aLegacyName, + gfxFontEntry* aFontEntry, + FontVisibility aVisibility); + + static const char* GetGenericName( + mozilla::StyleGenericFontFamily aGenericType); + + bool SkipFontFallbackForChar(FontVisibility aVisibility, uint32_t aCh) const { + AutoLock lock(mLock); + return mCodepointsWithNoFonts[aVisibility].test(aCh); + } + + // Return whether the given font-family record should be visible to CSS, + // in a context with the given FontVisibility setting. + bool IsVisibleToCSS(const gfxFontFamily& aFamily, + FontVisibility aVisibility) const; + bool IsVisibleToCSS(const mozilla::fontlist::Family& aFamily, + FontVisibility aVisibility) const; + + // (Re-)initialize the set of codepoints that we know cannot be rendered. + void InitializeCodepointsWithNoFonts() MOZ_REQUIRES(mLock); + + // If using the shared font list, returns a generation count that is + // incremented if/when the platform list is reinitialized (e.g. because + // fonts are installed/removed while the browser is running), such that + // existing references to shared font family or face objects and character + // maps will no longer be valid. + // (The legacy (non-shared) list just returns 0 here.) + uint32_t GetGeneration() const; + + // Sometimes we need to know if we're on the InitFontList startup thread. + static bool IsInitFontListThread() { + return PR_GetCurrentThread() == sInitFontListThread; + } + + bool IsKnownIconFontFamily(const nsAtom* aFamilyName) const; + void LoadIconFontOverrideList(); + + void Lock() MOZ_CAPABILITY_ACQUIRE(mLock) { mLock.Lock(); } + void Unlock() MOZ_CAPABILITY_RELEASE(mLock) { mLock.Unlock(); } + + // This is only public because some external callers want to be able to + // assert about the locked status. + mutable mozilla::RecursiveMutex mLock; + + protected: + friend class mozilla::fontlist::FontList; + friend class InitOtherFamilyNamesForStylo; + + template <size_t N> + static bool FamilyInList(const nsACString& aName, const char* (&aList)[N]) { + return FamilyInList(aName, aList, N); + } + static bool FamilyInList(const nsACString& aName, const char* aList[], + size_t aCount); + + // Check list is correctly sorted (in debug build only; no-op on release). + template <size_t N> + static void CheckFamilyList(const char* (&aList)[N]) { + CheckFamilyList(aList, N); + } + static void CheckFamilyList(const char* aList[], size_t aCount); + + class InitOtherFamilyNamesRunnable : public mozilla::CancelableRunnable { + public: + InitOtherFamilyNamesRunnable() + : CancelableRunnable( + "gfxPlatformFontList::InitOtherFamilyNamesRunnable"), + mIsCanceled(false) {} + + NS_IMETHOD Run() override { + if (mIsCanceled) { + return NS_OK; + } + + gfxPlatformFontList* fontList = gfxPlatformFontList::PlatformFontList(); + if (!fontList) { + return NS_OK; + } + + fontList->InitOtherFamilyNamesInternal(true); + + return NS_OK; + } + + nsresult Cancel() override { + mIsCanceled = true; + + return NS_OK; + } + + private: + bool mIsCanceled; + }; + + class MemoryReporter final : public nsIMemoryReporter { + ~MemoryReporter() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMEMORYREPORTER + }; + + class PrefName final : public nsAutoCString { + void Init(const nsACString& aGeneric, const nsACString& aLangGroup) { + Assign(aGeneric); + if (!aLangGroup.IsEmpty()) { + Append('.'); + Append(aLangGroup); + } + } + + public: + PrefName(const nsACString& aGeneric, const nsACString& aLangGroup) { + Init(aGeneric, aLangGroup); + } + + PrefName(const char* aGeneric, const char* aLangGroup) { + Init(nsDependentCString(aGeneric), nsDependentCString(aLangGroup)); + } + + PrefName(const char* aGeneric, nsAtom* aLangGroup) { + if (aLangGroup) { + Init(nsDependentCString(aGeneric), nsAtomCString(aLangGroup)); + } else { + Init(nsDependentCString(aGeneric), nsAutoCString()); + } + } + }; + + explicit gfxPlatformFontList(bool aNeedFullnamePostscriptNames = true); + + static gfxPlatformFontList* sPlatformFontList; + + /** + * Convenience method to return the first matching family (if any) as found + * by FindAndAddFamilies(). The family will be initialized (synchronously) + * if this has not already been done, so the returned pointer, if non-null, + * is ready for use. + */ + mozilla::fontlist::Family* FindSharedFamily( + nsPresContext* aPresContext, const nsACString& aFamily, + FindFamiliesFlags aFlags = FindFamiliesFlags(0), + gfxFontStyle* aStyle = nullptr, nsAtom* aLanguage = nullptr, + gfxFloat aDevToCssSize = 1.0) MOZ_REQUIRES(mLock); + + gfxFontFamily* FindUnsharedFamily( + nsPresContext* aPresContext, const nsACString& aFamily, + FindFamiliesFlags aFlags = FindFamiliesFlags(0), + gfxFontStyle* aStyle = nullptr, nsAtom* aLanguage = nullptr, + gfxFloat aDevToCssSize = 1.0) MOZ_REQUIRES(mLock) { + if (SharedFontList()) { + return nullptr; + } + AutoTArray<FamilyAndGeneric, 1> families; + if (FindAndAddFamiliesLocked( + aPresContext, mozilla::StyleGenericFontFamily::None, aFamily, + &families, aFlags, aStyle, aLanguage, aDevToCssSize)) { + return families[0].mFamily.mUnshared; + } + return nullptr; + } + + FontFamily FindFamily(nsPresContext* aPresContext, const nsACString& aFamily, + FindFamiliesFlags aFlags = FindFamiliesFlags(0), + gfxFontStyle* aStyle = nullptr, + nsAtom* aLanguage = nullptr, + gfxFloat aDevToCssSize = 1.0) MOZ_REQUIRES(mLock) { + if (SharedFontList()) { + return FontFamily(FindSharedFamily(aPresContext, aFamily, aFlags, aStyle, + aLanguage, aDevToCssSize)); + } + return FontFamily(FindUnsharedFamily(aPresContext, aFamily, aFlags, aStyle, + aLanguage, aDevToCssSize)); + } + + // Lookup family name in global family list without substitutions or + // localized family name lookup. Used for common font fallback families. + gfxFontFamily* FindFamilyByCanonicalName(const nsACString& aFamily) + MOZ_REQUIRES(mLock) { + nsAutoCString key; + gfxFontFamily* familyEntry; + GenerateFontListKey(aFamily, key); + if ((familyEntry = mFontFamilies.GetWeak(key))) { + return CheckFamily(familyEntry); + } + return nullptr; + } + + // returns default font for a given character, null otherwise + already_AddRefed<gfxFont> CommonFontFallback(nsPresContext* aPresContext, + uint32_t aCh, uint32_t aNextCh, + Script aRunScript, + eFontPresentation aPresentation, + const gfxFontStyle* aMatchStyle, + FontFamily& aMatchedFamily) + MOZ_REQUIRES(mLock); + + // Search fonts system-wide for a given character, null if not found. + already_AddRefed<gfxFont> GlobalFontFallback( + nsPresContext* aPresContext, uint32_t aCh, uint32_t aNextCh, + Script aRunScript, eFontPresentation aPresentation, + const gfxFontStyle* aMatchStyle, uint32_t& aCmapCount, + FontFamily& aMatchedFamily) MOZ_REQUIRES(mLock); + + // Platform-specific implementation of global font fallback, if any; + // this may return nullptr in which case the default cmap-based fallback + // will be performed. + virtual gfxFontEntry* PlatformGlobalFontFallback( + nsPresContext* aPresContext, const uint32_t aCh, Script aRunScript, + const gfxFontStyle* aMatchStyle, FontFamily& aMatchedFamily) { + return nullptr; + } + + // whether system-based font fallback is used or not + // if system fallback is used, no need to load all cmaps + virtual bool UsesSystemFallback() { return false; } + + void AppendCJKPrefLangs(eFontPrefLang aPrefLangs[], uint32_t& aLen, + eFontPrefLang aCharLang, eFontPrefLang aPageLang) + MOZ_REQUIRES(mLock); + + // verifies that a family contains a non-zero font count + gfxFontFamily* CheckFamily(gfxFontFamily* aFamily) MOZ_REQUIRES(mLock); + + // initialize localized family names + void InitOtherFamilyNamesInternal(bool aDeferOtherFamilyNamesLoading); + void CancelInitOtherFamilyNamesTask(); + + void AddToMissedNames(const nsCString& aKey) MOZ_REQUIRES(mLock); + + // search through font families, looking for a given name, initializing + // facename lists along the way. first checks all families with names + // close to face name, then searchs all families if not found. + gfxFontEntry* SearchFamiliesForFaceName(const nsACString& aFaceName) + MOZ_REQUIRES(mLock); + + // helper method for finding fullname/postscript names in facename lists + gfxFontEntry* FindFaceName(const nsACString& aFaceName) MOZ_REQUIRES(mLock); + + // look up a font by name, for cases where platform font list + // maintains explicit mappings of fullname/psname ==> font + virtual gfxFontEntry* LookupInFaceNameLists(const nsACString& aFaceName) + MOZ_REQUIRES(mLock); + + gfxFontEntry* LookupInSharedFaceNameList(nsPresContext* aPresContext, + const nsACString& aFaceName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) + MOZ_REQUIRES(mLock); + + // load the bad underline blocklist from pref. + void LoadBadUnderlineList(); + + void GenerateFontListKey(const nsACString& aKeyName, nsACString& aResult); + + virtual void GetFontFamilyNames(nsTArray<nsCString>& aFontFamilyNames) + MOZ_REQUIRES(mLock); + + // helper function to map lang to lang group + nsAtom* GetLangGroup(nsAtom* aLanguage); + + // gfxFontInfoLoader overrides, used to load in font cmaps + void InitLoader() MOZ_REQUIRES(mLock) override; + bool LoadFontInfo() override; + void CleanupLoader() override; + + void ForceGlobalReflowLocked( + gfxPlatform::NeedsReframe aNeedsReframe, + gfxPlatform::BroadcastToChildren aBroadcastToChildren = + gfxPlatform::BroadcastToChildren::Yes) MOZ_REQUIRES(mLock); + + // read the loader initialization prefs, and start it + void GetPrefsAndStartLoader(); + + // If aForgetLocalFaces is true, all gfxFontEntries for src:local fonts must + // be discarded (not potentially reused to satisfy the rebuilt rules), + // because they may no longer be valid. + void RebuildLocalFonts(bool aForgetLocalFaces = false) MOZ_REQUIRES(mLock); + + void ResolveGenericFontNames(nsPresContext* aPresContext, + mozilla::StyleGenericFontFamily aGenericType, + eFontPrefLang aPrefLang, + PrefFontList* aGenericFamilies) + MOZ_REQUIRES(mLock); + + void ResolveEmojiFontNames(nsPresContext* aPresContext, + PrefFontList* aGenericFamilies) + MOZ_REQUIRES(mLock); + + void GetFontFamiliesFromGenericFamilies( + nsPresContext* aPresContext, mozilla::StyleGenericFontFamily aGenericType, + nsTArray<nsCString>& aGenericNameFamilies, nsAtom* aLangGroup, + PrefFontList* aFontFamilies) MOZ_REQUIRES(mLock); + + virtual nsresult InitFontListForPlatform() MOZ_REQUIRES(mLock) = 0; + virtual void InitSharedFontListForPlatform() MOZ_REQUIRES(mLock) {} + + virtual gfxFontEntry* CreateFontEntry( + mozilla::fontlist::Face* aFace, + const mozilla::fontlist::Family* aFamily) { + return nullptr; + } + + /** + * Methods to apply the font.system.whitelist anti-fingerprinting pref, + * by filtering the list of installed fonts so that only whitelisted families + * are exposed. + * There are separate implementations of this for the per-process font list + * and for the shared-memory font list. + */ + void ApplyWhitelist() MOZ_REQUIRES(mLock); + void ApplyWhitelist(nsTArray<mozilla::fontlist::Family::InitData>& aFamilies); + + // Create a new gfxFontFamily of the appropriate subclass for the platform, + // used when AddWithLegacyFamilyName needs to create a new family. + virtual gfxFontFamily* CreateFontFamily(const nsACString& aName, + FontVisibility aVisibility) const = 0; + + /** + * For the post-startup font info loader task. + * Perform platform-specific work to read alternate names (if any) for a + * font family, recording them in mAliasTable. Once alternate names have been + * loaded for all families, the accumulated records are stored in the shared + * font list's mAliases list. + * Some platforms (currently Linux/fontconfig) may load alternate names as + * part of initially populating the font list with family records, in which + * case this method is unused. + */ + virtual void ReadFaceNamesForFamily(mozilla::fontlist::Family* aFamily, + bool aNeedFullnamePostscriptNames) + MOZ_REQUIRES(mLock) {} + + typedef nsRefPtrHashtable<nsCStringHashKey, gfxFontFamily> FontFamilyTable; + typedef nsRefPtrHashtable<nsCStringHashKey, gfxFontEntry> FontEntryTable; + + // used by memory reporter to accumulate sizes of family names in the table + static size_t SizeOfFontFamilyTableExcludingThis( + const FontFamilyTable& aTable, mozilla::MallocSizeOf aMallocSizeOf); + static size_t SizeOfFontEntryTableExcludingThis( + const FontEntryTable& aTable, mozilla::MallocSizeOf aMallocSizeOf); + + // Platform-specific helper for GetDefaultFont(...). + virtual FontFamily GetDefaultFontForPlatform(nsPresContext* aPresContext, + const gfxFontStyle* aStyle, + nsAtom* aLanguage = nullptr) + MOZ_REQUIRES(mLock) = 0; + + // canonical family name ==> family entry (unique, one name per family entry) + FontFamilyTable mFontFamilies MOZ_GUARDED_BY(mLock); + + // other family name ==> family entry (not unique, can have multiple names per + // family entry, only names *other* than the canonical names are stored here) + FontFamilyTable mOtherFamilyNames MOZ_GUARDED_BY(mLock); + + // flag set after InitOtherFamilyNames is called upon first name lookup miss + mozilla::Atomic<bool> mOtherFamilyNamesInitialized; + + // The pending InitOtherFamilyNames() task. + RefPtr<mozilla::CancelableRunnable> mPendingOtherFamilyNameTask; + + // flag set after fullname and Postcript name lists are populated + mozilla::Atomic<bool> mFaceNameListsInitialized; + + struct ExtraNames { + ExtraNames() = default; + + // fullname ==> font entry (unique, one name per font entry) + FontEntryTable mFullnames{64}; + // Postscript name ==> font entry (unique, one name per font entry) + FontEntryTable mPostscriptNames{64}; + }; + // The lock is needed to guard access to the actual name tables, but does not + // need to be held to just test whether mExtraNames is non-null as it is set + // during initialization before other threads have a chance to see it. + mozilla::UniquePtr<ExtraNames> mExtraNames MOZ_PT_GUARDED_BY(mLock); + + // face names missed when face name loading takes a long time + mozilla::UniquePtr<nsTHashSet<nsCString>> mFaceNamesMissed + MOZ_GUARDED_BY(mLock); + + // localized family names missed when face name loading takes a long time + mozilla::UniquePtr<nsTHashSet<nsCString>> mOtherNamesMissed + MOZ_GUARDED_BY(mLock); + + typedef mozilla::RangedArray<mozilla::UniquePtr<PrefFontList>, + size_t(mozilla::StyleGenericFontFamily::None), + size_t( + mozilla::StyleGenericFontFamily::MozEmoji)> + PrefFontsForLangGroup; + mozilla::RangedArray<PrefFontsForLangGroup, eFontPrefLang_First, + eFontPrefLang_Count> + mLangGroupPrefFonts MOZ_GUARDED_BY(mLock); + mozilla::UniquePtr<PrefFontList> mEmojiPrefFont MOZ_GUARDED_BY(mLock); + + // When system-wide font lookup fails for a character, cache it to skip future + // searches. This is an array of bitsets, one for each FontVisibility level. + mozilla::EnumeratedArray<FontVisibility, FontVisibility::Count, + gfxSparseBitSet> + mCodepointsWithNoFonts MOZ_GUARDED_BY(mLock); + + // the family to use for U+FFFD fallback, to avoid expensive search every time + // on pages with lots of problems + mozilla::EnumeratedArray<FontVisibility, FontVisibility::Count, FontFamily> + mReplacementCharFallbackFamily MOZ_GUARDED_BY(mLock); + + // Sorted array of lowercased family names; use ContainsSorted to test + nsTArray<nsCString> mBadUnderlineFamilyNames; + + // character map data shared across families + // contains weak ptrs to cmaps shared by font entry objects + nsTHashtable<CharMapHashKey> mSharedCmaps MOZ_GUARDED_BY(mLock); + + nsTHashtable<ShmemCharMapHashEntry> mShmemCharMaps MOZ_GUARDED_BY(mLock); + + // data used as part of the font cmap loading process + nsTArray<RefPtr<gfxFontFamily>> mFontFamiliesToLoad MOZ_GUARDED_BY(mLock); + uint32_t mStartIndex MOZ_GUARDED_BY(mLock) = 0; + uint32_t mNumFamilies MOZ_GUARDED_BY(mLock) = 0; + + // xxx - info for diagnosing no default font aborts + // see bugs 636957, 1070983, 1189129 + uint32_t mFontlistInitCount = 0; // num times InitFontList called + + nsTHashSet<gfxUserFontSet*> mUserFontSetList MOZ_GUARDED_BY(mLock); + + nsLanguageAtomService* mLangService = nullptr; + + nsTArray<uint32_t> mCJKPrefLangs MOZ_GUARDED_BY(mLock); + nsTArray<mozilla::StyleGenericFontFamily> mDefaultGenericsLangGroup + MOZ_GUARDED_BY(mLock); + + nsTArray<nsCString> mEnabledFontsList; + nsTHashSet<nsCString> mIconFontsSet; + + mozilla::UniquePtr<mozilla::fontlist::FontList> mSharedFontList; + + nsClassHashtable<nsCStringHashKey, mozilla::fontlist::AliasData> mAliasTable; + nsTHashMap<nsCStringHashKey, mozilla::fontlist::LocalFaceRec::InitData> + mLocalNameTable; + + nsRefPtrHashtable<nsPtrHashKey<const mozilla::fontlist::Face>, gfxFontEntry> + mFontEntries MOZ_GUARDED_BY(mLock); + + mozilla::UniquePtr<FontPrefs> mFontPrefs; + + RefPtr<gfxFontEntry> mDefaultFontEntry MOZ_GUARDED_BY(mLock); + + RefPtr<mozilla::CancelableRunnable> mLoadCmapsRunnable; + uint32_t mStartedLoadingCmapsFrom MOZ_GUARDED_BY(mLock) = 0xffffffffu; + + bool mFontFamilyWhitelistActive = false; + + static PRThread* sInitFontListThread; +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(gfxPlatformFontList::FindFamiliesFlags) + +#endif /* GFXPLATFORMFONTLIST_H_ */ diff --git a/gfx/thebes/gfxPlatformGtk.cpp b/gfx/thebes/gfxPlatformGtk.cpp new file mode 100644 index 0000000000..7be95b7080 --- /dev/null +++ b/gfx/thebes/gfxPlatformGtk.cpp @@ -0,0 +1,1038 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#define PANGO_ENABLE_BACKEND +#define PANGO_ENABLE_ENGINE + +#include "gfxPlatformGtk.h" + +#include <gtk/gtk.h> +#include <fontconfig/fontconfig.h> + +#include "base/task.h" +#include "base/thread.h" +#include "base/message_loop.h" +#include "cairo.h" +#include "gfx2DGlue.h" +#include "gfxFcPlatformFontList.h" +#include "gfxConfig.h" +#include "gfxContext.h" +#include "gfxImageSurface.h" +#include "gfxUserFontSet.h" +#include "gfxUtils.h" +#include "gfxFT2FontBase.h" +#include "gfxTextRun.h" +#include "GLContextProvider.h" +#include "mozilla/Atomics.h" +#include "mozilla/Components.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/Monitor.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/StaticPrefs_media.h" +#include "nsAppRunner.h" +#include "nsIGfxInfo.h" +#include "nsMathUtils.h" +#include "nsUnicharUtils.h" +#include "nsUnicodeProperties.h" +#include "prenv.h" +#include "VsyncSource.h" +#include "mozilla/WidgetUtilsGtk.h" + +#ifdef MOZ_X11 +# include "mozilla/gfx/XlibDisplay.h" +# include <gdk/gdkx.h> +# include <X11/extensions/Xrandr.h> +# include "cairo-xlib.h" +# include "gfxXlibSurface.h" +# include "GLContextGLX.h" +# include "GLXLibrary.h" +# include "mozilla/X11Util.h" +# include "SoftwareVsyncSource.h" + +/* Undefine the Status from Xlib since it will conflict with system headers on + * OSX */ +# if defined(__APPLE__) && defined(Status) +# undef Status +# endif +#endif /* MOZ_X11 */ + +#ifdef MOZ_WAYLAND +# include <gdk/gdkwayland.h> +# include "mozilla/widget/nsWaylandDisplay.h" +#endif +#ifdef MOZ_WIDGET_GTK +# include "mozilla/widget/DMABufLibWrapper.h" +# include "mozilla/StaticPrefs_widget.h" +#endif + +#define GDK_PIXMAP_SIZE_MAX 32767 + +#define GFX_PREF_MAX_GENERIC_SUBSTITUTIONS \ + "gfx.font_rendering.fontconfig.max_generic_substitutions" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::unicode; +using namespace mozilla::widget; + +static FT_Library gPlatformFTLibrary = nullptr; +static int32_t sDPI; + +static void screen_resolution_changed(GdkScreen* aScreen, GParamSpec* aPspec, + gpointer aClosure) { + sDPI = 0; +} + +#if defined(MOZ_X11) +// TODO(aosmond): The envvar is deprecated. We should remove it once EGL is the +// default in release. +static bool IsX11EGLEnvvarEnabled() { + const char* eglPref = PR_GetEnv("MOZ_X11_EGL"); + return (eglPref && *eglPref); +} +#endif + +gfxPlatformGtk::gfxPlatformGtk() { + if (!gfxPlatform::IsHeadless()) { + if (!gtk_init_check(nullptr, nullptr)) { + gfxCriticalNote << "Failed to init Gtk, missing display? DISPLAY=" + << getenv("DISPLAY") + << " WAYLAND_DISPLAY=" << getenv("WAYLAND_DISPLAY") + << "\n"; + abort(); + } + } + + mIsX11Display = gfxPlatform::IsHeadless() ? false : GdkIsX11Display(); + if (XRE_IsParentProcess()) { + InitX11EGLConfig(); + if (IsWaylandDisplay() || gfxConfig::IsEnabled(Feature::X11_EGL)) { + gfxVars::SetUseEGL(true); + } + InitDmabufConfig(); + if (gfxConfig::IsEnabled(Feature::DMABUF)) { + gfxVars::SetUseDMABuf(true); + } + } + + InitBackendPrefs(GetBackendPrefs()); + + gPlatformFTLibrary = Factory::NewFTLibrary(); + MOZ_RELEASE_ASSERT(gPlatformFTLibrary); + Factory::SetFTLibrary(gPlatformFTLibrary); + + GdkScreen* gdkScreen = gdk_screen_get_default(); + if (gdkScreen) { + g_signal_connect(gdkScreen, "notify::resolution", + G_CALLBACK(screen_resolution_changed), nullptr); + } + + // Bug 1714483: Force disable FXAA Antialiasing on NV drivers. This is a + // temporary workaround for a driver bug. + PR_SetEnv("__GL_ALLOW_FXAA_USAGE=0"); +} + +gfxPlatformGtk::~gfxPlatformGtk() { + Factory::ReleaseFTLibrary(gPlatformFTLibrary); + gPlatformFTLibrary = nullptr; +} + +void gfxPlatformGtk::InitAcceleration() { + gfxPlatform::InitAcceleration(); + + if (XRE_IsContentProcess()) { + ImportCachedContentDeviceData(); + } +} + +void gfxPlatformGtk::InitX11EGLConfig() { + FeatureState& feature = gfxConfig::GetFeature(Feature::X11_EGL); +#ifdef MOZ_X11 + feature.EnableByDefault(); + + if (StaticPrefs::gfx_x11_egl_force_enabled_AtStartup()) { + feature.UserForceEnable("Force enabled by pref"); + } else if (IsX11EGLEnvvarEnabled()) { + feature.UserForceEnable("Force enabled by envvar"); + } else if (StaticPrefs::gfx_x11_egl_force_disabled_AtStartup()) { + feature.UserDisable("Force disabled by pref", + "FEATURE_FAILURE_USER_FORCE_DISABLED"_ns); + } + + nsCString failureId; + int32_t status; + nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); + if (NS_FAILED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_X11_EGL, + failureId, &status))) { + feature.Disable(FeatureStatus::BlockedNoGfxInfo, "gfxInfo is broken", + "FEATURE_FAILURE_NO_GFX_INFO"_ns); + } else if (status != nsIGfxInfo::FEATURE_STATUS_OK) { + feature.Disable(FeatureStatus::Blocklisted, "Blocklisted by gfxInfo", + failureId); + } + + nsAutoString testType; + gfxInfo->GetTestType(testType); + // We can only use X11/EGL if we actually found the EGL library and + // successfully use it to determine system information in glxtest. + if (testType != u"EGL") { + feature.ForceDisable(FeatureStatus::Broken, "glxtest could not use EGL", + "FEATURE_FAILURE_GLXTEST_NO_EGL"_ns); + } + + if (feature.IsEnabled() && IsX11Display()) { + // Enabling glthread crashes on X11/EGL, see bug 1670545 + PR_SetEnv("mesa_glthread=false"); + } +#else + feature.DisableByDefault(FeatureStatus::Unavailable, "X11 support missing", + "FEATURE_FAILURE_NO_X11"_ns); +#endif +} + +void gfxPlatformGtk::InitDmabufConfig() { + FeatureState& feature = gfxConfig::GetFeature(Feature::DMABUF); + feature.EnableByDefault(); + + nsCString failureId; + int32_t status; + nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); + if (NS_FAILED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DMABUF, failureId, + &status))) { + feature.Disable(FeatureStatus::BlockedNoGfxInfo, "gfxInfo is broken", + "FEATURE_FAILURE_NO_GFX_INFO"_ns); + } else if (status != nsIGfxInfo::FEATURE_STATUS_OK) { + feature.Disable(FeatureStatus::Blocklisted, "Blocklisted by gfxInfo", + failureId); + } + + if (StaticPrefs::widget_dmabuf_force_enabled_AtStartup()) { + feature.UserForceEnable("Force enabled by pref"); + } else if (!StaticPrefs::widget_dmabuf_enabled_AtStartup()) { + feature.UserDisable("Force disable by pref", + "FEATURE_FAILURE_USER_FORCE_DISABLED"_ns); + } + + if (!gfxVars::UseEGL()) { + feature.ForceDisable(FeatureStatus::Unavailable, "Requires EGL", + "FEATURE_FAILURE_REQUIRES_EGL"_ns); + } + + if (!gfxVars::WebglUseHardware()) { + feature.Disable(FeatureStatus::Blocklisted, + "DMABuf disabled with software rendering", failureId); + } + + nsAutoCString drmRenderDevice; + gfxInfo->GetDrmRenderDevice(drmRenderDevice); + gfxVars::SetDrmRenderDevice(drmRenderDevice); + + if (feature.IsEnabled()) { + if (!GetDMABufDevice()->IsEnabled(failureId)) { + feature.ForceDisable(FeatureStatus::Failed, "Failed to configure", + failureId); + } + } +} + +bool gfxPlatformGtk::InitVAAPIConfig(bool aForceEnabledByUser) { + FeatureState& feature = + gfxConfig::GetFeature(Feature::HARDWARE_VIDEO_DECODING); + // We're already configured in parent process + if (!XRE_IsParentProcess()) { + return feature.IsEnabled(); + } + feature.EnableByDefault(); + + if (aForceEnabledByUser) { + feature.UserForceEnable("Force enabled by pref"); + } + + int32_t status = nsIGfxInfo::FEATURE_STATUS_UNKNOWN; + nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); + nsCString failureId; + if (NS_FAILED(gfxInfo->GetFeatureStatus( + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, failureId, &status))) { + feature.Disable(FeatureStatus::BlockedNoGfxInfo, "gfxInfo is broken", + "FEATURE_FAILURE_NO_GFX_INFO"_ns); + } else if (status == nsIGfxInfo::FEATURE_BLOCKED_PLATFORM_TEST) { + feature.ForceDisable(FeatureStatus::Unavailable, + "Force disabled by gfxInfo", failureId); + } else if (status != nsIGfxInfo::FEATURE_STATUS_OK) { + feature.Disable(FeatureStatus::Blocklisted, "Blocklisted by gfxInfo", + failureId); + } + if (!gfxVars::UseEGL()) { + feature.ForceDisable(FeatureStatus::Unavailable, "Requires EGL", + "FEATURE_FAILURE_REQUIRES_EGL"_ns); + } + + // Configure zero-copy playback feature. + if (feature.IsEnabled()) { + FeatureState& featureZeroCopy = + gfxConfig::GetFeature(Feature::HW_DECODED_VIDEO_ZERO_COPY); + + featureZeroCopy.EnableByDefault(); + uint32_t state = + StaticPrefs::media_ffmpeg_vaapi_force_surface_zero_copy_AtStartup(); + if (state == 0) { + featureZeroCopy.UserDisable("Force disable by pref", + "FEATURE_FAILURE_USER_FORCE_DISABLED"_ns); + } else if (state == 1) { + featureZeroCopy.UserEnable("Force enabled by pref"); + } else { + nsCString failureId; + int32_t status = nsIGfxInfo::FEATURE_STATUS_UNKNOWN; + nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); + if (NS_FAILED(gfxInfo->GetFeatureStatus( + nsIGfxInfo::FEATURE_HW_DECODED_VIDEO_ZERO_COPY, failureId, + &status))) { + featureZeroCopy.Disable(FeatureStatus::BlockedNoGfxInfo, + "gfxInfo is broken", + "FEATURE_FAILURE_NO_GFX_INFO"_ns); + } else if (status == nsIGfxInfo::FEATURE_BLOCKED_PLATFORM_TEST) { + featureZeroCopy.ForceDisable(FeatureStatus::Unavailable, + "Force disabled by gfxInfo", failureId); + } else if (status != nsIGfxInfo::FEATURE_ALLOW_ALWAYS) { + featureZeroCopy.Disable(FeatureStatus::Blocklisted, + "Blocklisted by gfxInfo", failureId); + } + } + if (featureZeroCopy.IsEnabled()) { + gfxVars::SetHwDecodedVideoZeroCopy(true); + } + } + return feature.IsEnabled(); +} + +void gfxPlatformGtk::InitWebRenderConfig() { + gfxPlatform::InitWebRenderConfig(); + + if (!XRE_IsParentProcess()) { + return; + } + + FeatureState& feature = gfxConfig::GetFeature(Feature::WEBRENDER_COMPOSITOR); +#ifdef RELEASE_OR_BETA + feature.ForceDisable(FeatureStatus::Blocked, + "Cannot be enabled in release or beta", + "FEATURE_FAILURE_DISABLE_RELEASE_OR_BETA"_ns); +#else + if (feature.IsEnabled()) { + if (!IsWaylandDisplay()) { + feature.ForceDisable(FeatureStatus::Unavailable, + "Wayland support missing", + "FEATURE_FAILURE_NO_WAYLAND"_ns); + } +# ifdef MOZ_WAYLAND + else if (gfxConfig::IsEnabled(Feature::WEBRENDER) && + !gfxConfig::IsEnabled(Feature::DMABUF)) { + // We use zwp_linux_dmabuf_v1 and GBM directly to manage FBOs. In theory + // this is also possible vie EGLstreams, but we don't bother to implement + // it as recent NVidia drivers support GBM and DMABuf as well. + feature.ForceDisable(FeatureStatus::Unavailable, + "Hardware Webrender requires DMAbuf support", + "FEATURE_FAILURE_NO_DMABUF"_ns); + } else if (!widget::WaylandDisplayGet()->GetViewporter()) { + feature.ForceDisable(FeatureStatus::Unavailable, + "Requires wp_viewporter protocol support", + "FEATURE_FAILURE_REQUIRES_WPVIEWPORTER"_ns); + } +# endif // MOZ_WAYLAND + } +#endif // RELEASE_OR_BETA + + gfxVars::SetUseWebRenderCompositor(feature.IsEnabled()); +} + +void gfxPlatformGtk::InitPlatformGPUProcessPrefs() { +#ifdef MOZ_WAYLAND + if (IsWaylandDisplay()) { + FeatureState& gpuProc = gfxConfig::GetFeature(Feature::GPU_PROCESS); + gpuProc.ForceDisable(FeatureStatus::Blocked, + "Wayland does not work in the GPU process", + "FEATURE_FAILURE_WAYLAND"_ns); + } +#endif +} + +already_AddRefed<gfxASurface> gfxPlatformGtk::CreateOffscreenSurface( + const IntSize& aSize, gfxImageFormat aFormat) { + if (!Factory::AllowedSurfaceSize(aSize)) { + return nullptr; + } + + RefPtr<gfxASurface> newSurface; + bool needsClear = true; + // XXX we really need a different interface here, something that passes + // in more context, including the display and/or target surface type that + // we should try to match + GdkScreen* gdkScreen = gdk_screen_get_default(); + if (gdkScreen) { + newSurface = new gfxImageSurface(aSize, aFormat); + // The gfxImageSurface ctor zeroes this for us, no need to + // waste time clearing again + needsClear = false; + } + + if (!newSurface) { + // We couldn't create a native surface for whatever reason; + // e.g., no display, no RENDER, bad size, etc. + // Fall back to image surface for the data. + newSurface = new gfxImageSurface(aSize, aFormat); + } + + if (newSurface->CairoStatus()) { + newSurface = nullptr; // surface isn't valid for some reason + } + + if (newSurface && needsClear) { + gfxUtils::ClearThebesSurface(newSurface); + } + + return newSurface.forget(); +} + +nsresult gfxPlatformGtk::GetFontList(nsAtom* aLangGroup, + const nsACString& aGenericFamily, + nsTArray<nsString>& aListOfFonts) { + gfxPlatformFontList::PlatformFontList()->GetFontList( + aLangGroup, aGenericFamily, aListOfFonts); + return NS_OK; +} + +// xxx - this is ubuntu centric, need to go through other distros and flesh +// out a more general list +static const char kFontDejaVuSans[] = "DejaVu Sans"; +static const char kFontDejaVuSerif[] = "DejaVu Serif"; +static const char kFontFreeSans[] = "FreeSans"; +static const char kFontFreeSerif[] = "FreeSerif"; +static const char kFontTakaoPGothic[] = "TakaoPGothic"; +static const char kFontTwemojiMozilla[] = "Twemoji Mozilla"; +static const char kFontDroidSansFallback[] = "Droid Sans Fallback"; +static const char kFontWenQuanYiMicroHei[] = "WenQuanYi Micro Hei"; +static const char kFontNanumGothic[] = "NanumGothic"; +static const char kFontSymbola[] = "Symbola"; +static const char kFontNotoSansSymbols[] = "Noto Sans Symbols"; +static const char kFontNotoSansSymbols2[] = "Noto Sans Symbols2"; + +void gfxPlatformGtk::GetCommonFallbackFonts(uint32_t aCh, Script aRunScript, + eFontPresentation aPresentation, + nsTArray<const char*>& aFontList) { + if (PrefersColor(aPresentation)) { + aFontList.AppendElement(kFontTwemojiMozilla); + } + + aFontList.AppendElement(kFontDejaVuSerif); + aFontList.AppendElement(kFontFreeSerif); + aFontList.AppendElement(kFontDejaVuSans); + aFontList.AppendElement(kFontFreeSans); + aFontList.AppendElement(kFontSymbola); + aFontList.AppendElement(kFontNotoSansSymbols); + aFontList.AppendElement(kFontNotoSansSymbols2); + + // add fonts for CJK ranges + // xxx - this isn't really correct, should use the same CJK font ordering + // as the pref font code + if (aCh >= 0x3000 && ((aCh < 0xe000) || (aCh >= 0xf900 && aCh < 0xfff0) || + ((aCh >> 16) == 2))) { + aFontList.AppendElement(kFontTakaoPGothic); + aFontList.AppendElement(kFontDroidSansFallback); + aFontList.AppendElement(kFontWenQuanYiMicroHei); + aFontList.AppendElement(kFontNanumGothic); + } +} + +void gfxPlatformGtk::ReadSystemFontList( + mozilla::dom::SystemFontList* retValue) { + gfxFcPlatformFontList::PlatformFontList()->ReadSystemFontList(retValue); +} + +bool gfxPlatformGtk::CreatePlatformFontList() { + return gfxPlatformFontList::Initialize(new gfxFcPlatformFontList); +} + +int32_t gfxPlatformGtk::GetFontScaleDPI() { + MOZ_ASSERT(XRE_IsParentProcess(), + "You can access this via LookAndFeel if you need it in child " + "processes"); + if (MOZ_LIKELY(sDPI != 0)) { + return sDPI; + } + GdkScreen* screen = gdk_screen_get_default(); + // Ensure settings in config files are processed. + gtk_settings_get_for_screen(screen); + int32_t dpi = int32_t(round(gdk_screen_get_resolution(screen))); + if (dpi <= 0) { + // Fall back to something reasonable + dpi = 96; + } + sDPI = dpi; + return dpi; +} + +double gfxPlatformGtk::GetFontScaleFactor() { + // Integer scale factors work well with GTK window scaling, image scaling, and + // pixel alignment, but there is a range where 1 is too small and 2 is too + // big. + // + // An additional step of 1.5 is added because this is common scale on WINNT + // and at this ratio the advantages of larger rendering outweigh the + // disadvantages from scaling and pixel mis-alignment. + // + // A similar step for 1.25 is added as well, because this is the scale that + // "Large text" settings use in gnome, and it seems worth to allow, especially + // on already-hidpi environments. + int32_t dpi = GetFontScaleDPI(); + if (dpi < 120) { + return 1.0; + } + if (dpi < 132) { + return 1.25; + } + if (dpi < 168) { + return 1.5; + } + return round(dpi / 96.0); +} + +gfxImageFormat gfxPlatformGtk::GetOffscreenFormat() { + // Make sure there is a screen + GdkScreen* screen = gdk_screen_get_default(); + if (screen && gdk_visual_get_depth(gdk_visual_get_system()) == 16) { + return SurfaceFormat::R5G6B5_UINT16; + } + + return SurfaceFormat::X8R8G8B8_UINT32; +} + +void gfxPlatformGtk::FontsPrefsChanged(const char* aPref) { + // only checking for generic substitions, pass other changes up + if (strcmp(GFX_PREF_MAX_GENERIC_SUBSTITUTIONS, aPref) != 0) { + gfxPlatform::FontsPrefsChanged(aPref); + return; + } + + gfxFcPlatformFontList* pfl = gfxFcPlatformFontList::PlatformFontList(); + pfl->ClearGenericMappings(); + FlushFontAndWordCaches(); +} + +bool gfxPlatformGtk::AccelerateLayersByDefault() { return true; } + +#if defined(MOZ_X11) + +static nsTArray<uint8_t> GetDisplayICCProfile(Display* dpy, Window& root) { + const char kIccProfileAtomName[] = "_ICC_PROFILE"; + Atom iccAtom = XInternAtom(dpy, kIccProfileAtomName, TRUE); + if (!iccAtom) { + return nsTArray<uint8_t>(); + } + + Atom retAtom; + int retFormat; + unsigned long retLength, retAfter; + unsigned char* retProperty; + + if (XGetWindowProperty(dpy, root, iccAtom, 0, INT_MAX /* length */, X11False, + AnyPropertyType, &retAtom, &retFormat, &retLength, + &retAfter, &retProperty) != Success) { + return nsTArray<uint8_t>(); + } + + nsTArray<uint8_t> result; + + if (retLength > 0) { + result.AppendElements(static_cast<uint8_t*>(retProperty), retLength); + } + + XFree(retProperty); + + return result; +} + +nsTArray<uint8_t> gfxPlatformGtk::GetPlatformCMSOutputProfileData() { + nsTArray<uint8_t> prefProfileData = GetPrefCMSOutputProfileData(); + if (!prefProfileData.IsEmpty()) { + return prefProfileData; + } + + if (XRE_IsContentProcess()) { + auto& cmsOutputProfileData = GetCMSOutputProfileData(); + // We should have set our profile data when we received our initial + // ContentDeviceData. + MOZ_ASSERT(cmsOutputProfileData.isSome(), + "Should have created output profile data when we received " + "initial content device data."); + if (cmsOutputProfileData.isSome()) { + return cmsOutputProfileData.ref().Clone(); + } + return nsTArray<uint8_t>(); + } + + if (!mIsX11Display) { + return nsTArray<uint8_t>(); + } + + GdkDisplay* display = gdk_display_get_default(); + Display* dpy = GDK_DISPLAY_XDISPLAY(display); + // In xpcshell tests, we never initialize X and hence don't have a Display. + // In this case, there's no output colour management to be done, so we just + // return with nullptr. + if (!dpy) { + return nsTArray<uint8_t>(); + } + + Window root = gdk_x11_get_default_root_xwindow(); + + // First try ICC Profile + nsTArray<uint8_t> iccResult = GetDisplayICCProfile(dpy, root); + if (!iccResult.IsEmpty()) { + return iccResult; + } + + // If ICC doesn't work, then try EDID + const char kEdid1AtomName[] = "XFree86_DDC_EDID1_RAWDATA"; + Atom edidAtom = XInternAtom(dpy, kEdid1AtomName, TRUE); + if (!edidAtom) { + return nsTArray<uint8_t>(); + } + + Atom retAtom; + int retFormat; + unsigned long retLength, retAfter; + unsigned char* retProperty; + + if (XGetWindowProperty(dpy, root, edidAtom, 0, 32, X11False, AnyPropertyType, + &retAtom, &retFormat, &retLength, &retAfter, + &retProperty) != Success) { + return nsTArray<uint8_t>(); + } + + if (retLength != 128) { + return nsTArray<uint8_t>(); + } + + // Format documented in "VESA E-EDID Implementation Guide" + float gamma = (100 + (float)retProperty[0x17]) / 100.0f; + + qcms_CIE_xyY whitePoint; + whitePoint.x = + ((retProperty[0x21] << 2) | (retProperty[0x1a] >> 2 & 3)) / 1024.0; + whitePoint.y = + ((retProperty[0x22] << 2) | (retProperty[0x1a] >> 0 & 3)) / 1024.0; + whitePoint.Y = 1.0; + + qcms_CIE_xyYTRIPLE primaries; + primaries.red.x = + ((retProperty[0x1b] << 2) | (retProperty[0x19] >> 6 & 3)) / 1024.0; + primaries.red.y = + ((retProperty[0x1c] << 2) | (retProperty[0x19] >> 4 & 3)) / 1024.0; + primaries.red.Y = 1.0; + + primaries.green.x = + ((retProperty[0x1d] << 2) | (retProperty[0x19] >> 2 & 3)) / 1024.0; + primaries.green.y = + ((retProperty[0x1e] << 2) | (retProperty[0x19] >> 0 & 3)) / 1024.0; + primaries.green.Y = 1.0; + + primaries.blue.x = + ((retProperty[0x1f] << 2) | (retProperty[0x1a] >> 6 & 3)) / 1024.0; + primaries.blue.y = + ((retProperty[0x20] << 2) | (retProperty[0x1a] >> 4 & 3)) / 1024.0; + primaries.blue.Y = 1.0; + + XFree(retProperty); + + void* mem = nullptr; + size_t size = 0; + qcms_data_create_rgb_with_gamma(whitePoint, primaries, gamma, &mem, &size); + if (!mem) { + return nsTArray<uint8_t>(); + } + + nsTArray<uint8_t> result; + result.AppendElements(static_cast<uint8_t*>(mem), size); + free(mem); + + // XXX: It seems like we get wrong colors when using this constructed profile: + // See bug 1696819. For now just forget that we made it. + return nsTArray<uint8_t>(); +} + +#else // defined(MOZ_X11) + +nsTArray<uint8_t> gfxPlatformGtk::GetPlatformCMSOutputProfileData() { + return nsTArray<uint8_t>(); +} + +#endif + +bool gfxPlatformGtk::CheckVariationFontSupport() { + // Although there was some variation/multiple-master support in FreeType + // in older versions, it seems too incomplete/unstable for us to use + // until at least 2.7.1. + FT_Int major, minor, patch; + FT_Library_Version(Factory::GetFTLibrary(), &major, &minor, &patch); + return major * 1000000 + minor * 1000 + patch >= 2007001; +} + +#ifdef MOZ_X11 + +class GtkVsyncSource final : public VsyncSource { + public: + GtkVsyncSource() + : mGLContext(nullptr), + mXDisplay(nullptr), + mSetupLock("GLXVsyncSetupLock"), + mVsyncThread("GLXVsyncThread"), + mVsyncTask(nullptr), + mVsyncEnabledLock("GLXVsyncEnabledLock"), + mVsyncEnabled(false) { + MOZ_ASSERT(NS_IsMainThread()); + } + + virtual ~GtkVsyncSource() { MOZ_ASSERT(NS_IsMainThread()); } + + // Sets up the display's GL context on a worker thread. + // Required as GLContexts may only be used by the creating thread. + // Returns true if setup was a success. + bool Setup() { + MonitorAutoLock lock(mSetupLock); + MOZ_ASSERT(NS_IsMainThread()); + if (!mVsyncThread.Start()) return false; + + RefPtr<Runnable> vsyncSetup = + NewRunnableMethod("GtkVsyncSource::SetupGLContext", this, + &GtkVsyncSource::SetupGLContext); + mVsyncThread.message_loop()->PostTask(vsyncSetup.forget()); + // Wait until the setup has completed. + lock.Wait(); + return mGLContext != nullptr; + } + + // Called on the Vsync thread to setup the GL context. + void SetupGLContext() { + MonitorAutoLock lock(mSetupLock); + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(!mGLContext, "GLContext already setup!"); + + // Create video sync timer on a separate Display to prevent locking the + // main thread X display. + mXDisplay = XOpenDisplay(nullptr); + if (!mXDisplay) { + lock.NotifyAll(); + return; + } + + // Most compositors wait for vsync events on the root window. + Window root = DefaultRootWindow(mXDisplay); + int screen = DefaultScreen(mXDisplay); + + GLXFBConfig config; + int visid; + bool forWebRender = false; + if (!gl::GLContextGLX::FindFBConfigForWindow( + mXDisplay, screen, root, &config, &visid, forWebRender)) { + lock.NotifyAll(); + return; + } + + mGLContext = gl::GLContextGLX::CreateGLContext( + {}, gfx::XlibDisplay::Borrow(mXDisplay), root, config); + + if (!mGLContext) { + lock.NotifyAll(); + return; + } + + mGLContext->MakeCurrent(); + + // Test that SGI_video_sync lets us get the counter. + unsigned int syncCounter = 0; + if (gl::sGLXLibrary.fGetVideoSync(&syncCounter) != 0) { + mGLContext = nullptr; + } + + lock.NotifyAll(); + } + + virtual void EnableVsync() override { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mGLContext, "GLContext not setup!"); + + MonitorAutoLock lock(mVsyncEnabledLock); + if (mVsyncEnabled) { + return; + } + mVsyncEnabled = true; + + // If the task has not nulled itself out, it hasn't yet realized + // that vsync was disabled earlier, so continue its execution. + if (!mVsyncTask) { + mVsyncTask = NewRunnableMethod("GtkVsyncSource::RunVsync", this, + &GtkVsyncSource::RunVsync); + RefPtr<Runnable> addrefedTask = mVsyncTask; + mVsyncThread.message_loop()->PostTask(addrefedTask.forget()); + } + } + + virtual void DisableVsync() override { + MonitorAutoLock lock(mVsyncEnabledLock); + mVsyncEnabled = false; + } + + virtual bool IsVsyncEnabled() override { + MonitorAutoLock lock(mVsyncEnabledLock); + return mVsyncEnabled; + } + + virtual void Shutdown() override { + MOZ_ASSERT(NS_IsMainThread()); + DisableVsync(); + + // Cleanup thread-specific resources before shutting down. + RefPtr<Runnable> shutdownTask = NewRunnableMethod( + "GtkVsyncSource::Cleanup", this, &GtkVsyncSource::Cleanup); + mVsyncThread.message_loop()->PostTask(shutdownTask.forget()); + + // Stop, waiting for the cleanup task to finish execution. + mVsyncThread.Stop(); + } + + private: + void RunVsync() { + MOZ_ASSERT(!NS_IsMainThread()); + + mGLContext->MakeCurrent(); + + unsigned int syncCounter = 0; + gl::sGLXLibrary.fGetVideoSync(&syncCounter); + for (;;) { + { + MonitorAutoLock lock(mVsyncEnabledLock); + if (!mVsyncEnabled) { + mVsyncTask = nullptr; + return; + } + } + + TimeStamp lastVsync = TimeStamp::Now(); + bool useSoftware = false; + + // Wait until the video sync counter reaches the next value by waiting + // until the parity of the counter value changes. + unsigned int nextSync = syncCounter + 1; + int status; + if ((status = gl::sGLXLibrary.fWaitVideoSync(2, (int)nextSync % 2, + &syncCounter)) != 0) { + gfxWarningOnce() << "glXWaitVideoSync returned " << status; + useSoftware = true; + } + + if (syncCounter == (nextSync - 1)) { + gfxWarningOnce() + << "glXWaitVideoSync failed to increment the sync counter."; + useSoftware = true; + } + + if (useSoftware) { + double remaining = + (1000.f / 60.f) - (TimeStamp::Now() - lastVsync).ToMilliseconds(); + if (remaining > 0) { + AUTO_PROFILER_THREAD_SLEEP; + PlatformThread::Sleep((int)remaining); + } + } + + lastVsync = TimeStamp::Now(); + TimeStamp outputTime = lastVsync + GetVsyncRate(); + NotifyVsync(lastVsync, outputTime); + } + } + + void Cleanup() { + MOZ_ASSERT(!NS_IsMainThread()); + + mGLContext = nullptr; + if (mXDisplay) XCloseDisplay(mXDisplay); + } + + // Owned by the vsync thread. + RefPtr<gl::GLContextGLX> mGLContext; + _XDisplay* mXDisplay; + Monitor mSetupLock MOZ_UNANNOTATED; + base::Thread mVsyncThread; + RefPtr<Runnable> mVsyncTask; + Monitor mVsyncEnabledLock MOZ_UNANNOTATED; + bool mVsyncEnabled; +}; + +class XrandrSoftwareVsyncSource final + : public mozilla::gfx::SoftwareVsyncSource { + public: + XrandrSoftwareVsyncSource() : SoftwareVsyncSource(ComputeVsyncRate()) { + MOZ_ASSERT(NS_IsMainThread()); + + GdkScreen* defaultScreen = gdk_screen_get_default(); + g_signal_connect(defaultScreen, "monitors-changed", + G_CALLBACK(monitors_changed), this); + } + + private: + // Request the current refresh rate via xrandr. It is hard to find the + // "correct" one, thus choose the highest one, assuming this will usually + // give the best user experience. + static mozilla::TimeDuration ComputeVsyncRate() { + struct _XDisplay* dpy = gdk_x11_get_default_xdisplay(); + + // Use the default software refresh rate as lower bound. Allowing lower + // rates makes a bunch of tests start to fail on CI. The main goal of this + // VsyncSource is to support refresh rates greater than the default one. + double highestRefreshRate = gfxPlatform::GetSoftwareVsyncRate(); + + // When running on remote X11 the xrandr version may be stuck on an + // ancient version. There are still setups using remote X11 out there, so + // make sure we don't crash. + int eventBase, errorBase, major, minor; + if (XRRQueryExtension(dpy, &eventBase, &errorBase) && + XRRQueryVersion(dpy, &major, &minor) && + (major > 1 || (major == 1 && minor >= 3))) { + Window root = gdk_x11_get_default_root_xwindow(); + XRRScreenResources* res = XRRGetScreenResourcesCurrent(dpy, root); + + if (res) { + // We can't use refresh rates far below the default one (60Hz) because + // otherwise random CI tests start to fail. However, many users have + // screens just below the default rate, e.g. 59.95Hz. So slightly + // decrease the lower bound. + highestRefreshRate -= 1.0; + + for (int i = 0; i < res->noutput; i++) { + XRROutputInfo* outputInfo = + XRRGetOutputInfo(dpy, res, res->outputs[i]); + if (outputInfo) { + if (outputInfo->crtc) { + XRRCrtcInfo* crtcInfo = + XRRGetCrtcInfo(dpy, res, outputInfo->crtc); + if (crtcInfo) { + for (int j = 0; j < res->nmode; j++) { + if (res->modes[j].id == crtcInfo->mode) { + double refreshRate = mode_refresh(&res->modes[j]); + if (refreshRate > highestRefreshRate) { + highestRefreshRate = refreshRate; + } + break; + } + } + + XRRFreeCrtcInfo(crtcInfo); + } + } + + XRRFreeOutputInfo(outputInfo); + } + } + } + XRRFreeScreenResources(res); + } + + const double rate = 1000.0 / highestRefreshRate; + return mozilla::TimeDuration::FromMilliseconds(rate); + } + + static void monitors_changed(GdkScreen* aScreen, gpointer aClosure) { + XrandrSoftwareVsyncSource* self = + static_cast<XrandrSoftwareVsyncSource*>(aClosure); + self->SetVsyncRate(ComputeVsyncRate()); + } + + // from xrandr.c + static double mode_refresh(const XRRModeInfo* mode_info) { + double rate; + double vTotal = mode_info->vTotal; + + if (mode_info->modeFlags & RR_DoubleScan) { + /* doublescan doubles the number of lines */ + vTotal *= 2; + } + + if (mode_info->modeFlags & RR_Interlace) { + /* interlace splits the frame into two fields */ + /* the field rate is what is typically reported by monitors */ + vTotal /= 2; + } + + if (mode_info->hTotal && vTotal) { + rate = ((double)mode_info->dotClock / + ((double)mode_info->hTotal * (double)vTotal)); + } else { + rate = 0; + } + return rate; + } +}; +#endif + +already_AddRefed<gfx::VsyncSource> +gfxPlatformGtk::CreateGlobalHardwareVsyncSource() { +#ifdef MOZ_X11 + if (IsHeadless() || IsWaylandDisplay()) { + // On Wayland we can not create a global hardware based vsync source, thus + // use a software based one here. We create window specific ones later. + return GetSoftwareVsyncSource(); + } + + nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); + nsString windowProtocol; + gfxInfo->GetWindowProtocol(windowProtocol); + bool isXwayland = windowProtocol.Find(u"xwayland") != -1; + nsString adapterDriverVendor; + gfxInfo->GetAdapterDriverVendor(adapterDriverVendor); + bool isMesa = adapterDriverVendor.Find(u"mesa") != -1; + + // Only use GLX vsync when the OpenGL compositor / WebRender is being used. + // The extra cost of initializing a GLX context while blocking the main thread + // is not worth it when using basic composition. Do not use it on Xwayland, as + // Xwayland will give us a software timer as we are listening for the root + // window, which does not have a Wayland equivalent. Don't call + // gl::sGLXLibrary.SupportsVideoSync() when EGL is used as NVIDIA drivers + // refuse to use EGL GL context when GLX was initialized first and fail + // silently. + if (StaticPrefs::gfx_x11_glx_sgi_video_sync_AtStartup() && + gfxConfig::IsEnabled(Feature::HW_COMPOSITING) && !isXwayland && + (!gfxVars::UseEGL() || isMesa) && + gl::sGLXLibrary.SupportsVideoSync(DefaultXDisplay())) { + RefPtr<GtkVsyncSource> vsyncSource = new GtkVsyncSource(); + if (!vsyncSource->Setup()) { + NS_WARNING("Failed to setup GLContext, falling back to software vsync."); + return GetSoftwareVsyncSource(); + } + return vsyncSource.forget(); + } + + RefPtr<VsyncSource> softwareVsync = new XrandrSoftwareVsyncSource(); + return softwareVsync.forget(); +#else + return GetSoftwareVsyncSource(); +#endif +} + +void gfxPlatformGtk::BuildContentDeviceData(ContentDeviceData* aOut) { + gfxPlatform::BuildContentDeviceData(aOut); + + aOut->cmsOutputProfileData() = GetPlatformCMSOutputProfileData(); +} + +// Wrapper for third party code (WebRTC for instance) where +// gfxVars can't be included. +namespace mozilla::gfx { +bool IsDMABufEnabled() { return gfxVars::UseDMABuf(); } +} // namespace mozilla::gfx diff --git a/gfx/thebes/gfxPlatformGtk.h b/gfx/thebes/gfxPlatformGtk.h new file mode 100644 index 0000000000..c28a224925 --- /dev/null +++ b/gfx/thebes/gfxPlatformGtk.h @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_PLATFORM_GTK_H +#define GFX_PLATFORM_GTK_H + +#include "gfxPlatform.h" +#include "nsAutoRef.h" +#include "nsTArray.h" +#include "mozilla/gfx/gfxVars.h" + +#ifdef MOZ_X11 +struct _XDisplay; +typedef struct _XDisplay Display; +#endif // MOZ_X11 + +class gfxPlatformGtk final : public gfxPlatform { + friend class gfxPlatform; + + public: + gfxPlatformGtk(); + virtual ~gfxPlatformGtk(); + + static gfxPlatformGtk* GetPlatform() { + return (gfxPlatformGtk*)gfxPlatform::GetPlatform(); + } + + void ReadSystemFontList(mozilla::dom::SystemFontList* retValue) override; + + already_AddRefed<gfxASurface> CreateOffscreenSurface( + const IntSize& aSize, gfxImageFormat aFormat) override; + + nsresult GetFontList(nsAtom* aLangGroup, const nsACString& aGenericFamily, + nsTArray<nsString>& aListOfFonts) override; + + void GetCommonFallbackFonts(uint32_t aCh, Script aRunScript, + eFontPresentation aPresentation, + nsTArray<const char*>& aFontList) override; + + bool CreatePlatformFontList() override; + + static int32_t GetFontScaleDPI(); + static double GetFontScaleFactor(); + + gfxImageFormat GetOffscreenFormat() override; + + bool SupportsApzWheelInput() const override { return true; } + + void FontsPrefsChanged(const char* aPref) override; + + bool SupportsPluginDirectBitmapDrawing() override { return true; } + + bool AccelerateLayersByDefault() override; + + already_AddRefed<mozilla::gfx::VsyncSource> CreateGlobalHardwareVsyncSource() + override; + + bool IsX11Display() { return mIsX11Display; } + bool IsWaylandDisplay() override { + return !mIsX11Display && !gfxPlatform::IsHeadless(); + } + + static bool CheckVariationFontSupport(); + + protected: + void InitAcceleration() override; + void InitX11EGLConfig(); + void InitDmabufConfig(); + bool InitVAAPIConfig(bool aForceEnabledByUser); + void InitPlatformGPUProcessPrefs() override; + void InitWebRenderConfig() override; + void BuildContentDeviceData(mozilla::gfx::ContentDeviceData* aOut) override; + + private: + nsTArray<uint8_t> GetPlatformCMSOutputProfileData() override; + + bool mIsX11Display; +}; + +// Wrapper for third party code (WebRTC for instance) where +// gfxVars can't be included. +namespace mozilla::gfx { +bool IsDMABufEnabled(); +} + +#endif /* GFX_PLATFORM_GTK_H */ diff --git a/gfx/thebes/gfxPlatformMac.cpp b/gfx/thebes/gfxPlatformMac.cpp new file mode 100644 index 0000000000..02ec1a7b28 --- /dev/null +++ b/gfx/thebes/gfxPlatformMac.cpp @@ -0,0 +1,1014 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxPlatformMac.h" + +#include "gfxQuartzSurface.h" +#include "mozilla/DataMutex.h" +#include "mozilla/gfx/2D.h" + +#include "gfxMacPlatformFontList.h" +#include "gfxMacFont.h" +#include "gfxCoreTextShaper.h" +#include "gfxTextRun.h" +#include "gfxUserFontSet.h" +#include "gfxConfig.h" + +#include "AppleUtils.h" +#include "nsTArray.h" +#include "mozilla/Preferences.h" +#include "mozilla/VsyncDispatcher.h" +#include "nsCocoaFeatures.h" +#include "nsComponentManagerUtils.h" +#include "nsIFile.h" +#include "nsUnicodeProperties.h" +#include "qcms.h" +#include "gfx2DGlue.h" +#include "GeckoProfiler.h" +#include "nsThreadUtils.h" + +#ifdef MOZ_BUNDLED_FONTS +# include "mozilla/Telemetry.h" +# include "nsDirectoryServiceDefs.h" +# include "mozilla/StaticPrefs_gfx.h" +#endif + +#include <dlfcn.h> +#include <CoreVideo/CoreVideo.h> + +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/SurfacePool.h" +#include "VsyncSource.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::unicode; + +using mozilla::dom::SystemFontList; + +// A bunch of fonts for "additional language support" are shipped in a +// "Language Support" directory, and don't show up in the standard font +// list returned by CTFontManagerCopyAvailableFontFamilyNames unless +// we explicitly activate them. +static const nsLiteralCString kLangFontsDirs[] = { + "/Library/Application Support/Apple/Fonts/Language Support"_ns, + "/System/Library/Fonts/Supplemental"_ns}; + +/* static */ +void gfxPlatformMac::FontRegistrationCallback(void* aUnused) { + AUTO_PROFILER_REGISTER_THREAD("RegisterFonts"); + PR_SetCurrentThreadName("RegisterFonts"); + + for (const auto& dir : kLangFontsDirs) { + gfxMacPlatformFontList::ActivateFontsFromDir(dir); + } +} + +PRThread* gfxPlatformMac::sFontRegistrationThread = nullptr; + +/* This is called from XPCOM_Init during startup (before gfxPlatform has been + initialized), so that it can kick off the font activation on a secondary + thread, and hope that it'll be finished by the time we're ready to build + our font list. */ +/* static */ +void gfxPlatformMac::RegisterSupplementalFonts() { + if (XRE_GetProcessType() == GeckoProcessType_Default) { + // We activate the fonts on a separate thread, to minimize the startup- + // time cost. + sFontRegistrationThread = PR_CreateThread( + PR_USER_THREAD, FontRegistrationCallback, nullptr, PR_PRIORITY_NORMAL, + PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0); + } +} + +/* static */ +void gfxPlatformMac::WaitForFontRegistration() { + if (sFontRegistrationThread) { + PR_JoinThread(sFontRegistrationThread); + sFontRegistrationThread = nullptr; + } +} + +gfxPlatformMac::gfxPlatformMac() { + mFontAntiAliasingThreshold = ReadAntiAliasingThreshold(); + + InitBackendPrefs(GetBackendPrefs()); +} + +gfxPlatformMac::~gfxPlatformMac() { gfxCoreTextShaper::Shutdown(); } + +BackendPrefsData gfxPlatformMac::GetBackendPrefs() const { + BackendPrefsData data; + + data.mCanvasBitmask = BackendTypeBit(BackendType::SKIA); + data.mContentBitmask = BackendTypeBit(BackendType::SKIA); + data.mCanvasDefault = BackendType::SKIA; + data.mContentDefault = BackendType::SKIA; + + return data; +} + +bool gfxPlatformMac::CreatePlatformFontList() { + return gfxPlatformFontList::Initialize(new gfxMacPlatformFontList); +} + +void gfxPlatformMac::ReadSystemFontList(SystemFontList* aFontList) { + gfxMacPlatformFontList::PlatformFontList()->ReadSystemFontList(aFontList); +} + +already_AddRefed<gfxASurface> gfxPlatformMac::CreateOffscreenSurface( + const IntSize& aSize, gfxImageFormat aFormat) { + if (!Factory::AllowedSurfaceSize(aSize)) { + return nullptr; + } + + RefPtr<gfxASurface> newSurface = new gfxQuartzSurface(aSize, aFormat); + return newSurface.forget(); +} + +void gfxPlatformMac::GetCommonFallbackFonts(uint32_t aCh, Script aRunScript, + eFontPresentation aPresentation, + nsTArray<const char*>& aFontList) { + if (PrefersColor(aPresentation)) { + aFontList.AppendElement("Apple Color Emoji"); + } + + switch (aRunScript) { + case Script::INVALID: + case Script::NUM_SCRIPT_CODES: + // Ensure the switch covers all the Script enum values. + MOZ_ASSERT_UNREACHABLE("bad script code"); + break; + + case Script::COMMON: + case Script::INHERITED: + // In most cases, COMMON and INHERITED characters will be merged into + // their context, but if they occur without any specific script context + // we'll just try common default fonts here. + case Script::LATIN: + case Script::CYRILLIC: + case Script::GREEK: + aFontList.AppendElement("Lucida Grande"); + break; + + case Script::MATHEMATICAL_NOTATION: + case Script::SYMBOLS: + case Script::SYMBOLS_EMOJI: + // Not currently returned by script run resolution (but see below, after + // the switch). + break; + + // CJK-related script codes are a bit troublesome because of unification; + // we'll probably just get HAN much of the time, so the choice of which + // language font to try for fallback is rather arbitrary. Usually, though, + // we hope that font prefs will have handled this earlier. + case Script::BOPOMOFO: + case Script::HAN_WITH_BOPOMOFO: + case Script::SIMPLIFIED_HAN: + case Script::HAN: + aFontList.AppendElement("Songti SC"); + if (aCh > 0x10000) { + // macOS installations with MS Office may have these -ExtB fonts + aFontList.AppendElement("SimSun-ExtB"); + } + break; + + // Currently, we don't resolve script runs to this value, but we may do so + // in future if we get better at handling things like `lang=zh-Hant`, not + // just resolving based on the Unicode text. + case Script::TRADITIONAL_HAN: + aFontList.AppendElement("Songti TC"); + if (aCh > 0x10000) { + // macOS installations with MS Office may have these -ExtB fonts + aFontList.AppendElement("MingLiU-ExtB"); + } + break; + + case Script::HIRAGANA: + case Script::KATAKANA: + case Script::KATAKANA_OR_HIRAGANA: + case Script::JAPANESE: + aFontList.AppendElement("Hiragino Sans"); + aFontList.AppendElement("Hiragino Kaku Gothic ProN"); + break; + + case Script::JAMO: + case Script::KOREAN: + case Script::HANGUL: + aFontList.AppendElement("Nanum Gothic"); + aFontList.AppendElement("Apple SD Gothic Neo"); + break; + + // For most other scripts, macOS comes with a default font we can use. + case Script::ARABIC: + aFontList.AppendElement("Geeza Pro"); + break; + case Script::ARMENIAN: + aFontList.AppendElement("Mshtakan"); + break; + case Script::BENGALI: + aFontList.AppendElement("Bangla Sangam MN"); + break; + case Script::CHEROKEE: + aFontList.AppendElement("Plantagenet Cherokee"); + break; + case Script::COPTIC: + aFontList.AppendElement("Noto Sans Coptic"); + break; + case Script::DESERET: + aFontList.AppendElement("Baskerville"); + break; + case Script::DEVANAGARI: + aFontList.AppendElement("Devanagari Sangam MN"); + break; + case Script::ETHIOPIC: + aFontList.AppendElement("Kefa"); + break; + case Script::GEORGIAN: + aFontList.AppendElement("Helvetica"); + break; + case Script::GOTHIC: + aFontList.AppendElement("Noto Sans Gothic"); + break; + case Script::GUJARATI: + aFontList.AppendElement("Gujarati Sangam MN"); + break; + case Script::GURMUKHI: + aFontList.AppendElement("Gurmukhi MN"); + break; + case Script::HEBREW: + aFontList.AppendElement("Lucida Grande"); + break; + case Script::KANNADA: + aFontList.AppendElement("Kannada MN"); + break; + case Script::KHMER: + aFontList.AppendElement("Khmer MN"); + break; + case Script::LAO: + aFontList.AppendElement("Lao MN"); + break; + case Script::MALAYALAM: + aFontList.AppendElement("Malayalam Sangam MN"); + break; + case Script::MONGOLIAN: + aFontList.AppendElement("Noto Sans Mongolian"); + break; + case Script::MYANMAR: + aFontList.AppendElement("Myanmar MN"); + break; + case Script::OGHAM: + aFontList.AppendElement("Noto Sans Ogham"); + break; + case Script::OLD_ITALIC: + aFontList.AppendElement("Noto Sans Old Italic"); + break; + case Script::ORIYA: + aFontList.AppendElement("Oriya Sangam MN"); + break; + case Script::RUNIC: + aFontList.AppendElement("Noto Sans Runic"); + break; + case Script::SINHALA: + aFontList.AppendElement("Sinhala Sangam MN"); + break; + case Script::SYRIAC: + aFontList.AppendElement("Noto Sans Syriac"); + break; + case Script::TAMIL: + aFontList.AppendElement("Tamil MN"); + break; + case Script::TELUGU: + aFontList.AppendElement("Telugu MN"); + break; + case Script::THAANA: + aFontList.AppendElement("Noto Sans Thaana"); + break; + case Script::THAI: + aFontList.AppendElement("Thonburi"); + break; + case Script::TIBETAN: + aFontList.AppendElement("Kailasa"); + break; + case Script::CANADIAN_ABORIGINAL: + aFontList.AppendElement("Euphemia UCAS"); + break; + case Script::YI: + aFontList.AppendElement("Noto Sans Yi"); + aFontList.AppendElement("STHeiti"); + break; + case Script::TAGALOG: + aFontList.AppendElement("Noto Sans Tagalog"); + break; + case Script::HANUNOO: + aFontList.AppendElement("Noto Sans Hanunoo"); + break; + case Script::BUHID: + aFontList.AppendElement("Noto Sans Buhid"); + break; + case Script::TAGBANWA: + aFontList.AppendElement("Noto Sans Tagbanwa"); + break; + case Script::BRAILLE: + aFontList.AppendElement("Apple Braille"); + break; + case Script::CYPRIOT: + aFontList.AppendElement("Noto Sans Cypriot"); + break; + case Script::LIMBU: + aFontList.AppendElement("Noto Sans Limbu"); + break; + case Script::LINEAR_B: + aFontList.AppendElement("Noto Sans Linear B"); + break; + case Script::OSMANYA: + aFontList.AppendElement("Noto Sans Osmanya"); + break; + case Script::SHAVIAN: + aFontList.AppendElement("Noto Sans Shavian"); + break; + case Script::TAI_LE: + aFontList.AppendElement("Noto Sans Tai Le"); + break; + case Script::UGARITIC: + aFontList.AppendElement("Noto Sans Ugaritic"); + break; + case Script::BUGINESE: + aFontList.AppendElement("Noto Sans Buginese"); + break; + case Script::GLAGOLITIC: + aFontList.AppendElement("Noto Sans Glagolitic"); + break; + case Script::KHAROSHTHI: + aFontList.AppendElement("Noto Sans Kharoshthi"); + break; + case Script::SYLOTI_NAGRI: + aFontList.AppendElement("Noto Sans Syloti Nagri"); + break; + case Script::NEW_TAI_LUE: + aFontList.AppendElement("Noto Sans New Tai Lue"); + break; + case Script::TIFINAGH: + aFontList.AppendElement("Noto Sans Tifinagh"); + break; + case Script::OLD_PERSIAN: + aFontList.AppendElement("Noto Sans Old Persian"); + break; + case Script::BALINESE: + aFontList.AppendElement("Noto Sans Balinese"); + break; + case Script::BATAK: + aFontList.AppendElement("Noto Sans Batak"); + break; + case Script::BRAHMI: + aFontList.AppendElement("Noto Sans Brahmi"); + break; + case Script::CHAM: + aFontList.AppendElement("Noto Sans Cham"); + break; + case Script::EGYPTIAN_HIEROGLYPHS: + aFontList.AppendElement("Noto Sans Egyptian Hieroglyphs"); + break; + case Script::PAHAWH_HMONG: + aFontList.AppendElement("Noto Sans Pahawh Hmong"); + break; + case Script::OLD_HUNGARIAN: + aFontList.AppendElement("Noto Sans Old Hungarian"); + break; + case Script::JAVANESE: + aFontList.AppendElement("Noto Sans Javanese"); + break; + case Script::KAYAH_LI: + aFontList.AppendElement("Noto Sans Kayah Li"); + break; + case Script::LEPCHA: + aFontList.AppendElement("Noto Sans Lepcha"); + break; + case Script::LINEAR_A: + aFontList.AppendElement("Noto Sans Linear A"); + break; + case Script::MANDAIC: + aFontList.AppendElement("Noto Sans Mandaic"); + break; + case Script::NKO: + aFontList.AppendElement("Noto Sans NKo"); + break; + case Script::OLD_TURKIC: + aFontList.AppendElement("Noto Sans Old Turkic"); + break; + case Script::OLD_PERMIC: + aFontList.AppendElement("Noto Sans Old Permic"); + break; + case Script::PHAGS_PA: + aFontList.AppendElement("Noto Sans PhagsPa"); + break; + case Script::PHOENICIAN: + aFontList.AppendElement("Noto Sans Phoenician"); + break; + case Script::MIAO: + aFontList.AppendElement("Noto Sans Miao"); + break; + case Script::VAI: + aFontList.AppendElement("Noto Sans Vai"); + break; + case Script::CUNEIFORM: + aFontList.AppendElement("Noto Sans Cuneiform"); + break; + case Script::CARIAN: + aFontList.AppendElement("Noto Sans Carian"); + break; + case Script::TAI_THAM: + aFontList.AppendElement("Noto Sans Tai Tham"); + break; + case Script::LYCIAN: + aFontList.AppendElement("Noto Sans Lycian"); + break; + case Script::LYDIAN: + aFontList.AppendElement("Noto Sans Lydian"); + break; + case Script::OL_CHIKI: + aFontList.AppendElement("Noto Sans Ol Chiki"); + break; + case Script::REJANG: + aFontList.AppendElement("Noto Sans Rejang"); + break; + case Script::SAURASHTRA: + aFontList.AppendElement("Noto Sans Saurashtra"); + break; + case Script::SUNDANESE: + aFontList.AppendElement("Noto Sans Sundanese"); + break; + case Script::MEETEI_MAYEK: + aFontList.AppendElement("Noto Sans Meetei Mayek"); + break; + case Script::IMPERIAL_ARAMAIC: + aFontList.AppendElement("Noto Sans Imperial Aramaic"); + break; + case Script::AVESTAN: + aFontList.AppendElement("Noto Sans Avestan"); + break; + case Script::CHAKMA: + aFontList.AppendElement("Noto Sans Chakma"); + break; + case Script::KAITHI: + aFontList.AppendElement("Noto Sans Kaithi"); + break; + case Script::MANICHAEAN: + aFontList.AppendElement("Noto Sans Manichaean"); + break; + case Script::INSCRIPTIONAL_PAHLAVI: + aFontList.AppendElement("Noto Sans Inscriptional Pahlavi"); + break; + case Script::PSALTER_PAHLAVI: + aFontList.AppendElement("Noto Sans Psalter Pahlavi"); + break; + case Script::INSCRIPTIONAL_PARTHIAN: + aFontList.AppendElement("Noto Sans Inscriptional Parthian"); + break; + case Script::SAMARITAN: + aFontList.AppendElement("Noto Sans Samaritan"); + break; + case Script::TAI_VIET: + aFontList.AppendElement("Noto Sans Tai Viet"); + break; + case Script::BAMUM: + aFontList.AppendElement("Noto Sans Bamum"); + break; + case Script::LISU: + aFontList.AppendElement("Noto Sans Lisu"); + break; + case Script::OLD_SOUTH_ARABIAN: + aFontList.AppendElement("Noto Sans Old South Arabian"); + break; + case Script::BASSA_VAH: + aFontList.AppendElement("Noto Sans Bassa Vah"); + break; + case Script::DUPLOYAN: + aFontList.AppendElement("Noto Sans Duployan"); + break; + case Script::ELBASAN: + aFontList.AppendElement("Noto Sans Elbasan"); + break; + case Script::GRANTHA: + aFontList.AppendElement("Noto Sans Grantha"); + break; + case Script::MENDE_KIKAKUI: + aFontList.AppendElement("Noto Sans Mende Kikakui"); + break; + case Script::MEROITIC_CURSIVE: + case Script::MEROITIC_HIEROGLYPHS: + aFontList.AppendElement("Noto Sans Meroitic"); + break; + case Script::OLD_NORTH_ARABIAN: + aFontList.AppendElement("Noto Sans Old North Arabian"); + break; + case Script::NABATAEAN: + aFontList.AppendElement("Noto Sans Nabataean"); + break; + case Script::PALMYRENE: + aFontList.AppendElement("Noto Sans Palmyrene"); + break; + case Script::KHUDAWADI: + aFontList.AppendElement("Noto Sans Khudawadi"); + break; + case Script::WARANG_CITI: + aFontList.AppendElement("Noto Sans Warang Citi"); + break; + case Script::MRO: + aFontList.AppendElement("Noto Sans Mro"); + break; + case Script::SHARADA: + aFontList.AppendElement("Noto Sans Sharada"); + break; + case Script::SORA_SOMPENG: + aFontList.AppendElement("Noto Sans Sora Sompeng"); + break; + case Script::TAKRI: + aFontList.AppendElement("Noto Sans Takri"); + break; + case Script::KHOJKI: + aFontList.AppendElement("Noto Sans Khojki"); + break; + case Script::TIRHUTA: + aFontList.AppendElement("Noto Sans Tirhuta"); + break; + case Script::CAUCASIAN_ALBANIAN: + aFontList.AppendElement("Noto Sans Caucasian Albanian"); + break; + case Script::MAHAJANI: + aFontList.AppendElement("Noto Sans Mahajani"); + break; + case Script::AHOM: + aFontList.AppendElement("Noto Serif Ahom"); + break; + case Script::HATRAN: + aFontList.AppendElement("Noto Sans Hatran"); + break; + case Script::MODI: + aFontList.AppendElement("Noto Sans Modi"); + break; + case Script::MULTANI: + aFontList.AppendElement("Noto Sans Multani"); + break; + case Script::PAU_CIN_HAU: + aFontList.AppendElement("Noto Sans Pau Cin Hau"); + break; + case Script::SIDDHAM: + aFontList.AppendElement("Noto Sans Siddham"); + break; + case Script::ADLAM: + aFontList.AppendElement("Noto Sans Adlam"); + break; + case Script::BHAIKSUKI: + aFontList.AppendElement("Noto Sans Bhaiksuki"); + break; + case Script::MARCHEN: + aFontList.AppendElement("Noto Sans Marchen"); + break; + case Script::NEWA: + aFontList.AppendElement("Noto Sans Newa"); + break; + case Script::OSAGE: + aFontList.AppendElement("Noto Sans Osage"); + break; + case Script::HANIFI_ROHINGYA: + aFontList.AppendElement("Noto Sans Hanifi Rohingya"); + break; + case Script::WANCHO: + aFontList.AppendElement("Noto Sans Wancho"); + break; + + // Script codes for which no commonly-installed font is currently known. + // Probably future macOS versions will add Noto fonts for many of these, + // so we should watch for updates. + case Script::OLD_CHURCH_SLAVONIC_CYRILLIC: + case Script::DEMOTIC_EGYPTIAN: + case Script::HIERATIC_EGYPTIAN: + case Script::BLISSYMBOLS: + case Script::CIRTH: + case Script::KHUTSURI: + case Script::HARAPPAN_INDUS: + case Script::LATIN_FRAKTUR: + case Script::LATIN_GAELIC: + case Script::MAYAN_HIEROGLYPHS: + case Script::RONGORONGO: + case Script::SARATI: + case Script::ESTRANGELO_SYRIAC: + case Script::WESTERN_SYRIAC: + case Script::EASTERN_SYRIAC: + case Script::TENGWAR: + case Script::VISIBLE_SPEECH: + case Script::UNWRITTEN_LANGUAGES: + case Script::UNKNOWN: + case Script::SIGNWRITING: + case Script::MOON: + case Script::BOOK_PAHLAVI: + case Script::NAKHI_GEBA: + case Script::KPELLE: + case Script::LOMA: + case Script::AFAKA: + case Script::JURCHEN: + case Script::NUSHU: + case Script::TANGUT: + case Script::WOLEAI: + case Script::ANATOLIAN_HIEROGLYPHS: + case Script::MASARAM_GONDI: + case Script::SOYOMBO: + case Script::ZANABAZAR_SQUARE: + case Script::DOGRA: + case Script::GUNJALA_GONDI: + case Script::MAKASAR: + case Script::MEDEFAIDRIN: + case Script::SOGDIAN: + case Script::OLD_SOGDIAN: + case Script::ELYMAIC: + case Script::NYIAKENG_PUACHUE_HMONG: + case Script::NANDINAGARI: + case Script::CHORASMIAN: + case Script::DIVES_AKURU: + case Script::KHITAN_SMALL_SCRIPT: + case Script::YEZIDI: + case Script::CYPRO_MINOAN: + case Script::OLD_UYGHUR: + case Script::TANGSA: + case Script::TOTO: + case Script::VITHKUQI: + case Script::KAWI: + case Script::NAG_MUNDARI: + break; + } + + // Symbols/dingbats are generally Script=COMMON but may be resolved to any + // surrounding script run. So we'll always append a couple of likely fonts + // for such characters. + const uint32_t b = aCh >> 8; + if (aRunScript == Script::COMMON || // Stray COMMON chars not resolved + (b >= 0x20 && b <= 0x2b) || b == 0x2e || // BMP symbols/punctuation/etc + GetGenCategory(aCh) == nsUGenCategory::kSymbol || + GetGenCategory(aCh) == nsUGenCategory::kPunctuation) { + if (b == 0x27) { + aFontList.AppendElement("Zapf Dingbats"); + } + aFontList.AppendElement("Geneva"); + aFontList.AppendElement("STIXGeneral"); + aFontList.AppendElement("Apple Symbols"); + // Japanese fonts also cover a lot of miscellaneous symbols + aFontList.AppendElement("Hiragino Sans"); + aFontList.AppendElement("Hiragino Kaku Gothic ProN"); + } + + // Arial Unicode MS has lots of glyphs for obscure characters; try it as a + // last resort. + aFontList.AppendElement("Arial Unicode MS"); +} + +/*static*/ +void gfxPlatformMac::LookupSystemFont( + mozilla::LookAndFeel::FontID aSystemFontID, nsACString& aSystemFontName, + gfxFontStyle& aFontStyle) { + return gfxMacPlatformFontList::LookupSystemFont(aSystemFontID, + aSystemFontName, aFontStyle); +} + +uint32_t gfxPlatformMac::ReadAntiAliasingThreshold() { + uint32_t threshold = 0; // default == no threshold + + // first read prefs flag to determine whether to use the setting or not + bool useAntiAliasingThreshold = + Preferences::GetBool("gfx.use_text_smoothing_setting", false); + + // if the pref setting is disabled, return 0 which effectively disables this + // feature + if (!useAntiAliasingThreshold) return threshold; + + // value set via Appearance pref panel, "Turn off text smoothing for font + // sizes xxx and smaller" + CFNumberRef prefValue = (CFNumberRef)CFPreferencesCopyAppValue( + CFSTR("AppleAntiAliasingThreshold"), kCFPreferencesCurrentApplication); + + if (prefValue) { + if (!CFNumberGetValue(prefValue, kCFNumberIntType, &threshold)) { + threshold = 0; + } + CFRelease(prefValue); + } + + return threshold; +} + +bool gfxPlatformMac::AccelerateLayersByDefault() { return true; } + +// This is the renderer output callback function, called on the vsync thread +static CVReturn VsyncCallback(CVDisplayLinkRef aDisplayLink, + const CVTimeStamp* aNow, + const CVTimeStamp* aOutputTime, + CVOptionFlags aFlagsIn, CVOptionFlags* aFlagsOut, + void* aDisplayLinkContext); + +class OSXVsyncSource final : public VsyncSource { + public: + OSXVsyncSource() + : mDisplayLink(nullptr, "OSXVsyncSource::OSXDisplay::mDisplayLink") { + MOZ_ASSERT(NS_IsMainThread()); + mTimer = NS_NewTimer(); + CGDisplayRegisterReconfigurationCallback(DisplayReconfigurationCallback, + this); + } + + virtual ~OSXVsyncSource() { + MOZ_ASSERT(NS_IsMainThread()); + CGDisplayRemoveReconfigurationCallback(DisplayReconfigurationCallback, + this); + } + + static void RetryEnableVsync(nsITimer* aTimer, void* aOsxVsyncSource) { + MOZ_ASSERT(NS_IsMainThread()); + OSXVsyncSource* osxVsyncSource = + static_cast<OSXVsyncSource*>(aOsxVsyncSource); + MOZ_ASSERT(osxVsyncSource); + osxVsyncSource->EnableVsync(); + } + + void EnableVsync() override { + MOZ_ASSERT(NS_IsMainThread()); + if (IsVsyncEnabled()) { + return; + } + + auto displayLink = mDisplayLink.Lock(); + + // Create a display link capable of being used with all active displays + // TODO: See if we need to create an active DisplayLink for each monitor + // in multi-monitor situations. According to the docs, it is compatible + // with all displays running on the computer But if we have different + // monitors at different display rates, we may hit issues. + CVReturn retval = CVDisplayLinkCreateWithActiveCGDisplays(&*displayLink); + + // Workaround for bug 1201401: CVDisplayLinkCreateWithCGDisplays() + // (called by CVDisplayLinkCreateWithActiveCGDisplays()) sometimes + // creates a CVDisplayLinkRef with an uninitialized (nulled) internal + // pointer. If we continue to use this CVDisplayLinkRef, we will + // eventually crash in CVCGDisplayLink::getDisplayTimes(), where the + // internal pointer is dereferenced. Fortunately, when this happens + // another internal variable is also left uninitialized (zeroed), + // which is accessible via CVDisplayLinkGetCurrentCGDisplay(). In + // normal conditions the current display is never zero. + if ((retval == kCVReturnSuccess) && + (CVDisplayLinkGetCurrentCGDisplay(*displayLink) == 0)) { + retval = kCVReturnInvalidDisplay; + } + + if (retval != kCVReturnSuccess) { + NS_WARNING( + "Could not create a display link with all active displays. " + "Retrying"); + CVDisplayLinkRelease(*displayLink); + *displayLink = nullptr; + + // bug 1142708 - When coming back from sleep, + // or when changing displays, active displays may not be ready yet, + // even if listening for the kIOMessageSystemHasPoweredOn event + // from OS X sleep notifications. + // Active displays are those that are drawable. + // bug 1144638 - When changing display configurations and getting + // notifications from CGDisplayReconfigurationCallBack, the + // callback gets called twice for each active display + // so it's difficult to know when all displays are active. + // Instead, try again soon. The delay is arbitrary. 100ms chosen + // because on a late 2013 15" retina, it takes about that + // long to come back up from sleep. + uint32_t delay = 100; + mTimer->InitWithNamedFuncCallback(RetryEnableVsync, this, delay, + nsITimer::TYPE_ONE_SHOT, + "RetryEnableVsync"); + return; + } + + if (CVDisplayLinkSetOutputCallback(*displayLink, &VsyncCallback, this) != + kCVReturnSuccess) { + NS_WARNING("Could not set displaylink output callback"); + CVDisplayLinkRelease(*displayLink); + *displayLink = nullptr; + return; + } + + mPreviousTimestamp = TimeStamp::Now(); + if (CVDisplayLinkStart(*displayLink) != kCVReturnSuccess) { + NS_WARNING("Could not activate the display link"); + CVDisplayLinkRelease(*displayLink); + *displayLink = nullptr; + } + + CVTime vsyncRate = + CVDisplayLinkGetNominalOutputVideoRefreshPeriod(*displayLink); + if (vsyncRate.flags & kCVTimeIsIndefinite) { + NS_WARNING("Could not get vsync rate, setting to 60."); + mVsyncRate = TimeDuration::FromMilliseconds(1000.0 / 60.0); + } else { + int64_t timeValue = vsyncRate.timeValue; + int64_t timeScale = vsyncRate.timeScale; + const int milliseconds = 1000; + float rateInMs = ((double)timeValue / (double)timeScale) * milliseconds; + mVsyncRate = TimeDuration::FromMilliseconds(rateInMs); + } + } + + void DisableVsync() override { + MOZ_ASSERT(NS_IsMainThread()); + if (!IsVsyncEnabled()) { + return; + } + + // Release the display link + auto displayLink = mDisplayLink.Lock(); + if (*displayLink) { + CVDisplayLinkRelease(*displayLink); + *displayLink = nullptr; + } + } + + bool IsVsyncEnabled() override { + MOZ_ASSERT(NS_IsMainThread()); + auto displayLink = mDisplayLink.Lock(); + return *displayLink != nullptr; + } + + TimeDuration GetVsyncRate() override { return mVsyncRate; } + + void Shutdown() override { + MOZ_ASSERT(NS_IsMainThread()); + mTimer->Cancel(); + mTimer = nullptr; + DisableVsync(); + } + + // The vsync timestamps given by the CVDisplayLinkCallback are + // in the future for the NEXT frame. Large parts of Gecko, such + // as animations assume a timestamp at either now or in the past. + // Normalize the timestamps given to the VsyncDispatchers to the vsync + // that just occured, not the vsync that is upcoming. + TimeStamp mPreviousTimestamp; + + private: + static void DisplayReconfigurationCallback(CGDirectDisplayID aDisplay, + CGDisplayChangeSummaryFlags aFlags, + void* aUserInfo) { + static_cast<OSXVsyncSource*>(aUserInfo)->OnDisplayReconfiguration(aDisplay, + aFlags); + } + + void OnDisplayReconfiguration(CGDirectDisplayID aDisplay, + CGDisplayChangeSummaryFlags aFlags) { + // Display reconfiguration notifications are fired in two phases: Before + // the reconfiguration and after the reconfiguration. + // All displays are notified before (with a "BeginConfiguration" flag), + // and the reconfigured displays are notified again after the + // configuration. + if (aFlags & kCGDisplayBeginConfigurationFlag) { + // We're only interested in the "after" notification, for the display + // link's current display. + return; + } + + if (!NS_IsMainThread()) { + return; + } + + bool didReconfigureCurrentDisplayLinkDisplay = false; + { // scope for lock + auto displayLink = mDisplayLink.Lock(); + didReconfigureCurrentDisplayLinkDisplay = + *displayLink && + CVDisplayLinkGetCurrentCGDisplay(*displayLink) == aDisplay; + } + + if (didReconfigureCurrentDisplayLinkDisplay) { + // The link's current display has been reconfigured. + // Recreate the display link, because otherwise it may be stuck with a + // "removed" display forever and never notify us again. + DisableVsync(); + EnableVsync(); + } + } + + // Accessed from main thread and from display reconfiguration callback + // thread... which also happens to be the main thread. + DataMutex<CVDisplayLinkRef> mDisplayLink; + + // Accessed only from the main thread. + RefPtr<nsITimer> mTimer; + TimeDuration mVsyncRate; +}; // OSXVsyncSource + +static CVReturn VsyncCallback(CVDisplayLinkRef aDisplayLink, + const CVTimeStamp* aNow, + const CVTimeStamp* aOutputTime, + CVOptionFlags aFlagsIn, CVOptionFlags* aFlagsOut, + void* aDisplayLinkContext) { + // Executed on OS X hardware vsync thread + OSXVsyncSource* vsyncSource = (OSXVsyncSource*)aDisplayLinkContext; + + mozilla::TimeStamp outputTime = + mozilla::TimeStamp::FromSystemTime(aOutputTime->hostTime); + mozilla::TimeStamp nextVsync = outputTime; + mozilla::TimeStamp previousVsync = vsyncSource->mPreviousTimestamp; + mozilla::TimeStamp now = TimeStamp::Now(); + + // Snow leopard sometimes sends vsync timestamps very far in the past. + // Normalize the vsync timestamps to now. + if (nextVsync <= previousVsync) { + nextVsync = now; + previousVsync = now; + } else if (now < previousVsync) { + // Bug 1158321 - The VsyncCallback can sometimes execute before the reported + // vsync time. In those cases, normalize the timestamp to Now() as sending + // timestamps in the future has undefined behavior. See the comment above + // OSXVsyncSource::mPreviousTimestamp + previousVsync = now; + } + + vsyncSource->mPreviousTimestamp = nextVsync; + + vsyncSource->NotifyVsync(previousVsync, outputTime); + return kCVReturnSuccess; +} + +already_AddRefed<mozilla::gfx::VsyncSource> +gfxPlatformMac::CreateGlobalHardwareVsyncSource() { + RefPtr<VsyncSource> osxVsyncSource = new OSXVsyncSource(); + osxVsyncSource->EnableVsync(); + if (!osxVsyncSource->IsVsyncEnabled()) { + NS_WARNING( + "OS X Vsync source not enabled. Falling back to software vsync."); + return GetSoftwareVsyncSource(); + } + + osxVsyncSource->DisableVsync(); + return osxVsyncSource.forget(); +} + +bool gfxPlatformMac::SupportsHDR() { + // HDR has 3 requirements: + // 1) high peak brightness + // 2) high contrast ratio + // 3) color depth > 24 + if (GetScreenDepth() <= 24) { + return false; + } + + // Screen is capable. Is the OS capable? +#ifdef EARLY_BETA_OR_EARLIER + // More-or-less supported in Catalina. + return true; +#else + // Definitely supported in Big Sur. + return nsCocoaFeatures::OnBigSurOrLater(); +#endif +} + +nsTArray<uint8_t> gfxPlatformMac::GetPlatformCMSOutputProfileData() { + nsTArray<uint8_t> prefProfileData = GetPrefCMSOutputProfileData(); + if (!prefProfileData.IsEmpty()) { + return prefProfileData; + } + + CGColorSpaceRef cspace = ::CGDisplayCopyColorSpace(::CGMainDisplayID()); + if (!cspace) { + cspace = ::CGColorSpaceCreateDeviceRGB(); + } + if (!cspace) { + return nsTArray<uint8_t>(); + } + + CFDataRef iccp = ::CGColorSpaceCopyICCData(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() { return true; } + +void gfxPlatformMac::InitPlatformGPUProcessPrefs() { + FeatureState& gpuProc = gfxConfig::GetFeature(Feature::GPU_PROCESS); + gpuProc.ForceDisable(FeatureStatus::Blocked, + "GPU process does not work on Mac", + "FEATURE_FAILURE_MAC_GPU_PROC"_ns); +} diff --git a/gfx/thebes/gfxPlatformMac.h b/gfx/thebes/gfxPlatformMac.h new file mode 100644 index 0000000000..29bbd7e877 --- /dev/null +++ b/gfx/thebes/gfxPlatformMac.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_PLATFORM_MAC_H +#define GFX_PLATFORM_MAC_H + +#include "nsTArrayForwardDeclare.h" +#include "gfxPlatform.h" +#include "mozilla/LookAndFeel.h" + +namespace mozilla { +namespace gfx { +class DrawTarget; +class VsyncSource; +} // namespace gfx +} // namespace mozilla + +class gfxPlatformMac : public gfxPlatform { + public: + gfxPlatformMac(); + virtual ~gfxPlatformMac(); + + // Call early in startup to register the macOS supplemental language fonts + // so that they're usable by the browser. This is intended to be called as + // early as possible, before most services etc are initialized; it starts + // a separate thread to register the fonts, because this is quite slow. + static void RegisterSupplementalFonts(); + + // Call from the main thread at the point where we need to start using the + // font list; this will wait (if necessary) for the registration thread to + // finish. + static void WaitForFontRegistration(); + + static gfxPlatformMac* GetPlatform() { + return (gfxPlatformMac*)gfxPlatform::GetPlatform(); + } + + already_AddRefed<gfxASurface> CreateOffscreenSurface( + const IntSize& aSize, gfxImageFormat aFormat) override; + + bool CreatePlatformFontList() override; + + void ReadSystemFontList(mozilla::dom::SystemFontList* aFontList) override; + + void GetCommonFallbackFonts(uint32_t aCh, Script aRunScript, + eFontPresentation aPresentation, + nsTArray<const char*>& aFontList) override; + + // lookup the system font for a particular system font type and set + // the name and style characteristics + static void LookupSystemFont(mozilla::LookAndFeel::FontID aSystemFontID, + nsACString& aSystemFontName, + gfxFontStyle& aFontStyle); + + bool SupportsApzWheelInput() const override { return true; } + + bool RespectsFontStyleSmoothing() const override { + // gfxMacFont respects the font smoothing hint. + return true; + } + + bool RequiresAcceleratedGLContextForCompositorOGL() const override { + // On OS X in a VM, unaccelerated CompositorOGL shows black flashes, so we + // require accelerated GL for CompositorOGL but allow unaccelerated GL for + // BasicCompositor. + return true; + } + + already_AddRefed<mozilla::gfx::VsyncSource> CreateGlobalHardwareVsyncSource() + override; + + // lower threshold on font anti-aliasing + uint32_t GetAntiAliasingThreshold() { return mFontAntiAliasingThreshold; } + + static bool CheckVariationFontSupport(); + + bool SupportsHDR() override; + + protected: + bool AccelerateLayersByDefault() override; + + BackendPrefsData GetBackendPrefs() const override; + + void InitPlatformGPUProcessPrefs() override; + + private: + nsTArray<uint8_t> GetPlatformCMSOutputProfileData() override; + + // read in the pref value for the lower threshold on font anti-aliasing + static uint32_t ReadAntiAliasingThreshold(); + + static void FontRegistrationCallback(void* aUnused); + + uint32_t mFontAntiAliasingThreshold; + + static PRThread* sFontRegistrationThread; +}; + +#endif /* GFX_PLATFORM_MAC_H */ diff --git a/gfx/thebes/gfxPlatformWorker.cpp b/gfx/thebes/gfxPlatformWorker.cpp new file mode 100644 index 0000000000..1a6183236e --- /dev/null +++ b/gfx/thebes/gfxPlatformWorker.cpp @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxPlatformWorker.h" +#include "mozilla/dom/WorkerCommon.h" +#include "mozilla/dom/WorkerRef.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/ThreadLocal.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::gfx; + +MOZ_THREAD_LOCAL(gfxPlatformWorker*) gfxPlatformWorker::sInstance; + +/* static */ gfxPlatformWorker* gfxPlatformWorker::Get() { + if (!sInstance.init()) { + return nullptr; + } + + gfxPlatformWorker* instance = sInstance.get(); + if (instance) { + return instance; + } + + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + if (!workerPrivate) { + return nullptr; + } + + RefPtr<WeakWorkerRef> workerRef = WeakWorkerRef::Create( + workerPrivate, []() { gfxPlatformWorker::Shutdown(); }); + if (!workerRef) { + return nullptr; + } + + instance = new gfxPlatformWorker(std::move(workerRef)); + sInstance.set(instance); + return instance; +} + +/* static */ void gfxPlatformWorker::Shutdown() { + if (!sInstance.init()) { + return; + } + + gfxPlatformWorker* instance = sInstance.get(); + if (!instance) { + return; + } + + sInstance.set(nullptr); + delete instance; +} + +gfxPlatformWorker::gfxPlatformWorker(RefPtr<WeakWorkerRef>&& aWorkerRef) + : mWorkerRef(std::move(aWorkerRef)) {} + +gfxPlatformWorker::~gfxPlatformWorker() = default; + +RefPtr<mozilla::gfx::DrawTarget> +gfxPlatformWorker::ScreenReferenceDrawTarget() { + if (!mScreenReferenceDrawTarget) { + mScreenReferenceDrawTarget = Factory::CreateDrawTarget( + BackendType::SKIA, IntSize(1, 1), SurfaceFormat::B8G8R8A8); + } + return mScreenReferenceDrawTarget; +} diff --git a/gfx/thebes/gfxPlatformWorker.h b/gfx/thebes/gfxPlatformWorker.h new file mode 100644 index 0000000000..047c7c9989 --- /dev/null +++ b/gfx/thebes/gfxPlatformWorker.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_PLATFORM_WORKER_H +#define GFX_PLATFORM_WORKER_H + +#include "mozilla/ThreadLocal.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { +namespace dom { +class WeakWorkerRef; +} // namespace dom + +namespace gfx { +class DrawTarget; +} // namespace gfx +} // namespace mozilla + +/** + * Threadlocal instance of gfxPlatform data that may be used/shared on a DOM + * worker thread. + */ +class gfxPlatformWorker final { + public: + static gfxPlatformWorker* Get(); + static void Shutdown(); + + RefPtr<mozilla::gfx::DrawTarget> ScreenReferenceDrawTarget(); + + private: + explicit gfxPlatformWorker(RefPtr<mozilla::dom::WeakWorkerRef>&& aWorkerRef); + ~gfxPlatformWorker(); + + static MOZ_THREAD_LOCAL(gfxPlatformWorker*) sInstance; + + RefPtr<mozilla::dom::WeakWorkerRef> mWorkerRef; + + RefPtr<mozilla::gfx::DrawTarget> mScreenReferenceDrawTarget; +}; + +#endif // GFX_PLATFORM_WORKER_H diff --git a/gfx/thebes/gfxPoint.h b/gfx/thebes/gfxPoint.h new file mode 100644 index 0000000000..624a06aaad --- /dev/null +++ b/gfx/thebes/gfxPoint.h @@ -0,0 +1,14 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_POINT_H +#define GFX_POINT_H + +#include "mozilla/gfx/Point.h" + +typedef mozilla::gfx::SizeDouble gfxSize; +typedef mozilla::gfx::PointDouble gfxPoint; + +#endif /* GFX_POINT_H */ diff --git a/gfx/thebes/gfxQuad.h b/gfx/thebes/gfxQuad.h new file mode 100644 index 0000000000..d1c9b1c55f --- /dev/null +++ b/gfx/thebes/gfxQuad.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_QUAD_H +#define GFX_QUAD_H + +#include "gfxTypes.h" +#include "gfxRect.h" +#include "gfxLineSegment.h" +#include <algorithm> + +struct gfxQuad { + gfxQuad(const gfxPoint& aOne, const gfxPoint& aTwo, const gfxPoint& aThree, + const gfxPoint& aFour) { + mPoints[0] = aOne; + mPoints[1] = aTwo; + mPoints[2] = aThree; + mPoints[3] = aFour; + } + + bool Contains(const gfxPoint& aPoint) { + return (gfxLineSegment(mPoints[0], mPoints[1]) + .PointsOnSameSide(aPoint, mPoints[2]) && + gfxLineSegment(mPoints[1], mPoints[2]) + .PointsOnSameSide(aPoint, mPoints[3]) && + gfxLineSegment(mPoints[2], mPoints[3]) + .PointsOnSameSide(aPoint, mPoints[0]) && + gfxLineSegment(mPoints[3], mPoints[0]) + .PointsOnSameSide(aPoint, mPoints[1])); + } + + gfxRect GetBounds() { + gfxFloat min_x, max_x; + gfxFloat min_y, max_y; + + min_x = max_x = mPoints[0].x; + min_y = max_y = mPoints[0].y; + + for (int i = 1; i < 4; i++) { + min_x = std::min(mPoints[i].x.value, min_x); + max_x = std::max(mPoints[i].x.value, max_x); + min_y = std::min(mPoints[i].y.value, min_y); + max_y = std::max(mPoints[i].y.value, max_y); + } + return gfxRect(min_x, min_y, max_x - min_x, max_y - min_y); + } + + gfxPoint mPoints[4]; +}; + +#endif /* GFX_QUAD_H */ diff --git a/gfx/thebes/gfxQuartzNativeDrawing.cpp b/gfx/thebes/gfxQuartzNativeDrawing.cpp new file mode 100644 index 0000000000..bd8658df24 --- /dev/null +++ b/gfx/thebes/gfxQuartzNativeDrawing.cpp @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxQuartzNativeDrawing.h" +#include "gfxPlatform.h" +#include "mozilla/gfx/Helpers.h" + +using namespace mozilla::gfx; +using namespace mozilla; + +gfxQuartzNativeDrawing::gfxQuartzNativeDrawing(DrawTarget& aDrawTarget, + const Rect& nativeRect) + : mDrawTarget(&aDrawTarget), mNativeRect(nativeRect), mCGContext(nullptr) {} + +CGContextRef gfxQuartzNativeDrawing::BeginNativeDrawing() { + NS_ASSERTION(!mCGContext, + "BeginNativeDrawing called when drawing already in progress"); + + DrawTarget* dt = mDrawTarget; + if (dt->IsTiledDrawTarget() || dt->GetBackendType() != BackendType::SKIA || + dt->IsRecording()) { + // We need a DrawTarget that we can get a CGContextRef from: + Matrix transform = dt->GetTransform(); + + mNativeRect = transform.TransformBounds(mNativeRect); + mNativeRect.RoundOut(); + if (mNativeRect.IsEmpty()) { + return nullptr; + } + + mTempDrawTarget = Factory::CreateDrawTarget( + BackendType::SKIA, + IntSize::Truncate(mNativeRect.width, mNativeRect.height), + SurfaceFormat::B8G8R8A8); + if (!mTempDrawTarget) { + return nullptr; + } + + transform.PostTranslate(-mNativeRect.x, -mNativeRect.y); + mTempDrawTarget->SetTransform(transform); + + dt = mTempDrawTarget; + } else { + // Clip the DT in case BorrowedCGContext needs to create a new layer. + // This prevents it from creating a new layer the size of the window. + // But make sure that this clip is device pixel aligned. + Matrix transform = dt->GetTransform(); + + Rect deviceRect = transform.TransformBounds(mNativeRect); + deviceRect.RoundOut(); + mNativeRect = transform.Inverse().TransformBounds(deviceRect); + mDrawTarget->PushClipRect(mNativeRect); + } + + MOZ_ASSERT(dt->GetBackendType() == BackendType::SKIA); + mCGContext = mBorrowedContext.Init(dt); + + if (NS_WARN_IF(!mCGContext)) { + // Failed borrowing CG context, so we need to clean up. + if (!mTempDrawTarget) { + mDrawTarget->PopClip(); + } + return nullptr; + } + + return mCGContext; +} + +void gfxQuartzNativeDrawing::EndNativeDrawing() { + NS_ASSERTION(mCGContext, + "EndNativeDrawing called without BeginNativeDrawing"); + + mBorrowedContext.Finish(); + if (mTempDrawTarget) { + RefPtr<SourceSurface> source = mTempDrawTarget->Snapshot(); + + AutoRestoreTransform autoRestore(mDrawTarget); + mDrawTarget->SetTransform(Matrix()); + mDrawTarget->DrawSurface(source, mNativeRect, + Rect(0, 0, mNativeRect.width, mNativeRect.height)); + } else { + mDrawTarget->PopClip(); + } +} diff --git a/gfx/thebes/gfxQuartzNativeDrawing.h b/gfx/thebes/gfxQuartzNativeDrawing.h new file mode 100644 index 0000000000..9e8b0bede6 --- /dev/null +++ b/gfx/thebes/gfxQuartzNativeDrawing.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _GFXQUARTZNATIVEDRAWING_H_ +#define _GFXQUARTZNATIVEDRAWING_H_ + +#include "mozilla/Attributes.h" + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/BorrowedContext.h" +#include "mozilla/RefPtr.h" + +class gfxQuartzNativeDrawing { + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::Rect Rect; + + public: + /* Create native Quartz drawing for a rectangle bounded by + * nativeRect. + * + * Typical usage looks like: + * + * gfxQuartzNativeDrawing nativeDraw(ctx, nativeRect); + * CGContextRef cgContext = nativeDraw.BeginNativeDrawing(); + * if (!cgContext) + * return NS_ERROR_FAILURE; + * + * ... call Quartz operations on CGContextRef to draw to nativeRect ... + * + * nativeDraw.EndNativeDrawing(); + * + * aNativeRect is the size of the surface (in Quartz/Cocoa points) that + * will be created _if_ the gfxQuartzNativeDrawing decides to create a new + * surface and CGContext for its drawing operations, which it then + * composites into the target DrawTarget. + * + * (Note that aNativeRect will be ignored if the gfxQuartzNativeDrawing + * uses the target DrawTarget directly.) + * + * The optional aBackingScale parameter is a scaling factor that will be + * applied when creating and rendering into such a temporary surface. + */ + gfxQuartzNativeDrawing(DrawTarget& aDrawTarget, const Rect& aNativeRect); + + /* Returns a CGContextRef which may be used for native drawing. This + * CGContextRef is valid until EndNativeDrawing is called; if it is used + * for drawing after that time, the result is undefined. */ + CGContextRef BeginNativeDrawing(); + + /* Marks the end of native drawing */ + void EndNativeDrawing(); + + private: + // don't allow copying via construction or assignment + gfxQuartzNativeDrawing(const gfxQuartzNativeDrawing&) = delete; + const gfxQuartzNativeDrawing& operator=(const gfxQuartzNativeDrawing&) = + delete; + + // Final destination context + RefPtr<DrawTarget> mDrawTarget; + RefPtr<DrawTarget> mTempDrawTarget; + mozilla::gfx::BorrowedCGContext mBorrowedContext; + mozilla::gfx::Rect mNativeRect; + + // saved state + CGContextRef mCGContext; +}; + +#endif diff --git a/gfx/thebes/gfxQuartzSurface.cpp b/gfx/thebes/gfxQuartzSurface.cpp new file mode 100644 index 0000000000..a11167db52 --- /dev/null +++ b/gfx/thebes/gfxQuartzSurface.cpp @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxQuartzSurface.h" +#include "gfxContext.h" +#include "gfxImageSurface.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/HelpersCairo.h" + +#include "cairo-quartz.h" + +void gfxQuartzSurface::MakeInvalid() { mSize = mozilla::gfx::IntSize(-1, -1); } + +gfxQuartzSurface::gfxQuartzSurface(const mozilla::gfx::IntSize& desiredSize, + gfxImageFormat format) + : mCGContext(nullptr), mSize(desiredSize) { + if (!mozilla::gfx::Factory::CheckSurfaceSize(desiredSize)) MakeInvalid(); + + unsigned int width = static_cast<unsigned int>(mSize.width); + unsigned int height = static_cast<unsigned int>(mSize.height); + + cairo_format_t cformat = GfxFormatToCairoFormat(format); + cairo_surface_t* surf = cairo_quartz_surface_create(cformat, width, height); + + mCGContext = cairo_quartz_surface_get_cg_context(surf); + + CGContextRetain(mCGContext); + + Init(surf); + if (mSurfaceValid) { + RecordMemoryUsed(mSize.height * 4 + sizeof(gfxQuartzSurface)); + } +} + +gfxQuartzSurface::gfxQuartzSurface(CGContextRef context, + const mozilla::gfx::IntSize& size) + : mCGContext(context), mSize(size) { + if (!mozilla::gfx::Factory::CheckSurfaceSize(size)) MakeInvalid(); + + unsigned int width = static_cast<unsigned int>(mSize.width); + unsigned int height = static_cast<unsigned int>(mSize.height); + + cairo_surface_t* surf = + cairo_quartz_surface_create_for_cg_context(context, width, height); + + CGContextRetain(mCGContext); + + Init(surf); + if (mSurfaceValid) { + RecordMemoryUsed(mSize.height * 4 + sizeof(gfxQuartzSurface)); + } +} + +gfxQuartzSurface::gfxQuartzSurface(cairo_surface_t* csurf, + const mozilla::gfx::IntSize& aSize) + : mSize(aSize) { + mCGContext = cairo_quartz_surface_get_cg_context(csurf); + CGContextRetain(mCGContext); + + Init(csurf, true); +} + +already_AddRefed<gfxImageSurface> gfxQuartzSurface::GetAsImageSurface() { + cairo_surface_t* surface = cairo_quartz_surface_get_image(mSurface); + if (!surface || cairo_surface_status(surface)) return nullptr; + + RefPtr<gfxASurface> img = Wrap(surface); + + // cairo_quartz_surface_get_image returns a referenced image, and thebes + // shares the refcounts of Cairo surfaces. However, Wrap also adds a + // reference to the image. We need to remove one of these references + // explicitly so we don't leak. + img.get()->Release(); + + img->SetOpaqueRect(GetOpaqueRect()); + + return img.forget().downcast<gfxImageSurface>(); +} + +gfxQuartzSurface::~gfxQuartzSurface() { CGContextRelease(mCGContext); } diff --git a/gfx/thebes/gfxQuartzSurface.h b/gfx/thebes/gfxQuartzSurface.h new file mode 100644 index 0000000000..fbc081197b --- /dev/null +++ b/gfx/thebes/gfxQuartzSurface.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_QUARTZSURFACE_H +#define GFX_QUARTZSURFACE_H + +#include "gfxASurface.h" +#include "nsSize.h" +#include "gfxPoint.h" + +#include <Carbon/Carbon.h> + +class gfxContext; +class gfxImageSurface; + +class gfxQuartzSurface : public gfxASurface { + public: + gfxQuartzSurface(const mozilla::gfx::IntSize&, gfxImageFormat format); + gfxQuartzSurface(CGContextRef context, const mozilla::gfx::IntSize& size); + gfxQuartzSurface(cairo_surface_t* csurf, const mozilla::gfx::IntSize& aSize); + + virtual ~gfxQuartzSurface(); + + virtual const mozilla::gfx::IntSize GetSize() const { return mSize; } + + CGContextRef GetCGContext() { return mCGContext; } + + already_AddRefed<gfxImageSurface> GetAsImageSurface(); + + protected: + void MakeInvalid(); + + CGContextRef mCGContext; + mozilla::gfx::IntSize mSize; +}; + +#endif /* GFX_QUARTZSURFACE_H */ diff --git a/gfx/thebes/gfxQuaternion.h b/gfx/thebes/gfxQuaternion.h new file mode 100644 index 0000000000..ec6f94cad9 --- /dev/null +++ b/gfx/thebes/gfxQuaternion.h @@ -0,0 +1,95 @@ +/* -*- 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; + } + + 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..4d688f5b8c --- /dev/null +++ b/gfx/thebes/gfxSVGGlyphs.cpp @@ -0,0 +1,466 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxSVGGlyphs.h" + +#include "mozilla/BasePrincipal.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/PresShell.h" +#include "mozilla/SMILAnimationController.h" +#include "mozilla/SVGContextPaint.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/ImageTracker.h" +#include "mozilla/dom/SVGDocument.h" +#include "nsError.h" +#include "nsString.h" +#include "nsICategoryManager.h" +#include "nsIDocumentLoaderFactory.h" +#include "nsIDocumentViewer.h" +#include "nsIStreamListener.h" +#include "nsServiceManagerUtils.h" +#include "nsNetUtil.h" +#include "nsIInputStream.h" +#include "nsStringStream.h" +#include "nsStreamUtils.h" +#include "nsIPrincipal.h" +#include "nsContentUtils.h" +#include "gfxFont.h" +#include "gfxContext.h" +#include "harfbuzz/hb.h" +#include "zlib.h" + +#define SVG_CONTENT_TYPE "image/svg+xml"_ns +#define UTF8_CHARSET "utf-8"_ns + +using namespace mozilla; +using mozilla::dom::Document; +using mozilla::dom::Element; + +/* static */ +const mozilla::gfx::DeviceColor SimpleTextContextPaint::sZero; + +gfxSVGGlyphs::gfxSVGGlyphs(hb_blob_t* aSVGTable, gfxFontEntry* aFontEntry) + : mSVGData(aSVGTable), mFontEntry(aFontEntry) { + unsigned int length; + const char* svgData = hb_blob_get_data(mSVGData, &length); + mHeader = reinterpret_cast<const Header*>(svgData); + mDocIndex = nullptr; + + if (sizeof(Header) <= length && uint16_t(mHeader->mVersion) == 0 && + uint64_t(mHeader->mDocIndexOffset) + 2 <= length) { + const DocIndex* docIndex = + reinterpret_cast<const DocIndex*>(svgData + mHeader->mDocIndexOffset); + // Limit the number of documents to avoid overflow + if (uint64_t(mHeader->mDocIndexOffset) + 2 + + uint16_t(docIndex->mNumEntries) * sizeof(IndexEntry) <= + length) { + mDocIndex = docIndex; + } + } +} + +gfxSVGGlyphs::~gfxSVGGlyphs() { hb_blob_destroy(mSVGData); } + +void gfxSVGGlyphs::DidRefresh() { mFontEntry->NotifyGlyphsChanged(); } + +/* + * Comparison operator for finding a range containing a given glyph ID. Simply + * checks whether |key| is less (greater) than every element of |range|, in + * which case return |key| < |range| (|key| > |range|). Otherwise |key| is in + * |range|, in which case return equality. + * The total ordering here is guaranteed by + * (1) the index ranges being disjoint; and + * (2) the (sole) key always being a singleton, so intersection => containment + * (note that this is wrong if we have more than one intersection or two + * sets intersecting of size > 1 -- so... don't do that) + */ +/* static */ +int gfxSVGGlyphs::CompareIndexEntries(const void* aKey, const void* aEntry) { + const uint32_t key = *(uint32_t*)aKey; + const IndexEntry* entry = (const IndexEntry*)aEntry; + + if (key < uint16_t(entry->mStartGlyph)) { + return -1; + } + if (key > uint16_t(entry->mEndGlyph)) { + return 1; + } + return 0; +} + +gfxSVGGlyphsDocument* gfxSVGGlyphs::FindOrCreateGlyphsDocument( + uint32_t aGlyphId) { + if (!mDocIndex) { + // Invalid table + return nullptr; + } + + IndexEntry* entry = (IndexEntry*)bsearch( + &aGlyphId, mDocIndex->mEntries, uint16_t(mDocIndex->mNumEntries), + sizeof(IndexEntry), CompareIndexEntries); + if (!entry) { + return nullptr; + } + + return mGlyphDocs.WithEntryHandle( + entry->mDocOffset, [&](auto&& glyphDocsEntry) -> gfxSVGGlyphsDocument* { + if (!glyphDocsEntry) { + unsigned int length; + const uint8_t* data = + (const uint8_t*)hb_blob_get_data(mSVGData, &length); + if (entry->mDocOffset > 0 && uint64_t(mHeader->mDocIndexOffset) + + entry->mDocOffset + + entry->mDocLength <= + length) { + return glyphDocsEntry + .Insert(MakeUnique<gfxSVGGlyphsDocument>( + data + mHeader->mDocIndexOffset + entry->mDocOffset, + entry->mDocLength, this)) + .get(); + } + + return nullptr; + } + + return glyphDocsEntry->get(); + }); +} + +nsresult gfxSVGGlyphsDocument::SetupPresentation() { + nsCOMPtr<nsICategoryManager> catMan = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID); + nsCString contractId; + nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", + "image/svg+xml", contractId); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory = + do_GetService(contractId.get()); + NS_ASSERTION(docLoaderFactory, "Couldn't get DocumentLoaderFactory"); + + nsCOMPtr<nsIDocumentViewer> viewer; + rv = docLoaderFactory->CreateInstanceForDocument(nullptr, mDocument, nullptr, + getter_AddRefs(viewer)); + NS_ENSURE_SUCCESS(rv, rv); + + auto upem = mOwner->FontEntry()->UnitsPerEm(); + rv = viewer->Init(nullptr, gfx::IntRect(0, 0, upem, upem), nullptr); + if (NS_SUCCEEDED(rv)) { + rv = viewer->Open(nullptr, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + } + + RefPtr<PresShell> presShell = viewer->GetPresShell(); + if (!presShell->DidInitialize()) { + rv = presShell->Initialize(); + NS_ENSURE_SUCCESS(rv, rv); + } + + mDocument->FlushPendingNotifications(FlushType::Layout); + + if (mDocument->HasAnimationController()) { + mDocument->GetAnimationController()->Resume(SMILTimeContainer::PAUSE_IMAGE); + } + mDocument->ImageTracker()->SetAnimatingState(true); + + mViewer = viewer; + mPresShell = presShell; + mPresShell->AddPostRefreshObserver(this); + + return NS_OK; +} + +void gfxSVGGlyphsDocument::DidRefresh() { mOwner->DidRefresh(); } + +/** + * Walk the DOM tree to find all glyph elements and insert them into the lookup + * table + * @param aElem The element to search from + */ +void gfxSVGGlyphsDocument::FindGlyphElements(Element* aElem) { + for (nsIContent* child = aElem->GetLastChild(); child; + child = child->GetPreviousSibling()) { + if (!child->IsElement()) { + continue; + } + FindGlyphElements(child->AsElement()); + } + + InsertGlyphId(aElem); +} + +/** + * If there exists an SVG glyph with the specified glyph id, render it and + * return true If no such glyph exists, or in the case of an error return false + * @param aContext The thebes aContext to draw to + * @param aGlyphId The glyph id + * @return true iff rendering succeeded + */ +void gfxSVGGlyphs::RenderGlyph(gfxContext* aContext, uint32_t aGlyphId, + SVGContextPaint* aContextPaint) { + gfxContextAutoSaveRestore aContextRestorer(aContext); + + Element* glyph = mGlyphIdMap.Get(aGlyphId); + MOZ_ASSERT(glyph, "No glyph element. Should check with HasSVGGlyph() first!"); + + AutoSetRestoreSVGContextPaint autoSetRestore(aContextPaint, + glyph->OwnerDoc()); + + SVGUtils::PaintSVGGlyph(glyph, aContext); + +#if DEBUG + // This will not have any effect, because we're about to restore the state + // via the aContextRestorer destructor, but it prevents debug builds from + // asserting if it turns out that PaintSVGGlyph didn't actually do anything. + // This happens if the SVG document consists of just an image, and the image + // hasn't finished loading yet so we can't draw it. + aContext->SetOp(gfx::CompositionOp::OP_OVER); +#endif +} + +bool gfxSVGGlyphs::GetGlyphExtents(uint32_t aGlyphId, + const gfxMatrix& aSVGToAppSpace, + gfxRect* aResult) { + Element* glyph = mGlyphIdMap.Get(aGlyphId); + NS_ASSERTION(glyph, + "No glyph element. Should check with HasSVGGlyph() first!"); + + return SVGUtils::GetSVGGlyphExtents(glyph, aSVGToAppSpace, aResult); +} + +Element* gfxSVGGlyphs::GetGlyphElement(uint32_t aGlyphId) { + return mGlyphIdMap.LookupOrInsertWith(aGlyphId, [&] { + Element* elem = nullptr; + if (gfxSVGGlyphsDocument* set = FindOrCreateGlyphsDocument(aGlyphId)) { + elem = set->GetGlyphElement(aGlyphId); + } + return elem; + }); +} + +bool gfxSVGGlyphs::HasSVGGlyph(uint32_t aGlyphId) { + return !!GetGlyphElement(aGlyphId); +} + +size_t gfxSVGGlyphs::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + // We don't include the size of mSVGData here, because (depending on the + // font backend implementation) it will either wrap a block of data owned + // by the system (and potentially shared), or a table that's in our font + // table cache and therefore already counted. + size_t result = aMallocSizeOf(this) + + mGlyphDocs.ShallowSizeOfExcludingThis(aMallocSizeOf) + + mGlyphIdMap.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& entry : mGlyphDocs.Values()) { + result += entry->SizeOfIncludingThis(aMallocSizeOf); + } + return result; +} + +Element* gfxSVGGlyphsDocument::GetGlyphElement(uint32_t aGlyphId) { + return mGlyphIdMap.Get(aGlyphId); +} + +gfxSVGGlyphsDocument::gfxSVGGlyphsDocument(const uint8_t* aBuffer, + uint32_t aBufLen, + gfxSVGGlyphs* aSVGGlyphs) + : mOwner(aSVGGlyphs) { + if (aBufLen >= 14 && aBuffer[0] == 31 && aBuffer[1] == 139) { + // It's a gzip-compressed document; decompress it before parsing. + // The original length (modulo 2^32) is found in the last 4 bytes + // of the data, stored in little-endian format. We read it as + // individual bytes to avoid possible alignment issues. + // (Note that if the original length was >2^32, then origLen here + // will be incorrect; but then the inflate() call will not return + // Z_STREAM_END and we'll bail out safely.) + size_t origLen = (size_t(aBuffer[aBufLen - 1]) << 24) + + (size_t(aBuffer[aBufLen - 2]) << 16) + + (size_t(aBuffer[aBufLen - 3]) << 8) + + size_t(aBuffer[aBufLen - 4]); + AutoTArray<uint8_t, 4096> outBuf; + if (outBuf.SetLength(origLen, mozilla::fallible)) { + z_stream s = {0}; + s.next_in = const_cast<Byte*>(aBuffer); + s.avail_in = aBufLen; + s.next_out = outBuf.Elements(); + s.avail_out = outBuf.Length(); + // The magic number 16 here is the zlib flag to expect gzip format, + // see http://www.zlib.net/manual.html#Advanced + if (Z_OK == inflateInit2(&s, 16 + MAX_WBITS)) { + int result = inflate(&s, Z_FINISH); + if (Z_STREAM_END == result) { + MOZ_ASSERT(size_t(s.next_out - outBuf.Elements()) == origLen); + ParseDocument(outBuf.Elements(), outBuf.Length()); + } else { + NS_WARNING("Failed to decompress SVG glyphs document"); + } + inflateEnd(&s); + } + } else { + NS_WARNING("Failed to allocate memory for SVG glyphs document"); + } + } else { + ParseDocument(aBuffer, aBufLen); + } + + if (!mDocument) { + NS_WARNING("Could not parse SVG glyphs document"); + return; + } + + Element* root = mDocument->GetRootElement(); + if (!root) { + NS_WARNING("Could not parse SVG glyphs document"); + return; + } + + nsresult rv = SetupPresentation(); + if (NS_FAILED(rv)) { + NS_WARNING("Couldn't setup presentation for SVG glyphs document"); + return; + } + + FindGlyphElements(root); +} + +gfxSVGGlyphsDocument::~gfxSVGGlyphsDocument() { + if (mDocument) { + mDocument->OnPageHide(false, nullptr); + } + if (mPresShell) { + mPresShell->RemovePostRefreshObserver(this); + } + if (mViewer) { + mViewer->Close(nullptr); + mViewer->Destroy(); + } +} + +static nsresult CreateBufferedStream(const uint8_t* aBuffer, uint32_t aBufLen, + nsCOMPtr<nsIInputStream>& aResult) { + nsCOMPtr<nsIInputStream> stream; + nsresult rv = NS_NewByteInputStream( + getter_AddRefs(stream), + Span(reinterpret_cast<const char*>(aBuffer), aBufLen), + NS_ASSIGNMENT_DEPEND); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIInputStream> aBufferedStream; + if (!NS_InputStreamIsBuffered(stream)) { + rv = NS_NewBufferedInputStream(getter_AddRefs(aBufferedStream), + stream.forget(), 4096); + NS_ENSURE_SUCCESS(rv, rv); + stream = aBufferedStream; + } + + aResult = stream; + + return NS_OK; +} + +nsresult gfxSVGGlyphsDocument::ParseDocument(const uint8_t* aBuffer, + uint32_t aBufLen) { + // Mostly pulled from nsDOMParser::ParseFromStream + + nsCOMPtr<nsIInputStream> stream; + nsresult rv = CreateBufferedStream(aBuffer, aBufLen, stream); + NS_ENSURE_SUCCESS(rv, rv); + + // We just need a dummy URI. + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), "moz-svg-glyphs://"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrincipal> principal = + NullPrincipal::CreateWithoutOriginAttributes(); + + RefPtr<Document> document; + rv = NS_NewDOMDocument(getter_AddRefs(document), + u""_ns, // aNamespaceURI + u""_ns, // aQualifiedName + nullptr, // aDoctype + uri, uri, principal, + false, // aLoadedAsData + nullptr, // aEventObject + DocumentFlavorSVG); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIChannel> channel; + rv = NS_NewInputStreamChannel( + getter_AddRefs(channel), uri, + nullptr, // aStream + principal, nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL, + nsIContentPolicy::TYPE_OTHER, SVG_CONTENT_TYPE, UTF8_CHARSET); + NS_ENSURE_SUCCESS(rv, rv); + + // Set this early because various decisions during page-load depend on it. + document->SetIsBeingUsedAsImage(); + document->SetIsSVGGlyphsDocument(); + document->SetReadyStateInternal(Document::READYSTATE_UNINITIALIZED); + + nsCOMPtr<nsIStreamListener> listener; + rv = document->StartDocumentLoad("external-resource", channel, + nullptr, // aLoadGroup + nullptr, // aContainer + getter_AddRefs(listener), true /* aReset */); + if (NS_FAILED(rv) || !listener) { + return NS_ERROR_FAILURE; + } + + rv = listener->OnStartRequest(channel); + if (NS_FAILED(rv)) { + channel->Cancel(rv); + } + + nsresult status; + channel->GetStatus(&status); + if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(status)) { + rv = listener->OnDataAvailable(channel, stream, 0, aBufLen); + if (NS_FAILED(rv)) { + channel->Cancel(rv); + } + channel->GetStatus(&status); + } + + rv = listener->OnStopRequest(channel, status); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + document.swap(mDocument); + + return NS_OK; +} + +void gfxSVGGlyphsDocument::InsertGlyphId(Element* aGlyphElement) { + nsAutoString glyphIdStr; + static const uint32_t glyphPrefixLength = 5; + // The maximum glyph ID is 65535 so the maximum length of the numeric part + // is 5. + if (!aGlyphElement->GetAttr(nsGkAtoms::id, glyphIdStr) || + !StringBeginsWith(glyphIdStr, u"glyph"_ns) || + glyphIdStr.Length() > glyphPrefixLength + 5) { + return; + } + + uint32_t id = 0; + for (uint32_t i = glyphPrefixLength; i < glyphIdStr.Length(); ++i) { + char16_t ch = glyphIdStr.CharAt(i); + if (ch < '0' || ch > '9') { + return; + } + if (ch == '0' && i == glyphPrefixLength) { + return; + } + id = id * 10 + (ch - '0'); + } + + mGlyphIdMap.InsertOrUpdate(id, aGlyphElement); +} + +size_t gfxSVGGlyphsDocument::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + + mGlyphIdMap.ShallowSizeOfExcludingThis(aMallocSizeOf); +} diff --git a/gfx/thebes/gfxSVGGlyphs.h b/gfx/thebes/gfxSVGGlyphs.h new file mode 100644 index 0000000000..d532a56ce1 --- /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 nsIDocumentViewer; +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<nsIDocumentViewer> mViewer; + RefPtr<mozilla::PresShell> mPresShell; + + nsBaseHashtable<nsUint32HashKey, Element*, Element*> mGlyphIdMap; +}; + +/** + * Used by |gfxFontEntry| to represent the SVG table of an OpenType font. + * Handles lazy parsing of the SVG documents in the table, looking up SVG glyphs + * and rendering SVG glyphs. + * Each |gfxFontEntry| owns at most one |gfxSVGGlyphs| instance. + */ +class gfxSVGGlyphs { + private: + typedef mozilla::dom::Element Element; + + public: + /** + * @param aSVGTable The SVG table from the OpenType font + * + * The gfxSVGGlyphs object takes over ownership of the blob references + * that are passed in, and will hb_blob_destroy() them when finished; + * the caller should -not- destroy these references. + */ + gfxSVGGlyphs(hb_blob_t* aSVGTable, gfxFontEntry* aFontEntry); + + /** + * Releases our references to the SVG table and cleans up everything else. + */ + ~gfxSVGGlyphs(); + + /** + * This is called when the refresh driver has ticked. + */ + void DidRefresh(); + + /** + * Find the |gfxSVGGlyphsDocument| containing an SVG glyph for |aGlyphId|. + * If |aGlyphId| does not map to an SVG document, return null. + * If a |gfxSVGGlyphsDocument| has not been created for the document, create + * one. + */ + gfxSVGGlyphsDocument* FindOrCreateGlyphsDocument(uint32_t aGlyphId); + + /** + * Return true iff there is an SVG glyph for |aGlyphId| + */ + bool HasSVGGlyph(uint32_t aGlyphId); + + /** + * Render the SVG glyph for |aGlyphId| + * @param aContextPaint Information on text context paints. + * See |SVGContextPaint|. + */ + void RenderGlyph(gfxContext* aContext, uint32_t aGlyphId, + mozilla::SVGContextPaint* aContextPaint); + + /** + * Get the extents for the SVG glyph associated with |aGlyphId| + * @param aSVGToAppSpace The matrix mapping the SVG glyph space to the + * target context space + */ + bool GetGlyphExtents(uint32_t aGlyphId, const gfxMatrix& aSVGToAppSpace, + gfxRect* aResult); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + gfxFontEntry* FontEntry() const { return mFontEntry; } + + private: + Element* GetGlyphElement(uint32_t aGlyphId); + + nsClassHashtable<nsUint32HashKey, gfxSVGGlyphsDocument> mGlyphDocs; + nsBaseHashtable<nsUint32HashKey, Element*, Element*> mGlyphIdMap; + + hb_blob_t* mSVGData; + + // pointer to the font entry that owns this gfxSVGGlyphs object + gfxFontEntry* MOZ_NON_OWNING_REF mFontEntry; + + const struct Header { + mozilla::AutoSwap_PRUint16 mVersion; + mozilla::AutoSwap_PRUint32 mDocIndexOffset; + mozilla::AutoSwap_PRUint32 mColorPalettesOffset; + }* mHeader; + + struct IndexEntry { + mozilla::AutoSwap_PRUint16 mStartGlyph; + mozilla::AutoSwap_PRUint16 mEndGlyph; + mozilla::AutoSwap_PRUint32 mDocOffset; + mozilla::AutoSwap_PRUint32 mDocLength; + }; + + const struct DocIndex { + mozilla::AutoSwap_PRUint16 mNumEntries; + IndexEntry mEntries[1]; /* actual length = mNumEntries */ + }* mDocIndex; + + static int CompareIndexEntries(const void* _a, const void* _b); +}; + +/** + * XXX This is a complete hack and should die (see bug 1291494). + * + * This class is used when code fails to pass through an SVGContextPaint from + * the context in which we are painting. In that case we create one of these + * as a fallback and have it wrap the gfxContext's current gfxPattern and + * pretend that that is the paint context's fill pattern. In some contexts + * that will be the case, in others it will not. As we convert more code to + * Moz2D the less likely it is that this hack will work. It will also make + * converting to Moz2D harder. + */ +class SimpleTextContextPaint : public mozilla::SVGContextPaint { + private: + static const mozilla::gfx::DeviceColor sZero; + + static gfxMatrix SetupDeviceToPatternMatrix(gfxPattern* aPattern, + const gfxMatrix& aCTM) { + if (!aPattern) { + return gfxMatrix(); + } + gfxMatrix deviceToUser = aCTM; + if (!deviceToUser.Invert()) { + return gfxMatrix(0, 0, 0, 0, 0, 0); // singular + } + return deviceToUser * aPattern->GetMatrix(); + } + + public: + SimpleTextContextPaint(gfxPattern* aFillPattern, gfxPattern* aStrokePattern, + const gfxMatrix& aCTM) + : mFillPattern(aFillPattern ? aFillPattern : new gfxPattern(sZero)), + mStrokePattern(aStrokePattern ? aStrokePattern + : new gfxPattern(sZero)) { + mFillMatrix = SetupDeviceToPatternMatrix(aFillPattern, aCTM); + mStrokeMatrix = SetupDeviceToPatternMatrix(aStrokePattern, aCTM); + } + + already_AddRefed<gfxPattern> GetFillPattern( + const DrawTarget* aDrawTarget, float aOpacity, const gfxMatrix& aCTM, + imgDrawingParams& aImgParams) override { + if (mFillPattern) { + mFillPattern->SetMatrix(aCTM * mFillMatrix); + } + RefPtr<gfxPattern> fillPattern = mFillPattern; + return fillPattern.forget(); + } + + already_AddRefed<gfxPattern> GetStrokePattern( + const DrawTarget* aDrawTarget, float aOpacity, const gfxMatrix& aCTM, + imgDrawingParams& aImgParams) override { + if (mStrokePattern) { + mStrokePattern->SetMatrix(aCTM * mStrokeMatrix); + } + RefPtr<gfxPattern> strokePattern = mStrokePattern; + return strokePattern.forget(); + } + + float GetFillOpacity() const override { return mFillPattern ? 1.0f : 0.0f; } + + float GetStrokeOpacity() const override { + return mStrokePattern ? 1.0f : 0.0f; + } + + private: + RefPtr<gfxPattern> mFillPattern; + RefPtr<gfxPattern> mStrokePattern; + + // Device space to pattern space transforms + gfxMatrix mFillMatrix; + gfxMatrix mStrokeMatrix; +}; + +#endif diff --git a/gfx/thebes/gfxScriptItemizer.cpp b/gfx/thebes/gfxScriptItemizer.cpp new file mode 100644 index 0000000000..29a2b4d0ed --- /dev/null +++ b/gfx/thebes/gfxScriptItemizer.cpp @@ -0,0 +1,256 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This file is based on usc_impl.c from ICU 4.2.0.1, slightly adapted + * for use within Mozilla Gecko, separate from a standard ICU build. + * + * The original ICU license of the code follows: + * + * ICU License - ICU 1.8.1 and later + * + * COPYRIGHT AND PERMISSION NOTICE + * + * Copyright (c) 1995-2009 International Business Machines Corporation and + * others + * + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, provided that the above copyright notice(s) and this + * permission notice appear in all copies of the Software and that both the + * above copyright notice(s) and this permission notice appear in supporting + * documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE + * BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, + * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, + * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + * + * Except as contained in this notice, the name of a copyright holder shall + * not be used in advertising or otherwise to promote the sale, use or other + * dealings in this Software without prior written authorization of the + * copyright holder. + * + * All trademarks and registered trademarks mentioned herein are the property + * of their respective owners. + */ + +#include "gfxScriptItemizer.h" +#include "mozilla/intl/UnicodeProperties.h" +#include "nsCharTraits.h" +#include "nsUnicodeProperties.h" +#include "harfbuzz/hb.h" + +using namespace mozilla::intl; +using namespace mozilla::unicode; + +#define MOD(sp) ((sp) % PAREN_STACK_DEPTH) +#define LIMIT_INC(sp) \ + (((sp) < PAREN_STACK_DEPTH) ? (sp) + 1 : PAREN_STACK_DEPTH) +#define INC(sp, count) (MOD((sp) + (count))) +#define INC1(sp) (INC(sp, 1)) +#define DEC(sp, count) (MOD((sp) + PAREN_STACK_DEPTH - (count))) +#define DEC1(sp) (DEC(sp, 1)) +#define STACK_IS_EMPTY() (pushCount <= 0) +#define STACK_IS_NOT_EMPTY() (!STACK_IS_EMPTY()) +#define TOP() (parenStack[parenSP]) +#define SYNC_FIXUP() (fixupCount = 0) + +void gfxScriptItemizer::push(uint32_t endPairChar, Script newScriptCode) { + pushCount = LIMIT_INC(pushCount); + fixupCount = LIMIT_INC(fixupCount); + + parenSP = INC1(parenSP); + parenStack[parenSP].endPairChar = endPairChar; + parenStack[parenSP].scriptCode = newScriptCode; +} + +void gfxScriptItemizer::pop() { + if (STACK_IS_EMPTY()) { + return; + } + + if (fixupCount > 0) { + fixupCount -= 1; + } + + pushCount -= 1; + parenSP = DEC1(parenSP); + + /* If the stack is now empty, reset the stack + pointers to their initial values. + */ + if (STACK_IS_EMPTY()) { + parenSP = -1; + } +} + +void gfxScriptItemizer::fixup(Script newScriptCode) { + int32_t fixupSP = DEC(parenSP, fixupCount); + + while (fixupCount-- > 0) { + fixupSP = INC1(fixupSP); + parenStack[fixupSP].scriptCode = newScriptCode; + } +} + +static inline bool CanMergeWithContext(Script aScript) { + return aScript <= Script::INHERITED || aScript == Script::UNKNOWN; +} + +// We regard the current char as having the same script as the in-progress run +// if either script is Common/Inherited/Unknown, or if the run script appears +// in the character's ScriptExtensions, or if the char is a cluster extender. +static inline bool SameScript(Script runScript, Script currCharScript, + uint32_t aCurrCh) { + return CanMergeWithContext(runScript) || + CanMergeWithContext(currCharScript) || currCharScript == runScript || + IsClusterExtender(aCurrCh) || + UnicodeProperties::HasScript(aCurrCh, runScript); +} + +gfxScriptItemizer::gfxScriptItemizer(const char16_t* src, uint32_t length) + : textPtr(src), textLength(length) { + reset(); +} + +void gfxScriptItemizer::SetText(const char16_t* src, uint32_t length) { + textPtr = src; + textLength = length; + + reset(); +} + +bool gfxScriptItemizer::Next(uint32_t& aRunStart, uint32_t& aRunLimit, + Script& aRunScript) { + /* if we've fallen off the end of the text, we're done */ + if (scriptLimit >= textLength) { + return false; + } + + SYNC_FIXUP(); + scriptCode = Script::COMMON; + Script fallbackScript = Script::UNKNOWN; + + for (scriptStart = scriptLimit; scriptLimit < textLength; scriptLimit += 1) { + uint32_t ch; + Script sc; + uint32_t startOfChar = scriptLimit; + + ch = textPtr[scriptLimit]; + + /* decode UTF-16 (may be surrogate pair) */ + if (NS_IS_HIGH_SURROGATE(ch) && scriptLimit < textLength - 1) { + uint32_t low = textPtr[scriptLimit + 1]; + if (NS_IS_LOW_SURROGATE(low)) { + ch = SURROGATE_TO_UCS4(ch, low); + scriptLimit += 1; + } + } + + // Initialize gc to UNASSIGNED; we'll only set it to the true GC + // if the character has script=COMMON, otherwise we don't care. + uint8_t gc = HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED; + + sc = UnicodeProperties::GetScriptCode(ch); + if (sc == Script::COMMON) { + /* + * Paired character handling: + * + * if it's an open character, push it onto the stack. + * if it's a close character, find the matching open on the + * stack, and use that script code. Any non-matching open + * characters above it on the stack will be popped. + * + * We only do this if the script is COMMON; for chars with + * specific script assignments, we just use them as-is. + */ + gc = GetGeneralCategory(ch); + if (gc == HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION) { + uint32_t endPairChar = UnicodeProperties::CharMirror(ch); + if (endPairChar != ch) { + push(endPairChar, scriptCode); + } + } else if (gc == HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION && + UnicodeProperties::IsMirrored(ch)) { + while (STACK_IS_NOT_EMPTY() && TOP().endPairChar != ch) { + pop(); + } + + if (STACK_IS_NOT_EMPTY()) { + sc = TOP().scriptCode; + } + } + } + + // Both Hiragana and Katakana are shaped as OpenType 'kana'. Merge them + // here to avoid script-run breaks and allow kerning to apply between the + // two alphabets. + if (sc == Script::HIRAGANA) { + sc = Script::KATAKANA; + } + + if (SameScript(scriptCode, sc, ch)) { + if (scriptCode == Script::COMMON) { + // If we have not yet resolved a specific scriptCode for the run, + // check whether this character provides it. + if (!CanMergeWithContext(sc)) { + // Use this character's script. + scriptCode = sc; + fixup(scriptCode); + } else if (fallbackScript == Script::UNKNOWN) { + // See if the character has a ScriptExtensions property we can + // store for use in the event the run remains unresolved. + UnicodeProperties::ScriptExtensionVector extensions; + auto extResult = UnicodeProperties::GetExtensions(ch, extensions); + if (extResult.isOk()) { + Script ext = Script(extensions[0]); + if (!CanMergeWithContext(ext)) { + fallbackScript = ext; + } + } + } + } + + /* + * if this character is a close paired character, + * pop the matching open character from the stack + */ + if (gc == HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION && + UnicodeProperties::IsMirrored(ch)) { + pop(); + } + } else { + /* + * reset scriptLimit in case it was advanced during reading a + * multiple-code-unit character + */ + scriptLimit = startOfChar; + + break; + } + } + + aRunStart = scriptStart; + aRunLimit = scriptLimit; + + if (scriptCode == Script::COMMON && fallbackScript != Script::UNKNOWN) { + aRunScript = fallbackScript; + } else { + aRunScript = scriptCode; + } + + return true; +} diff --git a/gfx/thebes/gfxScriptItemizer.h b/gfx/thebes/gfxScriptItemizer.h new file mode 100644 index 0000000000..6deb37e19c --- /dev/null +++ b/gfx/thebes/gfxScriptItemizer.h @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This file is based on usc_impl.c from ICU 4.2.0.1, slightly adapted + * for use within Mozilla Gecko, separate from a standard ICU build. + * + * The original ICU license of the code follows: + * + * ICU License - ICU 1.8.1 and later + * + * COPYRIGHT AND PERMISSION NOTICE + * + * Copyright (c) 1995-2009 International Business Machines Corporation and + * others + * + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, provided that the above copyright notice(s) and this + * permission notice appear in all copies of the Software and that both the + * above copyright notice(s) and this permission notice appear in supporting + * documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE + * BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, + * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, + * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + * + * Except as contained in this notice, the name of a copyright holder shall + * not be used in advertising or otherwise to promote the sale, use or other + * dealings in this Software without prior written authorization of the + * copyright holder. + * + * All trademarks and registered trademarks mentioned herein are the property + * of their respective owners. + */ + +#ifndef GFX_SCRIPTITEMIZER_H +#define GFX_SCRIPTITEMIZER_H + +#include <stdint.h> +#include "mozilla/intl/UnicodeScriptCodes.h" + +#define PAREN_STACK_DEPTH 32 + +class gfxScriptItemizer { + public: + typedef mozilla::intl::Script Script; + + gfxScriptItemizer(const char16_t* src, uint32_t length); + + void SetText(const char16_t* src, uint32_t length); + + bool Next(uint32_t& aRunStart, uint32_t& aRunLimit, Script& aRunScript); + + protected: + void reset() { + scriptStart = 0; + scriptLimit = 0; + scriptCode = Script::INVALID; + parenSP = -1; + pushCount = 0; + fixupCount = 0; + } + + void push(uint32_t endPairChar, Script newScriptCode); + void pop(); + void fixup(Script newScriptCode); + + struct ParenStackEntry { + uint32_t endPairChar; + Script scriptCode; + }; + + const char16_t* textPtr; + uint32_t textLength; + + uint32_t scriptStart; + uint32_t scriptLimit; + Script scriptCode; + + struct ParenStackEntry parenStack[PAREN_STACK_DEPTH]; + uint32_t parenSP; + uint32_t pushCount; + uint32_t fixupCount; +}; + +#endif /* GFX_SCRIPTITEMIZER_H */ diff --git a/gfx/thebes/gfxSharedImageSurface.h b/gfx/thebes/gfxSharedImageSurface.h new file mode 100644 index 0000000000..b8bf461ed1 --- /dev/null +++ b/gfx/thebes/gfxSharedImageSurface.h @@ -0,0 +1,27 @@ +// vim:set ts=4 sts=2 sw=2 et cin: +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_SHARED_IMAGESURFACE_H +#define GFX_SHARED_IMAGESURFACE_H + +#include "gfxBaseSharedMemorySurface.h" + +class gfxSharedImageSurface + : public gfxBaseSharedMemorySurface<gfxImageSurface, + gfxSharedImageSurface> { + typedef gfxBaseSharedMemorySurface<gfxImageSurface, gfxSharedImageSurface> + Super; + friend class gfxBaseSharedMemorySurface<gfxImageSurface, + gfxSharedImageSurface>; + + private: + gfxSharedImageSurface(const mozilla::gfx::IntSize& aSize, long aStride, + gfxImageFormat aFormat, + const mozilla::ipc::Shmem& aShmem) + : Super(aSize, aStride, aFormat, aShmem) {} +}; + +#endif /* GFX_SHARED_IMAGESURFACE_H */ diff --git a/gfx/thebes/gfxSkipChars.cpp b/gfx/thebes/gfxSkipChars.cpp new file mode 100644 index 0000000000..cd230efe74 --- /dev/null +++ b/gfx/thebes/gfxSkipChars.cpp @@ -0,0 +1,146 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxSkipChars.h" +#include "mozilla/BinarySearch.h" +#include "mozilla/gfx/Logging.h" + +struct SkippedRangeStartComparator { + const uint32_t mOffset; + explicit SkippedRangeStartComparator(const uint32_t aOffset) + : mOffset(aOffset) {} + int operator()(const gfxSkipChars::SkippedRange& aRange) const { + return (mOffset < aRange.Start()) ? -1 : 1; + } +}; + +void gfxSkipCharsIterator::SetOriginalOffset(int32_t aOffset) { + aOffset += mOriginalStringToSkipCharsOffset; + if (MOZ_UNLIKELY(uint32_t(aOffset) > mSkipChars->mCharCount)) { + gfxCriticalError() << "invalid offset " << aOffset + << " for gfxSkipChars length " << mSkipChars->mCharCount; + aOffset = mSkipChars->mCharCount; + } + + mOriginalStringOffset = aOffset; + + const uint32_t rangeCount = mSkipChars->mRanges.Length(); + if (rangeCount == 0) { + mSkippedStringOffset = aOffset; + return; + } + + // at start of string? + if (aOffset == 0) { + mSkippedStringOffset = 0; + mCurrentRangeIndex = + rangeCount && mSkipChars->mRanges[0].Start() == 0 ? 0 : -1; + return; + } + + // find the range that includes or precedes aOffset + const nsTArray<gfxSkipChars::SkippedRange>& ranges = mSkipChars->mRanges; + size_t idx; + mozilla::BinarySearchIf(ranges, 0, rangeCount, + SkippedRangeStartComparator(aOffset), &idx); + + if (idx == rangeCount) { + mCurrentRangeIndex = rangeCount - 1; + } else if (uint32_t(aOffset) < ranges[idx].Start()) { + mCurrentRangeIndex = idx - 1; + if (mCurrentRangeIndex == -1) { + mSkippedStringOffset = aOffset; + return; + } + } else { + mCurrentRangeIndex = idx; + } + + const gfxSkipChars::SkippedRange& r = ranges[mCurrentRangeIndex]; + if (uint32_t(aOffset) < r.End()) { + mSkippedStringOffset = r.SkippedOffset(); + return; + } + + mSkippedStringOffset = aOffset - r.NextDelta(); +} + +struct SkippedRangeOffsetComparator { + const uint32_t mOffset; + explicit SkippedRangeOffsetComparator(const uint32_t aOffset) + : mOffset(aOffset) {} + int operator()(const gfxSkipChars::SkippedRange& aRange) const { + return (mOffset < aRange.SkippedOffset()) ? -1 : 1; + } +}; + +void gfxSkipCharsIterator::SetSkippedOffset(uint32_t aOffset) { + NS_ASSERTION( + (mSkipChars->mRanges.IsEmpty() && aOffset <= mSkipChars->mCharCount) || + (aOffset <= mSkipChars->LastRange().SkippedOffset() + + mSkipChars->mCharCount - + mSkipChars->LastRange().End()), + "Invalid skipped offset"); + mSkippedStringOffset = aOffset; + + uint32_t rangeCount = mSkipChars->mRanges.Length(); + if (rangeCount == 0) { + mOriginalStringOffset = aOffset; + return; + } + + const nsTArray<gfxSkipChars::SkippedRange>& ranges = mSkipChars->mRanges; + size_t idx; + mozilla::BinarySearchIf(ranges, 0, rangeCount, + SkippedRangeOffsetComparator(aOffset), &idx); + + if (idx == rangeCount) { + mCurrentRangeIndex = rangeCount - 1; + } else if (aOffset < ranges[idx].SkippedOffset()) { + mCurrentRangeIndex = idx - 1; + if (mCurrentRangeIndex == -1) { + mOriginalStringOffset = aOffset; + return; + } + } else { + mCurrentRangeIndex = idx; + } + + const gfxSkipChars::SkippedRange& r = ranges[mCurrentRangeIndex]; + mOriginalStringOffset = r.End() + aOffset - r.SkippedOffset(); +} + +bool gfxSkipCharsIterator::IsOriginalCharSkipped(int32_t* aRunLength) const { + if (mCurrentRangeIndex == -1) { + // we're before the first skipped range (if any) + if (aRunLength) { + uint32_t end = mSkipChars->mRanges.IsEmpty() + ? mSkipChars->mCharCount + : mSkipChars->mRanges[0].Start(); + *aRunLength = end - mOriginalStringOffset; + } + return mSkipChars->mCharCount == uint32_t(mOriginalStringOffset); + } + + const gfxSkipChars::SkippedRange& range = + mSkipChars->mRanges[mCurrentRangeIndex]; + + if (uint32_t(mOriginalStringOffset) < range.End()) { + if (aRunLength) { + *aRunLength = range.End() - mOriginalStringOffset; + } + return true; + } + + if (aRunLength) { + uint32_t end = + uint32_t(mCurrentRangeIndex) + 1 < mSkipChars->mRanges.Length() + ? mSkipChars->mRanges[mCurrentRangeIndex + 1].Start() + : mSkipChars->mCharCount; + *aRunLength = end - mOriginalStringOffset; + } + + return mSkipChars->mCharCount == uint32_t(mOriginalStringOffset); +} diff --git a/gfx/thebes/gfxSkipChars.h b/gfx/thebes/gfxSkipChars.h new file mode 100644 index 0000000000..39b46c2806 --- /dev/null +++ b/gfx/thebes/gfxSkipChars.h @@ -0,0 +1,253 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_SKIP_CHARS_H +#define GFX_SKIP_CHARS_H + +#include "mozilla/Attributes.h" +#include "nsTArray.h" + +/* + * gfxSkipChars is a data structure representing a list of characters that + * have been skipped. The initial string is called the "original string" + * and after skipping some characters, the result is called the "skipped + * string". gfxSkipChars provides efficient ways to translate between offsets in + * the original string and the skipped string. It is used by textrun code to + * keep track of offsets before and after text transformations such as + * whitespace compression and control code deletion. + */ + +/** + * The gfxSkipChars is represented as a sorted array of skipped ranges. + * + * A freshly-created gfxSkipChars means "all chars kept". + */ +class gfxSkipChars { + friend struct SkippedRangeStartComparator; + friend struct SkippedRangeOffsetComparator; + + private: + class SkippedRange { + public: + SkippedRange(uint32_t aOffset, uint32_t aLength, uint32_t aDelta) + : mOffset(aOffset), mLength(aLength), mDelta(aDelta) {} + + uint32_t Start() const { return mOffset; } + + uint32_t End() const { return mOffset + mLength; } + + uint32_t Length() const { return mLength; } + + uint32_t SkippedOffset() const { return mOffset - mDelta; } + + uint32_t Delta() const { return mDelta; } + + uint32_t NextDelta() const { return mDelta + mLength; } + + void Extend(uint32_t aChars) { mLength += aChars; } + + private: + uint32_t mOffset; // original-string offset at which we want to skip + uint32_t mLength; // number of skipped chars at this offset + uint32_t mDelta; // sum of lengths of preceding skipped-ranges + }; + + public: + gfxSkipChars() : mCharCount(0) {} + + void SkipChars(uint32_t aChars) { + NS_ASSERTION(mCharCount + aChars > mCharCount, "Character count overflow"); + uint32_t rangeCount = mRanges.Length(); + uint32_t delta = 0; + if (rangeCount > 0) { + SkippedRange& lastRange = mRanges[rangeCount - 1]; + if (lastRange.End() == mCharCount) { + lastRange.Extend(aChars); + mCharCount += aChars; + return; + } + delta = lastRange.NextDelta(); + } + mRanges.AppendElement(SkippedRange(mCharCount, aChars, delta)); + mCharCount += aChars; + } + + void KeepChars(uint32_t aChars) { + NS_ASSERTION(mCharCount + aChars > mCharCount, "Character count overflow"); + mCharCount += aChars; + } + + void SkipChar() { SkipChars(1); } + + void KeepChar() { KeepChars(1); } + + void TakeFrom(gfxSkipChars* aSkipChars) { + mRanges = std::move(aSkipChars->mRanges); + mCharCount = aSkipChars->mCharCount; + aSkipChars->mCharCount = 0; + } + + int32_t GetOriginalCharCount() const { return mCharCount; } + + const SkippedRange& LastRange() const { + // this is only valid if mRanges is non-empty; no assertion here + // because nsTArray will already assert if we abuse it + return mRanges[mRanges.Length() - 1]; + } + + friend class gfxSkipCharsIterator; + + private: + nsTArray<SkippedRange> mRanges; + uint32_t mCharCount; +}; + +/** + * A gfxSkipCharsIterator represents a position in the original string. It lets + * you map efficiently to and from positions in the string after skipped + * characters have been removed. You can also specify an offset that is added to + * all incoming original string offsets and subtracted from all outgoing + * original string offsets --- useful when the gfxSkipChars corresponds to + * something offset from the original DOM coordinates, which it often does for + * gfxTextRuns. + * + * The current positions (in both the original and skipped strings) are + * always constrained to be >= 0 and <= the string length. When the position + * is equal to the string length, it is at the end of the string. The current + * positions do not include any aOriginalStringToSkipCharsOffset. + * + * When the position in the original string corresponds to a skipped character, + * the skipped-characters offset is the offset of the next unskipped character, + * or the skipped-characters string length if there is no next unskipped + * character. + */ +class MOZ_STACK_CLASS gfxSkipCharsIterator { + public: + /** + * @param aOriginalStringToSkipCharsOffset add this to all incoming and + * outgoing original string offsets + */ + gfxSkipCharsIterator(const gfxSkipChars& aSkipChars, + int32_t aOriginalStringToSkipCharsOffset, + int32_t aOriginalStringOffset) + : mSkipChars(&aSkipChars), + mOriginalStringOffset(0), + mSkippedStringOffset(0), + mCurrentRangeIndex(-1), + mOriginalStringToSkipCharsOffset(aOriginalStringToSkipCharsOffset) { + SetOriginalOffset(aOriginalStringOffset); + } + + explicit gfxSkipCharsIterator(const gfxSkipChars& aSkipChars, + int32_t aOriginalStringToSkipCharsOffset = 0) + : mSkipChars(&aSkipChars), + mOriginalStringOffset(0), + mSkippedStringOffset(0), + mOriginalStringToSkipCharsOffset(aOriginalStringToSkipCharsOffset) { + mCurrentRangeIndex = + mSkipChars->mRanges.IsEmpty() || mSkipChars->mRanges[0].Start() > 0 ? -1 + : 0; + } + + gfxSkipCharsIterator(const gfxSkipCharsIterator& aIterator) = default; + + /** + * The empty constructor creates an object that is useless until it is + * assigned. + */ + gfxSkipCharsIterator() + : mSkipChars(nullptr), + mOriginalStringOffset(0), + mSkippedStringOffset(0), + mCurrentRangeIndex(0), + mOriginalStringToSkipCharsOffset(0) {} + + /** + * Return true if this iterator is properly initialized and usable. + */ + bool IsInitialized() const { return mSkipChars != nullptr; } + + /** + * Set the iterator to aOriginalStringOffset in the original string. + * This can efficiently move forward or backward from the current position. + * aOriginalStringOffset is clamped to [0,originalStringLength]. + */ + void SetOriginalOffset(int32_t aOriginalStringOffset); + + /** + * Set the iterator to aSkippedStringOffset in the skipped string. + * This can efficiently move forward or backward from the current position. + * aSkippedStringOffset is clamped to [0,skippedStringLength]. + */ + void SetSkippedOffset(uint32_t aSkippedStringOffset); + + uint32_t ConvertOriginalToSkipped(int32_t aOriginalStringOffset) { + SetOriginalOffset(aOriginalStringOffset); + return GetSkippedOffset(); + } + + int32_t ConvertSkippedToOriginal(uint32_t aSkippedStringOffset) { + SetSkippedOffset(aSkippedStringOffset); + return GetOriginalOffset(); + } + + /** + * Test if the character at the current position in the original string + * is skipped or not. If aRunLength is non-null, then *aRunLength is set + * to a number of characters all of which are either skipped or not, starting + * at this character. When the current position is at the end of the original + * string, we return true and *aRunLength is set to zero. + */ + bool IsOriginalCharSkipped(int32_t* aRunLength = nullptr) const; + + void AdvanceOriginal(int32_t aDelta) { + SetOriginalOffset(GetOriginalOffset() + aDelta); + } + + void AdvanceSkipped(int32_t aDelta) { + SetSkippedOffset(GetSkippedOffset() + aDelta); + } + + /** + * @return the offset within the original string + */ + int32_t GetOriginalOffset() const { + return mOriginalStringOffset - mOriginalStringToSkipCharsOffset; + } + + /** + * @return the offset within the skipped string corresponding to the + * current position in the original string. If the current position + * in the original string is a character that is skipped, then we return + * the position corresponding to the first non-skipped character in the + * original string after the current position, or the length of the skipped + * string if there is no such character. + */ + uint32_t GetSkippedOffset() const { return mSkippedStringOffset; } + + int32_t GetOriginalEnd() const { + return mSkipChars->GetOriginalCharCount() - + mOriginalStringToSkipCharsOffset; + } + + private: + const gfxSkipChars* mSkipChars; + + // Current position + int32_t mOriginalStringOffset; + uint32_t mSkippedStringOffset; + + // Index of the last skippedRange that precedes or contains the current + // position in the original string. + // If index == -1 then we are before the first skipped char. + int32_t mCurrentRangeIndex; + + // This offset is added to map from "skipped+unskipped characters in + // the original DOM string" character space to "skipped+unskipped + // characters in the textrun's gfxSkipChars" character space + int32_t mOriginalStringToSkipCharsOffset; +}; + +#endif /*GFX_SKIP_CHARS_H*/ diff --git a/gfx/thebes/gfxTextRun.cpp b/gfx/thebes/gfxTextRun.cpp new file mode 100644 index 0000000000..0b1b5ed83f --- /dev/null +++ b/gfx/thebes/gfxTextRun.cpp @@ -0,0 +1,3865 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=4 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxTextRun.h" +#include "gfxGlyphExtents.h" +#include "gfxHarfBuzzShaper.h" +#include "gfxPlatformFontList.h" +#include "gfxUserFontSet.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/ServoStyleSet.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticPresData.h" + +#include "gfxContext.h" +#include "gfxFontConstants.h" +#include "gfxFontMissingGlyphs.h" +#include "gfxScriptItemizer.h" +#include "nsUnicodeProperties.h" +#include "nsStyleConsts.h" +#include "nsStyleUtil.h" +#include "mozilla/Likely.h" +#include "gfx2DGlue.h" +#include "mozilla/gfx/Logging.h" // for gfxCriticalError +#include "mozilla/intl/String.h" +#include "mozilla/intl/UnicodeProperties.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" +#include "SharedFontList-impl.h" +#include "TextDrawTarget.h" + +#ifdef XP_WIN +# include "gfxWindowsPlatform.h" +#endif + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::intl; +using namespace mozilla::unicode; +using mozilla::services::GetObserverService; + +static const char16_t kEllipsisChar[] = {0x2026, 0x0}; +static const char16_t kASCIIPeriodsChar[] = {'.', '.', '.', 0x0}; + +#ifdef DEBUG_roc +# define DEBUG_TEXT_RUN_STORAGE_METRICS +#endif + +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS +extern uint32_t gTextRunStorageHighWaterMark; +extern uint32_t gTextRunStorage; +extern uint32_t gFontCount; +extern uint32_t gGlyphExtentsCount; +extern uint32_t gGlyphExtentsWidthsTotalSize; +extern uint32_t gGlyphExtentsSetupEagerSimple; +extern uint32_t gGlyphExtentsSetupEagerTight; +extern uint32_t gGlyphExtentsSetupLazyTight; +extern uint32_t gGlyphExtentsSetupFallBackToTight; +#endif + +void gfxTextRun::GlyphRunIterator::NextRun() { + if (mReverse) { + if (mGlyphRun == mTextRun->mGlyphRuns.begin()) { + mGlyphRun = nullptr; + return; + } + --mGlyphRun; + } else { + MOZ_DIAGNOSTIC_ASSERT(mGlyphRun != mTextRun->mGlyphRuns.end()); + ++mGlyphRun; + if (mGlyphRun == mTextRun->mGlyphRuns.end()) { + mGlyphRun = nullptr; + return; + } + } + if (mGlyphRun->mCharacterOffset >= mEndOffset) { + mGlyphRun = nullptr; + return; + } + uint32_t glyphRunEndOffset = mGlyphRun == mTextRun->mGlyphRuns.end() - 1 + ? mTextRun->GetLength() + : (mGlyphRun + 1)->mCharacterOffset; + if (glyphRunEndOffset < mStartOffset) { + mGlyphRun = nullptr; + return; + } + mStringEnd = std::min(mEndOffset, glyphRunEndOffset); + mStringStart = std::max(mStartOffset, mGlyphRun->mCharacterOffset); +} + +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS +static void AccountStorageForTextRun(gfxTextRun* aTextRun, int32_t aSign) { + // Ignores detailed glyphs... we don't know when those have been constructed + // Also ignores gfxSkipChars dynamic storage (which won't be anything + // for preformatted text) + // Also ignores GlyphRun array, again because it hasn't been constructed + // by the time this gets called. If there's only one glyphrun that's stored + // directly in the textrun anyway so no additional overhead. + uint32_t length = aTextRun->GetLength(); + int32_t bytes = length * sizeof(gfxTextRun::CompressedGlyph); + bytes += sizeof(gfxTextRun); + gTextRunStorage += bytes * aSign; + gTextRunStorageHighWaterMark = + std::max(gTextRunStorageHighWaterMark, gTextRunStorage); +} +#endif + +bool gfxTextRun::NeedsGlyphExtents() const { + if (GetFlags() & gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX) { + return true; + } + for (const auto& run : mGlyphRuns) { + if (run.mFont->GetFontEntry()->IsUserFont()) { + return true; + } + } + return false; +} + +// Helper for textRun creation to preallocate storage for glyph records; +// this function returns a pointer to the newly-allocated glyph storage. +// Returns nullptr if allocation fails. +void* gfxTextRun::AllocateStorageForTextRun(size_t aSize, uint32_t aLength) { + // Allocate the storage we need, returning nullptr on failure rather than + // throwing an exception (because web content can create huge runs). + void* storage = malloc(aSize + aLength * sizeof(CompressedGlyph)); + if (!storage) { + NS_WARNING("failed to allocate storage for text run!"); + return nullptr; + } + + // Initialize the glyph storage (beyond aSize) to zero + memset(reinterpret_cast<char*>(storage) + aSize, 0, + aLength * sizeof(CompressedGlyph)); + + return storage; +} + +already_AddRefed<gfxTextRun> gfxTextRun::Create( + const gfxTextRunFactory::Parameters* aParams, uint32_t aLength, + gfxFontGroup* aFontGroup, gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2) { + void* storage = AllocateStorageForTextRun(sizeof(gfxTextRun), aLength); + if (!storage) { + return nullptr; + } + + RefPtr<gfxTextRun> result = + new (storage) gfxTextRun(aParams, aLength, aFontGroup, aFlags, aFlags2); + return result.forget(); +} + +gfxTextRun::gfxTextRun(const gfxTextRunFactory::Parameters* aParams, + uint32_t aLength, gfxFontGroup* aFontGroup, + gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2) + : gfxShapedText(aLength, aFlags, aParams->mAppUnitsPerDevUnit), + mUserData(aParams->mUserData), + mFontGroup(aFontGroup), + mFlags2(aFlags2), + mReleasedFontGroup(false), + mReleasedFontGroupSkippedDrawing(false), + mShapingState(eShapingState_Normal) { + NS_ASSERTION(mAppUnitsPerDevUnit > 0, "Invalid app unit scale"); + NS_ADDREF(mFontGroup); + +#ifndef RELEASE_OR_BETA + gfxTextPerfMetrics* tp = aFontGroup->GetTextPerfMetrics(); + if (tp) { + tp->current.textrunConst++; + } +#endif + + mCharacterGlyphs = reinterpret_cast<CompressedGlyph*>(this + 1); + + if (aParams->mSkipChars) { + mSkipChars.TakeFrom(aParams->mSkipChars); + } + +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + AccountStorageForTextRun(this, 1); +#endif + + mDontSkipDrawing = + !!(aFlags2 & nsTextFrameUtils::Flags::DontSkipDrawingForPendingUserFonts); +} + +gfxTextRun::~gfxTextRun() { +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + AccountStorageForTextRun(this, -1); +#endif +#ifdef DEBUG + // Make it easy to detect a dead text run + mFlags = ~gfx::ShapedTextFlags(); + mFlags2 = ~nsTextFrameUtils::Flags(); +#endif + + // The cached ellipsis textrun (if any) in a fontgroup will have already + // been told to release its reference to the group, so we mustn't do that + // again here. + if (!mReleasedFontGroup) { +#ifndef RELEASE_OR_BETA + gfxTextPerfMetrics* tp = mFontGroup->GetTextPerfMetrics(); + if (tp) { + tp->current.textrunDestr++; + } +#endif + NS_RELEASE(mFontGroup); + } +} + +void gfxTextRun::ReleaseFontGroup() { + NS_ASSERTION(!mReleasedFontGroup, "doubly released!"); + + // After dropping our reference to the font group, we'll no longer be able + // to get up-to-date results for ShouldSkipDrawing(). Store the current + // value in mReleasedFontGroupSkippedDrawing. + // + // (It doesn't actually matter that we can't get up-to-date results for + // ShouldSkipDrawing(), since the only text runs that we call + // ReleaseFontGroup() for are ellipsis text runs, and we ask the font + // group for a new ellipsis text run each time we want to draw one, + // and ensure that the cached one is cleared in ClearCachedData() when + // font loading status changes.) + mReleasedFontGroupSkippedDrawing = mFontGroup->ShouldSkipDrawing(); + + NS_RELEASE(mFontGroup); + mReleasedFontGroup = true; +} + +bool gfxTextRun::SetPotentialLineBreaks(Range aRange, + const uint8_t* aBreakBefore) { + NS_ASSERTION(aRange.end <= GetLength(), "Overflow"); + + uint32_t changed = 0; + CompressedGlyph* cg = mCharacterGlyphs + aRange.start; + const CompressedGlyph* const end = cg + aRange.Length(); + while (cg < end) { + uint8_t canBreak = *aBreakBefore++; + if (canBreak && !cg->IsClusterStart()) { + // XXX If we replace the line-breaker with one based more closely + // on UAX#14 (e.g. using ICU), this may not be needed any more. + // Avoid possible breaks inside a cluster, EXCEPT when the previous + // character was a space (compare UAX#14 rules LB9, LB10). + if (cg == mCharacterGlyphs || !(cg - 1)->CharIsSpace()) { + canBreak = CompressedGlyph::FLAG_BREAK_TYPE_NONE; + } + } + // If a break is allowed here, set the break flag, but don't clear a + // possible pre-existing emergency-break flag already in the run. + if (canBreak) { + changed |= cg->SetCanBreakBefore(canBreak); + } + ++cg; + } + return changed != 0; +} + +gfxTextRun::LigatureData gfxTextRun::ComputeLigatureData( + Range aPartRange, const PropertyProvider* aProvider) const { + NS_ASSERTION(aPartRange.start < aPartRange.end, + "Computing ligature data for empty range"); + NS_ASSERTION(aPartRange.end <= GetLength(), "Character length overflow"); + + LigatureData result; + const CompressedGlyph* charGlyphs = mCharacterGlyphs; + + uint32_t i; + for (i = aPartRange.start; !charGlyphs[i].IsLigatureGroupStart(); --i) { + NS_ASSERTION(i > 0, "Ligature at the start of the run??"); + } + result.mRange.start = i; + for (i = aPartRange.start + 1; + i < GetLength() && !charGlyphs[i].IsLigatureGroupStart(); ++i) { + } + result.mRange.end = i; + + int32_t ligatureWidth = GetAdvanceForGlyphs(result.mRange); + // Count the number of started clusters we have seen + uint32_t totalClusterCount = 0; + uint32_t partClusterIndex = 0; + uint32_t partClusterCount = 0; + for (i = result.mRange.start; i < result.mRange.end; ++i) { + // Treat the first character of the ligature as the start of a + // cluster for our purposes of allocating ligature width to its + // characters. + if (i == result.mRange.start || charGlyphs[i].IsClusterStart()) { + ++totalClusterCount; + if (i < aPartRange.start) { + ++partClusterIndex; + } else if (i < aPartRange.end) { + ++partClusterCount; + } + } + } + NS_ASSERTION(totalClusterCount > 0, "Ligature involving no clusters??"); + result.mPartAdvance = partClusterIndex * (ligatureWidth / totalClusterCount); + result.mPartWidth = partClusterCount * (ligatureWidth / totalClusterCount); + + // Any rounding errors are apportioned to the final part of the ligature, + // so that measuring all parts of a ligature and summing them is equal to + // the ligature width. + if (aPartRange.end == result.mRange.end) { + gfxFloat allParts = totalClusterCount * (ligatureWidth / totalClusterCount); + result.mPartWidth += ligatureWidth - allParts; + } + + if (partClusterCount == 0) { + // nothing to draw + result.mClipBeforePart = result.mClipAfterPart = true; + } else { + // Determine whether we should clip before or after this part when + // drawing its slice of the ligature. + // We need to clip before the part if any cluster is drawn before + // this part. + result.mClipBeforePart = partClusterIndex > 0; + // We need to clip after the part if any cluster is drawn after + // this part. + result.mClipAfterPart = + partClusterIndex + partClusterCount < totalClusterCount; + } + + if (aProvider && (mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING)) { + gfxFont::Spacing spacing; + if (aPartRange.start == result.mRange.start) { + aProvider->GetSpacing(Range(aPartRange.start, aPartRange.start + 1), + &spacing); + result.mPartWidth += spacing.mBefore; + } + if (aPartRange.end == result.mRange.end) { + aProvider->GetSpacing(Range(aPartRange.end - 1, aPartRange.end), + &spacing); + result.mPartWidth += spacing.mAfter; + } + } + + return result; +} + +gfxFloat gfxTextRun::ComputePartialLigatureWidth( + Range aPartRange, const PropertyProvider* aProvider) const { + if (aPartRange.start >= aPartRange.end) return 0; + LigatureData data = ComputeLigatureData(aPartRange, aProvider); + return data.mPartWidth; +} + +int32_t gfxTextRun::GetAdvanceForGlyphs(Range aRange) const { + int32_t advance = 0; + for (auto i = aRange.start; i < aRange.end; ++i) { + advance += GetAdvanceForGlyph(i); + } + return advance; +} + +static void GetAdjustedSpacing( + const gfxTextRun* aTextRun, gfxTextRun::Range aRange, + const gfxTextRun::PropertyProvider& aProvider, + gfxTextRun::PropertyProvider::Spacing* aSpacing) { + if (aRange.start >= aRange.end) { + return; + } + + aProvider.GetSpacing(aRange, aSpacing); + +#ifdef DEBUG + // Check to see if we have spacing inside ligatures + + const gfxTextRun::CompressedGlyph* charGlyphs = + aTextRun->GetCharacterGlyphs(); + uint32_t i; + + for (i = aRange.start; i < aRange.end; ++i) { + if (!charGlyphs[i].IsLigatureGroupStart()) { + NS_ASSERTION(i == aRange.start || aSpacing[i - aRange.start].mBefore == 0, + "Before-spacing inside a ligature!"); + NS_ASSERTION( + i - 1 <= aRange.start || aSpacing[i - 1 - aRange.start].mAfter == 0, + "After-spacing inside a ligature!"); + } + } +#endif +} + +bool gfxTextRun::GetAdjustedSpacingArray( + Range aRange, const PropertyProvider* aProvider, Range aSpacingRange, + nsTArray<PropertyProvider::Spacing>* aSpacing) const { + if (!aProvider || !(mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING)) { + return false; + } + if (!aSpacing->AppendElements(aRange.Length(), fallible)) { + return false; + } + auto spacingOffset = aSpacingRange.start - aRange.start; + memset(aSpacing->Elements(), 0, sizeof(gfxFont::Spacing) * spacingOffset); + GetAdjustedSpacing(this, aSpacingRange, *aProvider, + aSpacing->Elements() + spacingOffset); + memset(aSpacing->Elements() + spacingOffset + aSpacingRange.Length(), 0, + sizeof(gfxFont::Spacing) * (aRange.end - aSpacingRange.end)); + return true; +} + +bool gfxTextRun::ShrinkToLigatureBoundaries(Range* aRange) const { + if (aRange->start >= aRange->end) { + return false; + } + + const CompressedGlyph* charGlyphs = mCharacterGlyphs; + bool adjusted = false; + while (aRange->start < aRange->end && + !charGlyphs[aRange->start].IsLigatureGroupStart()) { + ++aRange->start; + adjusted = true; + } + if (aRange->end < GetLength()) { + while (aRange->end > aRange->start && + !charGlyphs[aRange->end].IsLigatureGroupStart()) { + --aRange->end; + adjusted = true; + } + } + return adjusted; +} + +void gfxTextRun::DrawGlyphs(gfxFont* aFont, Range aRange, gfx::Point* aPt, + const PropertyProvider* aProvider, + Range aSpacingRange, TextRunDrawParams& aParams, + gfx::ShapedTextFlags aOrientation) const { + AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer; + bool haveSpacing = + GetAdjustedSpacingArray(aRange, aProvider, aSpacingRange, &spacingBuffer); + aParams.spacing = haveSpacing ? spacingBuffer.Elements() : nullptr; + aFont->Draw(this, aRange.start, aRange.end, aPt, aParams, aOrientation); +} + +static void ClipPartialLigature(const gfxTextRun* aTextRun, gfxFloat* aStart, + gfxFloat* aEnd, gfxFloat aOrigin, + gfxTextRun::LigatureData* aLigature) { + if (aLigature->mClipBeforePart) { + if (aTextRun->IsRightToLeft()) { + *aEnd = std::min(*aEnd, aOrigin); + } else { + *aStart = std::max(*aStart, aOrigin); + } + } + if (aLigature->mClipAfterPart) { + gfxFloat endEdge = + aOrigin + aTextRun->GetDirection() * aLigature->mPartWidth; + if (aTextRun->IsRightToLeft()) { + *aStart = std::max(*aStart, endEdge); + } else { + *aEnd = std::min(*aEnd, endEdge); + } + } +} + +void gfxTextRun::DrawPartialLigature(gfxFont* aFont, Range aRange, + gfx::Point* aPt, + const PropertyProvider* aProvider, + TextRunDrawParams& aParams, + gfx::ShapedTextFlags aOrientation) const { + if (aRange.start >= aRange.end) { + return; + } + + // Draw partial ligature. We hack this by clipping the ligature. + LigatureData data = ComputeLigatureData(aRange, aProvider); + gfxRect clipExtents = aParams.context->GetClipExtents(); + gfxFloat start, end; + if (aParams.isVerticalRun) { + start = clipExtents.Y() * mAppUnitsPerDevUnit; + end = clipExtents.YMost() * mAppUnitsPerDevUnit; + ClipPartialLigature(this, &start, &end, aPt->y, &data); + } else { + start = clipExtents.X() * mAppUnitsPerDevUnit; + end = clipExtents.XMost() * mAppUnitsPerDevUnit; + ClipPartialLigature(this, &start, &end, aPt->x, &data); + } + + gfxClipAutoSaveRestore autoSaveClip(aParams.context); + { + // use division here to ensure that when the rect is aligned on multiples + // of mAppUnitsPerDevUnit, we clip to true device unit boundaries. + // Also, make sure we snap the rectangle to device pixels. + Rect clipRect = + aParams.isVerticalRun + ? Rect(clipExtents.X(), start / mAppUnitsPerDevUnit, + clipExtents.Width(), (end - start) / mAppUnitsPerDevUnit) + : Rect(start / mAppUnitsPerDevUnit, clipExtents.Y(), + (end - start) / mAppUnitsPerDevUnit, clipExtents.Height()); + MaybeSnapToDevicePixels(clipRect, *aParams.dt, true); + + autoSaveClip.Clip(clipRect); + } + + gfx::Point pt; + if (aParams.isVerticalRun) { + pt = Point(aPt->x, aPt->y - aParams.direction * data.mPartAdvance); + } else { + pt = Point(aPt->x - aParams.direction * data.mPartAdvance, aPt->y); + } + + DrawGlyphs(aFont, data.mRange, &pt, aProvider, aRange, aParams, aOrientation); + + if (aParams.isVerticalRun) { + aPt->y += aParams.direction * data.mPartWidth; + } else { + aPt->x += aParams.direction * data.mPartWidth; + } +} + +// Returns true if the font has synthetic bolding enabled, +// or is a color font (COLR/SVG/sbix/CBDT), false otherwise. This is used to +// check whether the text run needs to be explicitly composited in order to +// support opacity. +static bool HasSyntheticBoldOrColor(gfxFont* aFont) { + if (aFont->ApplySyntheticBold()) { + return true; + } + gfxFontEntry* fe = aFont->GetFontEntry(); + if (fe->TryGetSVGData(aFont) || fe->TryGetColorGlyphs()) { + return true; + } +#if defined(XP_MACOSX) // sbix fonts only supported via Core Text + if (fe->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'))) { + return true; + } +#endif + return false; +} + +// helper class for double-buffering drawing with non-opaque color +struct MOZ_STACK_CLASS BufferAlphaColor { + explicit BufferAlphaColor(gfxContext* aContext) : mContext(aContext) {} + + ~BufferAlphaColor() = default; + + void PushSolidColor(const gfxRect& aBounds, const DeviceColor& aAlphaColor, + uint32_t appsPerDevUnit) { + mContext->Save(); + mContext->SnappedClip(gfxRect( + aBounds.X() / appsPerDevUnit, aBounds.Y() / appsPerDevUnit, + aBounds.Width() / appsPerDevUnit, aBounds.Height() / appsPerDevUnit)); + mContext->SetDeviceColor( + DeviceColor(aAlphaColor.r, aAlphaColor.g, aAlphaColor.b)); + mContext->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, aAlphaColor.a); + } + + void PopAlpha() { + // pop the text, using the color alpha as the opacity + mContext->PopGroupAndBlend(); + mContext->Restore(); + } + + gfxContext* mContext; +}; + +void gfxTextRun::Draw(const Range aRange, const gfx::Point aPt, + const DrawParams& aParams) const { + NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range"); + NS_ASSERTION(aParams.drawMode == DrawMode::GLYPH_PATH || + !(aParams.drawMode & DrawMode::GLYPH_PATH), + "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or " + "GLYPH_STROKE_UNDERNEATH"); + NS_ASSERTION(aParams.drawMode == DrawMode::GLYPH_PATH || !aParams.callbacks, + "callback must not be specified unless using GLYPH_PATH"); + + bool skipDrawing = + !mDontSkipDrawing && (mFontGroup ? mFontGroup->ShouldSkipDrawing() + : mReleasedFontGroupSkippedDrawing); + auto* textDrawer = aParams.context->GetTextDrawer(); + if (aParams.drawMode & DrawMode::GLYPH_FILL) { + DeviceColor currentColor; + if (aParams.context->GetDeviceColor(currentColor) && currentColor.a == 0 && + !textDrawer) { + skipDrawing = true; + } + } + + gfxFloat direction = GetDirection(); + + if (skipDrawing) { + // We don't need to draw anything; + // but if the caller wants advance width, we need to compute it here + if (aParams.advanceWidth) { + gfxTextRun::Metrics metrics = + MeasureText(aRange, gfxFont::LOOSE_INK_EXTENTS, + aParams.context->GetDrawTarget(), aParams.provider); + *aParams.advanceWidth = metrics.mAdvanceWidth * direction; + } + + // return without drawing + return; + } + + // synthetic bolding draws glyphs twice ==> colors with opacity won't draw + // correctly unless first drawn without alpha + BufferAlphaColor syntheticBoldBuffer(aParams.context); + DeviceColor currentColor; + bool mayNeedBuffering = + aParams.drawMode & DrawMode::GLYPH_FILL && + aParams.context->HasNonOpaqueNonTransparentColor(currentColor) && + !textDrawer; + + // If we need to double-buffer, we'll need to measure the text first to + // get the bounds of the area of interest. Ideally we'd do that just for + // the specific glyph run(s) that need buffering, but because of bug + // 1612610 we currently use the extent of the entire range even when + // just buffering a subrange. So we'll measure the full range once and + // keep the metrics on hand for any subsequent subranges. + gfxTextRun::Metrics metrics; + bool gotMetrics = false; + + // Set up parameters that will be constant across all glyph runs we need + // to draw, regardless of the font used. + TextRunDrawParams params(aParams.paletteCache); + params.context = aParams.context; + params.devPerApp = 1.0 / double(GetAppUnitsPerDevUnit()); + params.isVerticalRun = IsVertical(); + params.isRTL = IsRightToLeft(); + params.direction = direction; + params.strokeOpts = aParams.strokeOpts; + params.textStrokeColor = aParams.textStrokeColor; + params.fontPalette = aParams.fontPalette; + params.textStrokePattern = aParams.textStrokePattern; + params.drawOpts = aParams.drawOpts; + params.drawMode = aParams.drawMode; + params.hasTextShadow = aParams.hasTextShadow; + params.callbacks = aParams.callbacks; + params.runContextPaint = aParams.contextPaint; + params.paintSVGGlyphs = + !aParams.callbacks || aParams.callbacks->mShouldPaintSVGGlyphs; + params.dt = aParams.context->GetDrawTarget(); + params.textDrawer = textDrawer; + if (textDrawer) { + params.clipRect = textDrawer->GeckoClipRect(); + } + params.allowGDI = aParams.allowGDI; + + gfxFloat advance = 0.0; + gfx::Point pt = aPt; + + for (GlyphRunIterator iter(this, aRange); !iter.AtEnd(); iter.NextRun()) { + gfxFont* font = iter.GlyphRun()->mFont; + Range runRange(iter.StringStart(), iter.StringEnd()); + + bool needToRestore = false; + if (mayNeedBuffering && HasSyntheticBoldOrColor(font)) { + needToRestore = true; + if (!gotMetrics) { + // Measure text; use the bounding box to determine the area we need + // to buffer. We measure the entire range, rather than just the glyph + // run that we're actually handling, because of bug 1612610: if the + // bounding box passed to PushSolidColor does not intersect the + // drawTarget's current clip, the skia backend fails to clip properly. + // This means we may use a larger buffer than actually needed, but is + // otherwise harmless. + metrics = MeasureText(aRange, gfxFont::LOOSE_INK_EXTENTS, params.dt, + aParams.provider); + if (IsRightToLeft()) { + metrics.mBoundingBox.MoveBy( + gfxPoint(aPt.x - metrics.mAdvanceWidth, aPt.y)); + } else { + metrics.mBoundingBox.MoveBy(gfxPoint(aPt.x, aPt.y)); + } + gotMetrics = true; + } + syntheticBoldBuffer.PushSolidColor(metrics.mBoundingBox, currentColor, + GetAppUnitsPerDevUnit()); + } + + Range ligatureRange(runRange); + bool adjusted = ShrinkToLigatureBoundaries(&ligatureRange); + + bool drawPartial = + adjusted && + ((aParams.drawMode & (DrawMode::GLYPH_FILL | DrawMode::GLYPH_STROKE)) || + (aParams.drawMode == DrawMode::GLYPH_PATH && aParams.callbacks)); + gfx::Point origPt = pt; + + if (drawPartial) { + DrawPartialLigature(font, Range(runRange.start, ligatureRange.start), &pt, + aParams.provider, params, + iter.GlyphRun()->mOrientation); + } + + DrawGlyphs(font, ligatureRange, &pt, aParams.provider, ligatureRange, + params, iter.GlyphRun()->mOrientation); + + if (drawPartial) { + DrawPartialLigature(font, Range(ligatureRange.end, runRange.end), &pt, + aParams.provider, params, + iter.GlyphRun()->mOrientation); + } + + if (params.isVerticalRun) { + advance += (pt.y - origPt.y) * params.direction; + } else { + advance += (pt.x - origPt.x) * params.direction; + } + + // composite result when synthetic bolding used + if (needToRestore) { + syntheticBoldBuffer.PopAlpha(); + } + } + + if (aParams.advanceWidth) { + *aParams.advanceWidth = advance; + } +} + +// This method is mostly parallel to Draw(). +void gfxTextRun::DrawEmphasisMarks( + gfxContext* aContext, gfxTextRun* aMark, gfxFloat aMarkAdvance, + gfx::Point aPt, Range aRange, const PropertyProvider* aProvider, + mozilla::gfx::PaletteCache& aPaletteCache) const { + MOZ_ASSERT(aRange.end <= GetLength()); + + EmphasisMarkDrawParams params(aContext, aPaletteCache); + params.mark = aMark; + params.advance = aMarkAdvance; + params.direction = GetDirection(); + params.isVertical = IsVertical(); + + float& inlineCoord = params.isVertical ? aPt.y.value : aPt.x.value; + float direction = params.direction; + + for (GlyphRunIterator iter(this, aRange); !iter.AtEnd(); iter.NextRun()) { + gfxFont* font = iter.GlyphRun()->mFont; + uint32_t start = iter.StringStart(); + uint32_t end = iter.StringEnd(); + Range ligatureRange(start, end); + bool adjusted = ShrinkToLigatureBoundaries(&ligatureRange); + + if (adjusted) { + inlineCoord += + direction * ComputePartialLigatureWidth( + Range(start, ligatureRange.start), aProvider); + } + + AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer; + bool haveSpacing = GetAdjustedSpacingArray(ligatureRange, aProvider, + ligatureRange, &spacingBuffer); + params.spacing = haveSpacing ? spacingBuffer.Elements() : nullptr; + font->DrawEmphasisMarks(this, &aPt, ligatureRange.start, + ligatureRange.Length(), params); + + if (adjusted) { + inlineCoord += direction * ComputePartialLigatureWidth( + Range(ligatureRange.end, end), aProvider); + } + } +} + +void gfxTextRun::AccumulateMetricsForRun( + gfxFont* aFont, Range aRange, gfxFont::BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, const PropertyProvider* aProvider, + Range aSpacingRange, gfx::ShapedTextFlags aOrientation, + Metrics* aMetrics) const { + AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer; + bool haveSpacing = + GetAdjustedSpacingArray(aRange, aProvider, aSpacingRange, &spacingBuffer); + Metrics metrics = aFont->Measure( + this, aRange.start, aRange.end, aBoundingBoxType, aRefDrawTarget, + haveSpacing ? spacingBuffer.Elements() : nullptr, aOrientation); + aMetrics->CombineWith(metrics, IsRightToLeft()); +} + +void gfxTextRun::AccumulatePartialLigatureMetrics( + gfxFont* aFont, Range aRange, gfxFont::BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, const PropertyProvider* aProvider, + gfx::ShapedTextFlags aOrientation, Metrics* aMetrics) const { + if (aRange.start >= aRange.end) return; + + // Measure partial ligature. We hack this by clipping the metrics in the + // same way we clip the drawing. + LigatureData data = ComputeLigatureData(aRange, aProvider); + + // First measure the complete ligature + Metrics metrics; + AccumulateMetricsForRun(aFont, data.mRange, aBoundingBoxType, aRefDrawTarget, + aProvider, aRange, aOrientation, &metrics); + + // Clip the bounding box to the ligature part + gfxFloat bboxLeft = metrics.mBoundingBox.X(); + gfxFloat bboxRight = metrics.mBoundingBox.XMost(); + // Where we are going to start "drawing" relative to our left baseline origin + gfxFloat origin = + IsRightToLeft() ? metrics.mAdvanceWidth - data.mPartAdvance : 0; + ClipPartialLigature(this, &bboxLeft, &bboxRight, origin, &data); + metrics.mBoundingBox.SetBoxX(bboxLeft, bboxRight); + + // mBoundingBox is now relative to the left baseline origin for the entire + // ligature. Shift it left. + metrics.mBoundingBox.MoveByX( + -(IsRightToLeft() + ? metrics.mAdvanceWidth - (data.mPartAdvance + data.mPartWidth) + : data.mPartAdvance)); + metrics.mAdvanceWidth = data.mPartWidth; + + aMetrics->CombineWith(metrics, IsRightToLeft()); +} + +gfxTextRun::Metrics gfxTextRun::MeasureText( + Range aRange, gfxFont::BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, const PropertyProvider* aProvider) const { + NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range"); + + Metrics accumulatedMetrics; + for (GlyphRunIterator iter(this, aRange); !iter.AtEnd(); iter.NextRun()) { + gfxFont* font = iter.GlyphRun()->mFont; + uint32_t start = iter.StringStart(); + uint32_t end = iter.StringEnd(); + Range ligatureRange(start, end); + bool adjusted = ShrinkToLigatureBoundaries(&ligatureRange); + + if (adjusted) { + AccumulatePartialLigatureMetrics(font, Range(start, ligatureRange.start), + aBoundingBoxType, aRefDrawTarget, + aProvider, iter.GlyphRun()->mOrientation, + &accumulatedMetrics); + } + + // XXX This sucks. We have to get glyph extents just so we can detect + // glyphs outside the font box, even when aBoundingBoxType is LOOSE, + // even though in almost all cases we could get correct results just + // by getting some ascent/descent from the font and using our stored + // advance widths. + AccumulateMetricsForRun(font, ligatureRange, aBoundingBoxType, + aRefDrawTarget, aProvider, ligatureRange, + iter.GlyphRun()->mOrientation, &accumulatedMetrics); + + if (adjusted) { + AccumulatePartialLigatureMetrics( + font, Range(ligatureRange.end, end), aBoundingBoxType, aRefDrawTarget, + aProvider, iter.GlyphRun()->mOrientation, &accumulatedMetrics); + } + } + + return accumulatedMetrics; +} + +void gfxTextRun::GetLineHeightMetrics(Range aRange, gfxFloat& aAscent, + gfxFloat& aDescent) const { + Metrics accumulatedMetrics; + for (GlyphRunIterator iter(this, aRange); !iter.AtEnd(); iter.NextRun()) { + gfxFont* font = iter.GlyphRun()->mFont; + auto metrics = + font->Measure(this, 0, 0, gfxFont::LOOSE_INK_EXTENTS, nullptr, nullptr, + iter.GlyphRun()->mOrientation); + accumulatedMetrics.CombineWith(metrics, false); + } + aAscent = accumulatedMetrics.mAscent; + aDescent = accumulatedMetrics.mDescent; +} + +#define MEASUREMENT_BUFFER_SIZE 100 + +void gfxTextRun::ClassifyAutoHyphenations(uint32_t aStart, Range aRange, + nsTArray<HyphenType>& aHyphenBuffer, + HyphenationState* aWordState) { + MOZ_ASSERT( + aRange.end - aStart <= aHyphenBuffer.Length() && aRange.start >= aStart, + "Range out of bounds"); + MOZ_ASSERT(aWordState->mostRecentBoundary >= aStart, + "Unexpected aMostRecentWordBoundary!!"); + + uint32_t start = + std::min<uint32_t>(aRange.start, aWordState->mostRecentBoundary); + + for (uint32_t i = start; i < aRange.end; ++i) { + if (aHyphenBuffer[i - aStart] == HyphenType::Explicit && + !aWordState->hasExplicitHyphen) { + aWordState->hasExplicitHyphen = true; + } + if (!aWordState->hasManualHyphen && + (aHyphenBuffer[i - aStart] == HyphenType::Soft || + aHyphenBuffer[i - aStart] == HyphenType::Explicit)) { + aWordState->hasManualHyphen = true; + // This is the first manual hyphen in the current word. We can only + // know if the current word has a manual hyphen until now. So, we need + // to run a sub loop to update the auto hyphens between the start of + // the current word and this manual hyphen. + if (aWordState->hasAutoHyphen) { + for (uint32_t j = aWordState->mostRecentBoundary; j < i; j++) { + if (aHyphenBuffer[j - aStart] == + HyphenType::AutoWithoutManualInSameWord) { + aHyphenBuffer[j - aStart] = HyphenType::AutoWithManualInSameWord; + } + } + } + } + if (aHyphenBuffer[i - aStart] == HyphenType::AutoWithoutManualInSameWord) { + if (!aWordState->hasAutoHyphen) { + aWordState->hasAutoHyphen = true; + } + if (aWordState->hasManualHyphen) { + aHyphenBuffer[i - aStart] = HyphenType::AutoWithManualInSameWord; + } + } + + // If we're at the word boundary, clear/reset couple states. + if (mCharacterGlyphs[i].CharIsSpace() || mCharacterGlyphs[i].CharIsTab() || + mCharacterGlyphs[i].CharIsNewline() || + // Since we will not have a boundary in the end of the string, let's + // call the end of the string a special case for word boundary. + i == GetLength() - 1) { + // We can only get to know whether we should raise/clear an explicit + // manual hyphen until we get to the end of a word, because this depends + // on whether there exists at least one auto hyphen in the same word. + if (!aWordState->hasAutoHyphen && aWordState->hasExplicitHyphen) { + for (uint32_t j = aWordState->mostRecentBoundary; j <= i; j++) { + if (aHyphenBuffer[j - aStart] == HyphenType::Explicit) { + aHyphenBuffer[j - aStart] = HyphenType::None; + } + } + } + aWordState->mostRecentBoundary = i; + aWordState->hasManualHyphen = false; + aWordState->hasAutoHyphen = false; + aWordState->hasExplicitHyphen = false; + } + } +} + +uint32_t gfxTextRun::BreakAndMeasureText( + uint32_t aStart, uint32_t aMaxLength, bool aLineBreakBefore, + gfxFloat aWidth, const PropertyProvider& aProvider, + SuppressBreak aSuppressBreak, gfxFont::BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, bool aCanWordWrap, bool aCanWhitespaceWrap, + bool aIsBreakSpaces, + // output params: + TrimmableWS* aOutTrimmableWhitespace, Metrics& aOutMetrics, + bool& aOutUsedHyphenation, uint32_t& aOutLastBreak, + gfxBreakPriority& aBreakPriority) { + aMaxLength = std::min(aMaxLength, GetLength() - aStart); + + NS_ASSERTION(aStart + aMaxLength <= GetLength(), "Substring out of range"); + + Range bufferRange( + aStart, aStart + std::min<uint32_t>(aMaxLength, MEASUREMENT_BUFFER_SIZE)); + PropertyProvider::Spacing spacingBuffer[MEASUREMENT_BUFFER_SIZE]; + bool haveSpacing = !!(mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING); + if (haveSpacing) { + GetAdjustedSpacing(this, bufferRange, aProvider, spacingBuffer); + } + AutoTArray<HyphenType, 4096> hyphenBuffer; + HyphenationState wordState; + wordState.mostRecentBoundary = aStart; + bool haveHyphenation = + (aProvider.GetHyphensOption() == StyleHyphens::Auto || + (aProvider.GetHyphensOption() == StyleHyphens::Manual && + !!(mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS))); + if (haveHyphenation) { + if (hyphenBuffer.AppendElements(bufferRange.Length(), fallible)) { + aProvider.GetHyphenationBreaks(bufferRange, hyphenBuffer.Elements()); + if (aProvider.GetHyphensOption() == StyleHyphens::Auto) { + ClassifyAutoHyphenations(aStart, bufferRange, hyphenBuffer, &wordState); + } + } else { + haveHyphenation = false; + } + } + + gfxFloat width = 0; + gfxFloat advance = 0; + // The number of space characters that can be trimmed or hang at a soft-wrap + uint32_t trimmableChars = 0; + // The amount of space removed by ignoring trimmableChars + gfxFloat trimmableAdvance = 0; + int32_t lastBreak = -1; + int32_t lastBreakTrimmableChars = -1; + gfxFloat lastBreakTrimmableAdvance = -1; + // Cache the last candidate break + int32_t lastCandidateBreak = -1; + int32_t lastCandidateBreakTrimmableChars = -1; + gfxFloat lastCandidateBreakTrimmableAdvance = -1; + bool lastCandidateBreakUsedHyphenation = false; + gfxBreakPriority lastCandidateBreakPriority = gfxBreakPriority::eNoBreak; + bool aborted = false; + uint32_t end = aStart + aMaxLength; + bool lastBreakUsedHyphenation = false; + Range ligatureRange(aStart, end); + ShrinkToLigatureBoundaries(&ligatureRange); + + // We may need to move `i` backwards in the following loop, and re-scan + // part of the textrun; we'll use `rescanLimit` so we can tell when that + // is happening: if `i < rescanLimit` then we're rescanning. + uint32_t rescanLimit = aStart; + for (uint32_t i = aStart; i < end; ++i) { + if (i >= bufferRange.end) { + // Fetch more spacing and hyphenation data + uint32_t oldHyphenBufferLength = hyphenBuffer.Length(); + bufferRange.start = i; + bufferRange.end = + std::min(aStart + aMaxLength, i + MEASUREMENT_BUFFER_SIZE); + // For spacing, we always overwrite the old data with the newly + // fetched one. However, for hyphenation, hyphenation data sometimes + // depends on the context in every word (if "hyphens: auto" is set). + // To ensure we get enough information between neighboring buffers, + // we grow the hyphenBuffer instead of overwrite it. + // NOTE that this means bufferRange does not correspond to the + // entire hyphenBuffer, but only to the most recently added portion. + // Therefore, we need to add the old length to hyphenBuffer.Elements() + // when getting more data. + if (haveSpacing) { + GetAdjustedSpacing(this, bufferRange, aProvider, spacingBuffer); + } + if (haveHyphenation) { + if (hyphenBuffer.AppendElements(bufferRange.Length(), fallible)) { + aProvider.GetHyphenationBreaks( + bufferRange, hyphenBuffer.Elements() + oldHyphenBufferLength); + if (aProvider.GetHyphensOption() == StyleHyphens::Auto) { + uint32_t prevMostRecentWordBoundary = wordState.mostRecentBoundary; + ClassifyAutoHyphenations(aStart, bufferRange, hyphenBuffer, + &wordState); + // If the buffer boundary is in the middle of a word, + // we need to go back to the start of the current word. + // So, we can correct the wrong candidates that we set + // in the previous runs of the loop. + if (prevMostRecentWordBoundary < oldHyphenBufferLength) { + rescanLimit = i; + i = prevMostRecentWordBoundary - 1; + continue; + } + } + } else { + haveHyphenation = false; + } + } + } + + // There can't be a word-wrap break opportunity at the beginning of the + // line: if the width is too small for even one character to fit, it + // could be the first and last break opportunity on the line, and that + // would trigger an infinite loop. + if (aSuppressBreak != eSuppressAllBreaks && + (aSuppressBreak != eSuppressInitialBreak || i > aStart)) { + bool atNaturalBreak = mCharacterGlyphs[i].CanBreakBefore() == + CompressedGlyph::FLAG_BREAK_TYPE_NORMAL; + // atHyphenationBreak indicates we're at a "soft" hyphen, where an extra + // hyphen glyph will need to be painted. It is NOT set for breaks at an + // explicit hyphen present in the text. + // + // NOTE(emilio): If you change this condition you also need to change + // nsTextFrame::AddInlineMinISizeForFlow to match. + bool atHyphenationBreak = !atNaturalBreak && haveHyphenation && + IsOptionalHyphenBreak(hyphenBuffer[i - aStart]); + bool atAutoHyphenWithManualHyphenInSameWord = + atHyphenationBreak && + hyphenBuffer[i - aStart] == HyphenType::AutoWithManualInSameWord; + bool atBreak = atNaturalBreak || atHyphenationBreak; + bool wordWrapping = + (aCanWordWrap || + (aCanWhitespaceWrap && + mCharacterGlyphs[i].CanBreakBefore() == + CompressedGlyph::FLAG_BREAK_TYPE_EMERGENCY_WRAP)) && + 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 = + aIsBreakSpaces && + (g.CharIsSpace() || g.CharIsTab() || g.CharIsNewline()); + } + + if (atBreak || wordWrapping || whitespaceWrapping) { + gfxFloat hyphenatedAdvance = advance; + if (atHyphenationBreak) { + hyphenatedAdvance += aProvider.GetHyphenWidth(); + } + + if (lastBreak < 0 || + width + hyphenatedAdvance - trimmableAdvance <= aWidth) { + // We can break here. + lastBreak = i; + lastBreakTrimmableChars = trimmableChars; + lastBreakTrimmableAdvance = trimmableAdvance; + lastBreakUsedHyphenation = atHyphenationBreak; + aBreakPriority = (atBreak || whitespaceWrapping) + ? gfxBreakPriority::eNormalBreak + : gfxBreakPriority::eWordWrapBreak; + } + + width += advance; + advance = 0; + if (width - trimmableAdvance > aWidth) { + // No more text fits. Abort + aborted = true; + break; + } + // There are various kinds of break opportunities: + // 1. word wrap break, + // 2. natural break, + // 3. manual hyphenation break, + // 4. auto hyphenation break without any manual hyphenation + // in the same word, + // 5. auto hyphenation break with another manual hyphenation + // in the same word. + // Allow all of them except the last one to be a candidate. + // So, we can ensure that we don't use an automatic + // hyphenation opportunity within a word that contains another + // manual hyphenation, unless it is the only choice. + if (wordWrapping || !atAutoHyphenWithManualHyphenInSameWord) { + lastCandidateBreak = lastBreak; + lastCandidateBreakTrimmableChars = lastBreakTrimmableChars; + lastCandidateBreakTrimmableAdvance = lastBreakTrimmableAdvance; + lastCandidateBreakUsedHyphenation = lastBreakUsedHyphenation; + lastCandidateBreakPriority = aBreakPriority; + } + } + } + + // If we're re-scanning part of a word (to re-process potential + // hyphenation types) then we don't want to accumulate widths again + // for the characters that were already added to `advance`. + if (i < rescanLimit) { + continue; + } + + gfxFloat charAdvance; + if (i >= ligatureRange.start && i < ligatureRange.end) { + charAdvance = GetAdvanceForGlyphs(Range(i, i + 1)); + if (haveSpacing) { + PropertyProvider::Spacing* space = + &spacingBuffer[i - bufferRange.start]; + charAdvance += space->mBefore + space->mAfter; + } + } else { + charAdvance = ComputePartialLigatureWidth(Range(i, i + 1), &aProvider); + } + + advance += charAdvance; + if (aOutTrimmableWhitespace) { + if (mCharacterGlyphs[i].CharIsSpace()) { + ++trimmableChars; + trimmableAdvance += charAdvance; + } else { + trimmableAdvance = 0; + trimmableChars = 0; + } + } + } + + if (!aborted) { + width += advance; + } + + // There are three possibilities: + // 1) all the text fit (width <= aWidth) + // 2) some of the text fit up to a break opportunity (width > aWidth && + // lastBreak >= 0) + // 3) none of the text fits before a break opportunity (width > aWidth && + // lastBreak < 0) + uint32_t charsFit; + aOutUsedHyphenation = false; + if (width - trimmableAdvance <= aWidth) { + charsFit = aMaxLength; + } else if (lastBreak >= 0) { + if (lastCandidateBreak >= 0 && lastCandidateBreak != lastBreak) { + lastBreak = lastCandidateBreak; + lastBreakTrimmableChars = lastCandidateBreakTrimmableChars; + lastBreakTrimmableAdvance = lastCandidateBreakTrimmableAdvance; + lastBreakUsedHyphenation = lastCandidateBreakUsedHyphenation; + aBreakPriority = lastCandidateBreakPriority; + } + charsFit = lastBreak - aStart; + trimmableChars = lastBreakTrimmableChars; + trimmableAdvance = lastBreakTrimmableAdvance; + aOutUsedHyphenation = lastBreakUsedHyphenation; + } else { + charsFit = aMaxLength; + } + + // Get the overall metrics of the range that fit (including any potentially + // trimmable or hanging whitespace). + aOutMetrics = MeasureText(Range(aStart, aStart + charsFit), aBoundingBoxType, + aRefDrawTarget, &aProvider); + + if (aOutTrimmableWhitespace) { + aOutTrimmableWhitespace->mAdvance = trimmableAdvance; + aOutTrimmableWhitespace->mCount = trimmableChars; + } + + if (charsFit == aMaxLength) { + if (lastBreak < 0) { + aOutLastBreak = UINT32_MAX; + } else { + aOutLastBreak = lastBreak - aStart; + } + } + + return charsFit; +} + +gfxFloat gfxTextRun::GetAdvanceWidth( + Range aRange, const PropertyProvider* aProvider, + PropertyProvider::Spacing* aSpacing) const { + NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range"); + + Range ligatureRange = aRange; + bool adjusted = ShrinkToLigatureBoundaries(&ligatureRange); + + gfxFloat result = + adjusted ? ComputePartialLigatureWidth( + Range(aRange.start, ligatureRange.start), aProvider) + + ComputePartialLigatureWidth( + Range(ligatureRange.end, aRange.end), aProvider) + : 0.0; + + if (aSpacing) { + aSpacing->mBefore = aSpacing->mAfter = 0; + } + + // Account for all remaining spacing here. This is more efficient than + // processing it along with the glyphs. + if (aProvider && (mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING)) { + uint32_t i; + AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer; + if (spacingBuffer.AppendElements(aRange.Length(), fallible)) { + GetAdjustedSpacing(this, ligatureRange, *aProvider, + spacingBuffer.Elements()); + for (i = 0; i < ligatureRange.Length(); ++i) { + PropertyProvider::Spacing* space = &spacingBuffer[i]; + result += space->mBefore + space->mAfter; + } + if (aSpacing) { + aSpacing->mBefore = spacingBuffer[0].mBefore; + aSpacing->mAfter = spacingBuffer.LastElement().mAfter; + } + } + } + + return result + GetAdvanceForGlyphs(ligatureRange); +} + +gfxFloat gfxTextRun::GetMinAdvanceWidth(Range aRange) { + MOZ_ASSERT(aRange.end <= GetLength(), "Substring out of range"); + + Range ligatureRange = aRange; + bool adjusted = ShrinkToLigatureBoundaries(&ligatureRange); + + gfxFloat result = + adjusted + ? std::max(ComputePartialLigatureWidth( + Range(aRange.start, ligatureRange.start), nullptr), + ComputePartialLigatureWidth( + Range(ligatureRange.end, aRange.end), nullptr)) + : 0.0; + + // Compute min advance width by assuming each grapheme cluster takes its own + // line. + gfxFloat clusterAdvance = 0; + for (uint32_t i = ligatureRange.start; i < ligatureRange.end; ++i) { + if (mCharacterGlyphs[i].CharIsSpace()) { + // Skip space char to prevent its advance width contributing to the + // result. That is, don't consider a space can be in its own line. + continue; + } + clusterAdvance += GetAdvanceForGlyph(i); + if (i + 1 == ligatureRange.end || IsClusterStart(i + 1)) { + result = std::max(result, clusterAdvance); + clusterAdvance = 0; + } + } + + return result; +} + +bool gfxTextRun::SetLineBreaks(Range aRange, bool aLineBreakBefore, + bool aLineBreakAfter, + gfxFloat* aAdvanceWidthDelta) { + // Do nothing because our shaping does not currently take linebreaks into + // account. There is no change in advance width. + if (aAdvanceWidthDelta) { + *aAdvanceWidthDelta = 0; + } + return false; +} + +const gfxTextRun::GlyphRun* gfxTextRun::FindFirstGlyphRunContaining( + uint32_t aOffset) const { + MOZ_ASSERT(aOffset <= GetLength(), "Bad offset looking for glyphrun"); + MOZ_ASSERT(GetLength() == 0 || !mGlyphRuns.IsEmpty(), + "non-empty text but no glyph runs present!"); + if (mGlyphRuns.Length() <= 1) { + return mGlyphRuns.begin(); + } + if (aOffset == GetLength()) { + return mGlyphRuns.end() - 1; + } + const auto* start = mGlyphRuns.begin(); + const auto* limit = mGlyphRuns.end(); + while (limit - start > 1) { + const auto* mid = start + (limit - start) / 2; + if (mid->mCharacterOffset <= aOffset) { + start = mid; + } else { + limit = mid; + } + } + MOZ_ASSERT(start->mCharacterOffset <= aOffset, + "Hmm, something went wrong, aOffset should have been found"); + return start; +} + +void gfxTextRun::AddGlyphRun(gfxFont* aFont, FontMatchType aMatchType, + uint32_t aUTF16Offset, bool aForceNewRun, + gfx::ShapedTextFlags aOrientation, bool aIsCJK) { + MOZ_ASSERT(aFont, "adding glyph run for null font!"); + MOZ_ASSERT(aOrientation != gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED, + "mixed orientation should have been resolved"); + if (!aFont) { + return; + } + + if (mGlyphRuns.IsEmpty()) { + mGlyphRuns.AppendElement( + GlyphRun{aFont, aUTF16Offset, aOrientation, aMatchType, aIsCJK}); + return; + } + + uint32_t numGlyphRuns = mGlyphRuns.Length(); + if (!aForceNewRun) { + GlyphRun* lastGlyphRun = &mGlyphRuns.LastElement(); + + MOZ_ASSERT(lastGlyphRun->mCharacterOffset <= aUTF16Offset, + "Glyph runs out of order (and run not forced)"); + + // Don't append a run if the font is already the one we want + if (lastGlyphRun->Matches(aFont, aOrientation, aIsCJK, aMatchType)) { + return; + } + + // If the offset has not changed, avoid leaving a zero-length run + // by overwriting the last entry instead of appending... + if (lastGlyphRun->mCharacterOffset == aUTF16Offset) { + // ...except that if the run before the last entry had the same + // font as the new one wants, merge with it instead of creating + // adjacent runs with the same font + if (numGlyphRuns > 1 && mGlyphRuns[numGlyphRuns - 2].Matches( + aFont, aOrientation, aIsCJK, aMatchType)) { + mGlyphRuns.TruncateLength(numGlyphRuns - 1); + return; + } + + lastGlyphRun->SetProperties(aFont, aOrientation, aIsCJK, aMatchType); + return; + } + } + + MOZ_ASSERT( + aForceNewRun || numGlyphRuns > 0 || aUTF16Offset == 0, + "First run doesn't cover the first character (and run not forced)?"); + + mGlyphRuns.AppendElement( + GlyphRun{aFont, aUTF16Offset, aOrientation, aMatchType, aIsCJK}); +} + +void gfxTextRun::SanitizeGlyphRuns() { + if (mGlyphRuns.Length() < 2) { + return; + } + + auto& runs = mGlyphRuns.Array(); + + // The runs are almost certain to be already sorted, so it's worth avoiding + // the Sort() call if possible. + bool isSorted = true; + uint32_t prevOffset = 0; + for (const auto& r : runs) { + if (r.mCharacterOffset < prevOffset) { + isSorted = false; + break; + } + prevOffset = r.mCharacterOffset; + } + if (!isSorted) { + runs.Sort(GlyphRunOffsetComparator()); + } + + // Coalesce adjacent glyph runs that have the same properties, and eliminate + // any empty runs. + GlyphRun* prevRun = nullptr; + const CompressedGlyph* charGlyphs = mCharacterGlyphs; + + runs.RemoveElementsBy([&](GlyphRun& aRun) -> bool { + // First run is always retained. + if (!prevRun) { + prevRun = &aRun; + return false; + } + + // Merge any run whose properties match its predecessor. + if (prevRun->Matches(aRun.mFont, aRun.mOrientation, aRun.mIsCJK, + aRun.mMatchType)) { + return true; + } + + if (prevRun->mCharacterOffset >= aRun.mCharacterOffset) { + // Preceding run is empty (or has become so due to the adjusting for + // ligature boundaries), so we will overwrite it with this one, which + // will then be discarded. + *prevRun = aRun; + return true; + } + + // If any glyph run starts with ligature-continuation characters, we need to + // advance it to the first "real" character to avoid drawing partial + // ligature glyphs from wrong font (seen with U+FEFF in reftest 474417-1, as + // Core Text eliminates the glyph, which makes it appear as if a ligature + // has been formed) + while (charGlyphs[aRun.mCharacterOffset].IsLigatureContinuation() && + aRun.mCharacterOffset < GetLength()) { + aRun.mCharacterOffset++; + } + + // We're keeping another run, so update prevRun pointer to refer to it (in + // its new position). + ++prevRun; + return false; + }); + + MOZ_ASSERT(prevRun == &runs.LastElement(), "lost track of prevRun!"); + + // Drop any trailing empty run. + if (runs.Length() > 1 && prevRun->mCharacterOffset == GetLength()) { + runs.RemoveLastElement(); + } + + MOZ_ASSERT(!runs.IsEmpty()); + if (runs.Length() == 1) { + mGlyphRuns.ConvertToElement(); + } +} + +void gfxTextRun::CopyGlyphDataFrom(gfxShapedWord* aShapedWord, + uint32_t aOffset) { + uint32_t wordLen = aShapedWord->GetLength(); + MOZ_ASSERT(aOffset + wordLen <= GetLength(), "word overruns end of textrun"); + + CompressedGlyph* charGlyphs = GetCharacterGlyphs(); + const CompressedGlyph* wordGlyphs = aShapedWord->GetCharacterGlyphs(); + if (aShapedWord->HasDetailedGlyphs()) { + for (uint32_t i = 0; i < wordLen; ++i, ++aOffset) { + const CompressedGlyph& g = wordGlyphs[i]; + if (!g.IsSimpleGlyph()) { + const DetailedGlyph* details = + g.GetGlyphCount() > 0 ? aShapedWord->GetDetailedGlyphs(i) : nullptr; + SetDetailedGlyphs(aOffset, g.GetGlyphCount(), details); + } + charGlyphs[aOffset] = g; + } + } else { + memcpy(charGlyphs + aOffset, wordGlyphs, wordLen * sizeof(CompressedGlyph)); + } +} + +void gfxTextRun::CopyGlyphDataFrom(gfxTextRun* aSource, Range aRange, + uint32_t aDest) { + MOZ_ASSERT(aRange.end <= aSource->GetLength(), + "Source substring out of range"); + MOZ_ASSERT(aDest + aRange.Length() <= GetLength(), + "Destination substring out of range"); + + if (aSource->mDontSkipDrawing) { + mDontSkipDrawing = true; + } + + // Copy base glyph data, and DetailedGlyph data where present + const CompressedGlyph* srcGlyphs = aSource->mCharacterGlyphs + aRange.start; + CompressedGlyph* dstGlyphs = mCharacterGlyphs + aDest; + for (uint32_t i = 0; i < aRange.Length(); ++i) { + CompressedGlyph g = srcGlyphs[i]; + g.SetCanBreakBefore(!g.IsClusterStart() + ? CompressedGlyph::FLAG_BREAK_TYPE_NONE + : dstGlyphs[i].CanBreakBefore()); + if (!g.IsSimpleGlyph()) { + uint32_t count = g.GetGlyphCount(); + if (count > 0) { + // DetailedGlyphs allocation is infallible, so this should never be + // null unless the source textrun is somehow broken. + DetailedGlyph* src = aSource->GetDetailedGlyphs(i + aRange.start); + MOZ_ASSERT(src, "missing DetailedGlyphs?"); + if (src) { + DetailedGlyph* dst = AllocateDetailedGlyphs(i + aDest, count); + ::memcpy(dst, src, count * sizeof(DetailedGlyph)); + } else { + g.SetMissing(); + } + } + } + dstGlyphs[i] = g; + } + + // Copy glyph runs +#ifdef DEBUG + GlyphRun* prevRun = nullptr; +#endif + for (GlyphRunIterator iter(aSource, aRange); !iter.AtEnd(); iter.NextRun()) { + gfxFont* font = iter.GlyphRun()->mFont; + MOZ_ASSERT(!prevRun || !prevRun->Matches(iter.GlyphRun()->mFont, + iter.GlyphRun()->mOrientation, + iter.GlyphRun()->mIsCJK, + FontMatchType::Kind::kUnspecified), + "Glyphruns not coalesced?"); +#ifdef DEBUG + prevRun = const_cast<GlyphRun*>(iter.GlyphRun()); + uint32_t end = iter.StringEnd(); +#endif + uint32_t start = iter.StringStart(); + + // These used to be NS_ASSERTION()s, but WARNING is more appropriate. + // Although it's unusual (and not desirable), it's possible for us to assign + // different fonts to a base character and a following diacritic. + // Example on OSX 10.5/10.6 with default fonts installed: + // data:text/html,<p style="font-family:helvetica, arial, sans-serif;"> + // &%23x043E;&%23x0486;&%23x20;&%23x043E;&%23x0486; + // This means the rendering of the cluster will probably not be very good, + // but it's the best we can do for now if the specified font only covered + // the initial base character and not its applied marks. + NS_WARNING_ASSERTION(aSource->IsClusterStart(start), + "Started font run in the middle of a cluster"); + NS_WARNING_ASSERTION( + end == aSource->GetLength() || aSource->IsClusterStart(end), + "Ended font run in the middle of a cluster"); + + AddGlyphRun(font, iter.GlyphRun()->mMatchType, start - aRange.start + aDest, + false, iter.GlyphRun()->mOrientation, iter.GlyphRun()->mIsCJK); + } +} + +void gfxTextRun::ClearGlyphsAndCharacters() { + ResetGlyphRuns(); + memset(reinterpret_cast<char*>(mCharacterGlyphs), 0, + mLength * sizeof(CompressedGlyph)); + mDetailedGlyphs = nullptr; +} + +void gfxTextRun::SetSpaceGlyph(gfxFont* aFont, DrawTarget* aDrawTarget, + uint32_t aCharIndex, + gfx::ShapedTextFlags aOrientation) { + if (SetSpaceGlyphIfSimple(aFont, aCharIndex, ' ', aOrientation)) { + return; + } + + gfx::ShapedTextFlags flags = + gfx::ShapedTextFlags::TEXT_IS_8BIT | aOrientation; + bool vertical = + !!(GetFlags() & gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT); + gfxFontShaper::RoundingFlags roundingFlags = + aFont->GetRoundOffsetsToPixels(aDrawTarget); + aFont->ProcessSingleSpaceShapedWord( + aDrawTarget, vertical, mAppUnitsPerDevUnit, flags, roundingFlags, + [&](gfxShapedWord* aShapedWord) { + const GlyphRun* prevRun = TrailingGlyphRun(); + bool isCJK = prevRun && prevRun->mFont == aFont && + prevRun->mOrientation == aOrientation + ? prevRun->mIsCJK + : false; + AddGlyphRun(aFont, FontMatchType::Kind::kUnspecified, aCharIndex, false, + aOrientation, isCJK); + CopyGlyphDataFrom(aShapedWord, aCharIndex); + GetCharacterGlyphs()[aCharIndex].SetIsSpace(); + }); +} + +bool gfxTextRun::SetSpaceGlyphIfSimple(gfxFont* aFont, uint32_t aCharIndex, + char16_t aSpaceChar, + gfx::ShapedTextFlags aOrientation) { + uint32_t spaceGlyph = aFont->GetSpaceGlyph(); + if (!spaceGlyph || !CompressedGlyph::IsSimpleGlyphID(spaceGlyph)) { + return false; + } + + gfxFont::Orientation fontOrientation = + (aOrientation & gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT) + ? nsFontMetrics::eVertical + : nsFontMetrics::eHorizontal; + uint32_t spaceWidthAppUnits = NS_lroundf( + aFont->GetMetrics(fontOrientation).spaceWidth * mAppUnitsPerDevUnit); + if (!CompressedGlyph::IsSimpleAdvance(spaceWidthAppUnits)) { + return false; + } + + const GlyphRun* prevRun = TrailingGlyphRun(); + bool isCJK = prevRun && prevRun->mFont == aFont && + prevRun->mOrientation == aOrientation + ? prevRun->mIsCJK + : false; + AddGlyphRun(aFont, FontMatchType::Kind::kUnspecified, aCharIndex, false, + aOrientation, isCJK); + CompressedGlyph g = + CompressedGlyph::MakeSimpleGlyph(spaceWidthAppUnits, spaceGlyph); + if (aSpaceChar == ' ') { + g.SetIsSpace(); + } + GetCharacterGlyphs()[aCharIndex] = g; + return true; +} + +void gfxTextRun::FetchGlyphExtents(DrawTarget* aRefDrawTarget) const { + bool needsGlyphExtents = NeedsGlyphExtents(); + if (!needsGlyphExtents && !mDetailedGlyphs) { + return; + } + + uint32_t runCount; + const GlyphRun* glyphRuns = GetGlyphRuns(&runCount); + CompressedGlyph* charGlyphs = mCharacterGlyphs; + for (uint32_t i = 0; i < runCount; ++i) { + const GlyphRun& run = glyphRuns[i]; + gfxFont* font = run.mFont; + if (MOZ_UNLIKELY(font->GetStyle()->AdjustedSizeMustBeZero())) { + continue; + } + + uint32_t start = run.mCharacterOffset; + uint32_t end = + i + 1 < runCount ? glyphRuns[i + 1].mCharacterOffset : GetLength(); + gfxGlyphExtents* extents = + font->GetOrCreateGlyphExtents(mAppUnitsPerDevUnit); + + AutoReadLock lock(extents->mLock); + for (uint32_t j = start; j < end; ++j) { + const gfxTextRun::CompressedGlyph* glyphData = &charGlyphs[j]; + if (glyphData->IsSimpleGlyph()) { + // If we're in speed mode, don't set up glyph extents here; we'll + // just return "optimistic" glyph bounds later + if (needsGlyphExtents) { + uint32_t glyphIndex = glyphData->GetSimpleGlyph(); + if (!extents->IsGlyphKnownLocked(glyphIndex)) { +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + ++gGlyphExtentsSetupEagerSimple; +#endif + extents->mLock.ReadUnlock(); + font->SetupGlyphExtents(aRefDrawTarget, glyphIndex, false, extents); + extents->mLock.ReadLock(); + } + } + } else if (!glyphData->IsMissing()) { + uint32_t glyphCount = glyphData->GetGlyphCount(); + if (glyphCount == 0) { + continue; + } + const gfxTextRun::DetailedGlyph* details = GetDetailedGlyphs(j); + if (!details) { + continue; + } + for (uint32_t k = 0; k < glyphCount; ++k, ++details) { + uint32_t glyphIndex = details->mGlyphID; + if (!extents->IsGlyphKnownWithTightExtentsLocked(glyphIndex)) { +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + ++gGlyphExtentsSetupEagerTight; +#endif + extents->mLock.ReadUnlock(); + font->SetupGlyphExtents(aRefDrawTarget, glyphIndex, true, extents); + extents->mLock.ReadLock(); + } + } + } + } + } +} + +size_t gfxTextRun::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) { + size_t total = mGlyphRuns.ShallowSizeOfExcludingThis(aMallocSizeOf); + + if (mDetailedGlyphs) { + total += mDetailedGlyphs->SizeOfIncludingThis(aMallocSizeOf); + } + + return total; +} + +size_t gfxTextRun::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +#ifdef DEBUG_FRAME_DUMP +void gfxTextRun::Dump(FILE* out) { +# define APPEND_FLAG(string_, enum_, field_, flag_) \ + if (field_ & enum_::flag_) { \ + string_.AppendPrintf(remaining != field_ ? " %s" : "%s", #flag_); \ + remaining &= ~enum_::flag_; \ + } +# define APPEND_FLAGS(string_, enum_, field_, flags_) \ + { \ + auto remaining = field_; \ + MOZ_FOR_EACH(APPEND_FLAG, (string_, enum_, field_, ), flags_) \ + if (int(remaining)) { \ + string_.AppendPrintf(" %s(0x%0x)", #enum_, int(remaining)); \ + } \ + } + + nsCString flagsString; + ShapedTextFlags orient = mFlags & ShapedTextFlags::TEXT_ORIENT_MASK; + ShapedTextFlags otherFlags = mFlags & ~ShapedTextFlags::TEXT_ORIENT_MASK; + APPEND_FLAGS(flagsString, ShapedTextFlags, otherFlags, + (TEXT_IS_RTL, TEXT_ENABLE_SPACING, TEXT_IS_8BIT, + TEXT_ENABLE_HYPHEN_BREAKS, TEXT_NEED_BOUNDING_BOX, + TEXT_DISABLE_OPTIONAL_LIGATURES, TEXT_OPTIMIZE_SPEED, + TEXT_HIDE_CONTROL_CHARACTERS, TEXT_TRAILING_ARABICCHAR, + TEXT_INCOMING_ARABICCHAR, TEXT_USE_MATH_SCRIPT)) + + if (orient != ShapedTextFlags::TEXT_ORIENT_HORIZONTAL && + !flagsString.IsEmpty()) { + flagsString += ' '; + } + + switch (orient) { + case ShapedTextFlags::TEXT_ORIENT_HORIZONTAL: + break; + case ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT: + flagsString += "TEXT_ORIENT_VERTICAL_UPRIGHT"; + break; + case ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT: + flagsString += "TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT"; + break; + case ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED: + flagsString += "TEXT_ORIENT_VERTICAL_MIXED"; + break; + case ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT: + flagsString += "TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT"; + break; + default: + flagsString.AppendPrintf("UNKNOWN_TEXT_ORIENT_MASK(0x%0x)", int(orient)); + break; + } + + nsCString flags2String; + APPEND_FLAGS( + flags2String, nsTextFrameUtils::Flags, mFlags2, + (HasTab, HasShy, HasNewline, DontSkipDrawingForPendingUserFonts, + IsSimpleFlow, IncomingWhitespace, TrailingWhitespace, + CompressedLeadingWhitespace, NoBreaks, IsTransformed, HasTrailingBreak, + IsSingleCharMi, MightHaveGlyphChanges, RunSizeAccounted)) + +# undef APPEND_FLAGS +# undef APPEND_FLAG + + nsAutoCString lang; + mFontGroup->Language()->ToUTF8String(lang); + fprintf(out, "gfxTextRun@%p (length %u) [%s] [%s] [%s]\n", this, mLength, + flagsString.get(), flags2String.get(), lang.get()); + + fprintf(out, " Glyph runs:\n"); + for (const auto& run : mGlyphRuns) { + gfxFont* font = run.mFont; + const gfxFontStyle* style = font->GetStyle(); + nsAutoCString styleString; + style->style.ToString(styleString); + fprintf(out, " offset=%d %s %f/%g/%s\n", run.mCharacterOffset, + font->GetName().get(), style->size, style->weight.ToFloat(), + styleString.get()); + } + + fprintf(out, " Glyphs:\n"); + for (uint32_t i = 0; i < mLength; ++i) { + auto glyphData = GetCharacterGlyphs()[i]; + + nsCString line; + line.AppendPrintf(" [%d] 0x%p %s", i, GetCharacterGlyphs() + i, + glyphData.IsSimpleGlyph() ? "simple" : "detailed"); + + if (glyphData.IsSimpleGlyph()) { + line.AppendPrintf(" id=%d adv=%d", glyphData.GetSimpleGlyph(), + glyphData.GetSimpleAdvance()); + } else { + uint32_t count = glyphData.GetGlyphCount(); + if (count) { + line += " ids="; + for (uint32_t j = 0; j < count; j++) { + line.AppendPrintf(j ? ",%d" : "%d", GetDetailedGlyphs(i)[j].mGlyphID); + } + line += " advs="; + for (uint32_t j = 0; j < count; j++) { + line.AppendPrintf(j ? ",%d" : "%d", GetDetailedGlyphs(i)[j].mAdvance); + } + line += " offsets="; + for (uint32_t j = 0; j < count; j++) { + auto offset = GetDetailedGlyphs(i)[j].mOffset; + line.AppendPrintf(j ? ",(%g,%g)" : "(%g,%g)", offset.x.value, + offset.y.value); + } + } else { + line += " (no glyphs)"; + } + } + + if (glyphData.CharIsSpace()) { + line += " CHAR_IS_SPACE"; + } + if (glyphData.CharIsTab()) { + line += " CHAR_IS_TAB"; + } + if (glyphData.CharIsNewline()) { + line += " CHAR_IS_NEWLINE"; + } + if (glyphData.CharIsFormattingControl()) { + line += " CHAR_IS_FORMATTING_CONTROL"; + } + if (glyphData.CharTypeFlags() & + CompressedGlyph::FLAG_CHAR_NO_EMPHASIS_MARK) { + line += " CHAR_NO_EMPHASIS_MARK"; + } + + if (!glyphData.IsSimpleGlyph()) { + if (!glyphData.IsMissing()) { + line += " NOT_MISSING"; + } + if (!glyphData.IsClusterStart()) { + line += " NOT_IS_CLUSTER_START"; + } + if (!glyphData.IsLigatureGroupStart()) { + line += " NOT_LIGATURE_GROUP_START"; + } + } + + switch (glyphData.CanBreakBefore()) { + case CompressedGlyph::FLAG_BREAK_TYPE_NORMAL: + line += " BREAK_TYPE_NORMAL"; + break; + case CompressedGlyph::FLAG_BREAK_TYPE_HYPHEN: + line += " BREAK_TYPE_HYPHEN"; + break; + } + + fprintf(out, "%s\n", line.get()); + } +} +#endif + +gfxFontGroup::gfxFontGroup(nsPresContext* aPresContext, + const StyleFontFamilyList& aFontFamilyList, + const gfxFontStyle* aStyle, nsAtom* aLanguage, + bool aExplicitLanguage, + gfxTextPerfMetrics* aTextPerf, + gfxUserFontSet* aUserFontSet, gfxFloat aDevToCssSize, + StyleFontVariantEmoji aVariantEmoji) + : mPresContext(aPresContext), // Note that aPresContext may be null! + mFamilyList(aFontFamilyList), + mStyle(*aStyle), + mLanguage(aLanguage), + mUnderlineOffset(UNDERLINE_OFFSET_NOT_SET), + mHyphenWidth(-1), + mDevToCssSize(aDevToCssSize), + mUserFontSet(aUserFontSet), + mTextPerf(aTextPerf), + mLastPrefLang(eFontPrefLang_Western), + mPageLang(gfxPlatformFontList::GetFontPrefLangFor(aLanguage)), + mLastPrefFirstFont(false), + mSkipDrawing(false), + mExplicitLanguage(aExplicitLanguage) { + switch (aVariantEmoji) { + case StyleFontVariantEmoji::Normal: + case StyleFontVariantEmoji::Unicode: + break; + case StyleFontVariantEmoji::Text: + mEmojiPresentation = eFontPresentation::Text; + break; + case StyleFontVariantEmoji::Emoji: + mEmojiPresentation = eFontPresentation::EmojiExplicit; + break; + } + // We don't use SetUserFontSet() here, as we want to unconditionally call + // BuildFontList() rather than only do UpdateUserFonts() if it changed. + mCurrGeneration = GetGeneration(); + BuildFontList(); +} + +gfxFontGroup::~gfxFontGroup() { + // Should not be dropped by stylo + MOZ_ASSERT(!Servo_IsWorkerThread()); +} + +static StyleGenericFontFamily GetDefaultGeneric(nsAtom* aLanguage) { + return StaticPresData::Get() + ->GetFontPrefsForLang(aLanguage) + ->GetDefaultGeneric(); +} + +void gfxFontGroup::BuildFontList() { + // initialize fonts in the font family list + AutoTArray<FamilyAndGeneric, 10> fonts; + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + mFontListGeneration = pfl->GetGeneration(); + + // lookup fonts in the fontlist + for (const StyleSingleFontFamily& name : mFamilyList.list.AsSpan()) { + if (name.IsFamilyName()) { + const auto& familyName = name.AsFamilyName(); + AddPlatformFont(nsAtomCString(familyName.name.AsAtom()), + familyName.syntax == StyleFontFamilyNameSyntax::Quoted, + fonts); + } else { + MOZ_ASSERT(name.IsGeneric()); + const StyleGenericFontFamily generic = name.AsGeneric(); + // system-ui is usually a single family, so it doesn't work great as + // fallback. Prefer the following generic or the language default instead. + if (mFallbackGeneric == StyleGenericFontFamily::None && + generic != StyleGenericFontFamily::SystemUi) { + mFallbackGeneric = generic; + } + pfl->AddGenericFonts(mPresContext, generic, mLanguage, fonts); + if (mTextPerf) { + mTextPerf->current.genericLookups++; + } + } + } + + // If necessary, append default language generic onto the end. + if (mFallbackGeneric == StyleGenericFontFamily::None && !mStyle.systemFont) { + auto defaultLanguageGeneric = GetDefaultGeneric(mLanguage); + + pfl->AddGenericFonts(mPresContext, defaultLanguageGeneric, mLanguage, + fonts); + if (mTextPerf) { + mTextPerf->current.genericLookups++; + } + } + + // build the fontlist from the specified families + for (const auto& f : fonts) { + if (f.mFamily.mShared) { + AddFamilyToFontList(f.mFamily.mShared, f.mGeneric); + } else { + AddFamilyToFontList(f.mFamily.mUnshared, f.mGeneric); + } + } +} + +void gfxFontGroup::AddPlatformFont(const nsACString& aName, bool aQuotedName, + nsTArray<FamilyAndGeneric>& aFamilyList) { + // First, look up in the user font set... + // If the fontSet matches the family, we must not look for a platform + // font of the same name, even if we fail to actually get a fontEntry + // here; we'll fall back to the next name in the CSS font-family list. + if (mUserFontSet) { + // Add userfonts to the fontlist whether already loaded + // or not. Loading is initiated during font matching. + RefPtr<gfxFontFamily> family = mUserFontSet->LookupFamily(aName); + if (family) { + aFamilyList.AppendElement(std::move(family)); + return; + } + } + + // Not known in the user font set ==> check system fonts + gfxPlatformFontList::PlatformFontList()->FindAndAddFamilies( + mPresContext, StyleGenericFontFamily::None, aName, &aFamilyList, + aQuotedName ? gfxPlatformFontList::FindFamiliesFlags::eQuotedFamilyName + : gfxPlatformFontList::FindFamiliesFlags(0), + &mStyle, mLanguage.get(), mDevToCssSize); +} + +void gfxFontGroup::AddFamilyToFontList(gfxFontFamily* aFamily, + StyleGenericFontFamily aGeneric) { + if (!aFamily) { + MOZ_ASSERT_UNREACHABLE("don't try to add a null font family!"); + return; + } + AutoTArray<gfxFontEntry*, 4> fontEntryList; + aFamily->FindAllFontsForStyle(mStyle, fontEntryList); + // add these to the fontlist + for (gfxFontEntry* fe : fontEntryList) { + if (!HasFont(fe)) { + FamilyFace ff(aFamily, fe, aGeneric); + if (fe->mIsUserFontContainer) { + ff.CheckState(mSkipDrawing); + } + mFonts.AppendElement(ff); + } + } + // for a family marked as "check fallback faces", only mark the last + // entry so that fallbacks for a family are only checked once + if (aFamily->CheckForFallbackFaces() && !fontEntryList.IsEmpty() && + !mFonts.IsEmpty()) { + mFonts.LastElement().SetCheckForFallbackFaces(); + } +} + +void gfxFontGroup::AddFamilyToFontList(fontlist::Family* aFamily, + StyleGenericFontFamily aGeneric) { + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + if (!aFamily->IsInitialized()) { + if (ServoStyleSet* set = gfxFontUtils::CurrentServoStyleSet()) { + // If we need to initialize a Family record, but we're on a style + // worker thread, we have to defer it. + set->AppendTask(PostTraversalTask::InitializeFamily(aFamily)); + set->AppendTask(PostTraversalTask::FontInfoUpdate(set)); + return; + } + if (!pfl->InitializeFamily(aFamily)) { + return; + } + } + AutoTArray<fontlist::Face*, 4> faceList; + aFamily->FindAllFacesForStyle(pfl->SharedFontList(), mStyle, faceList); + for (auto* face : faceList) { + gfxFontEntry* fe = pfl->GetOrCreateFontEntry(face, aFamily); + if (fe && !HasFont(fe)) { + FamilyFace ff(aFamily, fe, aGeneric); + mFonts.AppendElement(ff); + } + } +} + +bool gfxFontGroup::HasFont(const gfxFontEntry* aFontEntry) { + for (auto& f : mFonts) { + if (f.FontEntry() == aFontEntry) { + return true; + } + } + return false; +} + +already_AddRefed<gfxFont> gfxFontGroup::GetFontAt(uint32_t i, uint32_t aCh, + bool* aLoading) { + if (i >= mFonts.Length()) { + return nullptr; + } + + FamilyFace& ff = mFonts[i]; + if (ff.IsInvalid() || ff.IsLoading()) { + return nullptr; + } + + RefPtr<gfxFont> font = ff.Font(); + if (!font) { + gfxFontEntry* fe = ff.FontEntry(); + if (!fe) { + return nullptr; + } + gfxCharacterMap* unicodeRangeMap = nullptr; + if (fe->mIsUserFontContainer) { + gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe); + if (ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED && + ufe->CharacterInUnicodeRange(aCh) && !*aLoading) { + ufe->Load(); + ff.CheckState(mSkipDrawing); + *aLoading = ff.IsLoading(); + } + fe = ufe->GetPlatformFontEntry(); + if (!fe) { + return nullptr; + } + unicodeRangeMap = ufe->GetUnicodeRangeMap(); + } + font = fe->FindOrMakeFont(&mStyle, unicodeRangeMap); + if (!font || !font->Valid()) { + ff.SetInvalid(); + return nullptr; + } + ff.SetFont(font); + } + return font.forget(); +} + +void gfxFontGroup::FamilyFace::CheckState(bool& aSkipDrawing) { + gfxFontEntry* fe = FontEntry(); + if (!fe) { + return; + } + if (fe->mIsUserFontContainer) { + gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe); + gfxUserFontEntry::UserFontLoadState state = ufe->LoadState(); + switch (state) { + case gfxUserFontEntry::STATUS_LOAD_PENDING: + case gfxUserFontEntry::STATUS_LOADING: + SetLoading(true); + break; + case gfxUserFontEntry::STATUS_FAILED: + SetInvalid(); + // fall-thru to the default case + [[fallthrough]]; + default: + SetLoading(false); + } + if (ufe->WaitForUserFont()) { + aSkipDrawing = true; + } + } +} + +bool gfxFontGroup::FamilyFace::EqualsUserFont( + const gfxUserFontEntry* aUserFont) const { + gfxFontEntry* fe = FontEntry(); + // if there's a font, the entry is the underlying platform font + if (mFontCreated) { + gfxFontEntry* pfe = aUserFont->GetPlatformFontEntry(); + if (pfe == fe) { + return true; + } + } else if (fe == aUserFont) { + return true; + } + return false; +} + +static nsAutoCString FamilyListToString( + const StyleFontFamilyList& aFamilyList) { + return StringJoin(","_ns, aFamilyList.list.AsSpan(), + [](nsACString& dst, const StyleSingleFontFamily& name) { + name.AppendToString(dst); + }); +} + +already_AddRefed<gfxFont> gfxFontGroup::GetDefaultFont() { + if (mDefaultFont) { + return do_AddRef(mDefaultFont); + } + + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + FontFamily family = pfl->GetDefaultFont(mPresContext, &mStyle); + MOZ_ASSERT(!family.IsNull(), + "invalid default font returned by GetDefaultFont"); + + gfxFontEntry* fe = nullptr; + if (family.mShared) { + fontlist::Family* fam = family.mShared; + if (!fam->IsInitialized()) { + // If this fails, FindFaceForStyle will just safely return nullptr + Unused << pfl->InitializeFamily(fam); + } + fontlist::Face* face = fam->FindFaceForStyle(pfl->SharedFontList(), mStyle); + if (face) { + fe = pfl->GetOrCreateFontEntry(face, fam); + } + } else { + fe = family.mUnshared->FindFontForStyle(mStyle); + } + if (fe) { + mDefaultFont = fe->FindOrMakeFont(&mStyle); + } + + uint32_t numInits, loaderState; + pfl->GetFontlistInitInfo(numInits, loaderState); + + MOZ_ASSERT(numInits != 0, + "must initialize system fontlist before getting default font!"); + + uint32_t numFonts = 0; + if (!mDefaultFont) { + // Try for a "font of last resort...." + // Because an empty font list would be Really Bad for later code + // that assumes it will be able to get valid metrics for layout, + // just look for the first usable font and put in the list. + // (see bug 554544) + if (pfl->SharedFontList()) { + fontlist::FontList* list = pfl->SharedFontList(); + numFonts = list->NumFamilies(); + fontlist::Family* families = list->Families(); + for (uint32_t i = 0; i < numFonts; ++i) { + fontlist::Family* fam = &families[i]; + if (!fam->IsInitialized()) { + Unused << pfl->InitializeFamily(fam); + } + fontlist::Face* face = + fam->FindFaceForStyle(pfl->SharedFontList(), mStyle); + if (face) { + fe = pfl->GetOrCreateFontEntry(face, fam); + if (fe) { + mDefaultFont = fe->FindOrMakeFont(&mStyle); + if (mDefaultFont) { + break; + } + NS_WARNING("FindOrMakeFont failed"); + } + } + } + } else { + AutoTArray<RefPtr<gfxFontFamily>, 200> familyList; + pfl->GetFontFamilyList(familyList); + numFonts = familyList.Length(); + for (uint32_t i = 0; i < numFonts; ++i) { + gfxFontEntry* fe = familyList[i]->FindFontForStyle(mStyle, true); + if (fe) { + mDefaultFont = fe->FindOrMakeFont(&mStyle); + if (mDefaultFont) { + break; + } + } + } + } + } + + if (!mDefaultFont && pfl->SharedFontList() && !XRE_IsParentProcess()) { + // If we're a content process, it's possible this is failing because the + // chrome process has just updated the shared font list and we haven't yet + // refreshed our reference to it. If that's the case, update and retry. + // But if we're not on the main thread, we can't do this, so just use + // the platform default font directly. + if (NS_IsMainThread()) { + uint32_t oldGeneration = pfl->SharedFontList()->GetGeneration(); + pfl->UpdateFontList(); + if (pfl->SharedFontList()->GetGeneration() != oldGeneration) { + return GetDefaultFont(); + } + } + } + + if (!mDefaultFont) { + // We must have failed to find anything usable in our font-family list, + // or it's badly broken. One more last-ditch effort to make a font: + gfxFontEntry* fe = pfl->GetDefaultFontEntry(); + if (fe) { + RefPtr<gfxFont> f = fe->FindOrMakeFont(&mStyle); + if (f) { + return f.forget(); + } + } + } + + if (!mDefaultFont) { + // an empty font list at this point is fatal; we're not going to + // be able to do even the most basic layout operations + + // annotate crash report with fontlist info + nsAutoCString fontInitInfo; + fontInitInfo.AppendPrintf("no fonts - init: %d fonts: %d loader: %d", + numInits, numFonts, loaderState); +#ifdef XP_WIN + bool dwriteEnabled = gfxWindowsPlatform::GetPlatform()->DWriteEnabled(); + double upTime = (double)GetTickCount(); + fontInitInfo.AppendPrintf(" backend: %s system-uptime: %9.3f sec", + dwriteEnabled ? "directwrite" : "gdi", + upTime / 1000); +#endif + gfxCriticalError() << fontInitInfo.get(); + + char msg[256]; // CHECK buffer length if revising message below + SprintfLiteral(msg, "unable to find a usable font (%.220s)", + FamilyListToString(mFamilyList).get()); + MOZ_CRASH_UNSAFE(msg); + } + + return do_AddRef(mDefaultFont); +} + +already_AddRefed<gfxFont> gfxFontGroup::GetFirstValidFont( + uint32_t aCh, StyleGenericFontFamily* aGeneric, bool* aIsFirst) { + // Ensure cached font instances are valid. + CheckForUpdatedPlatformList(); + + uint32_t count = mFonts.Length(); + bool loading = false; + + // Check whether the font supports the given character, unless aCh is the + // kCSSFirstAvailableFont constant, in which case (as per CSS Fonts spec) + // we want the first font whose unicode-range does not exclude <space>, + // regardless of whether it in fact supports the <space> character. + auto isValidForChar = [](gfxFont* aFont, uint32_t aCh) -> bool { + if (!aFont) { + return false; + } + if (aCh == kCSSFirstAvailableFont) { + if (const auto* unicodeRange = aFont->GetUnicodeRangeMap()) { + return unicodeRange->test(' '); + } + return true; + } + return aFont->HasCharacter(aCh); + }; + + for (uint32_t i = 0; i < count; ++i) { + FamilyFace& ff = mFonts[i]; + if (ff.IsInvalid()) { + continue; + } + + // already have a font? + RefPtr<gfxFont> font = ff.Font(); + if (isValidForChar(font, aCh)) { + if (aGeneric) { + *aGeneric = ff.Generic(); + } + if (aIsFirst) { + *aIsFirst = (i == 0); + } + return font.forget(); + } + + // Need to build a font, loading userfont if not loaded. In + // cases where unicode range might apply, use the character + // provided. + gfxFontEntry* fe = ff.FontEntry(); + if (fe && fe->mIsUserFontContainer) { + gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe); + bool inRange = ufe->CharacterInUnicodeRange( + aCh == kCSSFirstAvailableFont ? ' ' : aCh); + if (inRange) { + if (!loading && + ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED) { + ufe->Load(); + ff.CheckState(mSkipDrawing); + } + if (ff.IsLoading()) { + loading = true; + } + } + if (ufe->LoadState() != gfxUserFontEntry::STATUS_LOADED || !inRange) { + continue; + } + } + + font = GetFontAt(i, aCh, &loading); + if (isValidForChar(font, aCh)) { + if (aGeneric) { + *aGeneric = ff.Generic(); + } + if (aIsFirst) { + *aIsFirst = (i == 0); + } + return font.forget(); + } + } + if (aGeneric) { + *aGeneric = StyleGenericFontFamily::None; + } + if (aIsFirst) { + *aIsFirst = false; + } + return GetDefaultFont(); +} + +already_AddRefed<gfxFont> gfxFontGroup::GetFirstMathFont() { + uint32_t count = mFonts.Length(); + for (uint32_t i = 0; i < count; ++i) { + RefPtr<gfxFont> font = GetFontAt(i); + if (font && font->TryGetMathTable()) { + return font.forget(); + } + } + return nullptr; +} + +bool gfxFontGroup::IsInvalidChar(uint8_t ch) { + return ((ch & 0x7f) < 0x20 || ch == 0x7f); +} + +bool gfxFontGroup::IsInvalidChar(char16_t ch) { + // All printable 7-bit ASCII values are OK + if (ch >= ' ' && ch < 0x7f) { + return false; + } + // No point in sending non-printing control chars through font shaping + if (ch <= 0x9f) { + return true; + } + // Word-separating format/bidi control characters are not shaped as part + // of words. + return (((ch & 0xFF00) == 0x2000 /* Unicode control character */ && + (ch == 0x200B /*ZWSP*/ || ch == 0x2028 /*LSEP*/ || + ch == 0x2029 /*PSEP*/ || ch == 0x2060 /*WJ*/)) || + ch == 0xfeff /*ZWNBSP*/ || IsBidiControl(ch)); +} + +already_AddRefed<gfxTextRun> gfxFontGroup::MakeEmptyTextRun( + const Parameters* aParams, gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2) { + aFlags |= ShapedTextFlags::TEXT_IS_8BIT; + return gfxTextRun::Create(aParams, 0, this, aFlags, aFlags2); +} + +already_AddRefed<gfxTextRun> gfxFontGroup::MakeSpaceTextRun( + const Parameters* aParams, gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2) { + aFlags |= ShapedTextFlags::TEXT_IS_8BIT; + + RefPtr<gfxTextRun> textRun = + gfxTextRun::Create(aParams, 1, this, aFlags, aFlags2); + if (!textRun) { + return nullptr; + } + + gfx::ShapedTextFlags orientation = aFlags & ShapedTextFlags::TEXT_ORIENT_MASK; + if (orientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) { + orientation = ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; + } + + RefPtr<gfxFont> font = GetFirstValidFont(); + if (MOZ_UNLIKELY(GetStyle()->AdjustedSizeMustBeZero())) { + // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle + // them, and always create at least size 1 fonts, i.e. they still + // render something for size 0 fonts. + textRun->AddGlyphRun(font, FontMatchType::Kind::kUnspecified, 0, false, + orientation, false); + } else { + if (font->GetSpaceGlyph()) { + // Normally, the font has a cached space glyph, so we can avoid + // the cost of calling FindFontForChar. + textRun->SetSpaceGlyph(font, aParams->mDrawTarget, 0, orientation); + } else { + // In case the primary font doesn't have <space> (bug 970891), + // find one that does. + FontMatchType matchType; + RefPtr<gfxFont> spaceFont = + FindFontForChar(' ', 0, 0, Script::LATIN, nullptr, &matchType); + if (spaceFont) { + textRun->SetSpaceGlyph(spaceFont, aParams->mDrawTarget, 0, orientation); + } + } + } + + // Note that the gfxGlyphExtents glyph bounds storage for the font will + // always contain an entry for the font's space glyph, so we don't have + // to call FetchGlyphExtents here. + return textRun.forget(); +} + +template <typename T> +already_AddRefed<gfxTextRun> gfxFontGroup::MakeBlankTextRun( + const T* aString, uint32_t aLength, const Parameters* aParams, + gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2) { + RefPtr<gfxTextRun> textRun = + gfxTextRun::Create(aParams, aLength, this, aFlags, aFlags2); + if (!textRun) { + return nullptr; + } + + gfx::ShapedTextFlags orientation = aFlags & ShapedTextFlags::TEXT_ORIENT_MASK; + if (orientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) { + orientation = ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT; + } + RefPtr<gfxFont> font = GetFirstValidFont(); + textRun->AddGlyphRun(font, FontMatchType::Kind::kUnspecified, 0, false, + orientation, false); + + textRun->SetupClusterBoundaries(0, aString, aLength); + + for (uint32_t i = 0; i < aLength; i++) { + if (aString[i] == '\n') { + textRun->SetIsNewline(i); + } else if (aString[i] == '\t') { + textRun->SetIsTab(i); + } + } + + return textRun.forget(); +} + +already_AddRefed<gfxTextRun> gfxFontGroup::MakeHyphenTextRun( + DrawTarget* aDrawTarget, gfx::ShapedTextFlags aFlags, + uint32_t aAppUnitsPerDevUnit) { + // only use U+2010 if it is supported by the first font in the group; + // it's better to use ASCII '-' from the primary font than to fall back to + // U+2010 from some other, possibly poorly-matching face + static const char16_t hyphen = 0x2010; + RefPtr<gfxFont> font = GetFirstValidFont(uint32_t(hyphen)); + if (font->HasCharacter(hyphen)) { + return MakeTextRun(&hyphen, 1, aDrawTarget, aAppUnitsPerDevUnit, aFlags, + nsTextFrameUtils::Flags(), nullptr); + } + + static const uint8_t dash = '-'; + return MakeTextRun(&dash, 1, aDrawTarget, aAppUnitsPerDevUnit, aFlags, + nsTextFrameUtils::Flags(), nullptr); +} + +gfxFloat gfxFontGroup::GetHyphenWidth( + const gfxTextRun::PropertyProvider* aProvider) { + if (mHyphenWidth < 0) { + RefPtr<DrawTarget> dt(aProvider->GetDrawTarget()); + if (dt) { + RefPtr<gfxTextRun> hyphRun( + MakeHyphenTextRun(dt, aProvider->GetShapedTextFlags(), + aProvider->GetAppUnitsPerDevUnit())); + mHyphenWidth = hyphRun.get() ? hyphRun->GetAdvanceWidth() : 0; + } + } + return mHyphenWidth; +} + +template <typename T> +already_AddRefed<gfxTextRun> gfxFontGroup::MakeTextRun( + const T* aString, uint32_t aLength, const Parameters* aParams, + gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2, + gfxMissingFontRecorder* aMFR) { + if (aLength == 0) { + return MakeEmptyTextRun(aParams, aFlags, aFlags2); + } + if (aLength == 1 && aString[0] == ' ') { + return MakeSpaceTextRun(aParams, aFlags, aFlags2); + } + + if (sizeof(T) == 1) { + aFlags |= ShapedTextFlags::TEXT_IS_8BIT; + } + + if (MOZ_UNLIKELY(GetStyle()->AdjustedSizeMustBeZero())) { + // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle + // them, and always create at least size 1 fonts, i.e. they still + // render something for size 0 fonts. + return MakeBlankTextRun(aString, aLength, aParams, aFlags, aFlags2); + } + + RefPtr<gfxTextRun> textRun = + gfxTextRun::Create(aParams, aLength, this, aFlags, aFlags2); + if (!textRun) { + return nullptr; + } + + InitTextRun(aParams->mDrawTarget, textRun.get(), aString, aLength, aMFR); + + textRun->FetchGlyphExtents(aParams->mDrawTarget); + + return textRun.forget(); +} + +// MakeTextRun instantiations (needed by Linux64 base-toolchain build). +template already_AddRefed<gfxTextRun> gfxFontGroup::MakeTextRun( + const uint8_t* aString, uint32_t aLength, const Parameters* aParams, + gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2, + gfxMissingFontRecorder* aMFR); +template already_AddRefed<gfxTextRun> gfxFontGroup::MakeTextRun( + const char16_t* aString, uint32_t aLength, const Parameters* aParams, + gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2, + gfxMissingFontRecorder* aMFR); + +template <typename T> +void gfxFontGroup::InitTextRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun, + const T* aString, uint32_t aLength, + gfxMissingFontRecorder* aMFR) { + NS_ASSERTION(aLength > 0, "don't call InitTextRun for a zero-length run"); + + // we need to do numeral processing even on 8-bit text, + // in case we're converting Western to Hindi/Arabic digits + uint32_t numOption = gfxPlatform::GetPlatform()->GetBidiNumeralOption(); + UniquePtr<char16_t[]> transformedString; + if (numOption != IBMBIDI_NUMERAL_NOMINAL) { + // scan the string for numerals that may need to be transformed; + // if we find any, we'll make a local copy here and use that for + // font matching and glyph generation/shaping + bool prevIsArabic = + !!(aTextRun->GetFlags() & ShapedTextFlags::TEXT_INCOMING_ARABICCHAR); + for (uint32_t i = 0; i < aLength; ++i) { + char16_t origCh = aString[i]; + char16_t newCh = HandleNumberInChar(origCh, prevIsArabic, numOption); + if (newCh != origCh) { + if (!transformedString) { + transformedString = MakeUnique<char16_t[]>(aLength); + if constexpr (sizeof(T) == sizeof(char16_t)) { + memcpy(transformedString.get(), aString, i * sizeof(char16_t)); + } else { + for (uint32_t j = 0; j < i; ++j) { + transformedString[j] = aString[j]; + } + } + } + } + if (transformedString) { + transformedString[i] = newCh; + } + prevIsArabic = IS_ARABIC_CHAR(newCh); + } + } + + LogModule* log = mStyle.systemFont ? gfxPlatform::GetLog(eGfxLog_textrunui) + : gfxPlatform::GetLog(eGfxLog_textrun); + + // variant fallback handling may end up passing through this twice + bool redo; + do { + redo = false; + + if (sizeof(T) == sizeof(uint8_t) && !transformedString) { + if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Warning))) { + nsAutoCString lang; + mLanguage->ToUTF8String(lang); + nsAutoCString str((const char*)aString, aLength); + nsAutoCString styleString; + mStyle.style.ToString(styleString); + auto defaultLanguageGeneric = GetDefaultGeneric(mLanguage); + MOZ_LOG( + log, LogLevel::Warning, + ("(%s) fontgroup: [%s] default: %s lang: %s script: %d " + "len %d weight: %g stretch: %g%% style: %s size: %6.2f %zu-byte " + "TEXTRUN [%s] ENDTEXTRUN\n", + (mStyle.systemFont ? "textrunui" : "textrun"), + FamilyListToString(mFamilyList).get(), + (defaultLanguageGeneric == StyleGenericFontFamily::Serif + ? "serif" + : (defaultLanguageGeneric == StyleGenericFontFamily::SansSerif + ? "sans-serif" + : "none")), + lang.get(), static_cast<int>(Script::LATIN), aLength, + mStyle.weight.ToFloat(), mStyle.stretch.ToFloat(), + styleString.get(), mStyle.size, sizeof(T), str.get())); + } + + // the text is still purely 8-bit; bypass the script-run itemizer + // and treat it as a single Latin run + InitScriptRun(aDrawTarget, aTextRun, aString, 0, aLength, Script::LATIN, + aMFR); + } else { + const char16_t* textPtr; + if (transformedString) { + textPtr = transformedString.get(); + } else { + // typecast to avoid compilation error for the 8-bit version, + // even though this is dead code in that case + textPtr = reinterpret_cast<const char16_t*>(aString); + } + + // split into script runs so that script can potentially influence + // the font matching process below + gfxScriptItemizer scriptRuns(textPtr, aLength); + + uint32_t runStart = 0, runLimit = aLength; + Script runScript = Script::LATIN; + while (scriptRuns.Next(runStart, runLimit, runScript)) { + if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Warning))) { + nsAutoCString lang; + mLanguage->ToUTF8String(lang); + nsAutoCString styleString; + mStyle.style.ToString(styleString); + auto defaultLanguageGeneric = GetDefaultGeneric(mLanguage); + uint32_t runLen = runLimit - runStart; + MOZ_LOG(log, LogLevel::Warning, + ("(%s) fontgroup: [%s] default: %s lang: %s script: %d " + "len %d weight: %g stretch: %g%% style: %s size: %6.2f " + "%zu-byte TEXTRUN [%s] ENDTEXTRUN\n", + (mStyle.systemFont ? "textrunui" : "textrun"), + FamilyListToString(mFamilyList).get(), + (defaultLanguageGeneric == StyleGenericFontFamily::Serif + ? "serif" + : (defaultLanguageGeneric == + StyleGenericFontFamily::SansSerif + ? "sans-serif" + : "none")), + lang.get(), static_cast<int>(runScript), runLen, + mStyle.weight.ToFloat(), mStyle.stretch.ToFloat(), + styleString.get(), mStyle.size, sizeof(T), + NS_ConvertUTF16toUTF8(textPtr + runStart, runLen).get())); + } + + InitScriptRun(aDrawTarget, aTextRun, textPtr + runStart, runStart, + runLimit - runStart, runScript, aMFR); + } + } + + // if shaping was aborted due to lack of feature support, clear out + // glyph runs and redo shaping with fallback forced on + if (aTextRun->GetShapingState() == gfxTextRun::eShapingState_Aborted) { + redo = true; + aTextRun->SetShapingState(gfxTextRun::eShapingState_ForceFallbackFeature); + aTextRun->ClearGlyphsAndCharacters(); + } + + } while (redo); + + if (sizeof(T) == sizeof(char16_t) && aLength > 0) { + gfxTextRun::CompressedGlyph* glyph = aTextRun->GetCharacterGlyphs(); + if (!glyph->IsSimpleGlyph()) { + glyph->SetClusterStart(true); + } + } + + // It's possible for CoreText to omit glyph runs if it decides they contain + // only invisibles (e.g., U+FEFF, see reftest 474417-1). In this case, we + // need to eliminate them from the glyph run array to avoid drawing "partial + // ligatures" with the wrong font. + // We don't do this during InitScriptRun (or gfxFont::InitTextRun) because + // it will iterate back over all glyphruns in the textrun, which leads to + // pathologically-bad perf in the case where a textrun contains many script + // changes (see bug 680402) - we'd end up re-sanitizing all the earlier runs + // every time a new script subrun is processed. + aTextRun->SanitizeGlyphRuns(); +} + +static inline bool IsPUA(uint32_t aUSV) { + // We could look up the General Category of the codepoint here, + // but it's simpler to check PUA codepoint ranges. + return (aUSV >= 0xE000 && aUSV <= 0xF8FF) || (aUSV >= 0xF0000); +} + +template <typename T> +void gfxFontGroup::InitScriptRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun, + const T* aString, // text for this script run, + // not the entire textrun + uint32_t aOffset, // position of the script + // run within the textrun + uint32_t aLength, // length of the script run + Script aRunScript, + gfxMissingFontRecorder* aMFR) { + NS_ASSERTION(aLength > 0, "don't call InitScriptRun for a 0-length run"); + NS_ASSERTION(aTextRun->GetShapingState() != gfxTextRun::eShapingState_Aborted, + "don't call InitScriptRun with aborted shaping state"); + + // confirm the load state of userfonts in the list + if (mUserFontSet && mCurrGeneration != mUserFontSet->GetGeneration()) { + UpdateUserFonts(); + } + + RefPtr<gfxFont> mainFont = GetFirstValidFont(); + + ShapedTextFlags orientation = + aTextRun->GetFlags() & ShapedTextFlags::TEXT_ORIENT_MASK; + + if (orientation != ShapedTextFlags::TEXT_ORIENT_HORIZONTAL && + (aRunScript == Script::MONGOLIAN || aRunScript == Script::PHAGS_PA)) { + // Mongolian and Phags-pa text should ignore text-orientation and + // always render in its "native" vertical mode, implemented by fonts + // as sideways-right (i.e as if shaped horizontally, and then the + // entire line is rotated to render vertically). Therefore, we ignore + // the aOrientation value from the textrun's flags, and make all + // vertical Mongolian/Phags-pa use sideways-right. + orientation = ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; + } + + uint32_t runStart = 0; + AutoTArray<TextRange, 3> fontRanges; + ComputeRanges(fontRanges, aString, aLength, aRunScript, orientation); + uint32_t numRanges = fontRanges.Length(); + bool missingChars = false; + bool isCJK = gfxTextRun::IsCJKScript(aRunScript); + + for (uint32_t r = 0; r < numRanges; r++) { + const TextRange& range = fontRanges[r]; + uint32_t matchedLength = range.Length(); + RefPtr<gfxFont> matchedFont = range.font; + // create the glyph run for this range + if (matchedFont && mStyle.noFallbackVariantFeatures) { + // common case - just do glyph layout and record the + // resulting positioned glyphs + aTextRun->AddGlyphRun(matchedFont, range.matchType, aOffset + runStart, + (matchedLength > 0), range.orientation, isCJK); + if (!matchedFont->SplitAndInitTextRun( + aDrawTarget, aTextRun, aString + runStart, aOffset + runStart, + matchedLength, aRunScript, mLanguage, range.orientation)) { + // glyph layout failed! treat as missing glyphs + matchedFont = nullptr; + } + } else if (matchedFont) { + // shape with some variant feature that requires fallback handling + bool petiteToSmallCaps = false; + bool syntheticLower = false; + bool syntheticUpper = false; + + if (mStyle.variantSubSuper != NS_FONT_VARIANT_POSITION_NORMAL && + mStyle.useSyntheticPosition && + (aTextRun->GetShapingState() == + gfxTextRun::eShapingState_ForceFallbackFeature || + !matchedFont->SupportsSubSuperscript(mStyle.variantSubSuper, aString, + aLength, aRunScript))) { + // fallback for subscript/superscript variant glyphs + + // if the feature was already used, abort and force + // fallback across the entire textrun + gfxTextRun::ShapingState ss = aTextRun->GetShapingState(); + + if (ss == gfxTextRun::eShapingState_Normal) { + aTextRun->SetShapingState( + gfxTextRun::eShapingState_ShapingWithFallback); + } else if (ss == gfxTextRun::eShapingState_ShapingWithFeature) { + aTextRun->SetShapingState(gfxTextRun::eShapingState_Aborted); + return; + } + + RefPtr<gfxFont> subSuperFont = matchedFont->GetSubSuperscriptFont( + aTextRun->GetAppUnitsPerDevUnit()); + aTextRun->AddGlyphRun(subSuperFont, range.matchType, aOffset + runStart, + (matchedLength > 0), range.orientation, isCJK); + if (!subSuperFont->SplitAndInitTextRun( + aDrawTarget, aTextRun, aString + runStart, aOffset + runStart, + matchedLength, aRunScript, mLanguage, range.orientation)) { + // glyph layout failed! treat as missing glyphs + matchedFont = nullptr; + } + } else if (mStyle.variantCaps != NS_FONT_VARIANT_CAPS_NORMAL && + mStyle.allowSyntheticSmallCaps && + !matchedFont->SupportsVariantCaps( + aRunScript, mStyle.variantCaps, petiteToSmallCaps, + syntheticLower, syntheticUpper)) { + // fallback for small-caps variant glyphs + if (!matchedFont->InitFakeSmallCapsRun( + mPresContext, aDrawTarget, aTextRun, aString + runStart, + aOffset + runStart, matchedLength, range.matchType, + range.orientation, aRunScript, + mExplicitLanguage ? mLanguage.get() : nullptr, syntheticLower, + syntheticUpper)) { + matchedFont = nullptr; + } + } else { + // shape normally with variant feature enabled + gfxTextRun::ShapingState ss = aTextRun->GetShapingState(); + + // adjust the shaping state if necessary + if (ss == gfxTextRun::eShapingState_Normal) { + aTextRun->SetShapingState( + gfxTextRun::eShapingState_ShapingWithFeature); + } else if (ss == gfxTextRun::eShapingState_ShapingWithFallback) { + // already have shaping results using fallback, need to redo + aTextRun->SetShapingState(gfxTextRun::eShapingState_Aborted); + return; + } + + // do glyph layout and record the resulting positioned glyphs + aTextRun->AddGlyphRun(matchedFont, range.matchType, aOffset + runStart, + (matchedLength > 0), range.orientation, isCJK); + if (!matchedFont->SplitAndInitTextRun( + aDrawTarget, aTextRun, aString + runStart, aOffset + runStart, + matchedLength, aRunScript, mLanguage, range.orientation)) { + // glyph layout failed! treat as missing glyphs + matchedFont = nullptr; + } + } + } else { + aTextRun->AddGlyphRun(mainFont, FontMatchType::Kind::kFontGroup, + aOffset + runStart, (matchedLength > 0), + range.orientation, isCJK); + } + + if (!matchedFont) { + // We need to set cluster boundaries (and mark spaces) so that + // surrogate pairs, combining characters, etc behave properly, + // even if we don't have glyphs for them + aTextRun->SetupClusterBoundaries(aOffset + runStart, aString + runStart, + matchedLength); + + // various "missing" characters may need special handling, + // so we check for them here + uint32_t runLimit = runStart + matchedLength; + for (uint32_t index = runStart; index < runLimit; index++) { + T ch = aString[index]; + + // tab and newline are not to be displayed as hexboxes, + // but do need to be recorded in the textrun + if (ch == '\n') { + aTextRun->SetIsNewline(aOffset + index); + continue; + } + if (ch == '\t') { + aTextRun->SetIsTab(aOffset + index); + continue; + } + + // for 16-bit textruns only, check for surrogate pairs and + // special Unicode spaces; omit these checks in 8-bit runs + if constexpr (sizeof(T) == sizeof(char16_t)) { + if (index + 1 < aLength && + NS_IS_SURROGATE_PAIR(ch, aString[index + 1])) { + uint32_t usv = SURROGATE_TO_UCS4(ch, aString[index + 1]); + aTextRun->SetMissingGlyph(aOffset + index, usv, mainFont); + index++; + if (!mSkipDrawing && !IsPUA(usv)) { + missingChars = true; + } + continue; + } + + // check if this is a known Unicode whitespace character that + // we can render using the space glyph with a custom width + gfxFloat wid = mainFont->SynthesizeSpaceWidth(ch); + if (wid >= 0.0) { + nscoord advance = + aTextRun->GetAppUnitsPerDevUnit() * floor(wid + 0.5); + if (gfxShapedText::CompressedGlyph::IsSimpleAdvance(advance)) { + aTextRun->GetCharacterGlyphs()[aOffset + index].SetSimpleGlyph( + advance, mainFont->GetSpaceGlyph()); + } else { + gfxTextRun::DetailedGlyph detailedGlyph; + detailedGlyph.mGlyphID = mainFont->GetSpaceGlyph(); + detailedGlyph.mAdvance = advance; + aTextRun->SetDetailedGlyphs(aOffset + index, 1, &detailedGlyph); + } + continue; + } + } + + if (IsInvalidChar(ch)) { + // invalid chars are left as zero-width/invisible + continue; + } + + // record char code so we can draw a box with the Unicode value + aTextRun->SetMissingGlyph(aOffset + index, ch, mainFont); + if (!mSkipDrawing && !IsPUA(ch)) { + missingChars = true; + } + } + } + + runStart += matchedLength; + } + + if (aMFR && missingChars) { + aMFR->RecordScript(aRunScript); + } +} + +gfxTextRun* gfxFontGroup::GetEllipsisTextRun( + int32_t aAppUnitsPerDevPixel, gfx::ShapedTextFlags aFlags, + LazyReferenceDrawTargetGetter& aRefDrawTargetGetter) { + MOZ_ASSERT(!(aFlags & ~ShapedTextFlags::TEXT_ORIENT_MASK), + "flags here should only be used to specify orientation"); + if (mCachedEllipsisTextRun && + (mCachedEllipsisTextRun->GetFlags() & + ShapedTextFlags::TEXT_ORIENT_MASK) == aFlags && + mCachedEllipsisTextRun->GetAppUnitsPerDevUnit() == aAppUnitsPerDevPixel) { + return mCachedEllipsisTextRun.get(); + } + + // Use a Unicode ellipsis if the font supports it, + // otherwise use three ASCII periods as fallback. + RefPtr<gfxFont> firstFont = GetFirstValidFont(); + nsString ellipsis = + firstFont->HasCharacter(kEllipsisChar[0]) + ? nsDependentString(kEllipsisChar, ArrayLength(kEllipsisChar) - 1) + : nsDependentString(kASCIIPeriodsChar, + ArrayLength(kASCIIPeriodsChar) - 1); + + RefPtr<DrawTarget> refDT = aRefDrawTargetGetter.GetRefDrawTarget(); + Parameters params = {refDT, nullptr, nullptr, + nullptr, 0, aAppUnitsPerDevPixel}; + mCachedEllipsisTextRun = + MakeTextRun(ellipsis.BeginReading(), ellipsis.Length(), ¶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(); +} + +already_AddRefed<gfxFont> gfxFontGroup::FindFallbackFaceForChar( + gfxFontFamily* aFamily, uint32_t aCh, uint32_t aNextCh, + eFontPresentation aPresentation) { + GlobalFontMatch data(aCh, aNextCh, mStyle, aPresentation); + aFamily->SearchAllFontsForChar(&data); + gfxFontEntry* fe = data.mBestMatch; + if (!fe) { + return nullptr; + } + return fe->FindOrMakeFont(&mStyle); +} + +already_AddRefed<gfxFont> gfxFontGroup::FindFallbackFaceForChar( + fontlist::Family* aFamily, uint32_t aCh, uint32_t aNextCh, + eFontPresentation aPresentation) { + auto* pfl = gfxPlatformFontList::PlatformFontList(); + auto* list = pfl->SharedFontList(); + + // If async fallback is enabled, and the family isn't fully initialized yet, + // just start the async cmap loading and return. + if (!aFamily->IsFullyInitialized() && + StaticPrefs::gfx_font_rendering_fallback_async() && + !XRE_IsParentProcess()) { + pfl->StartCmapLoadingFromFamily(aFamily - list->Families()); + return nullptr; + } + + GlobalFontMatch data(aCh, aNextCh, mStyle, aPresentation); + aFamily->SearchAllFontsForChar(list, &data); + gfxFontEntry* fe = data.mBestMatch; + if (!fe) { + return nullptr; + } + return fe->FindOrMakeFont(&mStyle); +} + +already_AddRefed<gfxFont> gfxFontGroup::FindFallbackFaceForChar( + const FamilyFace& aFamily, uint32_t aCh, uint32_t aNextCh, + eFontPresentation aPresentation) { + if (aFamily.IsSharedFamily()) { + return FindFallbackFaceForChar(aFamily.SharedFamily(), aCh, aNextCh, + aPresentation); + } + return FindFallbackFaceForChar(aFamily.OwnedFamily(), aCh, aNextCh, + aPresentation); +} + +gfxFloat gfxFontGroup::GetUnderlineOffset() { + if (mUnderlineOffset == UNDERLINE_OFFSET_NOT_SET) { + // if the fontlist contains a bad underline font, make the underline + // offset the min of the first valid font and bad font underline offsets + uint32_t len = mFonts.Length(); + for (uint32_t i = 0; i < len; i++) { + FamilyFace& ff = mFonts[i]; + gfxFontEntry* fe = ff.FontEntry(); + if (!fe) { + continue; + } + if (!fe->mIsUserFontContainer && !fe->IsUserFont() && + ((ff.IsSharedFamily() && ff.SharedFamily() && + ff.SharedFamily()->IsBadUnderlineFamily()) || + (!ff.IsSharedFamily() && ff.OwnedFamily() && + ff.OwnedFamily()->IsBadUnderlineFamily()))) { + RefPtr<gfxFont> font = GetFontAt(i); + if (!font) { + continue; + } + gfxFloat bad = + font->GetMetrics(nsFontMetrics::eHorizontal).underlineOffset; + RefPtr<gfxFont> firstValidFont = GetFirstValidFont(); + gfxFloat first = firstValidFont->GetMetrics(nsFontMetrics::eHorizontal) + .underlineOffset; + mUnderlineOffset = std::min(first, bad); + return mUnderlineOffset; + } + } + + // no bad underline fonts, use the first valid font's metric + RefPtr<gfxFont> firstValidFont = GetFirstValidFont(); + mUnderlineOffset = + firstValidFont->GetMetrics(nsFontMetrics::eHorizontal).underlineOffset; + } + + return mUnderlineOffset; +} + +#define NARROW_NO_BREAK_SPACE 0x202fu + +already_AddRefed<gfxFont> gfxFontGroup::FindFontForChar( + uint32_t aCh, uint32_t aPrevCh, uint32_t aNextCh, Script aRunScript, + gfxFont* aPrevMatchedFont, FontMatchType* aMatchType) { + // If the char is a cluster extender, we want to use the same font as the + // preceding character if possible. This is preferable to using the font + // group because it avoids breaks in shaping within a cluster. + if (aPrevMatchedFont && IsClusterExtender(aCh)) { + if (aPrevMatchedFont->HasCharacter(aCh) || IsDefaultIgnorable(aCh)) { + return do_AddRef(aPrevMatchedFont); + } + // Check if this char and preceding char can compose; if so, is the + // combination supported by the current font. + uint32_t composed = intl::String::ComposePairNFC(aPrevCh, aCh); + if (composed > 0 && aPrevMatchedFont->HasCharacter(composed)) { + return do_AddRef(aPrevMatchedFont); + } + } + + // Special cases for NNBSP (as used in Mongolian): + if (aCh == NARROW_NO_BREAK_SPACE) { + // If there is no preceding character, try the font that we'd use + // for the next char (unless it's just another NNBSP; we don't try + // to look ahead through a whole run of them). + if (!aPrevCh && aNextCh && aNextCh != NARROW_NO_BREAK_SPACE) { + RefPtr<gfxFont> nextFont = FindFontForChar(aNextCh, 0, 0, aRunScript, + aPrevMatchedFont, aMatchType); + if (nextFont && nextFont->HasCharacter(aCh)) { + return nextFont.forget(); + } + } + // Otherwise, treat NNBSP like a cluster extender (as above) and try + // to continue the preceding font run. + if (aPrevMatchedFont && aPrevMatchedFont->HasCharacter(aCh)) { + return do_AddRef(aPrevMatchedFont); + } + } + + // To optimize common cases, try the first font in the font-group + // before going into the more detailed checks below + uint32_t fontListLength = mFonts.Length(); + uint32_t nextIndex = 0; + bool isJoinControl = gfxFontUtils::IsJoinControl(aCh); + bool wasJoinCauser = gfxFontUtils::IsJoinCauser(aPrevCh); + bool isVarSelector = gfxFontUtils::IsVarSelector(aCh); + bool nextIsVarSelector = gfxFontUtils::IsVarSelector(aNextCh); + + // For Unicode hyphens, if not supported in the font then we'll try for + // the ASCII hyphen-minus as a fallback. + // Similarly, for NBSP we try normal <space> as a fallback. + uint32_t fallbackChar = (aCh == 0x2010 || aCh == 0x2011) ? '-' + : (aCh == 0x00A0) ? ' ' + : 0; + + // Whether we've seen a font that is currently loading a resource that may + // provide this character (so we should not start a new load). + bool loading = false; + + // Do we need to explicitly look for a font that does or does not provide a + // color glyph for the given character? + // For characters with no `EMOJI` property, we'll use whatever the family + // list calls for; but if it's a potential emoji codepoint, we need to check + // if there's a variation selector specifically asking for Text-style or + // Emoji-style rendering and look for a suitable font. + eFontPresentation presentation = eFontPresentation::Any; + EmojiPresentation emojiPresentation = GetEmojiPresentation(aCh); + if (emojiPresentation != TextOnly) { + // Default presentation from the font-variant-emoji property. + presentation = mEmojiPresentation; + // If the prefer-emoji selector is present, or if it's a default-emoji + // char and the prefer-text selector is NOT present, or if there's a + // skin-tone modifier, we specifically look for a font with a color + // glyph. + // If the prefer-text selector is present, we specifically look for a + // font that will provide a monochrome glyph. + // Otherwise, we'll accept either color or monochrome font-family + // entries, so that a color font can be explicitly applied via font- + // family even to characters that are not inherently emoji-style. + if (aNextCh == kVariationSelector16 || + (aNextCh >= kEmojiSkinToneFirst && aNextCh <= kEmojiSkinToneLast) || + gfxFontUtils::IsEmojiFlagAndTag(aCh, aNextCh)) { + // Emoji presentation is explicitly requested by a variation selector + // or the presence of a skin-tone codepoint. + presentation = eFontPresentation::EmojiExplicit; + } else if (emojiPresentation == EmojiPresentation::EmojiDefault && + aNextCh != kVariationSelector15) { + // Emoji presentation is the default for this Unicode character. but we + // will allow an explicitly-specified webfont to apply to it, + // regardless of its glyph type. + presentation = eFontPresentation::EmojiDefault; + } else if (aNextCh == kVariationSelector15) { + // Text presentation is explicitly requested. + presentation = eFontPresentation::Text; + } + } + + if (!isJoinControl && !wasJoinCauser && !isVarSelector && + !nextIsVarSelector && presentation == eFontPresentation::Any) { + RefPtr<gfxFont> firstFont = GetFontAt(0, aCh, &loading); + if (firstFont) { + if (firstFont->HasCharacter(aCh) || + (fallbackChar && firstFont->HasCharacter(fallbackChar))) { + *aMatchType = {FontMatchType::Kind::kFontGroup, mFonts[0].Generic()}; + return firstFont.forget(); + } + + RefPtr<gfxFont> font; + if (mFonts[0].CheckForFallbackFaces()) { + font = FindFallbackFaceForChar(mFonts[0], aCh, aNextCh, presentation); + } else if (!firstFont->GetFontEntry()->IsUserFont()) { + // For platform fonts (but not userfonts), we may need to do + // fallback within the family to handle cases where some faces + // such as Italic or Black have reduced character sets compared + // to the family's Regular face. + font = FindFallbackFaceForChar(mFonts[0], aCh, aNextCh, presentation); + } + if (font) { + *aMatchType = {FontMatchType::Kind::kFontGroup, mFonts[0].Generic()}; + return font.forget(); + } + } else { + if (fontListLength > 0) { + loading = loading || mFonts[0].IsLoadingFor(aCh); + } + } + + // we don't need to check the first font again below + ++nextIndex; + } + + if (aPrevMatchedFont) { + // Don't switch fonts for control characters, regardless of + // whether they are present in the current font, as they won't + // actually be rendered (see bug 716229) + if (isJoinControl || + GetGeneralCategory(aCh) == HB_UNICODE_GENERAL_CATEGORY_CONTROL) { + return do_AddRef(aPrevMatchedFont); + } + + // if previous character was a join-causer (ZWJ), + // use the same font as the previous range if we can + if (wasJoinCauser) { + if (aPrevMatchedFont->HasCharacter(aCh)) { + return do_AddRef(aPrevMatchedFont); + } + } + } + + // If this character is a variation selector or default-ignorable, use the + // previous font regardless of whether it supports the codepoint or not. + // (We don't want to unnecessarily split glyph runs, and the character will + // not be visibly rendered.) + if (isVarSelector || IsDefaultIgnorable(aCh)) { + return do_AddRef(aPrevMatchedFont); + } + + // Used to remember the first "candidate" font that would provide a fallback + // text-style rendering if no color glyph can be found. + // If we decide NOT to return this font, we must AddRef/Release it to ensure + // that it goes into the global font cache as a candidate for deletion. + // This is done for us by CheckCandidate, but any code path that returns + // WITHOUT calling CheckCandidate needs to handle it explicitly. + RefPtr<gfxFont> candidateFont; + FontMatchType candidateMatchType; + + // Handle a candidate font that could support the character, returning true + // if we should go ahead and return |f|, false to continue searching. + // If there is already a saved candidate font, and the new candidate is + // accepted, we AddRef/Release the existing candidate so it won't leak. + auto CheckCandidate = [&](gfxFont* f, FontMatchType t) -> bool { + // If no preference, then just accept the font. + if (presentation == eFontPresentation::Any || + (presentation == eFontPresentation::EmojiDefault && + f->GetFontEntry()->IsUserFont())) { + *aMatchType = t; + return true; + } + // Does the candidate font provide a color glyph for the current character? + bool hasColorGlyph = f->HasColorGlyphFor(aCh, aNextCh); + // If the provided glyph matches the preference, accept the font. + if (hasColorGlyph == PrefersColor(presentation)) { + *aMatchType = t; + return true; + } + // If the character was a TextDefault char, but the next char is VS16, + // and the font is a COLR font that supports both these codepoints, then + // we'll assume it knows what it is doing (eg Twemoji Mozilla keycap + // sequences). + // TODO: reconsider all this as part of any fix for bug 543200. + if (aNextCh == kVariationSelector16 && emojiPresentation == TextDefault && + f->HasCharacter(aNextCh) && f->GetFontEntry()->TryGetColorGlyphs()) { + return true; + } + // Otherwise, remember the first potential fallback, but keep searching. + if (!candidateFont) { + candidateFont = f; + candidateMatchType = t; + } + return false; + }; + + // 1. check remaining fonts in the font group + for (uint32_t i = nextIndex; i < fontListLength; i++) { + FamilyFace& ff = mFonts[i]; + if (ff.IsInvalid() || ff.IsLoading()) { + if (ff.IsLoadingFor(aCh)) { + loading = true; + } + continue; + } + + RefPtr<gfxFont> font = ff.Font(); + if (font) { + // if available, use already-made gfxFont and check for character + if (font->HasCharacter(aCh) || + (fallbackChar && font->HasCharacter(fallbackChar))) { + if (CheckCandidate(font, + {FontMatchType::Kind::kFontGroup, ff.Generic()})) { + return font.forget(); + } + } + } else { + // don't have a gfxFont yet, test charmap before instantiating + gfxFontEntry* fe = ff.FontEntry(); + if (fe && fe->mIsUserFontContainer) { + // for userfonts, need to test both the unicode range map and + // the cmap of the platform font entry + gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe); + + // never match a character outside the defined unicode range + if (!ufe->CharacterInUnicodeRange(aCh)) { + continue; + } + + // Load if not already loaded, unless we've already seen an in- + // progress load that is expected to satisfy this request. + if (!loading && + ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED) { + ufe->Load(); + ff.CheckState(mSkipDrawing); + } + + if (ff.IsLoading()) { + loading = true; + } + + gfxFontEntry* pfe = ufe->GetPlatformFontEntry(); + if (pfe && (pfe->HasCharacter(aCh) || + (fallbackChar && pfe->HasCharacter(fallbackChar)))) { + font = GetFontAt(i, aCh, &loading); + if (font) { + if (CheckCandidate(font, {FontMatchType::Kind::kFontGroup, + mFonts[i].Generic()})) { + return font.forget(); + } + } + } + } else if (fe && (fe->HasCharacter(aCh) || + (fallbackChar && fe->HasCharacter(fallbackChar)))) { + // for normal platform fonts, after checking the cmap + // build the font via GetFontAt + font = GetFontAt(i, aCh, &loading); + if (font) { + if (CheckCandidate(font, {FontMatchType::Kind::kFontGroup, + mFonts[i].Generic()})) { + return font.forget(); + } + } + } + } + + // check other family faces if needed + if (ff.CheckForFallbackFaces()) { +#ifdef DEBUG + if (i > 0) { + fontlist::FontList* list = + gfxPlatformFontList::PlatformFontList()->SharedFontList(); + nsCString s1 = mFonts[i - 1].IsSharedFamily() + ? mFonts[i - 1].SharedFamily()->Key().AsString(list) + : mFonts[i - 1].OwnedFamily()->Name(); + nsCString s2 = ff.IsSharedFamily() + ? ff.SharedFamily()->Key().AsString(list) + : ff.OwnedFamily()->Name(); + MOZ_ASSERT(!mFonts[i - 1].CheckForFallbackFaces() || !s1.Equals(s2), + "should only do fallback once per font family"); + } +#endif + font = FindFallbackFaceForChar(ff, aCh, aNextCh, presentation); + if (font) { + if (CheckCandidate(font, + {FontMatchType::Kind::kFontGroup, ff.Generic()})) { + return font.forget(); + } + } + } else { + // For platform fonts, but not user fonts, consider intra-family + // fallback to handle styles with reduced character sets (see + // also above). + gfxFontEntry* fe = ff.FontEntry(); + if (fe && !fe->mIsUserFontContainer && !fe->IsUserFont()) { + font = FindFallbackFaceForChar(ff, aCh, aNextCh, presentation); + if (font) { + if (CheckCandidate(font, + {FontMatchType::Kind::kFontGroup, ff.Generic()})) { + return font.forget(); + } + } + } + } + } + + if (fontListLength == 0) { + RefPtr<gfxFont> defaultFont = GetDefaultFont(); + if (defaultFont->HasCharacter(aCh) || + (fallbackChar && defaultFont->HasCharacter(fallbackChar))) { + if (CheckCandidate(defaultFont, FontMatchType::Kind::kFontGroup)) { + return defaultFont.forget(); + } + } + } + + // If character is in Private Use Area, or is unassigned in Unicode, don't do + // matching against pref or system fonts. We only support such codepoints + // when used with an explicitly-specified font, as they have no standard/ + // interoperable meaning. + // Also don't attempt any fallback for control characters or noncharacters, + // where we won't be rendering a glyph anyhow, or for codepoints where global + // fallback has already noted a failure. + FontVisibility level = + mPresContext ? mPresContext->GetFontVisibility() : FontVisibility::User; + auto* pfl = gfxPlatformFontList::PlatformFontList(); + if (pfl->SkipFontFallbackForChar(level, aCh) || + (!StaticPrefs::gfx_font_rendering_fallback_unassigned_chars() && + GetGeneralCategory(aCh) == HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED)) { + if (candidateFont) { + *aMatchType = candidateMatchType; + } + return candidateFont.forget(); + } + + // 2. search pref fonts + RefPtr<gfxFont> font = WhichPrefFontSupportsChar(aCh, aNextCh, presentation); + if (font) { + if (PrefersColor(presentation) && pfl->EmojiPrefHasUserValue()) { + // For emoji, always accept the font from preferences if it's explicitly + // user-set, even if it isn't actually a color-emoji font, as some users + // may want to set their emoji font preference to a monochrome font like + // Symbola. + // So a user-provided font.name-list.emoji preference takes precedence + // over the Unicode presentation style here. + RefPtr<gfxFont> autoRefDeref(candidateFont); + *aMatchType = FontMatchType::Kind::kPrefsFallback; + return font.forget(); + } + if (CheckCandidate(font, FontMatchType::Kind::kPrefsFallback)) { + return font.forget(); + } + } + + // For fallback searches, we don't want to use a color-emoji font unless + // emoji-style presentation is specifically required, so we map Any to + // Text here. + if (presentation == eFontPresentation::Any) { + presentation = eFontPresentation::Text; + } + + // 3. use fallback fonts + // -- before searching for something else check the font used for the + // previous character + if (aPrevMatchedFont && + (aPrevMatchedFont->HasCharacter(aCh) || + (fallbackChar && aPrevMatchedFont->HasCharacter(fallbackChar)))) { + if (CheckCandidate(aPrevMatchedFont, + FontMatchType::Kind::kSystemFallback)) { + return do_AddRef(aPrevMatchedFont); + } + } + + // for known "space" characters, don't do a full system-fallback search; + // we'll synthesize appropriate-width spaces instead of missing-glyph boxes + font = GetFirstValidFont(); + if (GetGeneralCategory(aCh) == HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR && + font->SynthesizeSpaceWidth(aCh) >= 0.0) { + return nullptr; + } + + // -- otherwise look for other stuff + font = WhichSystemFontSupportsChar(aCh, aNextCh, aRunScript, presentation); + if (font) { + if (CheckCandidate(font, FontMatchType::Kind::kSystemFallback)) { + return font.forget(); + } + } + if (candidateFont) { + *aMatchType = candidateMatchType; + } + return candidateFont.forget(); +} + +template <typename T> +void gfxFontGroup::ComputeRanges(nsTArray<TextRange>& aRanges, const T* aString, + uint32_t aLength, Script aRunScript, + gfx::ShapedTextFlags aOrientation) { + NS_ASSERTION(aRanges.Length() == 0, "aRanges must be initially empty"); + NS_ASSERTION(aLength > 0, "don't call ComputeRanges for zero-length text"); + + uint32_t prevCh = 0; + uint32_t nextCh = aString[0]; + if constexpr (sizeof(T) == sizeof(char16_t)) { + if (aLength > 1 && NS_IS_SURROGATE_PAIR(nextCh, aString[1])) { + nextCh = SURROGATE_TO_UCS4(nextCh, aString[1]); + } + } + int32_t lastRangeIndex = -1; + + // initialize prevFont to the group's primary font, so that this will be + // used for string-initial control chars, etc rather than risk hitting font + // fallback for these (bug 716229) + StyleGenericFontFamily generic = StyleGenericFontFamily::None; + RefPtr<gfxFont> prevFont = GetFirstValidFont(' ', &generic); + + // if we use the initial value of prevFont, we treat this as a match from + // the font group; fixes bug 978313 + FontMatchType matchType = {FontMatchType::Kind::kFontGroup, generic}; + + for (uint32_t i = 0; i < aLength; i++) { + const uint32_t origI = i; // save off in case we increase for surrogate + + // set up current ch + uint32_t ch = nextCh; + + // Get next char (if any) so that FindFontForChar can look ahead + // for a possible variation selector. + + if constexpr (sizeof(T) == sizeof(char16_t)) { + // In 16-bit case only, check for surrogate pairs. + if (ch > 0xffffu) { + i++; + } + if (i < aLength - 1) { + nextCh = aString[i + 1]; + if (i + 2 < aLength && NS_IS_SURROGATE_PAIR(nextCh, aString[i + 2])) { + nextCh = SURROGATE_TO_UCS4(nextCh, aString[i + 2]); + } + } else { + nextCh = 0; + } + } else { + // 8-bit case is trivial. + nextCh = i < aLength - 1 ? aString[i + 1] : 0; + } + + RefPtr<gfxFont> font; + + // Find the font for this char; but try to avoid calling the expensive + // FindFontForChar method for the most common case, where the first + // font in the list supports the current char, and it is not one of + // the special cases where FindFontForChar will attempt to propagate + // the font selected for an adjacent character. + if ((font = GetFontAt(0, ch)) != nullptr && font->HasCharacter(ch) && + (sizeof(T) == sizeof(uint8_t) || + (!IsClusterExtender(ch) && ch != NARROW_NO_BREAK_SPACE && + !gfxFontUtils::IsJoinControl(ch) && + !gfxFontUtils::IsJoinCauser(prevCh) && + !gfxFontUtils::IsVarSelector(ch) && + GetEmojiPresentation(ch) == TextOnly))) { + matchType = {FontMatchType::Kind::kFontGroup, mFonts[0].Generic()}; + } else { + font = + FindFontForChar(ch, prevCh, nextCh, aRunScript, prevFont, &matchType); + } + +#ifndef RELEASE_OR_BETA + if (MOZ_UNLIKELY(mTextPerf)) { + if (matchType.kind == FontMatchType::Kind::kPrefsFallback) { + mTextPerf->current.fallbackPrefs++; + } else if (matchType.kind == FontMatchType::Kind::kSystemFallback) { + mTextPerf->current.fallbackSystem++; + } + } +#endif + + prevCh = ch; + + ShapedTextFlags orient = aOrientation; + if (aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) { + // For CSS text-orientation:mixed, we need to resolve orientation + // on a per-character basis using the UTR50 orientation property. + switch (GetVerticalOrientation(ch)) { + case VERTICAL_ORIENTATION_U: + case VERTICAL_ORIENTATION_Tu: + orient = ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT; + break; + case VERTICAL_ORIENTATION_Tr: { + // We check for a vertical presentation form first as that's + // likely to be cheaper than inspecting lookups to see if the + // 'vert' feature is going to handle this character, and if the + // presentation form is available then it will be used as + // fallback if needed, so it's OK if the feature is missing. + // + // Because "common" CJK punctuation characters in isolation will be + // resolved to Bopomofo script (as the first script listed in their + // ScriptExtensions property), but this is not always well supported + // by fonts' OpenType tables, we also try Han script; harfbuzz will + // apply a 'vert' feature from any available script (see + // https://github.com/harfbuzz/harfbuzz/issues/63) when shaping, + // so this is OK. It's not quite as general as what harfbuzz does + // (it will find the feature in *any* script), but should be enough + // for likely real-world examples. + uint32_t v = gfxHarfBuzzShaper::GetVerticalPresentationForm(ch); + const uint32_t kVert = HB_TAG('v', 'e', 'r', 't'); + orient = (!font || (v && font->HasCharacter(v)) || + font->FeatureWillHandleChar(aRunScript, kVert, ch) || + (aRunScript == Script::BOPOMOFO && + font->FeatureWillHandleChar(Script::HAN, kVert, ch))) + ? ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT + : ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; + break; + } + case VERTICAL_ORIENTATION_R: + orient = ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; + break; + } + } + + if (lastRangeIndex == -1) { + // first char ==> make a new range + aRanges.AppendElement(TextRange(0, 1, font, matchType, orient)); + lastRangeIndex++; + prevFont = std::move(font); + } else { + // if font or orientation has changed, make a new range... + // unless ch is a variation selector (bug 1248248) + TextRange& prevRange = aRanges[lastRangeIndex]; + if (prevRange.font != font || + (prevRange.orientation != orient && !IsClusterExtender(ch))) { + // close out the previous range + prevRange.end = origI; + aRanges.AppendElement(TextRange(origI, i + 1, font, matchType, orient)); + lastRangeIndex++; + + // update prevFont for the next match, *unless* we switched + // fonts on a ZWJ, in which case propagating the changed font + // is probably not a good idea (see bug 619511) + if (sizeof(T) == sizeof(uint8_t) || !gfxFontUtils::IsJoinCauser(ch)) { + prevFont = std::move(font); + } + } else { + prevRange.matchType |= matchType; + } + } + } + + aRanges[lastRangeIndex].end = aLength; + +#ifndef RELEASE_OR_BETA + LogModule* log = mStyle.systemFont ? gfxPlatform::GetLog(eGfxLog_textrunui) + : gfxPlatform::GetLog(eGfxLog_textrun); + + if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Debug))) { + nsAutoCString lang; + mLanguage->ToUTF8String(lang); + auto defaultLanguageGeneric = GetDefaultGeneric(mLanguage); + + // collect the font matched for each range + nsAutoCString fontMatches; + for (size_t i = 0, i_end = aRanges.Length(); i < i_end; i++) { + const TextRange& r = aRanges[i]; + nsAutoCString matchTypes; + if (r.matchType.kind & FontMatchType::Kind::kFontGroup) { + matchTypes.AppendLiteral("list"); + } + if (r.matchType.kind & FontMatchType::Kind::kPrefsFallback) { + if (!matchTypes.IsEmpty()) { + matchTypes.AppendLiteral(","); + } + matchTypes.AppendLiteral("prefs"); + } + if (r.matchType.kind & FontMatchType::Kind::kSystemFallback) { + if (!matchTypes.IsEmpty()) { + matchTypes.AppendLiteral(","); + } + matchTypes.AppendLiteral("sys"); + } + fontMatches.AppendPrintf( + " [%u:%u] %.200s (%s)", r.start, r.end, + (r.font.get() ? r.font->GetName().get() : "<null>"), + matchTypes.get()); + } + MOZ_LOG(log, LogLevel::Debug, + ("(%s-fontmatching) fontgroup: [%s] default: %s lang: %s script: %d" + "%s\n", + (mStyle.systemFont ? "textrunui" : "textrun"), + FamilyListToString(mFamilyList).get(), + (defaultLanguageGeneric == StyleGenericFontFamily::Serif + ? "serif" + : (defaultLanguageGeneric == StyleGenericFontFamily::SansSerif + ? "sans-serif" + : "none")), + lang.get(), static_cast<int>(aRunScript), fontMatches.get())); + } +#endif +} + +gfxUserFontSet* gfxFontGroup::GetUserFontSet() { return mUserFontSet; } + +void gfxFontGroup::SetUserFontSet(gfxUserFontSet* aUserFontSet) { + if (aUserFontSet == mUserFontSet) { + return; + } + mUserFontSet = aUserFontSet; + mCurrGeneration = GetGeneration() - 1; + UpdateUserFonts(); +} + +uint64_t gfxFontGroup::GetGeneration() { + if (!mUserFontSet) return 0; + return mUserFontSet->GetGeneration(); +} + +uint64_t gfxFontGroup::GetRebuildGeneration() { + if (!mUserFontSet) return 0; + return mUserFontSet->GetRebuildGeneration(); +} + +void gfxFontGroup::UpdateUserFonts() { + if (mCurrGeneration < GetRebuildGeneration()) { + // fonts in userfont set changed, need to redo the fontlist + mFonts.Clear(); + ClearCachedData(); + BuildFontList(); + mCurrGeneration = GetGeneration(); + } else if (mCurrGeneration != GetGeneration()) { + // load state change occurred, verify load state and validity of fonts + ClearCachedData(); + + uint32_t len = mFonts.Length(); + for (uint32_t i = 0; i < len; i++) { + FamilyFace& ff = mFonts[i]; + if (ff.Font() || !ff.IsUserFontContainer()) { + continue; + } + ff.CheckState(mSkipDrawing); + } + + mCurrGeneration = GetGeneration(); + } +} + +bool gfxFontGroup::ContainsUserFont(const gfxUserFontEntry* aUserFont) { + UpdateUserFonts(); + // search through the fonts list for a specific user font + uint32_t len = mFonts.Length(); + for (uint32_t i = 0; i < len; i++) { + FamilyFace& ff = mFonts[i]; + if (ff.EqualsUserFont(aUserFont)) { + return true; + } + } + return false; +} + +already_AddRefed<gfxFont> gfxFontGroup::WhichPrefFontSupportsChar( + uint32_t aCh, uint32_t aNextCh, eFontPresentation aPresentation) { + eFontPrefLang charLang; + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + + if (PrefersColor(aPresentation)) { + charLang = eFontPrefLang_Emoji; + } else { + // get the pref font list if it hasn't been set up already + charLang = pfl->GetFontPrefLangFor(aCh); + } + + // if the last pref font was the first family in the pref list, no need to + // recheck through a list of families + if (mLastPrefFont && charLang == mLastPrefLang && mLastPrefFirstFont && + mLastPrefFont->HasCharacter(aCh)) { + return do_AddRef(mLastPrefFont); + } + + // based on char lang and page lang, set up list of pref lang fonts to check + eFontPrefLang prefLangs[kMaxLenPrefLangList]; + uint32_t i, numLangs = 0; + + pfl->GetLangPrefs(prefLangs, numLangs, charLang, mPageLang); + + for (i = 0; i < numLangs; i++) { + eFontPrefLang currentLang = prefLangs[i]; + StyleGenericFontFamily generic = + mFallbackGeneric != StyleGenericFontFamily::None + ? mFallbackGeneric + : pfl->GetDefaultGeneric(currentLang); + gfxPlatformFontList::PrefFontList* families = + pfl->GetPrefFontsLangGroup(mPresContext, generic, currentLang); + NS_ASSERTION(families, "no pref font families found"); + + // find the first pref font that includes the character + uint32_t j, numPrefs; + numPrefs = families->Length(); + for (j = 0; j < numPrefs; j++) { + // look up the appropriate face + FontFamily family = (*families)[j]; + if (family.IsNull()) { + continue; + } + + // if a pref font is used, it's likely to be used again in the same text + // run. the style doesn't change so the face lookup can be cached rather + // than calling FindOrMakeFont repeatedly. speeds up FindFontForChar + // lookup times for subsequent pref font lookups + if (family == mLastPrefFamily && mLastPrefFont->HasCharacter(aCh)) { + return do_AddRef(mLastPrefFont); + } + + gfxFontEntry* fe = nullptr; + if (family.mShared) { + fontlist::Family* fam = family.mShared; + if (!fam->IsInitialized()) { + Unused << pfl->InitializeFamily(fam); + } + fontlist::Face* face = + fam->FindFaceForStyle(pfl->SharedFontList(), mStyle); + if (face) { + fe = pfl->GetOrCreateFontEntry(face, fam); + } + } else { + fe = family.mUnshared->FindFontForStyle(mStyle); + } + if (!fe) { + continue; + } + + // if ch in cmap, create and return a gfxFont + RefPtr<gfxFont> prefFont; + if (fe->HasCharacter(aCh)) { + prefFont = fe->FindOrMakeFont(&mStyle); + if (!prefFont) { + continue; + } + if (aPresentation == eFontPresentation::EmojiExplicit && + !prefFont->HasColorGlyphFor(aCh, aNextCh)) { + continue; + } + } + + // If the char was not available, see if we can fall back to an + // alternative face in the same family. + if (!prefFont) { + prefFont = family.mShared + ? FindFallbackFaceForChar(family.mShared, aCh, aNextCh, + aPresentation) + : FindFallbackFaceForChar(family.mUnshared, aCh, aNextCh, + aPresentation); + } + if (prefFont) { + mLastPrefFamily = family; + mLastPrefFont = prefFont; + mLastPrefLang = charLang; + mLastPrefFirstFont = (i == 0 && j == 0); + return prefFont.forget(); + } + } + } + + return nullptr; +} + +already_AddRefed<gfxFont> gfxFontGroup::WhichSystemFontSupportsChar( + uint32_t aCh, uint32_t aNextCh, Script aRunScript, + eFontPresentation aPresentation) { + FontVisibility visibility; + return gfxPlatformFontList::PlatformFontList()->SystemFindFontForChar( + mPresContext, aCh, aNextCh, aRunScript, aPresentation, &mStyle, + &visibility); +} + +gfxFont::Metrics gfxFontGroup::GetMetricsForCSSUnits( + gfxFont::Orientation aOrientation) { + bool isFirst; + RefPtr<gfxFont> font = GetFirstValidFont(0x20, nullptr, &isFirst); + auto metrics = font->GetMetrics(aOrientation); + + // If the font we used to get metrics was not the first in the list, + // or if it doesn't support the ZERO character, check for the font that + // does support ZERO and use its metrics for the 'ch' unit. + if (!isFirst || !font->HasCharacter('0')) { + RefPtr<gfxFont> zeroFont = GetFirstValidFont('0'); + if (zeroFont != font) { + const auto& zeroMetrics = zeroFont->GetMetrics(aOrientation); + metrics.zeroWidth = zeroMetrics.zeroWidth; + } + } + + // Likewise for the WATER ideograph character used as the basis for 'ic'. + if (!isFirst || !font->HasCharacter(0x6C34)) { + RefPtr<gfxFont> icFont = GetFirstValidFont(0x6C34); + if (icFont != font) { + const auto& icMetrics = icFont->GetMetrics(aOrientation); + metrics.ideographicWidth = icMetrics.ideographicWidth; + } + } + + return metrics; +} + +void gfxMissingFontRecorder::Flush() { + static bool mNotifiedFontsInitialized = false; + static uint32_t mNotifiedFonts[gfxMissingFontRecorder::kNumScriptBitsWords]; + if (!mNotifiedFontsInitialized) { + memset(&mNotifiedFonts, 0, sizeof(mNotifiedFonts)); + mNotifiedFontsInitialized = true; + } + + nsAutoString fontNeeded; + for (uint32_t i = 0; i < kNumScriptBitsWords; ++i) { + mMissingFonts[i] &= ~mNotifiedFonts[i]; + if (!mMissingFonts[i]) { + continue; + } + for (uint32_t j = 0; j < 32; ++j) { + if (!(mMissingFonts[i] & (1 << j))) { + continue; + } + mNotifiedFonts[i] |= (1 << j); + if (!fontNeeded.IsEmpty()) { + fontNeeded.Append(char16_t(',')); + } + uint32_t sc = i * 32 + j; + MOZ_ASSERT(sc < static_cast<uint32_t>(Script::NUM_SCRIPT_CODES), + "how did we set the bit for an invalid script code?"); + uint32_t tag = GetScriptTagForCode(static_cast<Script>(sc)); + fontNeeded.Append(char16_t(tag >> 24)); + fontNeeded.Append(char16_t((tag >> 16) & 0xff)); + fontNeeded.Append(char16_t((tag >> 8) & 0xff)); + fontNeeded.Append(char16_t(tag & 0xff)); + } + mMissingFonts[i] = 0; + } + if (!fontNeeded.IsEmpty()) { + nsCOMPtr<nsIObserverService> service = GetObserverService(); + service->NotifyObservers(nullptr, "font-needed", fontNeeded.get()); + } +} diff --git a/gfx/thebes/gfxTextRun.h b/gfx/thebes/gfxTextRun.h new file mode 100644 index 0000000000..770370c9b1 --- /dev/null +++ b/gfx/thebes/gfxTextRun.h @@ -0,0 +1,1545 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=4 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_TEXTRUN_H +#define GFX_TEXTRUN_H + +#include <stdint.h> + +#include "gfxTypes.h" +#include "gfxPoint.h" +#include "gfxFont.h" +#include "gfxFontConstants.h" +#include "gfxSkipChars.h" +#include "gfxPlatform.h" +#include "gfxPlatformFontList.h" +#include "gfxUserFontSet.h" +#include "gfxUtils.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefPtr.h" +#include "mozilla/intl/UnicodeScriptCodes.h" +#include "nsPoint.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsTHashSet.h" +#include "nsTextFrameUtils.h" +#include "DrawMode.h" +#include "harfbuzz/hb.h" +#include "nsColor.h" +#include "nsFrameList.h" +#include "X11UndefineNone.h" + +#ifdef DEBUG_FRAME_DUMP +# include <stdio.h> +#endif + +class gfxContext; +class gfxFontGroup; +class nsAtom; +class nsLanguageAtomService; +class gfxMissingFontRecorder; + +namespace mozilla { +class PostTraversalTask; +class SVGContextPaint; +enum class StyleHyphens : uint8_t; +}; // namespace mozilla + +/** + * Callback for Draw() to use when drawing text with mode + * DrawMode::GLYPH_PATH. + */ +struct MOZ_STACK_CLASS gfxTextRunDrawCallbacks { + /** + * Constructs a new DrawCallbacks object. + * + * @param aShouldPaintSVGGlyphs If true, SVG glyphs will be painted. If + * false, SVG glyphs will not be painted; fallback plain glyphs are not + * emitted either. + */ + explicit gfxTextRunDrawCallbacks(bool aShouldPaintSVGGlyphs = false) + : mShouldPaintSVGGlyphs(aShouldPaintSVGGlyphs) {} + + /** + * Called when a path has been emitted to the gfxContext when + * painting a text run. This can be called any number of times, + * due to partial ligatures and intervening SVG glyphs. + */ + virtual void NotifyGlyphPathEmitted() = 0; + + bool mShouldPaintSVGGlyphs; +}; + +/** + * gfxTextRun is an abstraction for drawing and measuring substrings of a run + * of text. It stores runs of positioned glyph data, each run having a single + * gfxFont. The glyphs are associated with a string of source text, and the + * gfxTextRun APIs take parameters that are offsets into that source text. + * + * gfxTextRuns are mostly immutable. The only things that can change are + * inter-cluster spacing and line break placement. Spacing is always obtained + * lazily by methods that need it, it is not cached. Line breaks are stored + * persistently (insofar as they affect the shaping of glyphs; gfxTextRun does + * not actually do anything to explicitly account for line breaks). Initially + * there are no line breaks. The textrun can record line breaks before or after + * any given cluster. (Line breaks specified inside clusters are ignored.) + * + * It is important that zero-length substrings are handled correctly. This will + * be on the test! + */ +class gfxTextRun : public gfxShapedText { + NS_INLINE_DECL_REFCOUNTING(gfxTextRun); + + protected: + // Override operator delete to properly free the object that was + // allocated via malloc. + void operator delete(void* p) { free(p); } + + virtual ~gfxTextRun(); + + public: + typedef gfxFont::RunMetrics Metrics; + typedef mozilla::gfx::DrawTarget DrawTarget; + + // Public textrun API for general use + + bool IsClusterStart(uint32_t aPos) const { + MOZ_ASSERT(aPos < GetLength()); + return mCharacterGlyphs[aPos].IsClusterStart(); + } + bool IsLigatureGroupStart(uint32_t aPos) const { + MOZ_ASSERT(aPos < GetLength()); + return mCharacterGlyphs[aPos].IsLigatureGroupStart(); + } + bool CanBreakLineBefore(uint32_t aPos) const { + return CanBreakBefore(aPos) == CompressedGlyph::FLAG_BREAK_TYPE_NORMAL; + } + bool CanHyphenateBefore(uint32_t aPos) const { + return CanBreakBefore(aPos) == CompressedGlyph::FLAG_BREAK_TYPE_HYPHEN; + } + + // Returns a gfxShapedText::CompressedGlyph::FLAG_BREAK_TYPE_* value + // as defined in gfxFont.h (may be NONE, NORMAL or HYPHEN). + uint8_t CanBreakBefore(uint32_t aPos) const { + MOZ_ASSERT(aPos < GetLength()); + return mCharacterGlyphs[aPos].CanBreakBefore(); + } + + bool CharIsSpace(uint32_t aPos) const { + MOZ_ASSERT(aPos < GetLength()); + return mCharacterGlyphs[aPos].CharIsSpace(); + } + bool CharIsTab(uint32_t aPos) const { + MOZ_ASSERT(aPos < GetLength()); + return mCharacterGlyphs[aPos].CharIsTab(); + } + bool CharIsNewline(uint32_t aPos) const { + MOZ_ASSERT(aPos < GetLength()); + return mCharacterGlyphs[aPos].CharIsNewline(); + } + bool CharMayHaveEmphasisMark(uint32_t aPos) const { + MOZ_ASSERT(aPos < GetLength()); + return mCharacterGlyphs[aPos].CharMayHaveEmphasisMark(); + } + bool CharIsFormattingControl(uint32_t aPos) const { + MOZ_ASSERT(aPos < GetLength()); + return mCharacterGlyphs[aPos].CharIsFormattingControl(); + } + + // All offsets are in terms of the string passed into MakeTextRun. + + // Describe range [start, end) of a text run. The range is + // restricted to grapheme cluster boundaries. + struct Range { + uint32_t start; + uint32_t end; + uint32_t Length() const { return end - start; } + + Range() : start(0), end(0) {} + Range(uint32_t aStart, uint32_t aEnd) : start(aStart), end(aEnd) {} + explicit Range(const gfxTextRun* aTextRun) + : start(0), end(aTextRun->GetLength()) {} + }; + + // All coordinates are in layout/app units + + /** + * Set the potential linebreaks for a substring of the textrun. These are + * the "allow break before" points. Initially, there are no potential + * linebreaks. + * + * This can change glyphs and/or geometry! Some textruns' shapes + * depend on potential line breaks (e.g., title-case-converting textruns). + * This function is virtual so that those textruns can reshape themselves. + * + * @return true if this changed the linebreaks, false if the new line + * breaks are the same as the old + */ + virtual bool SetPotentialLineBreaks(Range aRange, + const uint8_t* aBreakBefore); + + enum class HyphenType : uint8_t { + // Code in BreakAndMeasureText depends on the ordering of these values! + None, + Explicit, + Soft, + AutoWithManualInSameWord, + AutoWithoutManualInSameWord + }; + + static bool IsOptionalHyphenBreak(HyphenType aType) { + return aType >= HyphenType::Soft; + } + + struct HyphenationState { + uint32_t mostRecentBoundary = 0; + bool hasManualHyphen = false; + bool hasExplicitHyphen = false; + bool hasAutoHyphen = false; + }; + + /** + * Layout provides PropertyProvider objects. These allow detection of + * potential line break points and computation of spacing. We pass the data + * this way to allow lazy data acquisition; for example BreakAndMeasureText + * will want to only ask for properties of text it's actually looking at. + * + * NOTE that requested spacing may not actually be applied, if the textrun + * is unable to apply it in some context. Exception: spacing around a + * whitespace character MUST always be applied. + */ + class PropertyProvider { + public: + // Detect hyphenation break opportunities in the given range; breaks + // not at cluster boundaries will be ignored. + virtual void GetHyphenationBreaks(Range aRange, + HyphenType* aBreakBefore) const = 0; + + // Returns the provider's hyphenation setting, so callers can decide + // whether it is necessary to call GetHyphenationBreaks. + // Result is an StyleHyphens value. + virtual mozilla::StyleHyphens GetHyphensOption() const = 0; + + // Returns the extra width that will be consumed by a hyphen. This should + // be constant for a given textrun. + virtual gfxFloat GetHyphenWidth() const = 0; + + // Return orientation flags to be used when creating a hyphen textrun. + virtual mozilla::gfx::ShapedTextFlags GetShapedTextFlags() const = 0; + + typedef gfxFont::Spacing Spacing; + + /** + * Get the spacing around the indicated characters. Spacing must be zero + * inside clusters. In other words, if character i is not + * CLUSTER_START, then character i-1 must have zero after-spacing and + * character i must have zero before-spacing. + */ + virtual void GetSpacing(Range aRange, Spacing* aSpacing) const = 0; + + // Returns a gfxContext that can be used to measure the hyphen glyph. + // Only called if the hyphen width is requested. + virtual already_AddRefed<DrawTarget> GetDrawTarget() const = 0; + + // Return the appUnitsPerDevUnit value to be used when measuring. + // Only called if the hyphen width is requested. + virtual uint32_t GetAppUnitsPerDevUnit() const = 0; + }; + + struct MOZ_STACK_CLASS DrawParams { + gfxContext* context; + mozilla::gfx::PaletteCache& paletteCache; + DrawMode drawMode = DrawMode::GLYPH_FILL; + nscolor textStrokeColor = 0; + nsAtom* fontPalette = nullptr; + gfxPattern* textStrokePattern = nullptr; + const mozilla::gfx::StrokeOptions* strokeOpts = nullptr; + const mozilla::gfx::DrawOptions* drawOpts = nullptr; + const PropertyProvider* provider = nullptr; + // If non-null, the advance width of the substring is set. + gfxFloat* advanceWidth = nullptr; + mozilla::SVGContextPaint* contextPaint = nullptr; + gfxTextRunDrawCallbacks* callbacks = nullptr; + bool allowGDI = true; + bool hasTextShadow = false; + DrawParams(gfxContext* aContext, mozilla::gfx::PaletteCache& aPaletteCache) + : context(aContext), paletteCache(aPaletteCache) {} + }; + + /** + * Draws a substring. Uses only GetSpacing from aBreakProvider. + * The provided point is the baseline origin on the left of the string + * for LTR, on the right of the string for RTL. + * + * Drawing should respect advance widths in the sense that for LTR runs, + * Draw(Range(start, middle), pt, ...) followed by + * Draw(Range(middle, end), gfxPoint(pt.x + advance, pt.y), ...) + * should have the same effect as + * Draw(Range(start, end), pt, ...) + * + * For RTL runs the rule is: + * Draw(Range(middle, end), pt, ...) followed by + * Draw(Range(start, middle), gfxPoint(pt.x + advance, pt.y), ...) + * should have the same effect as + * Draw(Range(start, end), pt, ...) + * + * Glyphs should be drawn in logical content order, which can be significant + * if they overlap (perhaps due to negative spacing). + */ + void Draw(const Range aRange, const mozilla::gfx::Point aPt, + const DrawParams& aParams) const; + + /** + * Draws the emphasis marks for this text run. Uses only GetSpacing + * from aProvider. The provided point is the baseline origin of the + * line of emphasis marks. + */ + void DrawEmphasisMarks(gfxContext* aContext, gfxTextRun* aMark, + gfxFloat aMarkAdvance, mozilla::gfx::Point aPt, + Range aRange, const PropertyProvider* aProvider, + mozilla::gfx::PaletteCache& aPaletteCache) const; + + /** + * Computes the ReflowMetrics for a substring. + * Uses GetSpacing from aBreakProvider. + * @param aBoundingBoxType which kind of bounding box (loose/tight) + */ + Metrics MeasureText(Range aRange, gfxFont::BoundingBoxType aBoundingBoxType, + DrawTarget* aDrawTargetForTightBoundingBox, + const PropertyProvider* aProvider) const; + + Metrics MeasureText(gfxFont::BoundingBoxType aBoundingBoxType, + DrawTarget* aDrawTargetForTightBoundingBox, + const PropertyProvider* aProvider = nullptr) const { + return MeasureText(Range(this), aBoundingBoxType, + aDrawTargetForTightBoundingBox, aProvider); + } + + void GetLineHeightMetrics(Range aRange, gfxFloat& aAscent, + gfxFloat& aDescent) const; + void GetLineHeightMetrics(gfxFloat& aAscent, gfxFloat& aDescent) const { + GetLineHeightMetrics(Range(this), aAscent, aDescent); + } + + /** + * Computes just the advance width for a substring. + * Uses GetSpacing from aBreakProvider. + * If aSpacing is not null, the spacing attached before and after + * the substring would be returned in it. NOTE: the spacing is + * included in the advance width. + */ + gfxFloat GetAdvanceWidth(Range aRange, const PropertyProvider* aProvider, + PropertyProvider::Spacing* aSpacing = nullptr) const; + + gfxFloat GetAdvanceWidth() const { + return GetAdvanceWidth(Range(this), nullptr); + } + + /** + * Computes the minimum advance width for a substring assuming line + * breaking is allowed everywhere. + */ + gfxFloat GetMinAdvanceWidth(Range aRange); + + /** + * Clear all stored line breaks for the given range (both before and after), + * and then set the line-break state before aRange.start to aBreakBefore and + * after the last cluster to aBreakAfter. + * + * We require that before and after line breaks be consistent. For clusters + * i and i+1, we require that if there is a break after cluster i, a break + * will be specified before cluster i+1. This may be temporarily violated + * (e.g. after reflowing line L and before reflowing line L+1); to handle + * these temporary violations, we say that there is a break betwen i and i+1 + * if a break is specified after i OR a break is specified before i+1. + * + * This can change textrun geometry! The existence of a linebreak can affect + * the advance width of the cluster before the break (when kerning) or the + * geometry of one cluster before the break or any number of clusters + * after the break. (The one-cluster-before-the-break limit is somewhat + * arbitrary; if some scripts require breaking it, then we need to + * alter nsTextFrame::TrimTrailingWhitespace, perhaps drastically becase + * it could affect the layout of frames before it...) + * + * We return true if glyphs or geometry changed, false otherwise. This + * function is virtual so that gfxTextRun subclasses can reshape + * properly. + * + * @param aAdvanceWidthDelta if non-null, returns the change in advance + * width of the given range. + */ + virtual bool SetLineBreaks(Range aRange, bool aLineBreakBefore, + bool aLineBreakAfter, + gfxFloat* aAdvanceWidthDelta); + + enum SuppressBreak { + eNoSuppressBreak, + // Measure the range of text as if there is no break before it. + eSuppressInitialBreak, + // Measure the range of text as if it contains no break + eSuppressAllBreaks + }; + + void ClassifyAutoHyphenations(uint32_t aStart, Range aRange, + nsTArray<HyphenType>& aHyphenBuffer, + HyphenationState* aWordState); + + // Struct used by BreakAndMeasureText to return the amount of trimmable + // trailing whitespace included in the run. + struct TrimmableWS { + mozilla::gfx::Float mAdvance = 0; + uint32_t mCount = 0; + }; + + /** + * Finds the longest substring that will fit into the given width. + * Uses GetHyphenationBreaks and GetSpacing from aProvider. + * Guarantees the following: + * -- 0 <= result <= aMaxLength + * -- result is the maximal value of N such that either + * N < aMaxLength && line break at N && + * GetAdvanceWidth(Range(aStart, N), aProvider) <= aWidth + * OR N < aMaxLength && hyphen break at N && + * GetAdvanceWidth(Range(aStart, N), aProvider) + + * GetHyphenWidth() <= aWidth + * OR N == aMaxLength && + * GetAdvanceWidth(Range(aStart, N), aProvider) <= aWidth + * where GetAdvanceWidth assumes the effect of + * SetLineBreaks(Range(aStart, N), + * aLineBreakBefore, N < aMaxLength, aProvider) + * -- if no such N exists, then result is the smallest N such that + * N < aMaxLength && line break at N + * OR N < aMaxLength && hyphen break at N + * OR N == aMaxLength + * + * The call has the effect of + * SetLineBreaks(Range(aStart, result), aLineBreakBefore, + * result < aMaxLength, aProvider) + * and the returned metrics and the invariants above reflect this. + * + * @param aMaxLength this can be UINT32_MAX, in which case the length used + * is up to the end of the string + * @param aLineBreakBefore set to true if and only if there is an actual + * line break at the start of this string. + * @param aSuppressBreak what break should be suppressed. + * @param aOutTrimmableWhitespace if non-null, returns the advance of any + * run of trailing spaces that might be trimmed if the run ends up at + * end-of-line. + * Trimmable spaces are still counted in the "characters fit" result, and + * contribute to the returned Metrics values. + * @param aOutMetrics we fill this in for the returned substring. + * If a hyphenation break was used, the hyphen is NOT included in the returned + * metrics. + * @param aBoundingBoxType whether to make the bounding box in aMetrics tight + * @param aRefDrawTarget a reference DrawTarget to get the tight bounding box, + * if requested + * @param aOutUsedHyphenation records if we selected a hyphenation break + * @param aOutLastBreak if result is aMaxLength, we set this to + * the maximal N such that + * N < aMaxLength && line break at N && + * GetAdvanceWidth(Range(aStart, N), aProvider) <= aWidth + * OR N < aMaxLength && hyphen break at N && + * GetAdvanceWidth(Range(aStart, N), aProvider) + + * GetHyphenWidth() <= aWidth + * or UINT32_MAX if no such N exists, where GetAdvanceWidth assumes + * the effect of + * SetLineBreaks(Range(aStart, N), aLineBreakBefore, + * N < aMaxLength, aProvider) + * + * @param aCanWordWrap true if we can break between any two grapheme + * clusters. This is set by overflow-wrap|word-wrap: break-word + * + * @param aBreakPriority in/out the priority of the break opportunity + * saved in the line. If we are prioritizing break opportunities, we will + * not set a break with a lower priority. @see gfxBreakPriority. + * + * Note that negative advance widths are possible especially if negative + * spacing is provided. + */ + uint32_t BreakAndMeasureText( + uint32_t aStart, uint32_t aMaxLength, bool aLineBreakBefore, + gfxFloat aWidth, const PropertyProvider& aProvider, + SuppressBreak aSuppressBreak, gfxFont::BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, bool aCanWordWrap, bool aCanWhitespaceWrap, + bool aIsBreakSpaces, + // Output parameters: + TrimmableWS* aOutTrimmableWhitespace, // may be null + Metrics& aOutMetrics, bool& aOutUsedHyphenation, uint32_t& aOutLastBreak, + // In/out: + gfxBreakPriority& aBreakPriority); + + // Utility getters + + void* GetUserData() const { return mUserData; } + void SetUserData(void* aUserData) { mUserData = aUserData; } + + void SetFlagBits(nsTextFrameUtils::Flags aFlags) { mFlags2 |= aFlags; } + void ClearFlagBits(nsTextFrameUtils::Flags aFlags) { mFlags2 &= ~aFlags; } + const gfxSkipChars& GetSkipChars() const { return mSkipChars; } + gfxFontGroup* GetFontGroup() const { return mFontGroup; } + + // Call this, don't call "new gfxTextRun" directly. This does custom + // allocation and initialization + static already_AddRefed<gfxTextRun> Create( + const gfxTextRunFactory::Parameters* aParams, uint32_t aLength, + gfxFontGroup* aFontGroup, mozilla::gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2); + + // The text is divided into GlyphRuns as necessary. (In the vast majority + // of cases, a gfxTextRun contains just a single GlyphRun.) + struct GlyphRun { + RefPtr<gfxFont> mFont; // never null in a valid GlyphRun + uint32_t mCharacterOffset; // into original UTF16 string + mozilla::gfx::ShapedTextFlags + mOrientation; // gfxTextRunFactory::TEXT_ORIENT_* value + FontMatchType mMatchType; + bool mIsCJK; // Whether the text was a CJK script run (used to decide if + // text-decoration-skip-ink should not be applied) + + // Set up the properties (but NOT offset) of the GlyphRun. + void SetProperties(gfxFont* aFont, + mozilla::gfx::ShapedTextFlags aOrientation, bool aIsCJK, + FontMatchType aMatchType) { + mFont = aFont; + mOrientation = aOrientation; + mIsCJK = aIsCJK; + mMatchType = aMatchType; + } + + // Return whether the GlyphRun matches the given properties; + // the given FontMatchType will be added to the run if not present. + bool Matches(gfxFont* aFont, mozilla::gfx::ShapedTextFlags aOrientation, + bool aIsCJK, FontMatchType aMatchType) { + if (mFont == aFont && mOrientation == aOrientation && mIsCJK == aIsCJK) { + mMatchType.kind |= aMatchType.kind; + if (mMatchType.generic == mozilla::StyleGenericFontFamily::None) { + mMatchType.generic = aMatchType.generic; + } + return true; + } + return false; + } + + bool IsSidewaysLeft() const { + return (mOrientation & mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_MASK) == + mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT; + } + + bool IsSidewaysRight() const { + return (mOrientation & mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_MASK) == + mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; + } + }; + + // Script run codes that we will mark as CJK to suppress skip-ink behavior. + static inline bool IsCJKScript(Script aScript) { + switch (aScript) { + case Script::BOPOMOFO: + case Script::HAN: + case Script::HANGUL: + case Script::HIRAGANA: + case Script::KATAKANA: + case Script::KATAKANA_OR_HIRAGANA: + case Script::SIMPLIFIED_HAN: + case Script::TRADITIONAL_HAN: + case Script::JAPANESE: + case Script::KOREAN: + case Script::HAN_WITH_BOPOMOFO: + case Script::JAMO: + return true; + default: + return false; + } + } + + class MOZ_STACK_CLASS GlyphRunIterator { + public: + GlyphRunIterator(const gfxTextRun* aTextRun, Range aRange, + bool aReverse = false) + : mTextRun(aTextRun), + mStartOffset(aRange.start), + mEndOffset(aRange.end), + mReverse(aReverse) { + mGlyphRun = mTextRun->FindFirstGlyphRunContaining( + aReverse ? aRange.end - 1 : aRange.start); + if (!mGlyphRun) { + mStringEnd = mStringStart = mStartOffset; + return; + } + uint32_t glyphRunEndOffset = mGlyphRun == mTextRun->mGlyphRuns.end() - 1 + ? mTextRun->GetLength() + : (mGlyphRun + 1)->mCharacterOffset; + mStringEnd = std::min(mEndOffset, glyphRunEndOffset); + mStringStart = std::max(mStartOffset, mGlyphRun->mCharacterOffset); + } + void NextRun(); + bool AtEnd() const { return mGlyphRun == nullptr; } + const struct GlyphRun* GlyphRun() const { return mGlyphRun; } + uint32_t StringStart() const { return mStringStart; } + uint32_t StringEnd() const { return mStringEnd; } + + private: + const gfxTextRun* mTextRun; + const struct GlyphRun* mGlyphRun; + uint32_t mStringStart; + uint32_t mStringEnd; + uint32_t mStartOffset; + uint32_t mEndOffset; + bool mReverse; + }; + + class GlyphRunOffsetComparator { + public: + bool Equals(const GlyphRun& a, const GlyphRun& b) const { + return a.mCharacterOffset == b.mCharacterOffset; + } + + bool LessThan(const GlyphRun& a, const GlyphRun& b) const { + return a.mCharacterOffset < b.mCharacterOffset; + } + }; + + // API for setting up the textrun glyphs. Should only be called by + // things that construct textruns. + /** + * We've found a run of text that should use a particular font. Call this + * only during initialization when font substitution has been computed. + * Call it before setting up the glyphs for the characters in this run; + * SetMissingGlyph requires that the correct glyphrun be installed. + * + * If aForceNewRun, a new glyph run will be added, even if the + * previously added run uses the same font. If glyph runs are + * added out of strictly increasing aStartCharIndex order (via + * force), then SortGlyphRuns must be called after all glyph runs + * are added before any further operations are performed with this + * TextRun. + */ + void AddGlyphRun(gfxFont* aFont, FontMatchType aMatchType, + uint32_t aUTF16Offset, bool aForceNewRun, + mozilla::gfx::ShapedTextFlags aOrientation, bool aIsCJK); + void ResetGlyphRuns() { mGlyphRuns.Clear(); } + void SanitizeGlyphRuns(); + + const CompressedGlyph* GetCharacterGlyphs() const final { + MOZ_ASSERT(mCharacterGlyphs, "failed to initialize mCharacterGlyphs"); + return mCharacterGlyphs; + } + CompressedGlyph* GetCharacterGlyphs() final { + MOZ_ASSERT(mCharacterGlyphs, "failed to initialize mCharacterGlyphs"); + return mCharacterGlyphs; + } + + // clean out results from shaping in progress, used for fallback scenarios + void ClearGlyphsAndCharacters(); + + void SetSpaceGlyph(gfxFont* aFont, DrawTarget* aDrawTarget, + uint32_t aCharIndex, + mozilla::gfx::ShapedTextFlags aOrientation); + + // Set the glyph data for the given character index to the font's + // space glyph, IF this can be done as a "simple" glyph record + // (not requiring a DetailedGlyph entry). This avoids the need to call + // the font shaper and go through the shaped-word cache for most spaces. + // + // The parameter aSpaceChar is the original character code for which + // this space glyph is being used; if this is U+0020, we need to record + // that it could be trimmed at a run edge, whereas other kinds of space + // (currently just U+00A0) would not be trimmable/breakable. + // + // Returns true if it was able to set simple glyph data for the space; + // if it returns false, the caller needs to fall back to some other + // means to create the necessary (detailed) glyph data. + bool SetSpaceGlyphIfSimple(gfxFont* aFont, uint32_t aCharIndex, + char16_t aSpaceChar, + mozilla::gfx::ShapedTextFlags aOrientation); + + // Record the positions of specific characters that layout may need to + // detect in the textrun, even though it doesn't have an explicit copy + // of the original text. These are recorded using flag bits in the + // CompressedGlyph record; if necessary, we convert "simple" glyph records + // to "complex" ones as the Tab and Newline flags are not present in + // simple CompressedGlyph records. + void SetIsTab(uint32_t aIndex) { EnsureComplexGlyph(aIndex).SetIsTab(); } + void SetIsNewline(uint32_t aIndex) { + EnsureComplexGlyph(aIndex).SetIsNewline(); + } + void SetNoEmphasisMark(uint32_t aIndex) { + EnsureComplexGlyph(aIndex).SetNoEmphasisMark(); + } + void SetIsFormattingControl(uint32_t aIndex) { + EnsureComplexGlyph(aIndex).SetIsFormattingControl(); + } + + /** + * Prefetch all the glyph extents needed to ensure that Measure calls + * on this textrun not requesting tight boundingBoxes will succeed. Note + * that some glyph extents might not be fetched due to OOM or other + * errors. + */ + void FetchGlyphExtents(DrawTarget* aRefDrawTarget) const; + + const GlyphRun* GetGlyphRuns(uint32_t* aNumGlyphRuns) const { + *aNumGlyphRuns = mGlyphRuns.Length(); + return mGlyphRuns.begin(); + } + + uint32_t GlyphRunCount() const { return mGlyphRuns.Length(); } + + const GlyphRun* TrailingGlyphRun() const { + return mGlyphRuns.IsEmpty() ? nullptr : mGlyphRuns.end() - 1; + } + + // Returns the GlyphRun containing the given offset. + // (Returns mGlyphRuns.end()-1 when aOffset is mCharacterCount; returns + // nullptr if textrun is empty and no glyph runs are present.) + const GlyphRun* FindFirstGlyphRunContaining(uint32_t aOffset) const; + + // Copy glyph data from a ShapedWord into this textrun. + void CopyGlyphDataFrom(gfxShapedWord* aSource, uint32_t aStart); + + // Copy glyph data for a range of characters from aSource to this + // textrun. + void CopyGlyphDataFrom(gfxTextRun* aSource, Range aRange, uint32_t aDest); + + // Tell the textrun to release its reference to its creating gfxFontGroup + // immediately, rather than on destruction. This is used for textruns + // that are actually owned by a gfxFontGroup, so that they don't keep it + // permanently alive due to a circular reference. (The caller of this is + // taking responsibility for ensuring the textrun will not outlive its + // mFontGroup.) + void ReleaseFontGroup(); + + struct LigatureData { + // textrun range of the containing ligature + Range mRange; + // appunits advance to the start of the ligature part within the ligature; + // never includes any spacing + gfxFloat mPartAdvance; + // appunits width of the ligature part; includes before-spacing + // when the part is at the start of the ligature, and after-spacing + // when the part is as the end of the ligature + gfxFloat mPartWidth; + + bool mClipBeforePart; + bool mClipAfterPart; + }; + + // return storage used by this run, for memory reporter; + // nsTransformedTextRun needs to override this as it holds additional data + virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) + MOZ_MUST_OVERRIDE; + virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) + MOZ_MUST_OVERRIDE; + + nsTextFrameUtils::Flags GetFlags2() const { return mFlags2; } + + // Get the size, if it hasn't already been gotten, marking as it goes. + size_t MaybeSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) { + if (mFlags2 & nsTextFrameUtils::Flags::RunSizeAccounted) { + return 0; + } + mFlags2 |= nsTextFrameUtils::Flags::RunSizeAccounted; + return SizeOfIncludingThis(aMallocSizeOf); + } + void ResetSizeOfAccountingFlags() { + mFlags2 &= ~nsTextFrameUtils::Flags::RunSizeAccounted; + } + + // shaping state - for some font features, fallback is required that + // affects the entire run. for example, fallback for one script/font + // portion of a textrun requires fallback to be applied to the entire run + + enum ShapingState : uint8_t { + eShapingState_Normal, // default state + eShapingState_ShapingWithFeature, // have shaped with feature + eShapingState_ShapingWithFallback, // have shaped with fallback + eShapingState_Aborted, // abort initial iteration + eShapingState_ForceFallbackFeature // redo with fallback forced on + }; + + ShapingState GetShapingState() const { return mShapingState; } + void SetShapingState(ShapingState aShapingState) { + mShapingState = aShapingState; + } + + int32_t GetAdvanceForGlyph(uint32_t aIndex) const { + const CompressedGlyph& glyphData = mCharacterGlyphs[aIndex]; + if (glyphData.IsSimpleGlyph()) { + return glyphData.GetSimpleAdvance(); + } + uint32_t glyphCount = glyphData.GetGlyphCount(); + if (!glyphCount) { + return 0; + } + const DetailedGlyph* details = GetDetailedGlyphs(aIndex); + int32_t advance = 0; + for (uint32_t j = 0; j < glyphCount; ++j, ++details) { + advance += details->mAdvance; + } + return advance; + } + +#ifdef DEBUG_FRAME_DUMP + void Dump(FILE* aOutput = stderr); +#endif + + protected: + /** + * Create a textrun, and set its mCharacterGlyphs to point immediately + * after the base object; this is ONLY used in conjunction with placement + * new, after allocating a block large enough for the glyph records to + * follow the base textrun object. + */ + gfxTextRun(const gfxTextRunFactory::Parameters* aParams, uint32_t aLength, + gfxFontGroup* aFontGroup, mozilla::gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2); + + // Whether we need to fetch actual glyph extents from the fonts. + bool NeedsGlyphExtents() const; + + /** + * Helper for the Create() factory method to allocate the required + * glyph storage for a textrun object with the basic size aSize, + * plus room for aLength glyph records. + */ + static void* AllocateStorageForTextRun(size_t aSize, uint32_t aLength); + + // Pointer to the array of CompressedGlyph records; must be initialized + // when the object is constructed. + CompressedGlyph* mCharacterGlyphs; + + private: + // **** general helpers **** + + // Get the total advance for a range of glyphs. + int32_t GetAdvanceForGlyphs(Range aRange) const; + + // Spacing for characters outside the range aSpacingStart/aSpacingEnd + // is assumed to be zero; such characters are not passed to aProvider. + // This is useful to protect aProvider from being passed character indices + // it is not currently able to handle. + bool GetAdjustedSpacingArray( + Range aRange, const PropertyProvider* aProvider, Range aSpacingRange, + nsTArray<PropertyProvider::Spacing>* aSpacing) const; + + CompressedGlyph& EnsureComplexGlyph(uint32_t aIndex) { + gfxShapedText::EnsureComplexGlyph(aIndex, mCharacterGlyphs[aIndex]); + return mCharacterGlyphs[aIndex]; + } + + // **** ligature helpers **** + // (Platforms do the actual ligaturization, but we need to do a bunch of stuff + // to handle requests that begin or end inside a ligature) + + // if aProvider is null then mBeforeSpacing and mAfterSpacing are set to zero + LigatureData ComputeLigatureData(Range aPartRange, + const PropertyProvider* aProvider) const; + gfxFloat ComputePartialLigatureWidth(Range aPartRange, + const PropertyProvider* aProvider) const; + void DrawPartialLigature(gfxFont* aFont, Range aRange, + mozilla::gfx::Point* aPt, + const PropertyProvider* aProvider, + TextRunDrawParams& aParams, + mozilla::gfx::ShapedTextFlags aOrientation) const; + // Advance aRange.start to the start of the nearest ligature, back + // up aRange.end to the nearest ligature end; may result in + // aRange->start == aRange->end. + // Returns whether any adjustment was made. + bool ShrinkToLigatureBoundaries(Range* aRange) const; + // result in appunits + gfxFloat GetPartialLigatureWidth(Range aRange, + const PropertyProvider* aProvider) const; + void AccumulatePartialLigatureMetrics( + gfxFont* aFont, Range aRange, gfxFont::BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, const PropertyProvider* aProvider, + mozilla::gfx::ShapedTextFlags aOrientation, Metrics* aMetrics) const; + + // **** measurement helper **** + void AccumulateMetricsForRun(gfxFont* aFont, Range aRange, + gfxFont::BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, + const PropertyProvider* aProvider, + Range aSpacingRange, + mozilla::gfx::ShapedTextFlags aOrientation, + Metrics* aMetrics) const; + + // **** drawing helper **** + void DrawGlyphs(gfxFont* aFont, Range aRange, mozilla::gfx::Point* aPt, + const PropertyProvider* aProvider, Range aSpacingRange, + TextRunDrawParams& aParams, + mozilla::gfx::ShapedTextFlags aOrientation) const; + + // The textrun holds either a single GlyphRun -or- an array. + mozilla::ElementOrArray<GlyphRun> mGlyphRuns; + + void* mUserData; + + // mFontGroup is usually a strong reference, but refcounting is managed + // manually because it may be explicitly released by ReleaseFontGroup() + // in the case where the font group actually owns the textrun. + gfxFontGroup* MOZ_OWNING_REF mFontGroup; + + gfxSkipChars mSkipChars; + + nsTextFrameUtils::Flags + mFlags2; // additional flags (see also gfxShapedText::mFlags) + + bool mDontSkipDrawing; // true if the text run must not skip drawing, even if + // waiting for a user font download, e.g. because we + // are using it to draw canvas text + bool mReleasedFontGroup; // we already called NS_RELEASE on + // mFontGroup, so don't do it again + bool mReleasedFontGroupSkippedDrawing; // whether our old mFontGroup value + // was set to skip drawing + + // shaping state for handling variant fallback features + // such as subscript/superscript variant glyphs + ShapingState mShapingState; +}; + +class gfxFontGroup final : public gfxTextRunFactory { + public: + typedef mozilla::intl::Script Script; + typedef gfxShapedText::CompressedGlyph CompressedGlyph; + + static void + Shutdown(); // platform must call this to release the languageAtomService + + gfxFontGroup(nsPresContext* aPresContext, + const mozilla::StyleFontFamilyList& aFontFamilyList, + const gfxFontStyle* aStyle, nsAtom* aLanguage, + bool aExplicitLanguage, gfxTextPerfMetrics* aTextPerf, + gfxUserFontSet* aUserFontSet, gfxFloat aDevToCssSize, + StyleFontVariantEmoji aVariantEmoji); + + virtual ~gfxFontGroup(); + + gfxFontGroup(const gfxFontGroup& aOther) = delete; + + // Returns first valid font in the fontlist or default font. + // Initiates userfont loads if userfont not loaded. + // aCh: character to look for, or kCSSFirstAvailableFont for default "first + // available font" as defined by CSS Fonts (i.e. the first font whose + // unicode-range includes <space>, but does not require space to + // actually be present) + // aGeneric: if non-null, returns the CSS generic type that was mapped to + // this font + // aIsFirst: if non-null, returns whether the font was first in the list + static constexpr uint32_t kCSSFirstAvailableFont = UINT32_MAX; + already_AddRefed<gfxFont> GetFirstValidFont( + uint32_t aCh = kCSSFirstAvailableFont, + mozilla::StyleGenericFontFamily* aGeneric = nullptr, + bool* aIsFirst = nullptr); + + // Returns the first font in the font-group that has an OpenType MATH table, + // or null if no such font is available. The GetMathConstant methods may be + // called on the returned font. + already_AddRefed<gfxFont> GetFirstMathFont(); + + const gfxFontStyle* GetStyle() const { return &mStyle; } + + // Get the presContext for which this fontGroup was constructed. This may be + // null! (In the case of canvas not connected to a document.) + nsPresContext* GetPresContext() const { return mPresContext; } + + /** + * The listed characters should be treated as invisible and zero-width + * when creating textruns. + */ + static bool IsInvalidChar(uint8_t ch); + static bool IsInvalidChar(char16_t ch); + + /** + * Make a textrun for a given string. + * If aText is not persistent (aFlags & TEXT_IS_PERSISTENT), the + * textrun will copy it. + * This calls FetchGlyphExtents on the textrun. + */ + template <typename T> + already_AddRefed<gfxTextRun> MakeTextRun(const T* aString, uint32_t aLength, + const Parameters* aParams, + mozilla::gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2, + gfxMissingFontRecorder* aMFR); + + /** + * Textrun creation helper for clients that don't want to pass + * a full Parameters record. + */ + template <typename T> + already_AddRefed<gfxTextRun> MakeTextRun(const T* aString, uint32_t aLength, + DrawTarget* aRefDrawTarget, + int32_t aAppUnitsPerDevUnit, + mozilla::gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2, + gfxMissingFontRecorder* aMFR) { + gfxTextRunFactory::Parameters params = { + aRefDrawTarget, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevUnit}; + return MakeTextRun(aString, aLength, ¶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, mozilla::gfx::ShapedTextFlags aFlags, + uint32_t aAppUnitsPerDevUnit); + + /** + * Check whether a given font (specified by its gfxFontEntry) + * is already in the fontgroup's list of actual fonts + */ + bool HasFont(const gfxFontEntry* aFontEntry); + + // This returns the preferred underline for this font group. + // Some CJK fonts have wrong underline offset in its metrics. + // If this group has such "bad" font, each platform's gfxFontGroup + // initialized mUnderlineOffset. The value should be lower value of + // first font's metrics and the bad font's metrics. Otherwise, this + // returns from first font's metrics. + static constexpr gfxFloat UNDERLINE_OFFSET_NOT_SET = INT16_MAX; + gfxFloat GetUnderlineOffset(); + + already_AddRefed<gfxFont> FindFontForChar(uint32_t ch, uint32_t prevCh, + uint32_t aNextCh, Script aRunScript, + gfxFont* aPrevMatchedFont, + FontMatchType* aMatchType); + + gfxUserFontSet* GetUserFontSet(); + + // With downloadable fonts, the composition of the font group can change as + // fonts are downloaded for each change in state of the user font set, the + // generation value is bumped to avoid picking up previously created text runs + // in the text run word cache. For font groups based on stylesheets with no + // @font-face rule, this always returns 0. + uint64_t GetGeneration(); + + // generation of the latest fontset rebuild, 0 when no fontset present + uint64_t GetRebuildGeneration(); + + // used when logging text performance + gfxTextPerfMetrics* GetTextPerfMetrics() const { return mTextPerf; } + + // This will call UpdateUserFonts() if the user font set is changed. + void SetUserFontSet(gfxUserFontSet* aUserFontSet); + + void ClearCachedData() { + mUnderlineOffset = UNDERLINE_OFFSET_NOT_SET; + mSkipDrawing = false; + mHyphenWidth = -1; + mCachedEllipsisTextRun = nullptr; + } + + // If there is a user font set, check to see whether the font list or any + // caches need updating. + void UpdateUserFonts(); + + // search for a specific userfont in the list of fonts + bool ContainsUserFont(const gfxUserFontEntry* aUserFont); + + bool ShouldSkipDrawing() const { return mSkipDrawing; } + + class LazyReferenceDrawTargetGetter { + public: + virtual already_AddRefed<DrawTarget> GetRefDrawTarget() = 0; + }; + // The gfxFontGroup keeps ownership of this textrun. + // It is only guaranteed to exist until the next call to GetEllipsisTextRun + // (which might use a different appUnitsPerDev value or flags) for the font + // group, or until UpdateUserFonts is called, or the fontgroup is destroyed. + // Get it/use it/forget it :) - don't keep a reference that might go stale. + gfxTextRun* GetEllipsisTextRun( + int32_t aAppUnitsPerDevPixel, mozilla::gfx::ShapedTextFlags aFlags, + LazyReferenceDrawTargetGetter& aRefDrawTargetGetter); + + void CheckForUpdatedPlatformList() { + auto* pfl = gfxPlatformFontList::PlatformFontList(); + if (mFontListGeneration != pfl->GetGeneration()) { + // Forget cached fonts that may no longer be valid. + mLastPrefFamily = FontFamily(); + mLastPrefFont = nullptr; + mDefaultFont = nullptr; + mFonts.Clear(); + BuildFontList(); + } + } + + nsAtom* Language() const { return mLanguage.get(); } + + // Get font metrics to be used as the basis for CSS font-relative units. + // Note that these may be a "composite" of metrics from multiple fonts, + // because the 'ch' and 'ic' units depend on the font that would be used + // to render specific characters, not simply the "first available" font. + // https://drafts.csswg.org/css-values-4/#ch + // https://drafts.csswg.org/css-values-4/#ic + gfxFont::Metrics GetMetricsForCSSUnits(gfxFont::Orientation aOrientation); + + protected: + friend class mozilla::PostTraversalTask; + + struct TextRange { + TextRange(uint32_t aStart, uint32_t aEnd, gfxFont* aFont, + FontMatchType aMatchType, + mozilla::gfx::ShapedTextFlags aOrientation) + : start(aStart), + end(aEnd), + font(aFont), + matchType(aMatchType), + orientation(aOrientation) {} + uint32_t Length() const { return end - start; } + uint32_t start, end; + RefPtr<gfxFont> font; + FontMatchType matchType; + mozilla::gfx::ShapedTextFlags orientation; + }; + + // search through pref fonts for a character, return nullptr if no matching + // pref font + already_AddRefed<gfxFont> WhichPrefFontSupportsChar( + uint32_t aCh, uint32_t aNextCh, eFontPresentation aPresentation); + + already_AddRefed<gfxFont> WhichSystemFontSupportsChar( + uint32_t aCh, uint32_t aNextCh, Script aRunScript, + eFontPresentation aPresentation); + + template <typename T> + void ComputeRanges(nsTArray<TextRange>& aRanges, const T* aString, + uint32_t aLength, Script aRunScript, + mozilla::gfx::ShapedTextFlags aOrientation); + + class FamilyFace { + public: + FamilyFace() + : mOwnedFamily(nullptr), + mFontEntry(nullptr), + mGeneric(mozilla::StyleGenericFontFamily::None), + mFontCreated(false), + mLoading(false), + mInvalid(false), + mCheckForFallbackFaces(false), + mIsSharedFamily(false), + mHasFontEntry(false) {} + + FamilyFace(gfxFontFamily* aFamily, gfxFont* aFont, + mozilla::StyleGenericFontFamily aGeneric) + : mOwnedFamily(aFamily), + mGeneric(aGeneric), + mFontCreated(true), + mLoading(false), + mInvalid(false), + mCheckForFallbackFaces(false), + mIsSharedFamily(false), + mHasFontEntry(false) { + NS_ASSERTION(aFont, "font pointer must not be null"); + NS_ASSERTION(!aFamily || aFamily->ContainsFace(aFont->GetFontEntry()), + "font is not a member of the given family"); + NS_IF_ADDREF(aFamily); + mFont = aFont; + NS_ADDREF(aFont); + } + + FamilyFace(gfxFontFamily* aFamily, gfxFontEntry* aFontEntry, + mozilla::StyleGenericFontFamily aGeneric) + : mOwnedFamily(aFamily), + mGeneric(aGeneric), + mFontCreated(false), + mLoading(false), + mInvalid(false), + mCheckForFallbackFaces(false), + mIsSharedFamily(false), + mHasFontEntry(true) { + NS_ASSERTION(aFontEntry, "font entry pointer must not be null"); + NS_ASSERTION(!aFamily || aFamily->ContainsFace(aFontEntry), + "font is not a member of the given family"); + NS_IF_ADDREF(aFamily); + mFontEntry = aFontEntry; + NS_ADDREF(aFontEntry); + } + + FamilyFace(mozilla::fontlist::Family* aFamily, gfxFontEntry* aFontEntry, + mozilla::StyleGenericFontFamily aGeneric) + : mSharedFamily(aFamily), + mGeneric(aGeneric), + mFontCreated(false), + mLoading(false), + mInvalid(false), + mCheckForFallbackFaces(false), + mIsSharedFamily(true), + mHasFontEntry(true) { + MOZ_ASSERT(aFamily && aFontEntry && aFontEntry->mShmemFace); + mFontEntry = aFontEntry; + NS_ADDREF(aFontEntry); + } + + FamilyFace(const FamilyFace& aOtherFamilyFace) + : mGeneric(aOtherFamilyFace.mGeneric), + mFontCreated(aOtherFamilyFace.mFontCreated), + mLoading(aOtherFamilyFace.mLoading), + mInvalid(aOtherFamilyFace.mInvalid), + mCheckForFallbackFaces(aOtherFamilyFace.mCheckForFallbackFaces), + mIsSharedFamily(aOtherFamilyFace.mIsSharedFamily), + mHasFontEntry(aOtherFamilyFace.mHasFontEntry) { + if (mIsSharedFamily) { + mSharedFamily = aOtherFamilyFace.mSharedFamily; + if (mFontCreated) { + mFont = aOtherFamilyFace.mFont; + NS_ADDREF(mFont); + } else if (mHasFontEntry) { + mFontEntry = aOtherFamilyFace.mFontEntry; + NS_ADDREF(mFontEntry); + } else { + mSharedFace = aOtherFamilyFace.mSharedFace; + } + } else { + mOwnedFamily = aOtherFamilyFace.mOwnedFamily; + NS_IF_ADDREF(mOwnedFamily); + if (mFontCreated) { + mFont = aOtherFamilyFace.mFont; + NS_ADDREF(mFont); + } else { + mFontEntry = aOtherFamilyFace.mFontEntry; + NS_IF_ADDREF(mFontEntry); + } + } + } + + ~FamilyFace() { + if (mFontCreated) { + NS_RELEASE(mFont); + } + if (!mIsSharedFamily) { + NS_IF_RELEASE(mOwnedFamily); + } + if (mHasFontEntry) { + NS_RELEASE(mFontEntry); + } + } + + FamilyFace& operator=(const FamilyFace& aOther) { + if (mFontCreated) { + NS_RELEASE(mFont); + } + if (!mIsSharedFamily) { + NS_IF_RELEASE(mOwnedFamily); + } + if (mHasFontEntry) { + NS_RELEASE(mFontEntry); + } + + mGeneric = aOther.mGeneric; + mFontCreated = aOther.mFontCreated; + mLoading = aOther.mLoading; + mInvalid = aOther.mInvalid; + mIsSharedFamily = aOther.mIsSharedFamily; + mHasFontEntry = aOther.mHasFontEntry; + + if (mIsSharedFamily) { + mSharedFamily = aOther.mSharedFamily; + if (mFontCreated) { + mFont = aOther.mFont; + NS_ADDREF(mFont); + } else if (mHasFontEntry) { + mFontEntry = aOther.mFontEntry; + NS_ADDREF(mFontEntry); + } else { + mSharedFace = aOther.mSharedFace; + } + } else { + mOwnedFamily = aOther.mOwnedFamily; + NS_IF_ADDREF(mOwnedFamily); + if (mFontCreated) { + mFont = aOther.mFont; + NS_ADDREF(mFont); + } else { + mFontEntry = aOther.mFontEntry; + NS_IF_ADDREF(mFontEntry); + } + } + + return *this; + } + + gfxFontFamily* OwnedFamily() const { + MOZ_ASSERT(!mIsSharedFamily); + return mOwnedFamily; + } + mozilla::fontlist::Family* SharedFamily() const { + MOZ_ASSERT(mIsSharedFamily); + return mSharedFamily; + } + gfxFont* Font() const { return mFontCreated ? mFont : nullptr; } + + gfxFontEntry* FontEntry() const { + if (mFontCreated) { + return mFont->GetFontEntry(); + } + if (mHasFontEntry) { + return mFontEntry; + } + if (mIsSharedFamily) { + return gfxPlatformFontList::PlatformFontList()->GetOrCreateFontEntry( + mSharedFace, SharedFamily()); + } + return nullptr; + } + + mozilla::StyleGenericFontFamily Generic() const { return mGeneric; } + + bool IsSharedFamily() const { return mIsSharedFamily; } + bool IsUserFontContainer() const { + gfxFontEntry* fe = FontEntry(); + return fe && fe->mIsUserFontContainer; + } + bool IsLoading() const { return mLoading; } + bool IsInvalid() const { return mInvalid; } + void CheckState(bool& aSkipDrawing); + void SetLoading(bool aIsLoading) { mLoading = aIsLoading; } + void SetInvalid() { mInvalid = true; } + bool CheckForFallbackFaces() const { return mCheckForFallbackFaces; } + void SetCheckForFallbackFaces() { mCheckForFallbackFaces = true; } + + // Return true if we're currently loading (or waiting for) a resource that + // may support the given character. + bool IsLoadingFor(uint32_t aCh) { + if (!IsLoading()) { + return false; + } + MOZ_ASSERT(IsUserFontContainer()); + auto* ufe = static_cast<gfxUserFontEntry*>(FontEntry()); + return ufe && ufe->CharacterInUnicodeRange(aCh); + } + + void SetFont(gfxFont* aFont) { + NS_ASSERTION(aFont, "font pointer must not be null"); + NS_ADDREF(aFont); + if (mFontCreated) { + NS_RELEASE(mFont); + } else if (mHasFontEntry) { + NS_RELEASE(mFontEntry); + mHasFontEntry = false; + } + mFont = aFont; + mFontCreated = true; + mLoading = false; + } + + bool EqualsUserFont(const gfxUserFontEntry* aUserFont) const; + + private: + union { + gfxFontFamily* MOZ_OWNING_REF mOwnedFamily; + mozilla::fontlist::Family* MOZ_NON_OWNING_REF mSharedFamily; + }; + // either a font or a font entry exists + union { + // Whichever of these fields is actually present will be a strong + // reference, with refcounting handled manually. + gfxFont* MOZ_OWNING_REF mFont; + gfxFontEntry* MOZ_OWNING_REF mFontEntry; + mozilla::fontlist::Face* MOZ_NON_OWNING_REF mSharedFace; + }; + mozilla::StyleGenericFontFamily mGeneric; + bool mFontCreated : 1; + bool mLoading : 1; + bool mInvalid : 1; + bool mCheckForFallbackFaces : 1; + bool mIsSharedFamily : 1; + bool mHasFontEntry : 1; + }; + + nsPresContext* mPresContext = nullptr; + + // List of font families, either named or generic. + // Generic names map to system pref fonts based on language. + mozilla::StyleFontFamilyList mFamilyList; + + // Fontlist containing a font entry for each family found. gfxFont objects + // are created as needed and userfont loads are initiated when needed. + // Code should be careful about addressing this array directly. + nsTArray<FamilyFace> mFonts; + + RefPtr<gfxFont> mDefaultFont; + gfxFontStyle mStyle; + + RefPtr<nsAtom> mLanguage; + + gfxFloat mUnderlineOffset; + gfxFloat mHyphenWidth; + gfxFloat mDevToCssSize; + + RefPtr<gfxUserFontSet> mUserFontSet; + uint64_t mCurrGeneration; // track the current user font set generation, + // rebuild font list if needed + + gfxTextPerfMetrics* mTextPerf; + + // Cache a textrun representing an ellipsis (useful for CSS text-overflow) + // at a specific appUnitsPerDevPixel size and orientation + RefPtr<gfxTextRun> mCachedEllipsisTextRun; + + // cache the most recent pref font to avoid general pref font lookup + FontFamily mLastPrefFamily; + RefPtr<gfxFont> mLastPrefFont; + eFontPrefLang mLastPrefLang; // lang group for last pref font + eFontPrefLang mPageLang; + bool mLastPrefFirstFont; // is this the first font in the list of pref fonts + // for this lang group? + + bool mSkipDrawing; // hide text while waiting for a font + // download to complete (or fallback + // timer to fire) + + bool mExplicitLanguage; // Does mLanguage come from an explicit attribute? + + eFontPresentation mEmojiPresentation = eFontPresentation::Any; + + // Generic font family used to select among font prefs during fallback. + mozilla::StyleGenericFontFamily mFallbackGeneric = + mozilla::StyleGenericFontFamily::None; + + uint32_t mFontListGeneration = 0; // platform font list generation for this + // fontgroup + + /** + * Textrun creation short-cuts for special cases where we don't need to + * call a font shaper to generate glyphs. + */ + already_AddRefed<gfxTextRun> MakeEmptyTextRun( + const Parameters* aParams, mozilla::gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2); + + already_AddRefed<gfxTextRun> MakeSpaceTextRun( + const Parameters* aParams, mozilla::gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2); + + template <typename T> + already_AddRefed<gfxTextRun> MakeBlankTextRun( + const T* aString, uint32_t aLength, const Parameters* aParams, + mozilla::gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2); + + // Initialize the list of fonts + void BuildFontList(); + + // Get the font at index i within the fontlist, for character aCh (in case + // of fonts with multiple resources and unicode-range partitioning). + // Will initiate userfont load if not already loaded. + // May return null if userfont not loaded or if font invalid. + // If *aLoading is true, a relevant resource is already being loaded so no + // new download will be initiated; if a download is started, *aLoading will + // be set to true on return. + already_AddRefed<gfxFont> GetFontAt(uint32_t i, uint32_t aCh, bool* aLoading); + + // Simplified version of GetFontAt() for use where we just need a font for + // metrics, math layout tables, etc. + already_AddRefed<gfxFont> GetFontAt(uint32_t i, uint32_t aCh = 0x20) { + bool loading = false; + return GetFontAt(i, aCh, &loading); + } + + // will always return a font or force a shutdown + already_AddRefed<gfxFont> GetDefaultFont(); + + // Init this font group's font metrics. If there no bad fonts, you don't need + // to call this. But if there are one or more bad fonts which have bad + // underline offset, you should call this with the *first* bad font. + void InitMetricsForBadFont(gfxFont* aBadFont); + + // Set up the textrun glyphs for an entire text run: + // find script runs, and then call InitScriptRun for each + template <typename T> + void InitTextRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun, + const T* aString, uint32_t aLength, + gfxMissingFontRecorder* aMFR); + + // InitTextRun helper to handle a single script run, by finding font ranges + // and calling each font's InitTextRun() as appropriate + template <typename T> + void InitScriptRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun, + const T* aString, uint32_t aScriptRunStart, + uint32_t aScriptRunEnd, Script aRunScript, + gfxMissingFontRecorder* aMFR); + + // Helper for font-matching: + // search all faces in a family for a fallback in cases where it's unclear + // whether the family might have a font for a given character + already_AddRefed<gfxFont> FindFallbackFaceForChar( + const FamilyFace& aFamily, uint32_t aCh, uint32_t aNextCh, + eFontPresentation aPresentation); + + already_AddRefed<gfxFont> FindFallbackFaceForChar( + mozilla::fontlist::Family* aFamily, uint32_t aCh, uint32_t aNextCh, + eFontPresentation aPresentation); + + already_AddRefed<gfxFont> FindFallbackFaceForChar( + gfxFontFamily* aFamily, uint32_t aCh, uint32_t aNextCh, + eFontPresentation aPresentation); + + // helper methods for looking up fonts + + // lookup and add a font with a given name (i.e. *not* a generic!) + void AddPlatformFont(const nsACString& aName, bool aQuotedName, + nsTArray<FamilyAndGeneric>& aFamilyList); + + // do style selection and add entries to list + void AddFamilyToFontList(gfxFontFamily* aFamily, + mozilla::StyleGenericFontFamily aGeneric); + void AddFamilyToFontList(mozilla::fontlist::Family* aFamily, + mozilla::StyleGenericFontFamily aGeneric); +}; + +// A "missing font recorder" is to be used during text-run creation to keep +// a record of any scripts encountered for which font coverage was lacking; +// when Flush() is called, it sends a notification that front-end code can use +// to download fonts on demand (or whatever else it wants to do). + +#define GFX_MISSING_FONTS_NOTIFY_PREF "gfx.missing_fonts.notify" + +class gfxMissingFontRecorder { + public: + gfxMissingFontRecorder() { + MOZ_COUNT_CTOR(gfxMissingFontRecorder); + memset(&mMissingFonts, 0, sizeof(mMissingFonts)); + } + + ~gfxMissingFontRecorder() { +#ifdef DEBUG + for (uint32_t i = 0; i < kNumScriptBitsWords; i++) { + NS_ASSERTION(mMissingFonts[i] == 0, + "failed to flush the missing-font recorder"); + } +#endif + MOZ_COUNT_DTOR(gfxMissingFontRecorder); + } + + // record this script code in our mMissingFonts bitset + void RecordScript(mozilla::intl::Script aScriptCode) { + mMissingFonts[static_cast<uint32_t>(aScriptCode) >> 5] |= + (1 << (static_cast<uint32_t>(aScriptCode) & 0x1f)); + } + + // send a notification of any missing-scripts that have been + // recorded, and clear the mMissingFonts set for re-use + void Flush(); + + // forget any missing-scripts that have been recorded up to now; + // called before discarding a recorder we no longer care about + void Clear() { memset(&mMissingFonts, 0, sizeof(mMissingFonts)); } + + private: + // Number of 32-bit words needed for the missing-script flags + static const uint32_t kNumScriptBitsWords = + ((static_cast<int>(mozilla::intl::Script::NUM_SCRIPT_CODES) + 31) / 32); + uint32_t mMissingFonts[kNumScriptBitsWords]; +}; + +#endif diff --git a/gfx/thebes/gfxTypes.h b/gfx/thebes/gfxTypes.h new file mode 100644 index 0000000000..0e08c21547 --- /dev/null +++ b/gfx/thebes/gfxTypes.h @@ -0,0 +1,163 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_TYPES_H +#define GFX_TYPES_H + +#include <stdint.h> +#include "mozilla/TypedEnumBits.h" + +namespace mozilla { +enum class StyleGenericFontFamily : uint32_t; +} + +typedef struct _cairo_surface cairo_surface_t; +typedef struct _cairo_user_data_key cairo_user_data_key_t; + +typedef void (*thebes_destroy_func_t)(void* data); + +/** + * Currently needs to be 'double' for Cairo compatibility. Could + * become 'float', perhaps, in some configurations. + */ +typedef double gfxFloat; + +/** + * Priority of a line break opportunity. + * + * eNoBreak The line has no break opportunities + * eWordWrapBreak The line has a break opportunity only within a word. With + * overflow-wrap|word-wrap: break-word we will break at this + * point only if there are no other break opportunities in the + * line. + * eNormalBreak The line has a break opportunity determined by the standard + * line-breaking algorithm. + * + * Future expansion: split eNormalBreak into multiple priorities, e.g. + * punctuation break and whitespace break (bug 389710). + * As and when we implement it, text-wrap: unrestricted will + * mean that priorities are ignored and all line-break + * opportunities are equal. + * + * @see gfxTextRun::BreakAndMeasureText + * @see nsLineLayout::NotifyOptionalBreakPosition + */ +enum class gfxBreakPriority { eNoBreak = 0, eWordWrapBreak, eNormalBreak }; + +enum class gfxSurfaceType { + Image, + PDF, + PS, + Xlib, + Xcb, + Glitz, // unused, but needed for cairo parity + Quartz, + Win32, + BeOS, + DirectFB, // unused, but needed for cairo parity + SVG, + OS2, + Win32Printing, + QuartzImage, + Script, + QPainter, + Recording, + VG, + GL, + DRM, + Tee, + XML, + Skia, + Subsurface, + Max +}; + +enum class gfxContentType { + COLOR = 0x1000, + ALPHA = 0x2000, + COLOR_ALPHA = 0x3000, + SENTINEL = 0xffff +}; + +enum class gfxAlphaType { + Opaque, + Premult, + NonPremult, +}; + +/** + * Type used to record how a particular font is selected during the font- + * matching process, so that this can be exposed to the Inspector. + */ +struct FontMatchType { + enum class Kind : uint8_t { + kUnspecified = 0, + kFontGroup = 1, + kPrefsFallback = 1 << 1, + kSystemFallback = 1 << 2, + }; + + inline FontMatchType& operator|=(const FontMatchType& aOther); + + bool operator==(const FontMatchType& aOther) const { + return kind == aOther.kind && generic == aOther.generic; + } + + bool operator!=(const FontMatchType& aOther) const { + return !(*this == aOther); + } + + MOZ_IMPLICIT FontMatchType() = default; + MOZ_IMPLICIT FontMatchType(Kind aKind) : kind(aKind) {} + FontMatchType(Kind aKind, mozilla::StyleGenericFontFamily aGeneric) + : kind(aKind), generic(aGeneric) {} + + Kind kind = static_cast<Kind>(0); + // Using 0 to avoid pulling ServoStyleConsts.h everywhere. + mozilla::StyleGenericFontFamily generic = mozilla::StyleGenericFontFamily(0); +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(FontMatchType::Kind) + +FontMatchType& FontMatchType::operator|=(const FontMatchType& aOther) { + kind |= aOther.kind; + // We only keep track of one generic. + if (generic != aOther.generic) { + generic = mozilla::StyleGenericFontFamily(0); + } + return *this; +} + +// Installation status (base system / langpack / user-installed) may determine +// whether the font is visible to CSS font-family or src:local() lookups. +// (Exactly what these mean and how accurate they are may be vary across +// platforms -- e.g. on Linux there is no clear "base" set of fonts.) +enum class FontVisibility : uint8_t { + Unknown = 0, // No categorization of families available on this system + Base = 1, // Standard part of the base OS installation + LangPack = 2, // From an optional OS component such as language support + User = 3, // User-installed font (or installed by another app, etc) + Hidden = 4, // Internal system font, should never exposed to users + Webfont = 5, // Webfont defined by @font-face + Count = 6, // Count of values, for IPC serialization +}; + +struct HwStretchingSupport { + uint32_t mBoth; + uint32_t mWindowOnly; + uint32_t mFullScreenOnly; + uint32_t mNone; + uint32_t mError; + + HwStretchingSupport() + : mBoth(0), mWindowOnly(0), mFullScreenOnly(0), mNone(0), mError(0) {} + + bool IsFullySupported() const { + return mBoth > 0 && mWindowOnly == 0 && mFullScreenOnly == 0 && + mNone == 0 && mError == 0; + } +}; + +#endif /* GFX_TYPES_H */ diff --git a/gfx/thebes/gfxUserFontSet.cpp b/gfx/thebes/gfxUserFontSet.cpp new file mode 100644 index 0000000000..aeaf296200 --- /dev/null +++ b/gfx/thebes/gfxUserFontSet.cpp @@ -0,0 +1,1428 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Logging.h" + +#include "gfxUserFontSet.h" +#include "gfxPlatform.h" +#include "gfxFontConstants.h" +#include "mozilla/Atomics.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/Telemetry.h" +#include "mozilla/gfx/2D.h" +#include "gfxPlatformFontList.h" +#include "mozilla/PostTraversalTask.h" +#include "mozilla/dom/WorkerCommon.h" +#include "gfxOTSUtils.h" +#include "nsIFontLoadCompleteCallback.h" +#include "nsProxyRelease.h" +#include "nsContentUtils.h" +#include "nsTHashSet.h" + +using namespace mozilla; + +mozilla::LogModule* gfxUserFontSet::GetUserFontsLog() { + static LazyLogModule sLog("userfonts"); + return sLog; +} + +#define LOG(args) \ + MOZ_LOG(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug, args) +#define LOG_ENABLED() \ + MOZ_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug) + +static Atomic<uint64_t> sFontSetGeneration(0); + +gfxUserFontEntry::gfxUserFontEntry(nsTArray<gfxFontFaceSrc>&& aFontFaceSrcList, + gfxUserFontAttributes&& aAttr) + : gfxFontEntry("userfont"_ns), + mUserFontLoadState(STATUS_NOT_LOADED), + mFontDataLoadingState(NOT_LOADING), + mSeenLocalSource(false), + mUnsupportedFormat(false), + mFontDisplay(aAttr.mFontDisplay), + mLoader(nullptr) { + mIsUserFontContainer = true; + mSrcList = std::move(aFontFaceSrcList); + mCurrentSrcIndex = 0; + mWeightRange = aAttr.mWeight; + mStretchRange = aAttr.mStretch; + mStyleRange = aAttr.mStyle; + mFeatureSettings = std::move(aAttr.mFeatureSettings); + mVariationSettings = std::move(aAttr.mVariationSettings); + mLanguageOverride = aAttr.mLanguageOverride; + SetUnicodeRangeMap(std::move(aAttr.mUnicodeRanges)); + mRangeFlags = aAttr.mRangeFlags; + mAscentOverride = aAttr.mAscentOverride; + mDescentOverride = aAttr.mDescentOverride; + mLineGapOverride = aAttr.mLineGapOverride; + mSizeAdjust = aAttr.mSizeAdjust; + mFamilyName = aAttr.mFamilyName; +} + +void gfxUserFontEntry::UpdateAttributes(gfxUserFontAttributes&& aAttr) { + MOZ_ASSERT(NS_IsMainThread()); + + // Remove the entry from the user font cache, if present there, as the cache + // key may no longer be correct with the new attributes. + gfxUserFontSet::UserFontCache::ForgetFont(this); + + mFontDisplay = aAttr.mFontDisplay; + mWeightRange = aAttr.mWeight; + mStretchRange = aAttr.mStretch; + mStyleRange = aAttr.mStyle; + mFeatureSettings = std::move(aAttr.mFeatureSettings); + mVariationSettings = std::move(aAttr.mVariationSettings); + mLanguageOverride = aAttr.mLanguageOverride; + SetUnicodeRangeMap(std::move(aAttr.mUnicodeRanges)); + mRangeFlags = aAttr.mRangeFlags; + mAscentOverride = aAttr.mAscentOverride; + mDescentOverride = aAttr.mDescentOverride; + mLineGapOverride = aAttr.mLineGapOverride; + mSizeAdjust = aAttr.mSizeAdjust; +} + +gfxUserFontEntry::~gfxUserFontEntry() { + // Assert that we don't drop any gfxUserFontEntry objects during a Servo + // traversal, since PostTraversalTask objects can hold raw pointers to + // gfxUserFontEntry objects. + MOZ_ASSERT(!gfxFontUtils::IsInServoTraversal()); +} + +bool gfxUserFontEntry::Matches(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, + const gfxUserFontAttributes& aAttr) { + return mWeightRange == aAttr.mWeight && mStretchRange == aAttr.mStretch && + mStyleRange == aAttr.mStyle && + mFeatureSettings == aAttr.mFeatureSettings && + mVariationSettings == aAttr.mVariationSettings && + mLanguageOverride == aAttr.mLanguageOverride && + mSrcList == aFontFaceSrcList && mFontDisplay == aAttr.mFontDisplay && + mRangeFlags == aAttr.mRangeFlags && + mAscentOverride == aAttr.mAscentOverride && + mDescentOverride == aAttr.mDescentOverride && + mLineGapOverride == aAttr.mLineGapOverride && + mSizeAdjust == aAttr.mSizeAdjust && + ((!aAttr.mUnicodeRanges && !mCharacterMap) || + (aAttr.mUnicodeRanges && mCharacterMap && + GetCharacterMap()->Equals(aAttr.mUnicodeRanges))); +} + +gfxFont* gfxUserFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle) { + MOZ_ASSERT_UNREACHABLE( + "should only be creating a gfxFont" + " with an actual platform font entry"); + + // userfont entry is a container, can't create font from the container + return nullptr; +} + +class MOZ_STACK_CLASS gfxOTSMessageContext : public gfxOTSContext { + public: + virtual ~gfxOTSMessageContext() { + MOZ_ASSERT(mMessages.IsEmpty(), "should have called TakeMessages"); + } + + virtual void Message(int level, const char* format, + ...) MSGFUNC_FMT_ATTR override { + va_list va; + + // Special-case glyph bounding box warnings: collect all bad glyph IDs, + // so we can issue a single message at the end. + if (level > 0 && strstr(format, "bbox was incorrect")) { + // Extract the glyph ID from the message: it follows the last space in + // the message string. + const char* lastSpace = strrchr(format, ' '); + if (lastSpace) { + int gid = atoi(lastSpace + 1); + mBadBBoxGlyphs.AppendElement(gid); + } + return; + } + + va_start(va, format); + + nsCString msg; + msg.AppendVprintf(format, va); + + va_end(va); + + if (level > 0) { + // For warnings (rather than errors that cause the font to fail), + // we only report the first instance of any given message. + if (!mWarningsIssued.EnsureInserted(msg)) { + return; + } + } + + mMessages.AppendElement(gfxUserFontEntry::OTSMessage{msg, level}); + } + + bool Process(ots::OTSStream* aOutput, const uint8_t* aInput, size_t aLength, + nsTArray<gfxUserFontEntry::OTSMessage>& aMessages) { + bool ok = ots::OTSContext::Process(aOutput, aInput, aLength); + aMessages = TakeMessages(); + return ok; + } + + nsTArray<gfxUserFontEntry::OTSMessage>&& TakeMessages() { + if (!mBadBBoxGlyphs.IsEmpty()) { + nsAutoCString msg("Glyph bbox was incorrect (glyph ids"); + for (const auto gid : mBadBBoxGlyphs) { + msg.Append(" "); + msg.AppendInt(gid); + } + msg.Append(")"); + mMessages.AppendElement(gfxUserFontEntry::OTSMessage{msg, 1}); + mBadBBoxGlyphs.Clear(); + } + return std::move(mMessages); + } + + private: + nsTHashSet<nsCString> mWarningsIssued; + nsTArray<gfxUserFontEntry::OTSMessage> mMessages; + nsTArray<uint16_t> mBadBBoxGlyphs; +}; + +// Call the OTS library to sanitize an sfnt before attempting to use it. +// Returns a newly-allocated block, or nullptr in case of fatal errors. +const uint8_t* gfxUserFontEntry::SanitizeOpenTypeData( + const uint8_t* aData, uint32_t aLength, uint32_t& aSanitaryLength, + gfxUserFontType& aFontType, nsTArray<OTSMessage>& aMessages) { + aFontType = gfxFontUtils::DetermineFontDataType(aData, aLength); + Telemetry::Accumulate(Telemetry::WEBFONT_FONTTYPE, uint32_t(aFontType)); + + size_t lengthHint = gfxOTSContext::GuessSanitizedFontSize(aLength, aFontType); + if (!lengthHint) { + aSanitaryLength = 0; + return nullptr; + } + + gfxOTSExpandingMemoryStream<gfxOTSMozAlloc> output(lengthHint); + + gfxOTSMessageContext otsContext; + if (!otsContext.Process(&output, aData, aLength, aMessages)) { + // Failed to decode/sanitize the font, so discard it. + aSanitaryLength = 0; + return nullptr; + } + + aSanitaryLength = output.Tell(); + return static_cast<const uint8_t*>(output.forget()); +} + +void gfxUserFontEntry::StoreUserFontData(gfxFontEntry* aFontEntry, + uint32_t aSrcIndex, bool aPrivate, + const nsACString& aOriginalName, + FallibleTArray<uint8_t>* aMetadata, + uint32_t aMetaOrigLen, + uint8_t aCompression) { + if (!aFontEntry->mUserFontData) { + aFontEntry->mUserFontData = MakeUnique<gfxUserFontData>(); + } + gfxUserFontData* userFontData = aFontEntry->mUserFontData.get(); + userFontData->mSrcIndex = aSrcIndex; + const gfxFontFaceSrc& src = mSrcList[aSrcIndex]; + switch (src.mSourceType) { + case gfxFontFaceSrc::eSourceType_Local: + userFontData->mLocalName = src.mLocalName; + break; + case gfxFontFaceSrc::eSourceType_URL: + userFontData->mURI = src.mURI; + userFontData->mPrincipal = mPrincipal; + break; + case gfxFontFaceSrc::eSourceType_Buffer: + userFontData->mIsBuffer = true; + break; + } + userFontData->mPrivate = aPrivate; + userFontData->mTechFlags = src.mTechFlags; + userFontData->mFormatHint = src.mFormatHint; + userFontData->mRealName = aOriginalName; + if (aMetadata) { + userFontData->mMetadata = std::move(*aMetadata); + userFontData->mMetaOrigLen = aMetaOrigLen; + userFontData->mCompression = aCompression; + } +} + +size_t gfxUserFontData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + + mMetadata.ShallowSizeOfExcludingThis(aMallocSizeOf) + + mLocalName.SizeOfExcludingThisIfUnshared(aMallocSizeOf) + + mRealName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + // Not counting mURI and mPrincipal, as those will be shared. +} + +/*virtual*/ +gfxUserFontFamily::~gfxUserFontFamily() { + // Should not be dropped by stylo + MOZ_ASSERT(!gfxFontUtils::IsInServoTraversal()); +} + +already_AddRefed<gfxFontSrcPrincipal> gfxFontFaceSrc::LoadPrincipal( + const gfxUserFontSet& aFontSet) const { + MOZ_ASSERT(mSourceType == eSourceType_URL); + if (mUseOriginPrincipal) { + MOZ_ASSERT(mOriginPrincipal); + return RefPtr{mOriginPrincipal}.forget(); + } + return aFontSet.GetStandardFontLoadPrincipal(); +} + +void gfxUserFontEntry::GetFamilyNameAndURIForLogging(uint32_t aSrcIndex, + nsACString& aFamilyName, + nsACString& aURI) { + aFamilyName = mFamilyName; + + aURI.Truncate(); + if (aSrcIndex >= mSrcList.Length()) { + aURI.AppendLiteral("(end of source list)"); + } else { + if (mSrcList[aSrcIndex].mURI) { + mSrcList[aSrcIndex].mURI->GetSpec(aURI); + // If the source URI was very long, elide the middle of it. + // In principle, the byte-oriented chopping here could leave us + // with partial UTF-8 characters at the point where we cut it, + // but it really doesn't matter as this is just for logging. + const uint32_t kMaxURILengthForLogging = 256; + // UTF-8 ellipsis, with spaces to allow additional wrap opportunities + // in the resulting log message + const char kEllipsis[] = {' ', '\xE2', '\x80', '\xA6', ' '}; + if (aURI.Length() > kMaxURILengthForLogging) { + aURI.Replace(kMaxURILengthForLogging / 2, + aURI.Length() - kMaxURILengthForLogging, kEllipsis, + ArrayLength(kEllipsis)); + } + } else { + aURI.AppendLiteral("(invalid URI)"); + } + } +} + +struct WOFFHeader { + AutoSwap_PRUint32 signature; + AutoSwap_PRUint32 flavor; + AutoSwap_PRUint32 length; + AutoSwap_PRUint16 numTables; + AutoSwap_PRUint16 reserved; + AutoSwap_PRUint32 totalSfntSize; + AutoSwap_PRUint16 majorVersion; + AutoSwap_PRUint16 minorVersion; + AutoSwap_PRUint32 metaOffset; + AutoSwap_PRUint32 metaCompLen; + AutoSwap_PRUint32 metaOrigLen; + AutoSwap_PRUint32 privOffset; + AutoSwap_PRUint32 privLen; +}; + +struct WOFF2Header { + AutoSwap_PRUint32 signature; + AutoSwap_PRUint32 flavor; + AutoSwap_PRUint32 length; + AutoSwap_PRUint16 numTables; + AutoSwap_PRUint16 reserved; + AutoSwap_PRUint32 totalSfntSize; + AutoSwap_PRUint32 totalCompressedSize; + AutoSwap_PRUint16 majorVersion; + AutoSwap_PRUint16 minorVersion; + AutoSwap_PRUint32 metaOffset; + AutoSwap_PRUint32 metaCompLen; + AutoSwap_PRUint32 metaOrigLen; + AutoSwap_PRUint32 privOffset; + AutoSwap_PRUint32 privLen; +}; + +template <typename HeaderT> +void CopyWOFFMetadata(const uint8_t* aFontData, uint32_t aLength, + FallibleTArray<uint8_t>* aMetadata, + uint32_t* aMetaOrigLen) { + // This function may be called with arbitrary, unvalidated "font" data + // from @font-face, so it needs to be careful to bounds-check, etc., + // before trying to read anything. + // This just saves a copy of the compressed data block; it does NOT check + // that the block can be successfully decompressed, or that it contains + // well-formed/valid XML metadata. + if (aLength < sizeof(HeaderT)) { + return; + } + const HeaderT* woff = reinterpret_cast<const HeaderT*>(aFontData); + uint32_t metaOffset = woff->metaOffset; + uint32_t metaCompLen = woff->metaCompLen; + if (!metaOffset || !metaCompLen || !woff->metaOrigLen) { + return; + } + if (metaOffset >= aLength || metaCompLen > aLength - metaOffset) { + return; + } + if (!aMetadata->SetLength(woff->metaCompLen, fallible)) { + return; + } + memcpy(aMetadata->Elements(), aFontData + metaOffset, metaCompLen); + *aMetaOrigLen = woff->metaOrigLen; +} + +void gfxUserFontEntry::LoadNextSrc() { + NS_ASSERTION(mCurrentSrcIndex < mSrcList.Length(), + "already at the end of the src list for user font"); + NS_ASSERTION((mUserFontLoadState == STATUS_NOT_LOADED || + mUserFontLoadState == STATUS_LOAD_PENDING || + mUserFontLoadState == STATUS_LOADING) && + mFontDataLoadingState < LOADING_FAILED, + "attempting to load a font that has either completed or failed"); + + if (mUserFontLoadState == STATUS_NOT_LOADED) { + SetLoadState(STATUS_LOADING); + mFontDataLoadingState = LOADING_STARTED; + mUnsupportedFormat = false; + } else { + // we were already loading; move to the next source, + // but don't reset state - if we've already timed out, + // that counts against the new download + mCurrentSrcIndex++; + } + + DoLoadNextSrc(false); +} + +void gfxUserFontEntry::ContinueLoad() { + MOZ_ASSERT(mUserFontLoadState == STATUS_LOAD_PENDING); + MOZ_ASSERT(mSrcList[mCurrentSrcIndex].mSourceType == + gfxFontFaceSrc::eSourceType_URL); + + SetLoadState(STATUS_LOADING); + DoLoadNextSrc(/* aIsContinue = */ 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 aIsContinue) { + RefPtr<gfxUserFontSet> fontSet = GetUserFontSet(); + if (NS_WARN_IF(!fontSet)) { + LOG(("userfonts (%p) failed expired font set for (%s)\n", fontSet.get(), + mFamilyName.get())); + mFontDataLoadingState = LOADING_FAILED; + SetLoadState(STATUS_FAILED); + return; + } + + uint32_t numSrc = mSrcList.Length(); + + // load each src entry in turn, until a local face is found + // or a download begins successfully + while (mCurrentSrcIndex < numSrc) { + gfxFontFaceSrc& currSrc = mSrcList[mCurrentSrcIndex]; + + // src local ==> lookup and load immediately + + if (currSrc.mSourceType == gfxFontFaceSrc::eSourceType_Local) { + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + pfl->AddUserFontSet(fontSet); + // Don't look up local fonts if the font whitelist is being used. + gfxFontEntry* fe = nullptr; + if (!pfl->IsFontFamilyWhitelistActive()) { + fe = gfxPlatform::GetPlatform()->LookupLocalFont( + fontSet->GetPresContext(), currSrc.mLocalName, Weight(), Stretch(), + SlantStyle()); + // Note that we've attempted a local lookup, even if it failed, + // as this means we are dependent on any updates to the font list. + mSeenLocalSource = true; + nsTArray<RefPtr<gfxUserFontSet>> fontSets; + GetUserFontSets(fontSets); + for (gfxUserFontSet* fontSet : fontSets) { + // We need to note on each gfxUserFontSet that contains the user + // font entry that we used a local() rule. + fontSet->SetLocalRulesUsed(); + } + } + if (fe) { + LOG(("userfonts (%p) [src %d] loaded local: (%s) for (%s) gen: %8.8x\n", + fontSet.get(), mCurrentSrcIndex, currSrc.mLocalName.get(), + mFamilyName.get(), uint32_t(fontSet->mGeneration))); + fe->mFeatureSettings.AppendElements(mFeatureSettings); + fe->mVariationSettings.AppendElements(mVariationSettings); + fe->mLanguageOverride = mLanguageOverride; + fe->mFamilyName = mFamilyName; + fe->mRangeFlags = mRangeFlags; + fe->mAscentOverride = mAscentOverride; + fe->mDescentOverride = mDescentOverride; + fe->mLineGapOverride = mLineGapOverride; + fe->mSizeAdjust = mSizeAdjust; + // For src:local(), we don't care whether the request is from + // a private window as there's no issue of caching resources; + // local fonts are just available all the time. + StoreUserFontData(fe, mCurrentSrcIndex, false, nsCString(), nullptr, 0, + gfxUserFontData::kUnknownCompression); + mPlatformFontEntry = fe; + SetLoadState(STATUS_LOADED); + Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE, + currSrc.mSourceType + 1); + return; + } + LOG(("userfonts (%p) [src %d] failed local: (%s) for (%s)\n", + fontSet.get(), mCurrentSrcIndex, currSrc.mLocalName.get(), + mFamilyName.get())); + } + + // src url ==> start the load process + else if (currSrc.mSourceType == gfxFontFaceSrc::eSourceType_URL) { + if (gfxPlatform::GetPlatform()->IsFontFormatSupported( + currSrc.mFormatHint, currSrc.mTechFlags)) { + if (ServoStyleSet* set = gfxFontUtils::CurrentServoStyleSet()) { + // Only support style worker threads synchronously getting + // entries from the font cache when it's not a data: URI + // @font-face that came from UA or user sheets, since we + // were not able to call IsFontLoadAllowed ahead of time + // for these entries. + if (currSrc.mUseOriginPrincipal && IgnorePrincipal(currSrc.mURI)) { + set->AppendTask(PostTraversalTask::LoadFontEntry(this)); + SetLoadState(STATUS_LOAD_PENDING); + return; + } + } + + // see if we have an existing entry for this source + gfxFontEntry* fe = + gfxUserFontSet::UserFontCache::GetFont(currSrc, *this); + if (fe) { + mPlatformFontEntry = fe; + SetLoadState(STATUS_LOADED); + LOG( + ("userfonts (%p) [src %d] " + "loaded uri from cache: (%s) for (%s)\n", + fontSet.get(), mCurrentSrcIndex, + currSrc.mURI->GetSpecOrDefault().get(), mFamilyName.get())); + return; + } + + if (ServoStyleSet* set = gfxFontUtils::CurrentServoStyleSet()) { + // If we need to start a font load and we're on a style + // worker thread, we have to defer it. + set->AppendTask(PostTraversalTask::LoadFontEntry(this)); + SetLoadState(STATUS_LOAD_PENDING); + return; + } + + // record the principal we should use for the load for use when + // creating a channel and when caching the loaded entry. + mPrincipal = currSrc.LoadPrincipal(*fontSet); + + const bool loadDoesntSpin = + !aIsContinue && currSrc.mURI->SyncLoadIsOK(); + if (loadDoesntSpin) { + uint8_t* buffer = nullptr; + uint32_t bufferLength = 0; + + // sync load font immediately + nsresult rv = + fontSet->SyncLoadFontData(this, &currSrc, buffer, bufferLength); + + if (NS_SUCCEEDED(rv) && + LoadPlatformFontSync(mCurrentSrcIndex, buffer, bufferLength)) { + SetLoadState(STATUS_LOADED); + Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE, + currSrc.mSourceType + 1); + return; + } + fontSet->LogMessage(this, mCurrentSrcIndex, "font load failed", + nsIScriptError::errorFlag, rv); + } else if (!aIsContinue) { + RefPtr<nsIRunnable> runnable = NS_NewRunnableFunction( + "gfxUserFontSet::AsyncContinueLoad", + [loader = RefPtr{this}] { loader->ContinueLoad(); }); + SetLoadState(STATUS_LOAD_PENDING); + // We don't want to trigger the channel open at random points in + // time, because it can run privileged JS. + if (!nsContentUtils::IsSafeToRunScript()) { + // There's a script-blocker on the stack. We know the sooner point + // where we can trigger the load. + nsContentUtils::AddScriptRunner(runnable.forget()); + } else { + // We dispatch with a rather high priority, since somebody actually + // cares about this font. + NS_DispatchToCurrentThreadQueue(runnable.forget(), + EventQueuePriority::MediumHigh); + } + return; + } else { + // Actually start the async load. + nsresult rv = fontSet->StartLoad(this, mCurrentSrcIndex); + if (NS_SUCCEEDED(rv)) { + LOG(("userfonts (%p) [src %d] loading uri: (%s) for (%s)\n", + fontSet.get(), mCurrentSrcIndex, + currSrc.mURI->GetSpecOrDefault().get(), mFamilyName.get())); + return; + } + fontSet->LogMessage(this, mCurrentSrcIndex, + "failed to start download", + nsIScriptError::errorFlag, rv); + } + } else { + // We don't log a warning to the web console yet, + // as another source may load successfully + mUnsupportedFormat = true; + } + } else { + // FontFace buffer ==> load immediately + MOZ_ASSERT(currSrc.mSourceType == gfxFontFaceSrc::eSourceType_Buffer); + + uint8_t* buffer = nullptr; + uint32_t bufferLength = 0; + + // sync load font immediately + currSrc.mBuffer->TakeBuffer(buffer, bufferLength); + if (buffer && + LoadPlatformFontSync(mCurrentSrcIndex, buffer, bufferLength)) { + // LoadPlatformFontSync takes ownership of the buffer, so no need + // to free it here. + SetLoadState(STATUS_LOADED); + Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE, + currSrc.mSourceType + 1); + return; + } + fontSet->LogMessage(this, mCurrentSrcIndex, "font load failed", + nsIScriptError::errorFlag); + } + + mCurrentSrcIndex++; + } + + if (mUnsupportedFormat) { + fontSet->LogMessage(this, mCurrentSrcIndex, "no supported format found", + nsIScriptError::warningFlag); + } + + // all src's failed; mark this entry as unusable (so fallback will occur) + LOG(("userfonts (%p) failed all src for (%s)\n", fontSet.get(), + mFamilyName.get())); + mFontDataLoadingState = LOADING_FAILED; + SetLoadState(STATUS_FAILED); +} + +void gfxUserFontEntry::SetLoadState(UserFontLoadState aLoadState) { + mUserFontLoadState = aLoadState; +} + +MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(UserFontMallocSizeOfOnAlloc) + +bool gfxUserFontEntry::LoadPlatformFontSync(uint32_t aSrcIndex, + const uint8_t* aFontData, + uint32_t aLength) { + AUTO_PROFILER_LABEL("gfxUserFontEntry::LoadPlatformFontSync", OTHER); + NS_ASSERTION((mUserFontLoadState == STATUS_NOT_LOADED || + mUserFontLoadState == STATUS_LOAD_PENDING || + mUserFontLoadState == STATUS_LOADING) && + mFontDataLoadingState < LOADING_FAILED, + "attempting to load a font that has either completed or failed"); + + // Unwrap/decompress/sanitize or otherwise munge the downloaded data + // to make a usable sfnt structure. + + // Call the OTS sanitizer; this will also decode WOFF to sfnt + // if necessary. The original data in aFontData is left unchanged. + uint32_t sanitaryLen; + gfxUserFontType fontType; + nsTArray<OTSMessage> messages; + const uint8_t* sanitaryData = + SanitizeOpenTypeData(aFontData, aLength, sanitaryLen, fontType, messages); + + return LoadPlatformFont(aSrcIndex, aFontData, aLength, fontType, sanitaryData, + sanitaryLen, std::move(messages)); +} + +void gfxUserFontEntry::StartPlatformFontLoadOnBackgroundThread( + uint32_t aSrcIndex, const uint8_t* aFontData, uint32_t aLength, + nsMainThreadPtrHandle<nsIFontLoadCompleteCallback> aCallback) { + MOZ_ASSERT(!NS_IsMainThread()); + + uint32_t sanitaryLen; + gfxUserFontType fontType; + nsTArray<OTSMessage> messages; + const uint8_t* sanitaryData = + SanitizeOpenTypeData(aFontData, aLength, sanitaryLen, fontType, messages); + + nsCOMPtr<nsIRunnable> event = + NewRunnableMethod<uint32_t, const uint8_t*, uint32_t, gfxUserFontType, + const uint8_t*, uint32_t, nsTArray<OTSMessage>&&, + nsMainThreadPtrHandle<nsIFontLoadCompleteCallback>>( + "gfxUserFontEntry::ContinuePlatformFontLoadOnMainThread", this, + &gfxUserFontEntry::ContinuePlatformFontLoadOnMainThread, aSrcIndex, + aFontData, aLength, fontType, sanitaryData, sanitaryLen, + std::move(messages), aCallback); + NS_DispatchToMainThread(event.forget()); +} + +bool gfxUserFontEntry::LoadPlatformFont(uint32_t aSrcIndex, + const uint8_t* aOriginalFontData, + uint32_t aOriginalLength, + gfxUserFontType aFontType, + const uint8_t* aSanitizedFontData, + uint32_t aSanitizedLength, + nsTArray<OTSMessage>&& aMessages) { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr<gfxUserFontSet> fontSet = GetUserFontSet(); + if (NS_WARN_IF(!fontSet)) { + free((void*)aOriginalFontData); + free((void*)aSanitizedFontData); + return false; + } + + for (const auto& msg : aMessages) { + fontSet->LogMessage(this, aSrcIndex, msg.mMessage.get(), + msg.mLevel > 0 ? nsIScriptError::warningFlag + : nsIScriptError::errorFlag); + } + + if (!aSanitizedFontData) { + fontSet->LogMessage(this, aSrcIndex, "rejected by sanitizer"); + } else { + // Check whether aSanitizedFontData is a known OpenType format; it might be + // a TrueType Collection, which OTS would accept but we don't yet + // know how to handle. If so, discard. + if (gfxFontUtils::DetermineFontDataType( + aSanitizedFontData, aSanitizedLength) != GFX_USERFONT_OPENTYPE) { + fontSet->LogMessage(this, aSrcIndex, "not a supported OpenType format"); + free((void*)aSanitizedFontData); + aSanitizedFontData = nullptr; + } + } + + // Because platform font activation code may replace the name table + // in the font with a synthetic one, we save the original name so that + // it can be reported via the InspectorUtils API. + nsAutoCString originalFullName; + + gfxFontEntry* fe = nullptr; + uint32_t fontCompressionRatio = 0; + size_t computedSize = 0; + + if (aSanitizedFontData) { + if (aSanitizedLength) { + fontCompressionRatio = + uint32_t(100.0 * aOriginalLength / aSanitizedLength + 0.5); + if (aFontType == GFX_USERFONT_WOFF || aFontType == GFX_USERFONT_WOFF2) { + Telemetry::Accumulate(aFontType == GFX_USERFONT_WOFF + ? Telemetry::WEBFONT_COMPRESSION_WOFF + : Telemetry::WEBFONT_COMPRESSION_WOFF2, + fontCompressionRatio); + } + } + + // The sanitizer ensures that we have a valid sfnt and a usable + // name table, so this should never fail unless we're out of + // memory, and GetFullNameFromSFNT is not directly exposed to + // arbitrary/malicious data from the web. + gfxFontUtils::GetFullNameFromSFNT(aSanitizedFontData, aSanitizedLength, + originalFullName); + + // Record size for memory reporting purposes. We measure this now + // because by the time we potentially want to collect reports, this + // data block may have been handed off to opaque OS font APIs that + // don't allow us to retrieve or measure it directly. + // The *OnAlloc function will also tell DMD about this block, as the + // OS font code may hold on to it for an extended period. + computedSize = UserFontMallocSizeOfOnAlloc(aSanitizedFontData); + + // Here ownership of aSanitizedFontData is passed to the platform, + // which will delete it when no longer required + fe = gfxPlatform::GetPlatform()->MakePlatformFont( + mName, Weight(), Stretch(), SlantStyle(), aSanitizedFontData, + aSanitizedLength); + if (!fe) { + fontSet->LogMessage(this, aSrcIndex, "not usable by platform"); + } + } + + if (fe) { + fe->mComputedSizeOfUserFont = computedSize; + + // Save a copy of the metadata block (if present) for InspectorUtils + // to use if required. Ownership of the metadata block will be passed + // to the gfxUserFontData record below. + FallibleTArray<uint8_t> metadata; + uint32_t metaOrigLen = 0; + uint8_t compression = gfxUserFontData::kUnknownCompression; + if (aFontType == GFX_USERFONT_WOFF) { + CopyWOFFMetadata<WOFFHeader>(aOriginalFontData, aOriginalLength, + &metadata, &metaOrigLen); + compression = gfxUserFontData::kZlibCompression; + } else if (aFontType == GFX_USERFONT_WOFF2) { + CopyWOFFMetadata<WOFF2Header>(aOriginalFontData, aOriginalLength, + &metadata, &metaOrigLen); + compression = gfxUserFontData::kBrotliCompression; + } + + // copy OpenType feature/language settings from the userfont entry to the + // newly-created font entry + fe->mFeatureSettings.AppendElements(mFeatureSettings); + fe->mVariationSettings.AppendElements(mVariationSettings); + fe->mLanguageOverride = mLanguageOverride; + fe->mFamilyName = mFamilyName; + fe->mRangeFlags = mRangeFlags; + fe->mAscentOverride = mAscentOverride; + fe->mDescentOverride = mDescentOverride; + fe->mLineGapOverride = mLineGapOverride; + fe->mSizeAdjust = mSizeAdjust; + StoreUserFontData(fe, aSrcIndex, fontSet->GetPrivateBrowsing(), + originalFullName, &metadata, metaOrigLen, compression); + LOG( + ("userfonts (%p) [src %d] loaded uri: (%s) for (%s) " + "(%p) gen: %8.8x compress: %d%%\n", + fontSet.get(), aSrcIndex, + mSrcList[aSrcIndex].mURI->GetSpecOrDefault().get(), mFamilyName.get(), + this, uint32_t(fontSet->mGeneration), fontCompressionRatio)); + mPlatformFontEntry = fe; + SetLoadState(STATUS_LOADED); + gfxUserFontSet::UserFontCache::CacheFont(fe); + } else { + LOG(( + "userfonts (%p) [src %d] failed uri: (%s) for (%s)" + " error making platform font\n", + fontSet.get(), aSrcIndex, + mSrcList[aSrcIndex].mURI->GetSpecOrDefault().get(), mFamilyName.get())); + } + + // The downloaded data can now be discarded; the font entry is using the + // sanitized copy + free((void*)aOriginalFontData); + + return fe != nullptr; +} + +void gfxUserFontEntry::Load() { + if (mUserFontLoadState != STATUS_NOT_LOADED) { + return; + } + if (dom::IsCurrentThreadRunningWorker()) { + // TODO: Maybe support loading the font entry in workers, at least for + // buffers or other sync sources? + NS_DispatchToMainThread(NewRunnableMethod("gfxUserFontEntry::Load", this, + &gfxUserFontEntry::Load)); + return; + } + LoadNextSrc(); +} + +void gfxUserFontEntry::IncrementGeneration() { + nsTArray<RefPtr<gfxUserFontSet>> fontSets; + GetUserFontSets(fontSets); + for (gfxUserFontSet* fontSet : fontSets) { + fontSet->IncrementGeneration(); + } +} + +// This is called when a font download finishes. +// Ownership of aFontData passes in here, and the font set must +// ensure that it is eventually deleted via free(). +void gfxUserFontEntry::FontDataDownloadComplete( + uint32_t aSrcIndex, const uint8_t* aFontData, uint32_t aLength, + nsresult aDownloadStatus, nsIFontLoadCompleteCallback* aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + + // forget about the loader, as we no longer potentially need to cancel it + // if the entry is obsoleted + mLoader = nullptr; + + // download successful, make platform font using font data + if (NS_SUCCEEDED(aDownloadStatus) && + mFontDataLoadingState != LOADING_TIMED_OUT) { + LoadPlatformFontAsync(aSrcIndex, aFontData, aLength, aCallback); + return; + } + + RefPtr<gfxUserFontSet> fontSet = GetUserFontSet(); + if (fontSet) { + // download failed or font-display timeout passed + if (mFontDataLoadingState == LOADING_TIMED_OUT) { + fontSet->LogMessage(this, aSrcIndex, + "font-display timeout, webfont not used", + nsIScriptError::infoFlag, aDownloadStatus); + } else { + fontSet->LogMessage(this, aSrcIndex, "download failed", + nsIScriptError::errorFlag, aDownloadStatus); + } + } + + if (aFontData) { + free((void*)aFontData); + } + + FontLoadFailed(aCallback); +} + +void gfxUserFontEntry::LoadPlatformFontAsync( + uint32_t aSrcIndex, const uint8_t* aFontData, uint32_t aLength, + nsIFontLoadCompleteCallback* aCallback) { + nsMainThreadPtrHandle<nsIFontLoadCompleteCallback> cb( + new nsMainThreadPtrHolder<nsIFontLoadCompleteCallback>("FontLoader", + aCallback)); + + // Do the OpenType sanitization over on the font loading thread. Once that is + // complete, we'll continue in ContinuePlatformFontLoadOnMainThread. + // + // We hold a strong reference to the gfxUserFontSet during this work, since + // the document might be closed while we are OMT, and release it at the end + // of ContinuePlatformFontLoadOnMainThread. + // + // If the set has already been freed, then the loading will fail when we + // resume on the main thread. + + MOZ_ASSERT(!mLoadingFontSet); + mLoadingFontSet = GetUserFontSet(); + + nsCOMPtr<nsIRunnable> event = + NewRunnableMethod<uint32_t, const uint8_t*, uint32_t, + nsMainThreadPtrHandle<nsIFontLoadCompleteCallback>>( + "gfxUserFontEntry::StartPlatformFontLoadOnBackgroundThread", this, + &gfxUserFontEntry::StartPlatformFontLoadOnBackgroundThread, aSrcIndex, + aFontData, aLength, cb); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchBackgroundTask(event.forget())); +} + +void gfxUserFontEntry::ContinuePlatformFontLoadOnMainThread( + uint32_t aSrcIndex, const uint8_t* aOriginalFontData, + uint32_t aOriginalLength, gfxUserFontType aFontType, + const uint8_t* aSanitizedFontData, uint32_t aSanitizedLength, + nsTArray<OTSMessage>&& aMessages, + nsMainThreadPtrHandle<nsIFontLoadCompleteCallback> aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + + bool loaded = LoadPlatformFont(aSrcIndex, aOriginalFontData, aOriginalLength, + aFontType, aSanitizedFontData, + aSanitizedLength, std::move(aMessages)); + aOriginalFontData = nullptr; + aSanitizedFontData = nullptr; + + if (loaded) { + IncrementGeneration(); + aCallback->FontLoadComplete(); + } else { + FontLoadFailed(aCallback); + } + + // Set in LoadPlatformFontAsync. If it is null, then the font set should have + // already been freed and we would not succeed in loading the font. + MOZ_ASSERT_IF(loaded, mLoadingFontSet); + mLoadingFontSet = nullptr; +} + +void gfxUserFontEntry::FontLoadFailed(nsIFontLoadCompleteCallback* aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + + // Error occurred. Make sure the FontFace's promise is rejected if the + // load timed out, or else load the next src. + if (mFontDataLoadingState == LOADING_TIMED_OUT) { + mFontDataLoadingState = LOADING_FAILED; + SetLoadState(STATUS_FAILED); + } else { + LoadNextSrc(); + } + + // We ignore the status returned by LoadNext(); + // even if loading failed, we need to bump the font-set generation + // and return true in order to trigger reflow, so that fallback + // will be used where the text was "masked" by the pending download + IncrementGeneration(); + aCallback->FontLoadComplete(); +} + +void gfxUserFontEntry::GetUserFontSets( + nsTArray<RefPtr<gfxUserFontSet>>& aResult) { + aResult.Clear(); + RefPtr<gfxUserFontSet> fontSet = GetUserFontSet(); + if (fontSet) { + aResult.AppendElement(std::move(fontSet)); + } +} + +gfxUserFontSet::gfxUserFontSet() + : mFontFamilies(4), + mRebuildGeneration(0), + mLocalRulesUsed(false), + mRebuildLocalRules(false), + mDownloadCount(0), + mDownloadSize(0) { + IncrementGeneration(true); +} + +gfxUserFontSet::~gfxUserFontSet() { Destroy(); } + +void gfxUserFontSet::Destroy() { + if (auto* pfl = gfxPlatformFontList::PlatformFontList(false)) { + pfl->RemoveUserFontSet(this); + } + + mFontFamilies.Clear(); +} + +already_AddRefed<gfxUserFontEntry> gfxUserFontSet::FindOrCreateUserFontEntry( + nsTArray<gfxFontFaceSrc>&& aFontFaceSrcList, + gfxUserFontAttributes&& aAttr) { + RefPtr<gfxUserFontEntry> entry; + + // If there's already a userfont entry in the family whose descriptors all + // match, we can just move it to the end of the list instead of adding a new + // face that will always "shadow" the old one. + // Note that we can't do this for platform font entries, even if the + // style descriptors match, as they might have had a different source list, + // but we no longer have the old source list available to check. + RefPtr<gfxUserFontFamily> family = LookupFamily(aAttr.mFamilyName); + if (family) { + entry = FindExistingUserFontEntry(family, aFontFaceSrcList, aAttr); + } + + if (!entry) { + entry = CreateUserFontEntry(std::move(aFontFaceSrcList), std::move(aAttr)); + } + + return entry.forget(); +} + +gfxUserFontEntry* gfxUserFontSet::FindExistingUserFontEntry( + gfxUserFontFamily* aFamily, + const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, + const gfxUserFontAttributes& aAttr) { + aFamily->ReadLock(); + const auto& fontList = aFamily->GetFontList(); + gfxUserFontEntry* result = nullptr; + + for (const auto& font : fontList) { + if (!font->mIsUserFontContainer) { + continue; + } + + gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(font.get()); + if (ufe->Matches(aFontFaceSrcList, aAttr)) { + result = ufe; + break; + } + } + aFamily->ReadUnlock(); + + return result; +} + +void gfxUserFontSet::AddUserFontEntry(const nsCString& aFamilyName, + gfxUserFontEntry* aUserFontEntry) { + RefPtr<gfxUserFontFamily> family = GetFamily(aFamilyName); + family->AddFontEntry(aUserFontEntry); + + if (LOG_ENABLED()) { + nsAutoCString weightString; + aUserFontEntry->Weight().ToString(weightString); + nsAutoCString stretchString; + aUserFontEntry->Stretch().ToString(stretchString); + LOG( + ("userfonts (%p) added to \"%s\" (%p) style: %s weight: %s " + "stretch: %s display: %d", + this, aFamilyName.get(), aUserFontEntry, + (aUserFontEntry->IsItalic() + ? "italic" + : (aUserFontEntry->IsOblique() ? "oblique" : "normal")), + weightString.get(), stretchString.get(), + static_cast<int>(aUserFontEntry->GetFontDisplay()))); + } +} + +void gfxUserFontSet::IncrementGeneration(bool aIsRebuild) { + // add one, increment again if zero + do { + mGeneration = ++sFontSetGeneration; + } while (mGeneration == 0); + if (aIsRebuild) { + mRebuildGeneration = mGeneration; + } +} + +void gfxUserFontSet::RebuildLocalRules() { + if (mLocalRulesUsed) { + mRebuildLocalRules = true; + DoRebuildUserFontSet(); + } +} + +already_AddRefed<gfxUserFontFamily> gfxUserFontSet::LookupFamily( + const nsACString& aFamilyName) const { + nsAutoCString key(aFamilyName); + ToLowerCase(key); + + return mFontFamilies.Get(key); +} + +already_AddRefed<gfxUserFontFamily> gfxUserFontSet::GetFamily( + const nsACString& aFamilyName) { + nsAutoCString key(aFamilyName); + ToLowerCase(key); + + return do_AddRef(mFontFamilies.GetOrInsertNew(key, aFamilyName)); +} + +void gfxUserFontSet::ForgetLocalFaces() { + for (const auto& fam : mFontFamilies.Values()) { + ForgetLocalFace(fam); + } +} + +void gfxUserFontSet::ForgetLocalFace(gfxUserFontFamily* aFontFamily) { + aFontFamily->ReadLock(); + const auto& fonts = aFontFamily->GetFontList(); + for (const auto& f : fonts) { + auto ufe = static_cast<gfxUserFontEntry*>(f.get()); + // If the user font entry has loaded an entry using src:local(), + // discard it as no longer valid. + if (ufe->GetPlatformFontEntry() && + ufe->GetPlatformFontEntry()->IsLocalUserFont()) { + ufe->mPlatformFontEntry = nullptr; + } + // We need to re-evaluate the source list in the context of the new + // platform fontlist, whether or not the entry actually used a local() + // source last time, as one might be newly available. + if (ufe->mSeenLocalSource) { + ufe->LoadCanceled(); + } + } + aFontFamily->ReadUnlock(); +} + +/////////////////////////////////////////////////////////////////////////////// +// gfxUserFontSet::UserFontCache - re-use platform font entries for user fonts +// across pages/fontsets rather than instantiating new platform fonts. +// +// Entries are added to this cache when a platform font is instantiated from +// downloaded data, and removed when the platform font entry is destroyed. +// We don't need to use a timed expiration scheme here because the gfxFontEntry +// for a downloaded font will be kept alive by its corresponding gfxFont +// instance(s) until they are deleted, and *that* happens using an expiration +// tracker (gfxFontCache). The result is that the downloaded font instances +// recorded here will persist between pages and can get reused (provided the +// source URI and principal match, of course). +/////////////////////////////////////////////////////////////////////////////// + +nsTHashtable<gfxUserFontSet::UserFontCache::Entry>* + gfxUserFontSet::UserFontCache::sUserFonts = nullptr; + +NS_IMPL_ISUPPORTS(gfxUserFontSet::UserFontCache::Flusher, nsIObserver) + +NS_IMETHODIMP +gfxUserFontSet::UserFontCache::Flusher::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + if (!sUserFonts) { + return NS_OK; + } + + if (!strcmp(aTopic, "cacheservice:empty-cache")) { + for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) { + i.Remove(); + } + } else if (!strcmp(aTopic, "last-pb-context-exited")) { + for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) { + if (i.Get()->IsPrivate()) { + i.Remove(); + } + } + } else if (!strcmp(aTopic, "xpcom-shutdown")) { + for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) { + i.Get()->GetFontEntry()->DisconnectSVG(); + } + } else { + MOZ_ASSERT_UNREACHABLE("unexpected topic"); + } + + return NS_OK; +} + +bool gfxUserFontSet::UserFontCache::Entry::KeyEquals( + const KeyTypePointer aKey) const { + const gfxFontEntry* fe = aKey->mFontEntry; + + if (!mURI->Equals(aKey->mURI)) { + return false; + } + + // For data: URIs, we don't care about the principal; otherwise, check it. + if (!IgnorePrincipal(mURI)) { + NS_ASSERTION(mPrincipal && aKey->mPrincipal, + "only data: URIs are allowed to omit the principal"); + if (!mPrincipal->Equals(aKey->mPrincipal)) { + return false; + } + } + + if (mPrivate != aKey->mPrivate) { + return false; + } + + if (mFontEntry->SlantStyle() != fe->SlantStyle() || + mFontEntry->Weight() != fe->Weight() || + mFontEntry->Stretch() != fe->Stretch() || + mFontEntry->mRangeFlags != fe->mRangeFlags || + mFontEntry->mFeatureSettings != fe->mFeatureSettings || + mFontEntry->mVariationSettings != fe->mVariationSettings || + mFontEntry->mLanguageOverride != fe->mLanguageOverride || + mFontEntry->mAscentOverride != fe->mAscentOverride || + mFontEntry->mDescentOverride != fe->mDescentOverride || + mFontEntry->mLineGapOverride != fe->mLineGapOverride || + mFontEntry->mSizeAdjust != fe->mSizeAdjust || + mFontEntry->mFamilyName != fe->mFamilyName) { + return false; + } + + return true; +} + +void gfxUserFontSet::UserFontCache::CacheFont(gfxFontEntry* aFontEntry) { + NS_ASSERTION(aFontEntry->mFamilyName.Length() != 0, + "caching a font associated with no family yet"); + + // if caching is disabled, simply return + if (Preferences::GetBool("gfx.downloadable_fonts.disable_cache")) { + return; + } + + gfxUserFontData* data = aFontEntry->mUserFontData.get(); + if (data->mIsBuffer) { +#ifdef DEBUG_USERFONT_CACHE + printf("userfontcache skipped fontentry with buffer source: %p\n", + aFontEntry); +#endif + return; + } + + if (!sUserFonts) { + sUserFonts = new nsTHashtable<Entry>; + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + Flusher* flusher = new Flusher; + obs->AddObserver(flusher, "cacheservice:empty-cache", false); + obs->AddObserver(flusher, "last-pb-context-exited", false); + obs->AddObserver(flusher, "xpcom-shutdown", false); + } + + // Create and register a memory reporter for sUserFonts. + // This reporter is never unregistered, but that's OK because + // the reporter checks whether sUserFonts is null, so it would + // be safe to call even after UserFontCache::Shutdown has deleted + // the cache. + RegisterStrongMemoryReporter(new MemoryReporter()); + } + + // For data: URIs, the principal is ignored; anyone who has the same + // data: URI is able to load it and get an equivalent font. + // Otherwise, the principal is used as part of the cache key. + gfxFontSrcPrincipal* principal; + if (IgnorePrincipal(data->mURI)) { + principal = nullptr; + } else { + principal = data->mPrincipal; + } + sUserFonts->PutEntry(Key(data->mURI, principal, aFontEntry, data->mPrivate)); + +#ifdef DEBUG_USERFONT_CACHE + printf("userfontcache added fontentry: %p\n", aFontEntry); + Dump(); +#endif +} + +void gfxUserFontSet::UserFontCache::ForgetFont(gfxFontEntry* aFontEntry) { + if (!sUserFonts) { + // if we've already deleted the cache (i.e. during shutdown), + // just ignore this + return; + } + + // We can't simply use RemoveEntry here because it's possible the principal + // may have changed since the font was cached, in which case the lookup + // would no longer find the entry (bug 838105). + for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) { + if (i.Get()->GetFontEntry() == aFontEntry) { + i.Remove(); + } + } + +#ifdef DEBUG_USERFONT_CACHE + printf("userfontcache removed fontentry: %p\n", aFontEntry); + Dump(); +#endif +} + +gfxFontEntry* gfxUserFontSet::UserFontCache::GetFont( + const gfxFontFaceSrc& aSrc, const gfxUserFontEntry& aUserFontEntry) { + if (!sUserFonts || + Preferences::GetBool("gfx.downloadable_fonts.disable_cache")) { + return nullptr; + } + + RefPtr<gfxUserFontSet> srcFontSet = aUserFontEntry.GetUserFontSet(); + if (NS_WARN_IF(!srcFontSet) || srcFontSet->BypassCache()) { + return nullptr; + } + + // Ignore principal when looking up a data: URI. + RefPtr<gfxFontSrcPrincipal> principal = + IgnorePrincipal(aSrc.mURI) ? nullptr : aSrc.LoadPrincipal(*srcFontSet); + + Entry* entry = sUserFonts->GetEntry( + Key(aSrc.mURI, principal, const_cast<gfxUserFontEntry*>(&aUserFontEntry), + srcFontSet->GetPrivateBrowsing())); + if (!entry) { + return nullptr; + } + + // We have to perform another content policy check here to prevent + // cache poisoning. E.g. a.com loads a font into the cache but + // b.com has a CSP not allowing any fonts to be loaded. + if (!srcFontSet->IsFontLoadAllowed(aSrc)) { + return nullptr; + } + + return entry->GetFontEntry(); +} + +void gfxUserFontSet::UserFontCache::Shutdown() { + if (sUserFonts) { + delete sUserFonts; + sUserFonts = nullptr; + } +} + +MOZ_DEFINE_MALLOC_SIZE_OF(UserFontsMallocSizeOf) + +void gfxUserFontSet::UserFontCache::Entry::ReportMemory( + nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) { + MOZ_ASSERT(mFontEntry); + nsAutoCString path("explicit/gfx/user-fonts/font("); + + if (aAnonymize) { + path.AppendPrintf("<anonymized-%p>", this); + } else { + path.AppendPrintf("family=%s", mFontEntry->mFamilyName.get()); + if (mURI) { + nsCString spec = mURI->GetSpecOrDefault(); + spec.ReplaceChar('/', '\\'); + // Some fonts are loaded using horrendously-long data: URIs; + // truncate those before reporting them. + if (mURI->get()->SchemeIs("data") && spec.Length() > 255) { + spec.Truncate(252); + spec.AppendLiteral("..."); + } + path.AppendPrintf(", url=%s", spec.get()); + } + if (mPrincipal) { + nsAutoCString spec; + mPrincipal->NodePrincipal()->GetAsciiSpec(spec); + if (!spec.IsEmpty()) { + // Include a clue as to who loaded this resource. (Note + // that because of font entry sharing, other pages may now + // be using this resource, and the original page may not + // even be loaded any longer.) + spec.ReplaceChar('/', '\\'); + path.AppendPrintf(", principal=%s", spec.get()); + } + } + } + path.Append(')'); + + aHandleReport->Callback( + ""_ns, path, nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, + mFontEntry->ComputedSizeOfExcludingThis(UserFontsMallocSizeOf), + "Memory used by @font-face resource."_ns, aData); +} + +NS_IMPL_ISUPPORTS(gfxUserFontSet::UserFontCache::MemoryReporter, + nsIMemoryReporter) + +NS_IMETHODIMP +gfxUserFontSet::UserFontCache::MemoryReporter::CollectReports( + nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) { + if (!sUserFonts) { + return NS_OK; + } + + for (auto it = sUserFonts->Iter(); !it.Done(); it.Next()) { + it.Get()->ReportMemory(aHandleReport, aData, aAnonymize); + } + + MOZ_COLLECT_REPORT( + "explicit/gfx/user-fonts/cache-overhead", KIND_HEAP, UNITS_BYTES, + sUserFonts->ShallowSizeOfIncludingThis(UserFontsMallocSizeOf), + "Memory used by the @font-face cache, not counting the actual font " + "resources."); + + return NS_OK; +} + +#ifdef DEBUG_USERFONT_CACHE + +void gfxUserFontSet::UserFontCache::Entry::Dump() { + nsresult rv; + + nsAutoCString principalURISpec("(null)"); + bool setDomain = false; + + if (mPrincipal) { + nsCOMPtr<nsIURI> principalURI; + rv = mPrincipal->NodePrincipal()->GetURI(getter_AddRefs(principalURI)); + if (NS_SUCCEEDED(rv)) { + principalURI->GetSpec(principalURISpec); + } + + nsCOMPtr<nsIURI> domainURI; + mPrincipal->NodePrincipal()->GetDomain(getter_AddRefs(domainURI)); + if (domainURI) { + setDomain = true; + } + } + + NS_ASSERTION(mURI, "null URI in userfont cache entry"); + + printf( + "userfontcache fontEntry: %p fonturihash: %8.8x " + "family: %s domainset: %s principal: [%s]\n", + mFontEntry, mURI->Hash(), mFontEntry->FamilyName().get(), + setDomain ? "true" : "false", principalURISpec.get()); +} + +void gfxUserFontSet::UserFontCache::Dump() { + if (!sUserFonts) { + return; + } + + printf("userfontcache dump count: %d ========\n", sUserFonts->Count()); + for (auto it = sUserFonts->Iter(); !it.Done(); it.Next()) { + it.Get()->Dump(); + } + printf("userfontcache dump ==================\n"); +} + +#endif + +#undef LOG +#undef LOG_ENABLED diff --git a/gfx/thebes/gfxUserFontSet.h b/gfx/thebes/gfxUserFontSet.h new file mode 100644 index 0000000000..d67d10fbe6 --- /dev/null +++ b/gfx/thebes/gfxUserFontSet.h @@ -0,0 +1,792 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_USER_FONT_SET_H +#define GFX_USER_FONT_SET_H + +#include <new> +#include "PLDHashTable.h" +#include "gfxFontEntry.h" +#include "gfxFontUtils.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Attributes.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefPtr.h" +#include "nsCOMPtr.h" +#include "nsHashKeys.h" +#include "nsIMemoryReporter.h" +#include "nsIObserver.h" +#include "nsIScriptError.h" +#include "nsISupports.h" +#include "nsRefPtrHashtable.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nscore.h" + +// Only needed for function bodies. +#include <utility> // for move, forward +#include "MainThreadUtils.h" // for NS_IsMainThread +#include "gfxFontFeatures.h" // for gfxFontFeature +#include "gfxFontSrcPrincipal.h" // for gfxFontSrcPrincipal +#include "gfxFontSrcURI.h" // for gfxFontSrcURI +#include "mozilla/Assertions.h" // for AssertionConditionType, MOZ_ASSERT_HELPER2, MOZ_ASSERT, MOZ_ASSERT_UNREACHABLE, MOZ_ASSER... +#include "mozilla/HashFunctions.h" // for HashBytes, HashGeneric +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "mozilla/gfx/FontVariation.h" // for FontVariation +#include "nsDebug.h" // for NS_WARNING +#include "nsIReferrerInfo.h" // for nsIReferrerInfo + +class gfxFont; +class gfxUserFontSet; +class nsIFontLoadCompleteCallback; +class nsIRunnable; +struct gfxFontStyle; +struct gfxFontVariationAxis; +struct gfxFontVariationInstance; +template <class T> +class nsMainThreadPtrHandle; + +namespace mozilla { +class LogModule; +class PostTraversalTask; +enum class StyleFontDisplay : uint8_t; +} // namespace mozilla +class nsFontFaceLoader; + +// #define DEBUG_USERFONT_CACHE + +class gfxFontFaceBufferSource { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxFontFaceBufferSource) + public: + virtual void TakeBuffer(uint8_t*& aBuffer, uint32_t& aLength) = 0; + + protected: + virtual ~gfxFontFaceBufferSource() = default; +}; + +// parsed CSS @font-face rule information +// lifetime: from when @font-face rule processed until font is loaded +struct gfxFontFaceSrc { + enum SourceType { eSourceType_Local, eSourceType_URL, eSourceType_Buffer }; + + SourceType mSourceType; + + // if url, whether to use the origin principal or not + bool mUseOriginPrincipal = false; + + // Required font technologies. + mozilla::StyleFontFaceSourceTechFlags mTechFlags; + + // Format hint, if any was specified. + mozilla::StyleFontFaceSourceFormatKeyword mFormatHint; + + nsCString mLocalName; // full font name if local + RefPtr<gfxFontSrcURI> mURI; // uri if url + nsCOMPtr<nsIReferrerInfo> mReferrerInfo; // referrer info if url + RefPtr<gfxFontSrcPrincipal> + mOriginPrincipal; // principal if url and mUseOriginPrincipal + + RefPtr<gfxFontFaceBufferSource> mBuffer; + + // The principal that should be used for the load. Should only be used for + // URL sources. + already_AddRefed<gfxFontSrcPrincipal> LoadPrincipal( + const gfxUserFontSet&) const; +}; + +inline bool operator==(const gfxFontFaceSrc& a, const gfxFontFaceSrc& b) { + if (a.mSourceType != b.mSourceType) { + return false; + } + switch (a.mSourceType) { + case gfxFontFaceSrc::eSourceType_Local: + return a.mLocalName == b.mLocalName; + case gfxFontFaceSrc::eSourceType_URL: { + if (a.mUseOriginPrincipal != b.mUseOriginPrincipal) { + return false; + } + if (a.mUseOriginPrincipal) { + if (!a.mOriginPrincipal->Equals(b.mOriginPrincipal)) { + return false; + } + } + bool equals; + return a.mFormatHint == b.mFormatHint && a.mTechFlags == b.mTechFlags && + (a.mURI == b.mURI || a.mURI->Equals(b.mURI)) && + NS_SUCCEEDED(a.mReferrerInfo->Equals(b.mReferrerInfo, &equals)) && + equals; + } + case gfxFontFaceSrc::eSourceType_Buffer: + return a.mBuffer == b.mBuffer; + } + NS_WARNING("unexpected mSourceType"); + return false; +} + +// Subclassed to store platform-specific code cleaned out when font entry is +// deleted. +// Lifetime: from when platform font is created until it is deactivated. +// If the platform does not need to add any platform-specific code/data here, +// then the gfxUserFontSet will allocate a base gfxUserFontData and attach +// to the entry to track the basic user font info fields here. +class gfxUserFontData { + public: + gfxUserFontData() + : mSrcIndex(0), + mMetaOrigLen(0), + mTechFlags(mozilla::StyleFontFaceSourceTechFlags::Empty()), + mFormatHint(mozilla::StyleFontFaceSourceFormatKeyword::None), + mCompression(kUnknownCompression), + mPrivate(false), + mIsBuffer(false) {} + virtual ~gfxUserFontData() = default; + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + nsTArray<uint8_t> mMetadata; // woff metadata block (compressed), if any + RefPtr<gfxFontSrcURI> mURI; // URI of the source, if it was url() + RefPtr<gfxFontSrcPrincipal> + mPrincipal; // principal for the download, if url() + nsCString mLocalName; // font name used for the source, if local() + nsCString mRealName; // original fullname from the font resource + uint32_t mSrcIndex; // index in the rule's source list + uint32_t mMetaOrigLen; // length needed to decompress metadata + mozilla::StyleFontFaceSourceTechFlags mTechFlags; // required font tech + mozilla::StyleFontFaceSourceFormatKeyword + mFormatHint; // format hint for the source used, if any + uint8_t mCompression; // compression type + bool mPrivate; // whether font belongs to a private window + bool mIsBuffer; // whether the font source was a buffer + + enum { + kUnknownCompression = 0, + kZlibCompression = 1, + kBrotliCompression = 2 + }; +}; + +// initially contains a set of userfont font entry objects, replaced with +// platform/user fonts as downloaded + +class gfxUserFontFamily : public gfxFontFamily { + public: + friend class gfxUserFontSet; + + explicit gfxUserFontFamily(const nsACString& aName) + : gfxFontFamily(aName, FontVisibility::Webfont) {} + + virtual ~gfxUserFontFamily(); + + // add the given font entry to the end of the family's list + void AddFontEntry(gfxFontEntry* aFontEntry) { + mozilla::AutoWriteLock lock(mLock); + MOZ_ASSERT(!mIsSimpleFamily, "not valid for user-font families"); + // keep ref while removing existing entry + RefPtr<gfxFontEntry> fe = aFontEntry; + // remove existing entry, if already present + mAvailableFonts.RemoveElement(aFontEntry); + // insert at the beginning so that the last-defined font is the first + // one in the fontlist used for matching, as per CSS Fonts spec + mAvailableFonts.InsertElementAt(0, aFontEntry); + + if (aFontEntry->mFamilyName.IsEmpty()) { + aFontEntry->mFamilyName = Name(); + } else { +#ifdef DEBUG + nsCString thisName = Name(); + nsCString entryName = aFontEntry->mFamilyName; + ToLowerCase(thisName); + ToLowerCase(entryName); + MOZ_ASSERT(thisName.Equals(entryName)); +#endif + } + ResetCharacterMap(); + } + + void RemoveFontEntry(gfxFontEntry* aFontEntry) { + mozilla::AutoWriteLock lock(mLock); + MOZ_ASSERT(!mIsSimpleFamily, "not valid for user-font families"); + mAvailableFonts.RemoveElement(aFontEntry); + } + + // Remove all font entries from the family + void DetachFontEntries() { + mozilla::AutoWriteLock lock(mLock); + mAvailableFonts.Clear(); + } +}; + +class gfxUserFontEntry; +class gfxOTSMessageContext; + +struct gfxUserFontAttributes { + using FontStretch = mozilla::FontStretch; + using StretchRange = mozilla::StretchRange; + using FontSlantStyle = mozilla::FontSlantStyle; + using SlantStyleRange = mozilla::SlantStyleRange; + using FontWeight = mozilla::FontWeight; + using WeightRange = mozilla::WeightRange; + using StyleFontFaceSourceListComponent = + mozilla::StyleFontFaceSourceListComponent; + using RangeFlags = gfxFontEntry::RangeFlags; + + WeightRange mWeight = WeightRange(FontWeight::NORMAL); + StretchRange mStretch = StretchRange(FontStretch::NORMAL); + SlantStyleRange mStyle = SlantStyleRange(FontSlantStyle::NORMAL); + RangeFlags mRangeFlags = RangeFlags::eAutoWeight | RangeFlags::eAutoStretch | + RangeFlags::eAutoSlantStyle; + mozilla::StyleFontDisplay mFontDisplay = mozilla::StyleFontDisplay::Auto; + float mAscentOverride = -1.0; + float mDescentOverride = -1.0; + float mLineGapOverride = -1.0; + float mSizeAdjust = 1.0; + uint32_t mLanguageOverride = NO_FONT_LANGUAGE_OVERRIDE; + nsTArray<gfxFontFeature> mFeatureSettings; + nsTArray<gfxFontVariation> mVariationSettings; + RefPtr<gfxCharacterMap> mUnicodeRanges; + + nsCString mFamilyName; + AutoTArray<StyleFontFaceSourceListComponent, 8> mSources; +}; + +class gfxUserFontSet { + friend class gfxUserFontEntry; + friend class gfxOTSMessageContext; + + public: + using FontStretch = mozilla::FontStretch; + using StretchRange = mozilla::StretchRange; + using FontSlantStyle = mozilla::FontSlantStyle; + using SlantStyleRange = mozilla::SlantStyleRange; + using FontWeight = mozilla::FontWeight; + using WeightRange = mozilla::WeightRange; + using RangeFlags = gfxFontEntry::RangeFlags; + + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + + gfxUserFontSet(); + + void Destroy(); + + // creates a font face without adding it to a particular family + // weight - [100, 900] (multiples of 100) + // stretch = [FontStretch::UltraCondensed(), FontStretch::UltraExpanded()] + // italic style = constants in gfxFontConstants.h, e.g. NS_FONT_STYLE_NORMAL + // language override = result of calling + // nsLayoutUtils::ParseFontLanguageOverride + // TODO: support for unicode ranges not yet implemented + virtual already_AddRefed<gfxUserFontEntry> CreateUserFontEntry( + nsTArray<gfxFontFaceSrc>&& aFontFaceSrcList, + gfxUserFontAttributes&& aAttr) = 0; + + // creates a font face for the specified family, or returns an existing + // matching entry on the family if there is one + already_AddRefed<gfxUserFontEntry> FindOrCreateUserFontEntry( + nsTArray<gfxFontFaceSrc>&& aFontFaceSrcList, + gfxUserFontAttributes&& aAttr); + + // add in a font face for which we have the gfxUserFontEntry already + void AddUserFontEntry(const nsCString& aFamilyName, + gfxUserFontEntry* aUserFontEntry); + + // Look up and return the gfxUserFontFamily in mFontFamilies with + // the given name + virtual already_AddRefed<gfxUserFontFamily> LookupFamily( + const nsACString& aName) const; + + virtual already_AddRefed<gfxFontSrcPrincipal> GetStandardFontLoadPrincipal() + const = 0; + virtual nsPresContext* GetPresContext() const = 0; + + // check whether content policies allow the given URI to load. + virtual bool IsFontLoadAllowed(const gfxFontFaceSrc&) = 0; + + // initialize the process that loads external font data, which upon + // completion will call FontDataDownloadComplete method + virtual nsresult StartLoad(gfxUserFontEntry* aUserFontEntry, + uint32_t aSrcIndex) = 0; + + // generation - each time a face is loaded, generation is + // incremented so that the change can be recognized + uint64_t GetGeneration() { return mGeneration; } + + // increment the generation on font load + void IncrementGeneration(bool aIsRebuild = false); + + // Generation is bumped on font loads but that doesn't affect name-style + // mappings. Rebuilds do however affect name-style mappings so need to + // lookup fontlists again when that happens. + uint64_t GetRebuildGeneration() { return mRebuildGeneration; } + + // rebuild if local rules have been used + void RebuildLocalRules(); + + // Discard any font entries created for src:local(), so that they will + // be reloaded next time they're needed. This is called when the platform + // font list has changed, which means local font entries that were set up + // may no longer be valid. + virtual void ForgetLocalFaces(); + + class UserFontCache { + public: + // Record a loaded user-font in the cache. This requires that the + // font-entry's userFontData has been set up already, as it relies + // on the URI and Principal recorded there. + static void CacheFont(gfxFontEntry* aFontEntry); + + // The given gfxFontEntry is being destroyed, so remove any record that + // refers to it. + static void ForgetFont(gfxFontEntry* aFontEntry); + + // Return the gfxFontEntry corresponding to a given URI and principal, + // and the features of the given userfont entry, or nullptr if none is + // available. The aPrivate flag is set for requests coming from private + // windows, so we can avoid leaking fonts cached in private windows mode out + // to normal windows. + static gfxFontEntry* GetFont(const gfxFontFaceSrc&, + const gfxUserFontEntry&); + + // Clear everything so that we don't leak URIs and Principals. + static void Shutdown(); + + // Memory-reporting support. + class MemoryReporter final : public nsIMemoryReporter { + private: + ~MemoryReporter() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMEMORYREPORTER + }; + +#ifdef DEBUG_USERFONT_CACHE + // dump contents + static void Dump(); +#endif + + private: + // Helper that we use to observe the empty-cache notification + // from nsICacheService. + class Flusher : public nsIObserver { + virtual ~Flusher() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + Flusher() = default; + }; + + // Key used to look up entries in the user-font cache. + // Note that key comparison does *not* use the mFontEntry field + // as a whole; it only compares specific fields within the entry + // (weight/width/style/features) that could affect font selection + // or rendering, and that must match between a font-set's userfont + // entry and the corresponding "real" font entry. + struct Key { + RefPtr<gfxFontSrcURI> mURI; + RefPtr<gfxFontSrcPrincipal> mPrincipal; // use nullptr with data: URLs + // The font entry MUST notify the cache when it is destroyed + // (by calling ForgetFont()). + gfxFontEntry* MOZ_NON_OWNING_REF mFontEntry; + bool mPrivate; + + Key(gfxFontSrcURI* aURI, gfxFontSrcPrincipal* aPrincipal, + gfxFontEntry* aFontEntry, bool aPrivate) + : mURI(aURI), + mPrincipal(aPrincipal), + mFontEntry(aFontEntry), + mPrivate(aPrivate) {} + }; + + class Entry : public PLDHashEntryHdr { + public: + typedef const Key& KeyType; + typedef const Key* KeyTypePointer; + + explicit Entry(KeyTypePointer aKey) + : mURI(aKey->mURI), + mPrincipal(aKey->mPrincipal), + mFontEntry(aKey->mFontEntry), + mPrivate(aKey->mPrivate) {} + + Entry(Entry&& aOther) + : PLDHashEntryHdr(std::move(aOther)), + mURI(std::move(aOther.mURI)), + mPrincipal(std::move(aOther.mPrincipal)), + mFontEntry(std::move(aOther.mFontEntry)), + mPrivate(std::move(aOther.mPrivate)) {} + + ~Entry() = default; + + bool KeyEquals(const KeyTypePointer aKey) const; + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + + static PLDHashNumber HashKey(const KeyTypePointer aKey) { + PLDHashNumber principalHash = + aKey->mPrincipal ? aKey->mPrincipal->Hash() : 0; + return mozilla::HashGeneric( + principalHash + int(aKey->mPrivate), aKey->mURI->Hash(), + HashFeatures(aKey->mFontEntry->mFeatureSettings), + HashVariations(aKey->mFontEntry->mVariationSettings), + mozilla::HashString(aKey->mFontEntry->mFamilyName), + aKey->mFontEntry->Weight().AsScalar(), + aKey->mFontEntry->SlantStyle().AsScalar(), + aKey->mFontEntry->Stretch().AsScalar(), + aKey->mFontEntry->mRangeFlags, aKey->mFontEntry->mLanguageOverride); + } + + enum { ALLOW_MEMMOVE = false }; + + gfxFontSrcURI* GetURI() const { return mURI; } + gfxFontSrcPrincipal* GetPrincipal() const { return mPrincipal; } + gfxFontEntry* GetFontEntry() const { return mFontEntry; } + bool IsPrivate() const { return mPrivate; } + + void ReportMemory(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize); + +#ifdef DEBUG_USERFONT_CACHE + void Dump(); +#endif + + private: + static uint32_t HashFeatures(const nsTArray<gfxFontFeature>& aFeatures) { + return mozilla::HashBytes(aFeatures.Elements(), + aFeatures.Length() * sizeof(gfxFontFeature)); + } + + static uint32_t HashVariations( + const nsTArray<mozilla::gfx::FontVariation>& aVariations) { + return mozilla::HashBytes( + aVariations.Elements(), + aVariations.Length() * sizeof(mozilla::gfx::FontVariation)); + } + + RefPtr<gfxFontSrcURI> mURI; + RefPtr<gfxFontSrcPrincipal> mPrincipal; // or nullptr for data: URLs + + // The "real" font entry corresponding to this downloaded font. + // The font entry MUST notify the cache when it is destroyed + // (by calling ForgetFont()). + gfxFontEntry* MOZ_NON_OWNING_REF mFontEntry; + + // Whether this font was loaded from a private window. + bool mPrivate; + }; + + static nsTHashtable<Entry>* sUserFonts; + }; + + void SetLocalRulesUsed() { mLocalRulesUsed = true; } + + static mozilla::LogModule* GetUserFontsLog(); + + // record statistics about font completion + virtual void RecordFontLoadDone(uint32_t aFontSize, + mozilla::TimeStamp aDoneTime) {} + + void GetLoadStatistics(uint32_t& aLoadCount, uint64_t& aLoadSize) const { + aLoadCount = mDownloadCount; + aLoadSize = mDownloadSize; + } + + protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~gfxUserFontSet(); + + // Return whether the font set is associated with a private-browsing tab. + virtual bool GetPrivateBrowsing() = 0; + + // Return whether the font set is associated with a document that was + // shift-reloaded, for example, and thus should bypass the font cache. + virtual bool BypassCache() = 0; + + // parse data for a data URL + virtual nsresult SyncLoadFontData(gfxUserFontEntry* aFontToLoad, + const gfxFontFaceSrc* aFontFaceSrc, + uint8_t*& aBuffer, + uint32_t& aBufferLength) = 0; + + // report a problem of some kind (implemented in nsUserFontSet) + virtual nsresult LogMessage(gfxUserFontEntry* aUserFontEntry, + uint32_t aSrcIndex, const char* aMessage, + uint32_t aFlags = nsIScriptError::errorFlag, + nsresult aStatus = NS_OK) = 0; + + // helper method for performing the actual userfont set rebuild + virtual void DoRebuildUserFontSet() = 0; + + // helper method for FindOrCreateUserFontEntry + gfxUserFontEntry* FindExistingUserFontEntry( + gfxUserFontFamily* aFamily, + const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, + const gfxUserFontAttributes& aAttr); + + // creates a new gfxUserFontFamily in mFontFamilies, or returns an existing + // family if there is one + virtual already_AddRefed<gfxUserFontFamily> GetFamily( + const nsACString& aFamilyName); + + void ForgetLocalFace(gfxUserFontFamily* aFontFamily); + + // font families defined by @font-face rules + nsRefPtrHashtable<nsCStringHashKey, gfxUserFontFamily> mFontFamilies; + + uint64_t mGeneration; // bumped on any font load change + uint64_t mRebuildGeneration; // only bumped on rebuilds + + // true when local names have been looked up, false otherwise + bool mLocalRulesUsed; + + // true when rules using local names need to be redone + bool mRebuildLocalRules; + + // performance stats + uint32_t mDownloadCount; + uint64_t mDownloadSize; +}; + +// acts a placeholder until the real font is downloaded + +class gfxUserFontEntry : public gfxFontEntry { + friend class mozilla::PostTraversalTask; + friend class gfxUserFontSet; + friend class nsUserFontSet; + friend class nsFontFaceLoader; + friend class gfxOTSMessageContext; + + public: + enum UserFontLoadState { + STATUS_NOT_LOADED = 0, + STATUS_LOAD_PENDING, + STATUS_LOADING, + STATUS_LOADED, + STATUS_FAILED + }; + + gfxUserFontEntry(nsTArray<gfxFontFaceSrc>&& aFontFaceSrcList, + gfxUserFontAttributes&& aAttr); + + ~gfxUserFontEntry() override; + + // Update the attributes of the entry to the given values, without disturbing + // the associated platform font entry or in-progress downloads. + void UpdateAttributes(gfxUserFontAttributes&& aAttr); + + // Return whether the entry matches the given list of attributes + bool Matches(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList, + const gfxUserFontAttributes& aAttr); + + gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle) override; + + gfxFontEntry* GetPlatformFontEntry() const { return mPlatformFontEntry; } + + // is the font loading or loaded, or did it fail? + UserFontLoadState LoadState() const { return mUserFontLoadState; } + + void LoadCanceled() { + MOZ_ASSERT(NS_IsMainThread()); + + mUserFontLoadState = STATUS_NOT_LOADED; + mFontDataLoadingState = NOT_LOADING; + mLoader = nullptr; + // Reset mCurrentSrcIndex so that all potential sources are re-considered. + mCurrentSrcIndex = 0; + mSeenLocalSource = false; + } + + // whether to wait before using fallback font or not + bool WaitForUserFont() const { + return (mUserFontLoadState == STATUS_LOAD_PENDING || + mUserFontLoadState == STATUS_LOADING) && + mFontDataLoadingState < LOADING_SLOWLY; + } + + // For userfonts, cmap is used to store the unicode range data, + // and is inert once set, so locking is not required here. + // no cmap ==> all codepoints permitted + bool CharacterInUnicodeRange(uint32_t ch) const { + if (const auto* map = GetUnicodeRangeMap()) { + return map->test(ch); + } + return true; + } + + gfxCharacterMap* GetUnicodeRangeMap() const { return GetCharacterMap(); } + void SetUnicodeRangeMap(RefPtr<gfxCharacterMap>&& aCharMap) { + auto* oldCmap = GetUnicodeRangeMap(); + if (oldCmap != aCharMap) { + auto* newCmap = aCharMap.forget().take(); + if (mCharacterMap.compareExchange(oldCmap, newCmap)) { + NS_IF_RELEASE(oldCmap); + } else { + NS_IF_RELEASE(newCmap); + } + } + } + + mozilla::StyleFontDisplay GetFontDisplay() const { return mFontDisplay; } + + // load the font - starts the loading of sources which continues until + // a valid font resource is found or all sources fail + void Load(); + + // methods to expose some information to FontFaceSet::UserFontSet + // since we can't make that class a friend + void SetLoader(nsFontFaceLoader* aLoader) { + MOZ_ASSERT(NS_IsMainThread()); + mLoader = aLoader; + } + + nsFontFaceLoader* GetLoader() const { + MOZ_ASSERT(NS_IsMainThread()); + return mLoader; + } + + gfxFontSrcPrincipal* GetPrincipal() const { return mPrincipal; } + void GetFamilyNameAndURIForLogging(uint32_t aSrcIndex, + nsACString& aFamilyName, nsACString& aURI); + + gfxFontEntry* Clone() const override { + MOZ_ASSERT_UNREACHABLE("cannot Clone user fonts"); + return nullptr; + } + + virtual already_AddRefed<gfxUserFontSet> GetUserFontSet() const = 0; + + const nsTArray<gfxFontFaceSrc>& SourceList() const { return mSrcList; } + + // Returns a weak reference to the requested source record, which is owned + // by the gfxUserFontEntry. + const gfxFontFaceSrc& SourceAt(uint32_t aSrcIndex) const { + return mSrcList[aSrcIndex]; + } + + // The variation-query APIs should not be called on placeholders. + bool HasVariations() override { + MOZ_ASSERT_UNREACHABLE("not meaningful for a userfont placeholder"); + return false; + } + void GetVariationAxes(nsTArray<gfxFontVariationAxis>&) override { + MOZ_ASSERT_UNREACHABLE("not meaningful for a userfont placeholder"); + } + void GetVariationInstances(nsTArray<gfxFontVariationInstance>&) override { + MOZ_ASSERT_UNREACHABLE("not meaningful for a userfont placeholder"); + } + + protected: + struct OTSMessage { + nsCString mMessage; + int mLevel; // see OTSContext in gfx/ots/include/opentype-sanitizer.h + }; + + const uint8_t* SanitizeOpenTypeData(const uint8_t* aData, uint32_t aLength, + uint32_t& aSanitaryLength, + gfxUserFontType& aFontType, + nsTArray<OTSMessage>& aMessages); + + // attempt to load the next resource in the src list. + void LoadNextSrc(); + void ContinueLoad(); + void DoLoadNextSrc(bool aIsContinue); + + // change the load state + virtual void SetLoadState(UserFontLoadState aLoadState); + + // when download has been completed, pass back data here + // aDownloadStatus == NS_OK ==> download succeeded, error otherwise + // Ownership of aFontData is passed in here; the font set must + // ensure that it is eventually deleted with free(). + void FontDataDownloadComplete(uint32_t aSrcIndex, const uint8_t* aFontData, + uint32_t aLength, nsresult aDownloadStatus, + nsIFontLoadCompleteCallback* aCallback); + + // helper method for creating a platform font + // returns true if platform font creation successful + // Ownership of aFontData is passed in here; the font must + // ensure that it is eventually deleted with free(). + bool LoadPlatformFontSync(uint32_t aSrcIndex, const uint8_t* aFontData, + uint32_t aLength); + + void LoadPlatformFontAsync(uint32_t aSrcIndex, const uint8_t* aFontData, + uint32_t aLength, + nsIFontLoadCompleteCallback* aCallback); + + // helper method for LoadPlatformFontAsync; runs on a background thread + void StartPlatformFontLoadOnBackgroundThread( + uint32_t aSrcIndex, const uint8_t* aFontData, uint32_t aLength, + nsMainThreadPtrHandle<nsIFontLoadCompleteCallback> aCallback); + + // helper method for LoadPlatformFontAsync; runs on the main thread + void ContinuePlatformFontLoadOnMainThread( + uint32_t aSrcIndex, const uint8_t* aOriginalFontData, + uint32_t aOriginalLength, gfxUserFontType aFontType, + const uint8_t* aSanitizedFontData, uint32_t aSanitizedLength, + nsTArray<OTSMessage>&& aMessages, + nsMainThreadPtrHandle<nsIFontLoadCompleteCallback> aCallback); + + // helper method for LoadPlatformFontSync and + // ContinuePlatformFontLoadOnMainThread; runs on the main thread + bool LoadPlatformFont(uint32_t aSrcIndex, const uint8_t* aOriginalFontData, + uint32_t aOriginalLength, gfxUserFontType aFontType, + const uint8_t* aSanitizedFontData, + uint32_t aSanitizedLength, + nsTArray<OTSMessage>&& aMessages); + + // helper method for FontDataDownloadComplete and + // ContinuePlatformFontLoadOnMainThread; runs on the main thread + void FontLoadFailed(nsIFontLoadCompleteCallback* aCallback); + + // store metadata and src details for current src into aFontEntry + void StoreUserFontData(gfxFontEntry* aFontEntry, uint32_t aSrcIndex, + bool aPrivate, const nsACString& aOriginalName, + FallibleTArray<uint8_t>* aMetadata, + uint32_t aMetaOrigLen, uint8_t aCompression); + + // Clears and then adds to aResult all of the user font sets that this user + // font entry has been added to. This will at least include the owner of this + // user font entry. + virtual void GetUserFontSets(nsTArray<RefPtr<gfxUserFontSet>>& aResult); + + // Calls IncrementGeneration() on all user font sets that contain this + // user font entry. + void IncrementGeneration(); + + // general load state + UserFontLoadState mUserFontLoadState; + + // detailed load state while font data is loading + // used to determine whether to use fallback font or not + // note that code depends on the ordering of these values! + enum FontDataLoadingState { + NOT_LOADING = 0, // not started to load any font resources yet + LOADING_STARTED, // loading has started; hide fallback font + LOADING_ALMOST_DONE, // timeout happened but we're nearly done, + // so keep hiding fallback font + LOADING_SLOWLY, // timeout happened and we're not nearly done, + // so use the fallback font + LOADING_TIMED_OUT, // font load took too long + LOADING_FAILED // failed to load any source: use fallback + }; + FontDataLoadingState mFontDataLoadingState; + + bool mSeenLocalSource; + bool mUnsupportedFormat; + mozilla::StyleFontDisplay mFontDisplay; // timing of userfont fallback + + RefPtr<gfxFontEntry> mPlatformFontEntry; + nsTArray<gfxFontFaceSrc> mSrcList; + uint32_t mCurrentSrcIndex; // index of src item to be loaded next + // This field is managed by the nsFontFaceLoader. In the destructor and + // Cancel() methods of nsFontFaceLoader this reference is nulled out. + nsFontFaceLoader* MOZ_NON_OWNING_REF + mLoader; // current loader for this entry, if any + RefPtr<gfxUserFontSet> mLoadingFontSet; + RefPtr<gfxFontSrcPrincipal> mPrincipal; +}; + +#endif /* GFX_USER_FONT_SET_H */ diff --git a/gfx/thebes/gfxUtils.cpp b/gfx/thebes/gfxUtils.cpp new file mode 100644 index 0000000000..ad3ab14f8b --- /dev/null +++ b/gfx/thebes/gfxUtils.cpp @@ -0,0 +1,1822 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxUtils.h" + +#include "cairo.h" +#include "gfxContext.h" +#include "gfxEnv.h" +#include "gfxImageSurface.h" +#include "gfxPlatform.h" +#include "gfxDrawable.h" +#include "gfxQuad.h" +#include "imgIEncoder.h" +#include "mozilla/Base64.h" +#include "mozilla/StyleColorInlines.h" +#include "mozilla/Components.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/ImageEncoder.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerRunnable.h" +#include "mozilla/ipc/CrossProcessSemaphore.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/gfx/Swizzle.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/image/nsBMPEncoder.h" +#include "mozilla/image/nsICOEncoder.h" +#include "mozilla/image/nsJPEGEncoder.h" +#include "mozilla/image/nsPNGEncoder.h" +#include "mozilla/layers/SynchronousTask.h" +#include "mozilla/Maybe.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ServoStyleConsts.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/Unused.h" +#include "mozilla/webrender/webrender_ffi.h" +#include "nsAppRunner.h" +#include "nsComponentManagerUtils.h" +#include "nsIClipboardHelper.h" +#include "nsIFile.h" +#include "nsIGfxInfo.h" +#include "nsMimeTypes.h" +#include "nsPresContext.h" +#include "nsRegion.h" +#include "nsServiceManagerUtils.h" +#include "nsRFPService.h" +#include "ImageContainer.h" +#include "ImageRegion.h" +#include "gfx2DGlue.h" + +#ifdef XP_WIN +# include "gfxWindowsPlatform.h" +#endif + +using namespace mozilla; +using namespace mozilla::image; +using namespace mozilla::layers; +using namespace mozilla::gfx; + +#undef compress +#include "mozilla/Compression.h" + +using namespace mozilla::Compression; +extern "C" { + +/** + * Dump a raw image to the default log. This function is exported + * from libxul, so it can be called from any library in addition to + * (of course) from a debugger. + * + * Note: this helper currently assumes that all 2-bytepp images are + * r5g6b5, and that all 4-bytepp images are r8g8b8a8. + */ +NS_EXPORT +void mozilla_dump_image(void* bytes, int width, int height, int bytepp, + int strideBytes) { + if (0 == strideBytes) { + strideBytes = width * bytepp; + } + SurfaceFormat format; + // TODO more flexible; parse string? + switch (bytepp) { + case 2: + format = SurfaceFormat::R5G6B5_UINT16; + break; + case 4: + default: + format = SurfaceFormat::R8G8B8A8; + break; + } + + RefPtr<DataSourceSurface> surf = Factory::CreateWrappingDataSourceSurface( + (uint8_t*)bytes, strideBytes, IntSize(width, height), format); + gfxUtils::DumpAsDataURI(surf); +} +} + +static bool MapSrcDest(DataSourceSurface* srcSurf, DataSourceSurface* destSurf, + DataSourceSurface::MappedSurface* out_srcMap, + DataSourceSurface::MappedSurface* out_destMap) { + MOZ_ASSERT(srcSurf && destSurf); + MOZ_ASSERT(out_srcMap && out_destMap); + + if (srcSurf->GetSize() != destSurf->GetSize()) { + MOZ_ASSERT(false, "Width and height must match."); + return false; + } + + if (srcSurf == destSurf) { + DataSourceSurface::MappedSurface map; + if (!srcSurf->Map(DataSourceSurface::MapType::READ_WRITE, &map)) { + NS_WARNING("Couldn't Map srcSurf/destSurf."); + return false; + } + + *out_srcMap = map; + *out_destMap = map; + return true; + } + + // Map src for reading. + DataSourceSurface::MappedSurface srcMap; + if (!srcSurf->Map(DataSourceSurface::MapType::READ, &srcMap)) { + NS_WARNING("Couldn't Map srcSurf."); + return false; + } + + // Map dest for writing. + DataSourceSurface::MappedSurface destMap; + if (!destSurf->Map(DataSourceSurface::MapType::WRITE, &destMap)) { + NS_WARNING("Couldn't Map aDest."); + srcSurf->Unmap(); + return false; + } + + *out_srcMap = srcMap; + *out_destMap = destMap; + return true; +} + +static void UnmapSrcDest(DataSourceSurface* srcSurf, + DataSourceSurface* destSurf) { + if (srcSurf == destSurf) { + srcSurf->Unmap(); + } else { + srcSurf->Unmap(); + destSurf->Unmap(); + } +} + +bool gfxUtils::PremultiplyDataSurface(DataSourceSurface* srcSurf, + DataSourceSurface* destSurf) { + MOZ_ASSERT(srcSurf && destSurf); + + DataSourceSurface::MappedSurface srcMap; + DataSourceSurface::MappedSurface destMap; + if (!MapSrcDest(srcSurf, destSurf, &srcMap, &destMap)) return false; + + PremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(), + destMap.mData, destMap.mStride, destSurf->GetFormat(), + srcSurf->GetSize()); + + UnmapSrcDest(srcSurf, destSurf); + return true; +} + +bool gfxUtils::UnpremultiplyDataSurface(DataSourceSurface* srcSurf, + DataSourceSurface* destSurf) { + MOZ_ASSERT(srcSurf && destSurf); + + DataSourceSurface::MappedSurface srcMap; + DataSourceSurface::MappedSurface destMap; + if (!MapSrcDest(srcSurf, destSurf, &srcMap, &destMap)) return false; + + UnpremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(), + destMap.mData, destMap.mStride, destSurf->GetFormat(), + srcSurf->GetSize()); + + UnmapSrcDest(srcSurf, destSurf); + return true; +} + +static bool MapSrcAndCreateMappedDest( + DataSourceSurface* srcSurf, RefPtr<DataSourceSurface>* out_destSurf, + DataSourceSurface::MappedSurface* out_srcMap, + DataSourceSurface::MappedSurface* out_destMap) { + MOZ_ASSERT(srcSurf); + MOZ_ASSERT(out_destSurf && out_srcMap && out_destMap); + + // Ok, map source for reading. + DataSourceSurface::MappedSurface srcMap; + if (!srcSurf->Map(DataSourceSurface::MapType::READ, &srcMap)) { + MOZ_ASSERT(false, "Couldn't Map srcSurf."); + return false; + } + + // Make our dest surface based on the src. + RefPtr<DataSourceSurface> destSurf = + Factory::CreateDataSourceSurfaceWithStride( + srcSurf->GetSize(), srcSurf->GetFormat(), srcMap.mStride); + if (NS_WARN_IF(!destSurf)) { + return false; + } + + DataSourceSurface::MappedSurface destMap; + if (!destSurf->Map(DataSourceSurface::MapType::WRITE, &destMap)) { + MOZ_ASSERT(false, "Couldn't Map destSurf."); + srcSurf->Unmap(); + return false; + } + + *out_destSurf = destSurf; + *out_srcMap = srcMap; + *out_destMap = destMap; + return true; +} + +already_AddRefed<DataSourceSurface> gfxUtils::CreatePremultipliedDataSurface( + DataSourceSurface* srcSurf) { + RefPtr<DataSourceSurface> destSurf; + DataSourceSurface::MappedSurface srcMap; + DataSourceSurface::MappedSurface destMap; + if (!MapSrcAndCreateMappedDest(srcSurf, &destSurf, &srcMap, &destMap)) { + MOZ_ASSERT(false, "MapSrcAndCreateMappedDest failed."); + RefPtr<DataSourceSurface> surface(srcSurf); + return surface.forget(); + } + + PremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(), + destMap.mData, destMap.mStride, destSurf->GetFormat(), + srcSurf->GetSize()); + + UnmapSrcDest(srcSurf, destSurf); + return destSurf.forget(); +} + +already_AddRefed<DataSourceSurface> gfxUtils::CreateUnpremultipliedDataSurface( + DataSourceSurface* srcSurf) { + RefPtr<DataSourceSurface> destSurf; + DataSourceSurface::MappedSurface srcMap; + DataSourceSurface::MappedSurface destMap; + if (!MapSrcAndCreateMappedDest(srcSurf, &destSurf, &srcMap, &destMap)) { + MOZ_ASSERT(false, "MapSrcAndCreateMappedDest failed."); + RefPtr<DataSourceSurface> surface(srcSurf); + return surface.forget(); + } + + UnpremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(), + destMap.mData, destMap.mStride, destSurf->GetFormat(), + srcSurf->GetSize()); + + UnmapSrcDest(srcSurf, destSurf); + return destSurf.forget(); +} + +void gfxUtils::ConvertBGRAtoRGBA(uint8_t* aData, uint32_t aLength) { + MOZ_ASSERT((aLength % 4) == 0, "Loop below will pass srcEnd!"); + SwizzleData(aData, aLength, SurfaceFormat::B8G8R8A8, aData, aLength, + SurfaceFormat::R8G8B8A8, IntSize(aLength / 4, 1)); +} + +#if !defined(MOZ_GFX_OPTIMIZE_MOBILE) +/** + * This returns the fastest operator to use for solid surfaces which have no + * alpha channel or their alpha channel is uniformly opaque. + * This differs per render mode. + */ +static CompositionOp OptimalFillOp() { +# ifdef XP_WIN + if (gfxWindowsPlatform::GetPlatform()->IsDirect2DBackend()) { + // D2D -really- hates operator source. + return CompositionOp::OP_OVER; + } +# endif + return CompositionOp::OP_SOURCE; +} + +// EXTEND_PAD won't help us here; we have to create a temporary surface to hold +// the subimage of pixels we're allowed to sample. +static already_AddRefed<gfxDrawable> CreateSamplingRestrictedDrawable( + gfxDrawable* aDrawable, gfxContext* aContext, const ImageRegion& aRegion, + const SurfaceFormat aFormat, bool aUseOptimalFillOp) { + AUTO_PROFILER_LABEL("CreateSamplingRestrictedDrawable", GRAPHICS); + + DrawTarget* destDrawTarget = aContext->GetDrawTarget(); + // We've been not using CreateSamplingRestrictedDrawable in a bunch of places + // for a while. Let's disable it everywhere and confirm that it's ok to get + // rid of. + if (destDrawTarget->GetBackendType() == BackendType::DIRECT2D1_1 || (true)) { + return nullptr; + } + + gfxRect clipExtents = aContext->GetClipExtents(); + + // Inflate by one pixel because bilinear filtering will sample at most + // one pixel beyond the computed image pixel coordinate. + clipExtents.Inflate(1.0); + + gfxRect needed = aRegion.IntersectAndRestrict(clipExtents); + needed.RoundOut(); + + // if 'needed' is empty, nothing will be drawn since aFill + // must be entirely outside the clip region, so it doesn't + // matter what we do here, but we should avoid trying to + // create a zero-size surface. + if (needed.IsEmpty()) return nullptr; + + IntSize size(int32_t(needed.Width()), int32_t(needed.Height())); + + RefPtr<DrawTarget> target = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(size, + aFormat); + if (!target || !target->IsValid()) { + return nullptr; + } + + gfxContext tmpCtx(target); + + if (aUseOptimalFillOp) { + tmpCtx.SetOp(OptimalFillOp()); + } + aDrawable->Draw(&tmpCtx, needed - needed.TopLeft(), ExtendMode::REPEAT, + SamplingFilter::LINEAR, 1.0, + gfxMatrix::Translation(needed.TopLeft())); + RefPtr<SourceSurface> surface = target->Snapshot(); + + RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable( + surface, size, gfxMatrix::Translation(-needed.TopLeft())); + return drawable.forget(); +} +#endif // !MOZ_GFX_OPTIMIZE_MOBILE + +/* These heuristics are based on + * Source/WebCore/platform/graphics/skia/ImageSkia.cpp:computeResamplingMode() + */ +#ifdef MOZ_GFX_OPTIMIZE_MOBILE +static SamplingFilter ReduceResamplingFilter(SamplingFilter aSamplingFilter, + int aImgWidth, int aImgHeight, + float aSourceWidth, + float aSourceHeight) { + // Images smaller than this in either direction are considered "small" and + // are not resampled ever (see below). + const int kSmallImageSizeThreshold = 8; + + // The amount an image can be stretched in a single direction before we + // say that it is being stretched so much that it must be a line or + // background that doesn't need resampling. + const float kLargeStretch = 3.0f; + + if (aImgWidth <= kSmallImageSizeThreshold || + aImgHeight <= kSmallImageSizeThreshold) { + // Never resample small images. These are often used for borders and + // rules (think 1x1 images used to make lines). + return SamplingFilter::POINT; + } + + if (aImgHeight * kLargeStretch <= aSourceHeight || + aImgWidth * kLargeStretch <= aSourceWidth) { + // Large image tiling detected. + + // Don't resample if it is being tiled a lot in only one direction. + // This is trying to catch cases where somebody has created a border + // (which might be large) and then is stretching it to fill some part + // of the page. + if (fabs(aSourceWidth - aImgWidth) / aImgWidth < 0.5 || + fabs(aSourceHeight - aImgHeight) / aImgHeight < 0.5) + return SamplingFilter::POINT; + + // The image is growing a lot and in more than one direction. Resampling + // is slow and doesn't give us very much when growing a lot. + return aSamplingFilter; + } + + /* Some notes on other heuristics: + The Skia backend also uses nearest for backgrounds that are stretched by + a large amount. I'm not sure this is common enough for us to worry about + now. It also uses nearest for backgrounds/avoids high quality for images + that are very slightly scaled. I'm also not sure that very slightly + scaled backgrounds are common enough us to worry about. + + We don't currently have much support for doing high quality interpolation. + The only place this currently happens is on Quartz and we don't have as + much control over it as would be needed. Webkit avoids using high quality + resampling during load. It also avoids high quality if the transformation + is not just a scale and translation + + WebKit bug #40045 added code to avoid resampling different parts + of an image with different methods by using a resampling hint size. + It currently looks unused in WebKit but it's something to watch out for. + */ + + return aSamplingFilter; +} +#else +static SamplingFilter ReduceResamplingFilter(SamplingFilter aSamplingFilter, + int aImgWidth, int aImgHeight, + int aSourceWidth, + int aSourceHeight) { + // Just pass the filter through unchanged + return aSamplingFilter; +} +#endif + +#ifdef MOZ_WIDGET_COCOA +// Only prescale a temporary surface if we're going to repeat it often. +// Scaling is expensive on OS X and without prescaling, we'd scale +// every tile of the repeated rect. However, using a temp surface also +// potentially uses more memory if the scaled image is large. So only prescale +// on a temp surface if we know we're going to repeat the image in either the X +// or Y axis multiple times. +static bool ShouldUseTempSurface(Rect aImageRect, Rect aNeededRect) { + int repeatX = aNeededRect.width / aImageRect.width; + int repeatY = aNeededRect.height / aImageRect.height; + return (repeatX >= 5) || (repeatY >= 5); +} + +static bool PrescaleAndTileDrawable(gfxDrawable* aDrawable, + gfxContext* aContext, + const ImageRegion& aRegion, Rect aImageRect, + const SamplingFilter aSamplingFilter, + const SurfaceFormat aFormat, + gfxFloat aOpacity, ExtendMode aExtendMode) { + MatrixScales scaleFactor = + aContext->CurrentMatrix().ScaleFactors().ConvertTo<float>(); + Matrix scaleMatrix = Matrix::Scaling(scaleFactor.xScale, scaleFactor.yScale); + const float fuzzFactor = 0.01; + + // If we aren't scaling or translating, don't go down this path + if ((FuzzyEqual(scaleFactor.xScale, 1.0f, fuzzFactor) && + FuzzyEqual(scaleFactor.yScale, 1.0f, fuzzFactor)) || + aContext->CurrentMatrix().HasNonAxisAlignedTransform()) { + return false; + } + + gfxRect clipExtents = aContext->GetClipExtents(); + + // Inflate by one pixel because bilinear filtering will sample at most + // one pixel beyond the computed image pixel coordinate. + clipExtents.Inflate(1.0); + + gfxRect needed = aRegion.IntersectAndRestrict(clipExtents); + Rect scaledNeededRect = scaleMatrix.TransformBounds(ToRect(needed)); + scaledNeededRect.RoundOut(); + if (scaledNeededRect.IsEmpty()) { + return false; + } + + Rect scaledImageRect = scaleMatrix.TransformBounds(aImageRect); + if (!ShouldUseTempSurface(scaledImageRect, scaledNeededRect)) { + return false; + } + + IntSize scaledImageSize((int32_t)scaledImageRect.width, + (int32_t)scaledImageRect.height); + if (scaledImageSize.width != scaledImageRect.width || + scaledImageSize.height != scaledImageRect.height) { + // If the scaled image isn't pixel aligned, we'll get artifacts + // so we have to take the slow path. + return false; + } + + RefPtr<DrawTarget> scaledDT = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( + scaledImageSize, aFormat); + if (!scaledDT || !scaledDT->IsValid()) { + return false; + } + + gfxContext tmpCtx(scaledDT); + + scaledDT->SetTransform(scaleMatrix); + gfxRect gfxImageRect(aImageRect.x, aImageRect.y, aImageRect.width, + aImageRect.height); + + // Since this is just the scaled image, we don't want to repeat anything yet. + aDrawable->Draw(&tmpCtx, gfxImageRect, ExtendMode::CLAMP, aSamplingFilter, + 1.0, gfxMatrix()); + + RefPtr<SourceSurface> scaledImage = scaledDT->Snapshot(); + + { + gfxContextMatrixAutoSaveRestore autoSR(aContext); + Matrix withoutScale = aContext->CurrentMatrix(); + DrawTarget* destDrawTarget = aContext->GetDrawTarget(); + + // The translation still is in scaled units + withoutScale.PreScale(1.0f / scaleFactor.xScale, 1.0f / scaleFactor.yScale); + aContext->SetMatrix(withoutScale); + + DrawOptions drawOptions(aOpacity, aContext->CurrentOp(), + aContext->CurrentAntialiasMode()); + + SurfacePattern scaledImagePattern(scaledImage, aExtendMode, Matrix(), + aSamplingFilter); + destDrawTarget->FillRect(scaledNeededRect, scaledImagePattern, drawOptions); + } + return true; +} +#endif // MOZ_WIDGET_COCOA + +/* static */ +void gfxUtils::DrawPixelSnapped(gfxContext* aContext, gfxDrawable* aDrawable, + const gfxSize& aImageSize, + const ImageRegion& aRegion, + const SurfaceFormat aFormat, + SamplingFilter aSamplingFilter, + uint32_t aImageFlags, gfxFloat aOpacity, + bool aUseOptimalFillOp) { + AUTO_PROFILER_LABEL("gfxUtils::DrawPixelSnapped", GRAPHICS); + + gfxRect imageRect(gfxPoint(0, 0), aImageSize); + gfxRect region(aRegion.Rect()); + ExtendMode extendMode = aRegion.GetExtendMode(); + + RefPtr<gfxDrawable> drawable = aDrawable; + + aSamplingFilter = ReduceResamplingFilter(aSamplingFilter, imageRect.Width(), + imageRect.Height(), region.Width(), + region.Height()); + + // OK now, the hard part left is to account for the subimage sampling + // restriction. If all the transforms involved are just integer + // translations, then we assume no resampling will occur so there's + // nothing to do. + // XXX if only we had source-clipping in cairo! + + if (aContext->CurrentMatrix().HasNonIntegerTranslation()) { + if ((extendMode != ExtendMode::CLAMP) || + !aRegion.RestrictionContains(imageRect)) { + if (drawable->DrawWithSamplingRect( + aContext->GetDrawTarget(), aContext->CurrentOp(), + aContext->CurrentAntialiasMode(), aRegion.Rect(), + aRegion.Restriction(), extendMode, aSamplingFilter, aOpacity)) { + return; + } + +#ifdef MOZ_WIDGET_COCOA + if (PrescaleAndTileDrawable(aDrawable, aContext, aRegion, + ToRect(imageRect), aSamplingFilter, aFormat, + aOpacity, extendMode)) { + return; + } +#endif + + // On Mobile, we don't ever want to do this; it has the potential for + // allocating very large temporary surfaces, especially since we'll + // do full-page snapshots often (see bug 749426). +#if !defined(MOZ_GFX_OPTIMIZE_MOBILE) + RefPtr<gfxDrawable> restrictedDrawable = CreateSamplingRestrictedDrawable( + aDrawable, aContext, aRegion, aFormat, aUseOptimalFillOp); + if (restrictedDrawable) { + drawable.swap(restrictedDrawable); + + // We no longer need to tile: Either we never needed to, or we already + // filled a surface with the tiled pattern; this surface can now be + // drawn without tiling. + extendMode = ExtendMode::CLAMP; + } +#endif + } + } + + drawable->Draw(aContext, aRegion.Rect(), extendMode, aSamplingFilter, + aOpacity, gfxMatrix()); +} + +/* static */ +int gfxUtils::ImageFormatToDepth(gfxImageFormat aFormat) { + switch (aFormat) { + case SurfaceFormat::A8R8G8B8_UINT32: + return 32; + case SurfaceFormat::X8R8G8B8_UINT32: + return 24; + case SurfaceFormat::R5G6B5_UINT16: + return 16; + default: + break; + } + return 0; +} + +/*static*/ +void gfxUtils::ClipToRegion(gfxContext* aContext, const nsIntRegion& aRegion) { + aContext->NewPath(); + for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& r = iter.Get(); + aContext->Rectangle(gfxRect(r.X(), r.Y(), r.Width(), r.Height())); + } + aContext->Clip(); +} + +/*static*/ +void gfxUtils::ClipToRegion(DrawTarget* aTarget, const nsIntRegion& aRegion) { + uint32_t numRects = aRegion.GetNumRects(); + // If there is only one rect, then the region bounds are equivalent to the + // contents. So just use push a single clip rect with the bounds. + if (numRects == 1) { + aTarget->PushClipRect(Rect(aRegion.GetBounds())); + return; + } + + // Check if the target's transform will preserve axis-alignment and + // pixel-alignment for each rect. For now, just handle the common case + // of integer translations. + Matrix transform = aTarget->GetTransform(); + if (transform.IsIntegerTranslation()) { + IntPoint translation = RoundedToInt(transform.GetTranslation()); + AutoTArray<IntRect, 16> rects; + rects.SetLength(numRects); + uint32_t i = 0; + // Build the list of transformed rects by adding in the translation. + for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) { + IntRect rect = iter.Get(); + rect.MoveBy(translation); + rects[i++] = rect; + } + aTarget->PushDeviceSpaceClipRects(rects.Elements(), rects.Length()); + } else { + // The transform does not produce axis-aligned rects or a rect was not + // pixel-aligned. So just build a path with all the rects and clip to it + // instead. + RefPtr<PathBuilder> pathBuilder = aTarget->CreatePathBuilder(); + for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) { + AppendRectToPath(pathBuilder, Rect(iter.Get())); + } + RefPtr<Path> path = pathBuilder->Finish(); + aTarget->PushClip(path); + } +} + +/*static*/ +float gfxUtils::ClampToScaleFactor(float aVal, bool aRoundDown) { + // Arbitary scale factor limitation. We can increase this + // for better scaling performance at the cost of worse + // quality. + static const float kScaleResolution = 2; + + // Negative scaling is just a flip and irrelevant to + // our resolution calculation. + if (aVal < 0.0) { + aVal = -aVal; + } + + bool inverse = false; + if (aVal < 1.0) { + inverse = true; + aVal = 1 / aVal; + } + + float power = logf(aVal) / logf(kScaleResolution); + + // If power is within 1e-5 of an integer, round to nearest to + // prevent floating point errors, otherwise round up to the + // next integer value. + if (fabs(power - NS_round(power)) < 1e-5) { + power = NS_round(power); + // Use floor when we are either inverted or rounding down, but + // not both. + } else if (inverse != aRoundDown) { + power = floor(power); + // Otherwise, ceil when we are not inverted and not rounding + // down, or we are inverted and rounding down. + } else { + power = ceil(power); + } + + float scale = powf(kScaleResolution, power); + + if (inverse) { + scale = 1 / scale; + } + + return scale; +} + +gfxMatrix gfxUtils::TransformRectToRect(const gfxRect& aFrom, + const gfxPoint& aToTopLeft, + const gfxPoint& aToTopRight, + const gfxPoint& aToBottomRight) { + gfxMatrix m; + if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) { + // Not a rotation, so xy and yx are zero + m._21 = m._12 = 0.0; + m._11 = (aToBottomRight.x - aToTopLeft.x) / aFrom.Width(); + m._22 = (aToBottomRight.y - aToTopLeft.y) / aFrom.Height(); + m._31 = aToTopLeft.x - m._11 * aFrom.X(); + m._32 = aToTopLeft.y - m._22 * aFrom.Y(); + } else { + NS_ASSERTION( + aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x, + "Destination rectangle not axis-aligned"); + m._11 = m._22 = 0.0; + m._21 = (aToBottomRight.x - aToTopLeft.x) / aFrom.Height(); + m._12 = (aToBottomRight.y - aToTopLeft.y) / aFrom.Width(); + m._31 = aToTopLeft.x - m._21 * aFrom.Y(); + m._32 = aToTopLeft.y - m._12 * aFrom.X(); + } + return m; +} + +Matrix gfxUtils::TransformRectToRect(const gfxRect& aFrom, + const IntPoint& aToTopLeft, + const IntPoint& aToTopRight, + const IntPoint& aToBottomRight) { + Matrix m; + if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) { + // Not a rotation, so xy and yx are zero + m._12 = m._21 = 0.0; + m._11 = (aToBottomRight.x - aToTopLeft.x) / aFrom.Width(); + m._22 = (aToBottomRight.y - aToTopLeft.y) / aFrom.Height(); + m._31 = aToTopLeft.x - m._11 * aFrom.X(); + m._32 = aToTopLeft.y - m._22 * aFrom.Y(); + } else { + NS_ASSERTION( + aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x, + "Destination rectangle not axis-aligned"); + m._11 = m._22 = 0.0; + m._21 = (aToBottomRight.x - aToTopLeft.x) / aFrom.Height(); + m._12 = (aToBottomRight.y - aToTopLeft.y) / aFrom.Width(); + m._31 = aToTopLeft.x - m._21 * aFrom.Y(); + m._32 = aToTopLeft.y - m._12 * aFrom.X(); + } + return m; +} + +/* This function is sort of shitty. We truncate doubles + * to ints then convert those ints back to doubles to make sure that + * they equal the doubles that we got in. */ +bool gfxUtils::GfxRectToIntRect(const gfxRect& aIn, IntRect* aOut) { + *aOut = IntRect(int32_t(aIn.X()), int32_t(aIn.Y()), int32_t(aIn.Width()), + int32_t(aIn.Height())); + return gfxRect(aOut->X(), aOut->Y(), aOut->Width(), aOut->Height()) + .IsEqualEdges(aIn); +} + +/* Clamp r to CAIRO_COORD_MIN .. CAIRO_COORD_MAX + * these are to be device coordinates. + * + * Cairo is currently using 24.8 fixed point, + * so -2^24 .. 2^24-1 is our valid + */ +/*static*/ +void gfxUtils::ConditionRect(gfxRect& aRect) { +#define CAIRO_COORD_MAX (16777215.0) +#define CAIRO_COORD_MIN (-16777216.0) + // if either x or y is way out of bounds; + // note that we don't handle negative w/h here + if (aRect.X() > CAIRO_COORD_MAX) { + aRect.SetRectX(CAIRO_COORD_MAX, 0.0); + } + + if (aRect.Y() > CAIRO_COORD_MAX) { + aRect.SetRectY(CAIRO_COORD_MAX, 0.0); + } + + if (aRect.X() < CAIRO_COORD_MIN) { + aRect.SetWidth(aRect.XMost() - CAIRO_COORD_MIN); + if (aRect.Width() < 0.0) { + aRect.SetWidth(0.0); + } + aRect.MoveToX(CAIRO_COORD_MIN); + } + + if (aRect.Y() < CAIRO_COORD_MIN) { + aRect.SetHeight(aRect.YMost() - CAIRO_COORD_MIN); + if (aRect.Height() < 0.0) { + aRect.SetHeight(0.0); + } + aRect.MoveToY(CAIRO_COORD_MIN); + } + + if (aRect.XMost() > CAIRO_COORD_MAX) { + aRect.SetRightEdge(CAIRO_COORD_MAX); + } + + if (aRect.YMost() > CAIRO_COORD_MAX) { + aRect.SetBottomEdge(CAIRO_COORD_MAX); + } +#undef CAIRO_COORD_MAX +#undef CAIRO_COORD_MIN +} + +/*static*/ +gfxQuad gfxUtils::TransformToQuad(const gfxRect& aRect, + const mozilla::gfx::Matrix4x4& aMatrix) { + gfxPoint points[4]; + + points[0] = aMatrix.TransformPoint(aRect.TopLeft()); + points[1] = aMatrix.TransformPoint(aRect.TopRight()); + points[2] = aMatrix.TransformPoint(aRect.BottomRight()); + points[3] = aMatrix.TransformPoint(aRect.BottomLeft()); + + // Could this ever result in lines that intersect? I don't think so. + return gfxQuad(points[0], points[1], points[2], points[3]); +} + +Matrix4x4 gfxUtils::SnapTransformTranslation(const Matrix4x4& aTransform, + Matrix* aResidualTransform) { + if (aResidualTransform) { + *aResidualTransform = Matrix(); + } + + Matrix matrix2D; + if (aTransform.CanDraw2D(&matrix2D) && !matrix2D.HasNonTranslation() && + matrix2D.HasNonIntegerTranslation()) { + return Matrix4x4::From2D( + SnapTransformTranslation(matrix2D, aResidualTransform)); + } + + return SnapTransformTranslation3D(aTransform, aResidualTransform); +} + +Matrix gfxUtils::SnapTransformTranslation(const Matrix& aTransform, + Matrix* aResidualTransform) { + if (aResidualTransform) { + *aResidualTransform = Matrix(); + } + + if (!aTransform.HasNonTranslation() && + aTransform.HasNonIntegerTranslation()) { + auto snappedTranslation = IntPoint::Round(aTransform.GetTranslation()); + Matrix snappedMatrix = + Matrix::Translation(snappedTranslation.x, snappedTranslation.y); + if (aResidualTransform) { + // set aResidualTransform so that aResidual * snappedMatrix == matrix2D. + // (I.e., appying snappedMatrix after aResidualTransform gives the + // ideal transform.) + *aResidualTransform = + Matrix::Translation(aTransform._31 - snappedTranslation.x, + aTransform._32 - snappedTranslation.y); + } + return snappedMatrix; + } + + return aTransform; +} + +Matrix4x4 gfxUtils::SnapTransformTranslation3D(const Matrix4x4& aTransform, + Matrix* aResidualTransform) { + if (aTransform.IsSingular() || aTransform.HasPerspectiveComponent() || + aTransform.HasNonTranslation() || + !aTransform.HasNonIntegerTranslation()) { + // For a singular transform, there is no reversed matrix, so we + // don't snap it. + // For a perspective transform, the content is transformed in + // non-linear, so we don't snap it too. + return aTransform; + } + + // Snap for 3D Transforms + + Point3D transformedOrigin = aTransform.TransformPoint(Point3D()); + + // Compute the transformed snap by rounding the values of + // transformed origin. + auto transformedSnapXY = + IntPoint::Round(transformedOrigin.x, transformedOrigin.y); + Matrix4x4 inverse = aTransform; + inverse.Invert(); + // see Matrix4x4::ProjectPoint() + Float transformedSnapZ = + inverse._33 == 0 ? 0 + : (-(transformedSnapXY.x * inverse._13 + + transformedSnapXY.y * inverse._23 + inverse._43) / + inverse._33); + Point3D transformedSnap = + Point3D(transformedSnapXY.x, transformedSnapXY.y, transformedSnapZ); + if (transformedOrigin == transformedSnap) { + return aTransform; + } + + // Compute the snap from the transformed snap. + Point3D snap = inverse.TransformPoint(transformedSnap); + if (snap.z > 0.001 || snap.z < -0.001) { + // Allow some level of accumulated computation error. + MOZ_ASSERT(inverse._33 == 0.0); + return aTransform; + } + + // The difference between the origin and snap is the residual transform. + if (aResidualTransform) { + // The residual transform is to translate the snap to the origin + // of the content buffer. + *aResidualTransform = Matrix::Translation(-snap.x, -snap.y); + } + + // Translate transformed origin to transformed snap since the + // residual transform would trnslate the snap to the origin. + Point3D transformedShift = transformedSnap - transformedOrigin; + Matrix4x4 result = aTransform; + result.PostTranslate(transformedShift.x, transformedShift.y, + transformedShift.z); + + // For non-2d transform, residual translation could be more than + // 0.5 pixels for every axis. + + return result; +} + +Matrix4x4 gfxUtils::SnapTransform(const Matrix4x4& aTransform, + const gfxRect& aSnapRect, + Matrix* aResidualTransform) { + if (aResidualTransform) { + *aResidualTransform = Matrix(); + } + + Matrix matrix2D; + if (aTransform.Is2D(&matrix2D)) { + return Matrix4x4::From2D( + SnapTransform(matrix2D, aSnapRect, aResidualTransform)); + } + return aTransform; +} + +Matrix gfxUtils::SnapTransform(const Matrix& aTransform, + const gfxRect& aSnapRect, + Matrix* aResidualTransform) { + if (aResidualTransform) { + *aResidualTransform = Matrix(); + } + + if (gfxSize(1.0, 1.0) <= aSnapRect.Size() && + aTransform.PreservesAxisAlignedRectangles()) { + auto transformedTopLeft = IntPoint::Round( + aTransform.TransformPoint(ToPoint(aSnapRect.TopLeft()))); + auto transformedTopRight = IntPoint::Round( + aTransform.TransformPoint(ToPoint(aSnapRect.TopRight()))); + auto transformedBottomRight = IntPoint::Round( + aTransform.TransformPoint(ToPoint(aSnapRect.BottomRight()))); + + Matrix snappedMatrix = gfxUtils::TransformRectToRect( + aSnapRect, transformedTopLeft, transformedTopRight, + transformedBottomRight); + + if (aResidualTransform && !snappedMatrix.IsSingular()) { + // set aResidualTransform so that aResidual * snappedMatrix == matrix2D. + // (i.e., appying snappedMatrix after aResidualTransform gives the + // ideal transform. + Matrix snappedMatrixInverse = snappedMatrix; + snappedMatrixInverse.Invert(); + *aResidualTransform = aTransform * snappedMatrixInverse; + } + return snappedMatrix; + } + return aTransform; +} + +/* static */ +void gfxUtils::ClearThebesSurface(gfxASurface* aSurface) { + if (aSurface->CairoStatus()) { + return; + } + cairo_surface_t* surf = aSurface->CairoSurface(); + if (cairo_surface_status(surf)) { + return; + } + cairo_t* ctx = cairo_create(surf); + cairo_set_source_rgba(ctx, 0.0, 0.0, 0.0, 0.0); + cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE); + IntRect bounds(nsIntPoint(0, 0), aSurface->GetSize()); + cairo_rectangle(ctx, bounds.X(), bounds.Y(), bounds.Width(), bounds.Height()); + cairo_fill(ctx); + cairo_destroy(ctx); +} + +/* static */ +already_AddRefed<DataSourceSurface> +gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface, + SurfaceFormat aFormat) { + MOZ_ASSERT(aFormat != aSurface->GetFormat(), + "Unnecessary - and very expersive - surface format conversion"); + + Rect bounds(0, 0, aSurface->GetSize().width, aSurface->GetSize().height); + + if (!aSurface->IsDataSourceSurface()) { + // If the surface is NOT of type DATA then its data is not mapped into main + // memory. Format conversion is probably faster on the GPU, and by doing it + // there we can avoid any expensive uploads/readbacks except for (possibly) + // a single readback due to the unavoidable GetDataSurface() call. Using + // CreateOffscreenContentDrawTarget ensures the conversion happens on the + // GPU. + RefPtr<DrawTarget> dt = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( + aSurface->GetSize(), aFormat); + if (!dt) { + gfxWarning() << "gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat " + "failed in CreateOffscreenContentDrawTarget"; + return nullptr; + } + + // Using DrawSurface() here rather than CopySurface() because CopySurface + // is optimized for memcpy and therefore isn't good for format conversion. + // Using OP_OVER since in our case it's equivalent to OP_SOURCE and + // generally more optimized. + dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(), + DrawOptions(1.0f, CompositionOp::OP_OVER)); + RefPtr<SourceSurface> surface = dt->Snapshot(); + return surface->GetDataSurface(); + } + + // If the surface IS of type DATA then it may or may not be in main memory + // depending on whether or not it has been mapped yet. We have no way of + // knowing, so we can't be sure if it's best to create a data wrapping + // DrawTarget for the conversion or an offscreen content DrawTarget. We could + // guess it's not mapped and create an offscreen content DrawTarget, but if + // it is then we'll end up uploading the surface data, and most likely the + // caller is going to be accessing the resulting surface data, resulting in a + // readback (both very expensive operations). Alternatively we could guess + // the data is mapped and create a data wrapping DrawTarget and, if the + // surface is not in main memory, then we will incure a readback. The latter + // of these two "wrong choices" is the least costly (a readback, vs an + // upload and a readback), and more than likely the DATA surface that we've + // been passed actually IS in main memory anyway. For these reasons it's most + // likely best to create a data wrapping DrawTarget here to do the format + // conversion. + RefPtr<DataSourceSurface> dataSurface = + Factory::CreateDataSourceSurface(aSurface->GetSize(), aFormat); + DataSourceSurface::MappedSurface map; + if (!dataSurface || + !dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) { + return nullptr; + } + RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData( + BackendType::CAIRO, map.mData, dataSurface->GetSize(), map.mStride, + aFormat); + if (!dt) { + dataSurface->Unmap(); + return nullptr; + } + // Using DrawSurface() here rather than CopySurface() because CopySurface + // is optimized for memcpy and therefore isn't good for format conversion. + // Using OP_OVER since in our case it's equivalent to OP_SOURCE and + // generally more optimized. + dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(), + DrawOptions(1.0f, CompositionOp::OP_OVER)); + dataSurface->Unmap(); + return dataSurface.forget(); +} + +const uint32_t gfxUtils::sNumFrameColors = 8; + +/* static */ +const gfx::DeviceColor& gfxUtils::GetColorForFrameNumber( + uint64_t aFrameNumber) { + static bool initialized = false; + static gfx::DeviceColor colors[sNumFrameColors]; + + if (!initialized) { + // This isn't truly device color, but it is just for debug. + uint32_t i = 0; + colors[i++] = gfx::DeviceColor::FromABGR(0xffff0000); + colors[i++] = gfx::DeviceColor::FromABGR(0xffcc00ff); + colors[i++] = gfx::DeviceColor::FromABGR(0xff0066cc); + colors[i++] = gfx::DeviceColor::FromABGR(0xff00ff00); + colors[i++] = gfx::DeviceColor::FromABGR(0xff33ffff); + colors[i++] = gfx::DeviceColor::FromABGR(0xffff0099); + colors[i++] = gfx::DeviceColor::FromABGR(0xff0000ff); + colors[i++] = gfx::DeviceColor::FromABGR(0xff999999); + MOZ_ASSERT(i == sNumFrameColors); + initialized = true; + } + + return colors[aFrameNumber % sNumFrameColors]; +} + +// static +nsresult gfxUtils::EncodeSourceSurfaceAsStream(SourceSurface* aSurface, + const ImageType aImageType, + const nsAString& aOutputOptions, + nsIInputStream** aOutStream) { + const IntSize size = aSurface->GetSize(); + if (size.IsEmpty()) { + return NS_ERROR_FAILURE; + } + + RefPtr<DataSourceSurface> dataSurface; + if (aSurface->GetFormat() != SurfaceFormat::B8G8R8A8) { + // FIXME bug 995807 (B8G8R8X8), bug 831898 (R5G6B5) + dataSurface = gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat( + aSurface, SurfaceFormat::B8G8R8A8); + } else { + dataSurface = aSurface->GetDataSurface(); + } + if (!dataSurface) { + return NS_ERROR_FAILURE; + } + + DataSourceSurface::MappedSurface map; + if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) { + return NS_ERROR_FAILURE; + } + + RefPtr<imgIEncoder> encoder = nullptr; + + switch (aImageType) { + case ImageType::BMP: + encoder = MakeRefPtr<nsBMPEncoder>(); + break; + + case ImageType::ICO: + encoder = MakeRefPtr<nsICOEncoder>(); + break; + + case ImageType::JPEG: + encoder = MakeRefPtr<nsJPEGEncoder>(); + break; + + case ImageType::PNG: + encoder = MakeRefPtr<nsPNGEncoder>(); + break; + + default: + break; + } + + MOZ_RELEASE_ASSERT(encoder != nullptr); + + nsresult rv = encoder->InitFromData( + map.mData, BufferSizeFromStrideAndHeight(map.mStride, size.height), + size.width, size.height, map.mStride, imgIEncoder::INPUT_FORMAT_HOSTARGB, + aOutputOptions); + dataSurface->Unmap(); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIInputStream> imgStream(encoder); + if (!imgStream) { + return NS_ERROR_FAILURE; + } + + imgStream.forget(aOutStream); + + return NS_OK; +} + +// static +Maybe<nsTArray<uint8_t>> gfxUtils::EncodeSourceSurfaceAsBytes( + SourceSurface* aSurface, const ImageType aImageType, + const nsAString& aOutputOptions) { + nsCOMPtr<nsIInputStream> imgStream; + nsresult rv = EncodeSourceSurfaceAsStream( + aSurface, aImageType, aOutputOptions, getter_AddRefs(imgStream)); + if (NS_FAILED(rv)) { + return Nothing(); + } + + uint64_t bufSize64; + rv = imgStream->Available(&bufSize64); + if (NS_FAILED(rv) || bufSize64 > UINT32_MAX) { + return Nothing(); + } + + uint32_t bytesLeft = static_cast<uint32_t>(bufSize64); + + nsTArray<uint8_t> imgData; + imgData.SetLength(bytesLeft); + uint8_t* bytePtr = imgData.Elements(); + + while (bytesLeft > 0) { + uint32_t bytesRead = 0; + rv = imgStream->Read(reinterpret_cast<char*>(bytePtr), bytesLeft, + &bytesRead); + if (NS_FAILED(rv) || bytesRead == 0) { + return Nothing(); + } + + bytePtr += bytesRead; + bytesLeft -= bytesRead; + } + +#ifdef DEBUG + + // Currently, all implementers of imgIEncoder report their exact size through + // nsIInputStream::Available(), but let's explicitly state that we rely on + // that behavior for the algorithm above. + + char dummy = 0; + uint32_t bytesRead = 0; + rv = imgStream->Read(&dummy, 1, &bytesRead); + MOZ_ASSERT(NS_SUCCEEDED(rv) && bytesRead == 0); + +#endif + + return Some(std::move(imgData)); +} + +/* static */ +nsresult gfxUtils::EncodeSourceSurface(SourceSurface* aSurface, + const ImageType aImageType, + const nsAString& aOutputOptions, + BinaryOrData aBinaryOrData, FILE* aFile, + nsACString* aStrOut) { + MOZ_ASSERT(aBinaryOrData == gfxUtils::eDataURIEncode || aFile || aStrOut, + "Copying binary encoding to clipboard not currently supported"); + + auto maybeImgData = + EncodeSourceSurfaceAsBytes(aSurface, aImageType, aOutputOptions); + if (!maybeImgData) { + return NS_ERROR_FAILURE; + } + + nsTArray<uint8_t>& imgData = *maybeImgData; + + if (aBinaryOrData == gfxUtils::eBinaryEncode) { + if (aFile) { + Unused << fwrite(imgData.Elements(), 1, imgData.Length(), aFile); + } + return NS_OK; + } + + nsCString stringBuf; + nsACString& dataURI = aStrOut ? *aStrOut : stringBuf; + dataURI.AppendLiteral("data:"); + + switch (aImageType) { + case ImageType::BMP: + dataURI.AppendLiteral(IMAGE_BMP); + break; + + case ImageType::ICO: + dataURI.AppendLiteral(IMAGE_ICO_MS); + break; + case ImageType::JPEG: + dataURI.AppendLiteral(IMAGE_JPEG); + break; + + case ImageType::PNG: + dataURI.AppendLiteral(IMAGE_PNG); + break; + + default: + break; + } + + dataURI.AppendLiteral(";base64,"); + nsresult rv = Base64EncodeAppend(reinterpret_cast<char*>(imgData.Elements()), + imgData.Length(), dataURI); + NS_ENSURE_SUCCESS(rv, rv); + + if (aFile) { +#ifdef ANDROID + if (aFile == stdout || aFile == stderr) { + // ADB logcat cuts off long strings so we will break it down + const char* cStr = dataURI.BeginReading(); + size_t len = strlen(cStr); + while (true) { + printf_stderr("IMG: %.140s\n", cStr); + if (len <= 140) break; + len -= 140; + cStr += 140; + } + } +#endif + fprintf(aFile, "%s", dataURI.BeginReading()); + } else if (!aStrOut) { + nsCOMPtr<nsIClipboardHelper> clipboard( + do_GetService("@mozilla.org/widget/clipboardhelper;1", &rv)); + if (clipboard) { + clipboard->CopyString(NS_ConvertASCIItoUTF16(dataURI)); + } + } + return NS_OK; +} + +static nsCString EncodeSourceSurfaceAsPNGURI(SourceSurface* aSurface) { + nsCString string; + gfxUtils::EncodeSourceSurface(aSurface, ImageType::PNG, u""_ns, + gfxUtils::eDataURIEncode, nullptr, &string); + return string; +} + +// https://jdashg.github.io/misc/colors/from-coeffs.html +const float kBT601NarrowYCbCrToRGB_RowMajor[16] = { + 1.16438f, 0.00000f, 1.59603f, -0.87420f, 1.16438f, -0.39176f, + -0.81297f, 0.53167f, 1.16438f, 2.01723f, 0.00000f, -1.08563f, + 0.00000f, 0.00000f, 0.00000f, 1.00000f}; +const float kBT709NarrowYCbCrToRGB_RowMajor[16] = { + 1.16438f, 0.00000f, 1.79274f, -0.97295f, 1.16438f, -0.21325f, + -0.53291f, 0.30148f, 1.16438f, 2.11240f, 0.00000f, -1.13340f, + 0.00000f, 0.00000f, 0.00000f, 1.00000f}; +const float kBT2020NarrowYCbCrToRGB_RowMajor[16] = { + 1.16438f, 0.00000f, 1.67867f, -0.91569f, 1.16438f, -0.18733f, + -0.65042f, 0.34746f, 1.16438f, 2.14177f, 0.00000f, -1.14815f, + 0.00000f, 0.00000f, 0.00000f, 1.00000f}; +const float kIdentityNarrowYCbCrToRGB_RowMajor[16] = { + 0.00000f, 0.00000f, 1.00000f, 0.00000f, 1.00000f, 0.00000f, + 0.00000f, 0.00000f, 0.00000f, 1.00000f, 0.00000f, 0.00000f, + 0.00000f, 0.00000f, 0.00000f, 1.00000f}; + +/* static */ const float* gfxUtils::YuvToRgbMatrix4x3RowMajor( + gfx::YUVColorSpace aYUVColorSpace) { +#define X(x) \ + { x[0], x[1], x[2], 0.0f, x[4], x[5], x[6], 0.0f, x[8], x[9], x[10], 0.0f } + + static const float rec601[12] = X(kBT601NarrowYCbCrToRGB_RowMajor); + static const float rec709[12] = X(kBT709NarrowYCbCrToRGB_RowMajor); + static const float rec2020[12] = X(kBT2020NarrowYCbCrToRGB_RowMajor); + static const float identity[12] = X(kIdentityNarrowYCbCrToRGB_RowMajor); + +#undef X + + switch (aYUVColorSpace) { + case gfx::YUVColorSpace::BT601: + return rec601; + case gfx::YUVColorSpace::BT709: + return rec709; + case gfx::YUVColorSpace::BT2020: + return rec2020; + case gfx::YUVColorSpace::Identity: + return identity; + default: + MOZ_CRASH("Bad YUVColorSpace"); + } +} + +/* static */ const float* gfxUtils::YuvToRgbMatrix3x3ColumnMajor( + gfx::YUVColorSpace aYUVColorSpace) { +#define X(x) \ + { x[0], x[4], x[8], x[1], x[5], x[9], x[2], x[6], x[10] } + + static const float rec601[9] = X(kBT601NarrowYCbCrToRGB_RowMajor); + static const float rec709[9] = X(kBT709NarrowYCbCrToRGB_RowMajor); + static const float rec2020[9] = X(kBT2020NarrowYCbCrToRGB_RowMajor); + static const float identity[9] = X(kIdentityNarrowYCbCrToRGB_RowMajor); + +#undef X + + switch (aYUVColorSpace) { + case gfx::YUVColorSpace::BT601: + return rec601; + case YUVColorSpace::BT709: + return rec709; + case YUVColorSpace::BT2020: + return rec2020; + case YUVColorSpace::Identity: + return identity; + default: + MOZ_CRASH("Bad YUVColorSpace"); + } +} + +/* static */ const float* gfxUtils::YuvToRgbMatrix4x4ColumnMajor( + YUVColorSpace aYUVColorSpace) { +#define X(x) \ + { \ + x[0], x[4], x[8], x[12], x[1], x[5], x[9], x[13], x[2], x[6], x[10], \ + x[14], x[3], x[7], x[11], x[15] \ + } + + static const float rec601[16] = X(kBT601NarrowYCbCrToRGB_RowMajor); + static const float rec709[16] = X(kBT709NarrowYCbCrToRGB_RowMajor); + static const float rec2020[16] = X(kBT2020NarrowYCbCrToRGB_RowMajor); + static const float identity[16] = X(kIdentityNarrowYCbCrToRGB_RowMajor); + +#undef X + + switch (aYUVColorSpace) { + case YUVColorSpace::BT601: + return rec601; + case YUVColorSpace::BT709: + return rec709; + case YUVColorSpace::BT2020: + return rec2020; + case YUVColorSpace::Identity: + return identity; + default: + MOZ_CRASH("Bad YUVColorSpace"); + } +} + +// Translate from CICP values to the color spaces we support, or return +// Nothing() if there is no appropriate match to let the caller choose +// a default or generate an error. +// +// See Rec. ITU-T H.273 (12/2016) for details on CICP +/* static */ Maybe<gfx::YUVColorSpace> gfxUtils::CicpToColorSpace( + const CICP::MatrixCoefficients aMatrixCoefficients, + const CICP::ColourPrimaries aColourPrimaries, LazyLogModule& aLogger) { + switch (aMatrixCoefficients) { + case CICP::MatrixCoefficients::MC_BT2020_NCL: + case CICP::MatrixCoefficients::MC_BT2020_CL: + return Some(gfx::YUVColorSpace::BT2020); + case CICP::MatrixCoefficients::MC_BT601: + return Some(gfx::YUVColorSpace::BT601); + case CICP::MatrixCoefficients::MC_BT709: + return Some(gfx::YUVColorSpace::BT709); + case CICP::MatrixCoefficients::MC_IDENTITY: + return Some(gfx::YUVColorSpace::Identity); + case CICP::MatrixCoefficients::MC_CHROMAT_NCL: + case CICP::MatrixCoefficients::MC_CHROMAT_CL: + case CICP::MatrixCoefficients::MC_UNSPECIFIED: + switch (aColourPrimaries) { + case CICP::ColourPrimaries::CP_BT601: + return Some(gfx::YUVColorSpace::BT601); + case CICP::ColourPrimaries::CP_BT709: + return Some(gfx::YUVColorSpace::BT709); + case CICP::ColourPrimaries::CP_BT2020: + return Some(gfx::YUVColorSpace::BT2020); + default: + MOZ_LOG(aLogger, LogLevel::Debug, + ("Couldn't infer color matrix from primaries: %hhu", + aColourPrimaries)); + return {}; + } + default: + MOZ_LOG(aLogger, LogLevel::Debug, + ("Unsupported color matrix value: %hhu", aMatrixCoefficients)); + return {}; + } +} + +// Translate from CICP values to the color primaries we support, or return +// Nothing() if there is no appropriate match to let the caller choose +// a default or generate an error. +// +// See Rec. ITU-T H.273 (12/2016) for details on CICP +/* static */ Maybe<gfx::ColorSpace2> gfxUtils::CicpToColorPrimaries( + const CICP::ColourPrimaries aColourPrimaries, LazyLogModule& aLogger) { + switch (aColourPrimaries) { + case CICP::ColourPrimaries::CP_BT709: + return Some(gfx::ColorSpace2::BT709); + case CICP::ColourPrimaries::CP_BT2020: + return Some(gfx::ColorSpace2::BT2020); + default: + MOZ_LOG(aLogger, LogLevel::Debug, + ("Unsupported color primaries value: %hhu", aColourPrimaries)); + return {}; + } +} + +// Translate from CICP values to the transfer functions we support, or return +// Nothing() if there is no appropriate match. +// +/* static */ Maybe<gfx::TransferFunction> gfxUtils::CicpToTransferFunction( + const CICP::TransferCharacteristics aTransferCharacteristics) { + switch (aTransferCharacteristics) { + case CICP::TransferCharacteristics::TC_BT709: + return Some(gfx::TransferFunction::BT709); + + case CICP::TransferCharacteristics::TC_SRGB: + return Some(gfx::TransferFunction::SRGB); + + case CICP::TransferCharacteristics::TC_SMPTE2084: + return Some(gfx::TransferFunction::PQ); + + case CICP::TransferCharacteristics::TC_HLG: + return Some(gfx::TransferFunction::HLG); + + default: + return {}; + } +} + +/* static */ +void gfxUtils::WriteAsPNG(SourceSurface* aSurface, const nsAString& aFile) { + WriteAsPNG(aSurface, NS_ConvertUTF16toUTF8(aFile).get()); +} + +/* static */ +void gfxUtils::WriteAsPNG(SourceSurface* aSurface, const char* aFile) { + FILE* file = fopen(aFile, "wb"); + + if (!file) { + // Maybe the directory doesn't exist; try creating it, then fopen again. + nsresult rv = NS_ERROR_FAILURE; + nsCOMPtr<nsIFile> comFile = do_CreateInstance("@mozilla.org/file/local;1"); + if (comFile) { + NS_ConvertUTF8toUTF16 utf16path((nsDependentCString(aFile))); + rv = comFile->InitWithPath(utf16path); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIFile> dirPath; + comFile->GetParent(getter_AddRefs(dirPath)); + if (dirPath) { + rv = dirPath->Create(nsIFile::DIRECTORY_TYPE, 0777); + if (NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_ALREADY_EXISTS) { + file = fopen(aFile, "wb"); + } + } + } + } + if (!file) { + NS_WARNING("Failed to open file to create PNG!"); + return; + } + } + + EncodeSourceSurface(aSurface, ImageType::PNG, u""_ns, eBinaryEncode, file); + fclose(file); +} + +/* static */ +void gfxUtils::WriteAsPNG(DrawTarget* aDT, const nsAString& aFile) { + WriteAsPNG(aDT, NS_ConvertUTF16toUTF8(aFile).get()); +} + +/* static */ +void gfxUtils::WriteAsPNG(DrawTarget* aDT, const char* aFile) { + RefPtr<SourceSurface> surface = aDT->Snapshot(); + if (surface) { + WriteAsPNG(surface, aFile); + } else { + NS_WARNING("Failed to get surface!"); + } +} + +/* static */ +void gfxUtils::DumpAsDataURI(SourceSurface* aSurface, FILE* aFile) { + EncodeSourceSurface(aSurface, ImageType::PNG, u""_ns, eDataURIEncode, aFile); +} + +/* static */ +nsCString gfxUtils::GetAsDataURI(SourceSurface* aSurface) { + return EncodeSourceSurfaceAsPNGURI(aSurface); +} + +/* static */ +void gfxUtils::DumpAsDataURI(DrawTarget* aDT, FILE* aFile) { + RefPtr<SourceSurface> surface = aDT->Snapshot(); + if (surface) { + DumpAsDataURI(surface, aFile); + } else { + NS_WARNING("Failed to get surface!"); + } +} + +/* static */ +nsCString gfxUtils::GetAsLZ4Base64Str(DataSourceSurface* aSourceSurface) { + DataSourceSurface::ScopedMap map(aSourceSurface, DataSourceSurface::READ); + int32_t dataSize = aSourceSurface->GetSize().height * map.GetStride(); + auto compressedData = MakeUnique<char[]>(LZ4::maxCompressedSize(dataSize)); + if (compressedData) { + int nDataSize = + LZ4::compress((char*)map.GetData(), dataSize, compressedData.get()); + if (nDataSize > 0) { + nsCString string; + string.AppendPrintf("data:image/lz4bgra;base64,%i,%i,%i,", + aSourceSurface->GetSize().width, map.GetStride(), + aSourceSurface->GetSize().height); + nsresult rv = Base64EncodeAppend(compressedData.get(), nDataSize, string); + if (rv == NS_OK) { + return string; + } + } + } + return {}; +} + +/* static */ +nsCString gfxUtils::GetAsDataURI(DrawTarget* aDT) { + RefPtr<SourceSurface> surface = aDT->Snapshot(); + if (surface) { + return EncodeSourceSurfaceAsPNGURI(surface); + } else { + NS_WARNING("Failed to get surface!"); + return nsCString(""); + } +} + +/* static */ +void gfxUtils::CopyAsDataURI(SourceSurface* aSurface) { + EncodeSourceSurface(aSurface, ImageType::PNG, u""_ns, eDataURIEncode, + nullptr); +} + +/* static */ +void gfxUtils::CopyAsDataURI(DrawTarget* aDT) { + RefPtr<SourceSurface> surface = aDT->Snapshot(); + if (surface) { + CopyAsDataURI(surface); + } else { + NS_WARNING("Failed to get surface!"); + } +} + +/* static */ +UniquePtr<uint8_t[]> gfxUtils::GetImageBuffer(gfx::DataSourceSurface* aSurface, + bool aIsAlphaPremultiplied, + int32_t* outFormat) { + *outFormat = 0; + + DataSourceSurface::MappedSurface map; + if (!aSurface->Map(DataSourceSurface::MapType::READ, &map)) return nullptr; + + uint32_t bufferSize = + aSurface->GetSize().width * aSurface->GetSize().height * 4; + auto imageBuffer = MakeUniqueFallible<uint8_t[]>(bufferSize); + if (!imageBuffer) { + aSurface->Unmap(); + return nullptr; + } + memcpy(imageBuffer.get(), map.mData, bufferSize); + + aSurface->Unmap(); + + int32_t format = imgIEncoder::INPUT_FORMAT_HOSTARGB; + if (!aIsAlphaPremultiplied) { + // We need to convert to INPUT_FORMAT_RGBA, otherwise + // we are automatically considered premult, and unpremult'd. + // Yes, it is THAT silly. + // Except for different lossy conversions by color, + // we could probably just change the label, and not change the data. + gfxUtils::ConvertBGRAtoRGBA(imageBuffer.get(), bufferSize); + format = imgIEncoder::INPUT_FORMAT_RGBA; + } + + *outFormat = format; + return imageBuffer; +} + +/* static */ +UniquePtr<uint8_t[]> gfxUtils::GetImageBufferWithRandomNoise( + gfx::DataSourceSurface* aSurface, bool aIsAlphaPremultiplied, + nsICookieJarSettings* aCookieJarSettings, int32_t* outFormat) { + UniquePtr<uint8_t[]> imageBuffer = + GetImageBuffer(aSurface, aIsAlphaPremultiplied, outFormat); + + nsRFPService::RandomizePixels( + aCookieJarSettings, imageBuffer.get(), aSurface->GetSize().width, + aSurface->GetSize().height, + aSurface->GetSize().width * aSurface->GetSize().height * 4, + SurfaceFormat::A8R8G8B8_UINT32); + + return imageBuffer; +} + +/* static */ +nsresult gfxUtils::GetInputStream(gfx::DataSourceSurface* aSurface, + bool aIsAlphaPremultiplied, + const char* aMimeType, + const nsAString& aEncoderOptions, + nsIInputStream** outStream) { + nsCString enccid("@mozilla.org/image/encoder;2?type="); + enccid += aMimeType; + nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get()); + if (!encoder) return NS_ERROR_FAILURE; + + int32_t format = 0; + UniquePtr<uint8_t[]> imageBuffer = + GetImageBuffer(aSurface, aIsAlphaPremultiplied, &format); + if (!imageBuffer) return NS_ERROR_FAILURE; + + return dom::ImageEncoder::GetInputStream( + aSurface->GetSize().width, aSurface->GetSize().height, imageBuffer.get(), + format, encoder, aEncoderOptions, outStream); +} + +/* static */ +nsresult gfxUtils::GetInputStreamWithRandomNoise( + gfx::DataSourceSurface* aSurface, bool aIsAlphaPremultiplied, + const char* aMimeType, const nsAString& aEncoderOptions, + nsICookieJarSettings* aCookieJarSettings, nsIInputStream** outStream) { + nsCString enccid("@mozilla.org/image/encoder;2?type="); + enccid += aMimeType; + nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get()); + if (!encoder) { + return NS_ERROR_FAILURE; + } + + int32_t format = 0; + UniquePtr<uint8_t[]> imageBuffer = + GetImageBuffer(aSurface, aIsAlphaPremultiplied, &format); + if (!imageBuffer) { + return NS_ERROR_FAILURE; + } + + nsRFPService::RandomizePixels( + aCookieJarSettings, imageBuffer.get(), aSurface->GetSize().width, + aSurface->GetSize().height, + aSurface->GetSize().width * aSurface->GetSize().height * 4, + SurfaceFormat::A8R8G8B8_UINT32); + + return dom::ImageEncoder::GetInputStream( + aSurface->GetSize().width, aSurface->GetSize().height, imageBuffer.get(), + format, encoder, aEncoderOptions, outStream); +} + +class GetFeatureStatusWorkerRunnable final + : public dom::WorkerMainThreadRunnable { + public: + GetFeatureStatusWorkerRunnable(dom::WorkerPrivate* workerPrivate, + const nsCOMPtr<nsIGfxInfo>& gfxInfo, + int32_t feature, nsACString& failureId, + int32_t* status) + : WorkerMainThreadRunnable(workerPrivate, "GFX :: GetFeatureStatus"_ns), + mGfxInfo(gfxInfo), + mFeature(feature), + mStatus(status), + mFailureId(failureId), + mNSResult(NS_OK) {} + + bool MainThreadRun() override { + if (mGfxInfo) { + mNSResult = mGfxInfo->GetFeatureStatus(mFeature, mFailureId, mStatus); + } + return true; + } + + nsresult GetNSResult() const { return mNSResult; } + + protected: + ~GetFeatureStatusWorkerRunnable() = default; + + private: + nsCOMPtr<nsIGfxInfo> mGfxInfo; + int32_t mFeature; + int32_t* mStatus; + nsACString& mFailureId; + nsresult mNSResult; +}; + +#define GFX_SHADER_CHECK_BUILD_VERSION_PREF "gfx-shader-check.build-version" +#define GFX_SHADER_CHECK_PTR_SIZE_PREF "gfx-shader-check.ptr-size" +#define GFX_SHADER_CHECK_DEVICE_ID_PREF "gfx-shader-check.device-id" +#define GFX_SHADER_CHECK_DRIVER_VERSION_PREF "gfx-shader-check.driver-version" + +/* static */ +void gfxUtils::RemoveShaderCacheFromDiskIfNecessary() { + if (!gfxVars::UseWebRenderProgramBinaryDisk()) { + return; + } + + nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); + + // Get current values + nsCString buildID(mozilla::PlatformBuildID()); + int ptrSize = sizeof(void*); + nsString deviceID, driverVersion; + gfxInfo->GetAdapterDeviceID(deviceID); + gfxInfo->GetAdapterDriverVersion(driverVersion); + + // Get pref stored values + nsAutoCString buildIDChecked; + Preferences::GetCString(GFX_SHADER_CHECK_BUILD_VERSION_PREF, buildIDChecked); + int ptrSizeChecked = Preferences::GetInt(GFX_SHADER_CHECK_PTR_SIZE_PREF, 0); + nsAutoString deviceIDChecked, driverVersionChecked; + Preferences::GetString(GFX_SHADER_CHECK_DEVICE_ID_PREF, deviceIDChecked); + Preferences::GetString(GFX_SHADER_CHECK_DRIVER_VERSION_PREF, + driverVersionChecked); + + if (buildID == buildIDChecked && ptrSize == ptrSizeChecked && + deviceID == deviceIDChecked && driverVersion == driverVersionChecked) { + return; + } + + nsAutoString path(gfx::gfxVars::ProfDirectory()); + + if (!wr::remove_program_binary_disk_cache(&path)) { + // Failed to remove program binary disk cache. The disk cache might have + // invalid data. Disable program binary disk cache usage. + gfxVars::SetUseWebRenderProgramBinaryDisk(false); + return; + } + + Preferences::SetCString(GFX_SHADER_CHECK_BUILD_VERSION_PREF, buildID); + Preferences::SetInt(GFX_SHADER_CHECK_PTR_SIZE_PREF, ptrSize); + Preferences::SetString(GFX_SHADER_CHECK_DEVICE_ID_PREF, deviceID); + Preferences::SetString(GFX_SHADER_CHECK_DRIVER_VERSION_PREF, driverVersion); +} + +/* static */ +bool gfxUtils::DumpDisplayList() { + return StaticPrefs::layout_display_list_dump() || + (StaticPrefs::layout_display_list_dump_parent() && + XRE_IsParentProcess()) || + (StaticPrefs::layout_display_list_dump_content() && + XRE_IsContentProcess()); +} + +FILE* gfxUtils::sDumpPaintFile = stderr; + +namespace mozilla { +namespace gfx { + +DeviceColor ToDeviceColor(const sRGBColor& aColor) { + // aColor is pass-by-value since to get return value optimization goodness we + // need to return the same object from all return points in this function. We + // could declare a local Color variable and use that, but we might as well + // just use aColor. + if (gfxPlatform::GetCMSMode() == CMSMode::All) { + qcms_transform* transform = gfxPlatform::GetCMSRGBTransform(); + if (transform) { + return gfxPlatform::TransformPixel(aColor, transform); + // Use the original alpha to avoid unnecessary float->byte->float + // conversion errors + } + } + return DeviceColor(aColor.r, aColor.g, aColor.b, aColor.a); +} + +DeviceColor ToDeviceColor(nscolor aColor) { + return ToDeviceColor(sRGBColor::FromABGR(aColor)); +} + +DeviceColor ToDeviceColor(const StyleAbsoluteColor& aColor) { + return ToDeviceColor(aColor.ToColor()); +} + +sRGBColor ToSRGBColor(const StyleAbsoluteColor& aColor) { + auto srgb = aColor.ToColorSpace(StyleColorSpace::Srgb); + + const auto ToComponent = [](float aF) -> float { + float component = std::min(std::max(0.0f, aF), 1.0f); + if (MOZ_UNLIKELY(!std::isfinite(component))) { + return 0.0f; + } + return component; + }; + return {ToComponent(srgb.components._0), ToComponent(srgb.components._1), + ToComponent(srgb.components._2), ToComponent(srgb.alpha)}; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/thebes/gfxUtils.h b/gfx/thebes/gfxUtils.h new file mode 100644 index 0000000000..a7b9f067eb --- /dev/null +++ b/gfx/thebes/gfxUtils.h @@ -0,0 +1,626 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_UTILS_H +#define GFX_UTILS_H + +#include "gfxMatrix.h" +#include "gfxRect.h" +#include "gfxTypes.h" +#include "ImageTypes.h" +#include "imgIContainer.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/Maybe.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "nsColor.h" +#include "nsContentUtils.h" +#include "nsPrintfCString.h" +#include "nsRegionFwd.h" +#include "mozilla/gfx/Rect.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "qcms.h" + +class gfxASurface; +class gfxDrawable; +class gfxTextRun; +struct gfxQuad; +class nsICookieJarSettings; +class nsIInputStream; +class nsIGfxInfo; + +namespace mozilla { +namespace dom { +class Element; +} // namespace dom +namespace layers { +class WebRenderBridgeChild; +class GlyphArray; +struct PlanarYCbCrData; +class WebRenderCommand; +} // namespace layers +namespace image { +class ImageRegion; +} // namespace image +namespace wr { +class DisplayListBuilder; +} // namespace wr +} // namespace mozilla + +enum class ImageType { + BMP, + ICO, + JPEG, + PNG, +}; + +class gfxUtils { + public: + typedef mozilla::gfx::DataSourceSurface DataSourceSurface; + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::IntPoint IntPoint; + typedef mozilla::gfx::Matrix Matrix; + typedef mozilla::gfx::Matrix4x4 Matrix4x4; + typedef mozilla::gfx::SourceSurface SourceSurface; + typedef mozilla::gfx::SurfaceFormat SurfaceFormat; + typedef mozilla::image::ImageRegion ImageRegion; + + /* + * Premultiply or Unpremultiply aSourceSurface, writing the result + * to aDestSurface or back into aSourceSurface if aDestSurface is null. + * + * If aDestSurface is given, it must have identical format, dimensions, and + * stride as the source. + * + * If the source is not SurfaceFormat::A8R8G8B8_UINT32, no operation is + * performed. If aDestSurface is given, the data is copied over. + */ + static bool PremultiplyDataSurface(DataSourceSurface* srcSurf, + DataSourceSurface* destSurf); + static bool UnpremultiplyDataSurface(DataSourceSurface* srcSurf, + DataSourceSurface* destSurf); + + static already_AddRefed<DataSourceSurface> CreatePremultipliedDataSurface( + DataSourceSurface* srcSurf); + static already_AddRefed<DataSourceSurface> CreateUnpremultipliedDataSurface( + DataSourceSurface* srcSurf); + + static void ConvertBGRAtoRGBA(uint8_t* aData, uint32_t aLength); + + /** + * Draw something drawable while working around limitations like bad support + * for EXTEND_PAD, lack of source-clipping, or cairo / pixman bugs with + * extreme user-space-to-image-space transforms. + * + * The input parameters here usually come from the output of our image + * snapping algorithm in nsLayoutUtils.cpp. + * This method is split from nsLayoutUtils::DrawPixelSnapped to allow for + * adjusting the parameters. For example, certain images with transparent + * margins only have a drawable subimage. For those images, imgFrame::Draw + * will tweak the rects and transforms that it gets from the pixel snapping + * algorithm before passing them on to this method. + */ + static void DrawPixelSnapped(gfxContext* aContext, gfxDrawable* aDrawable, + const gfxSize& aImageSize, + const ImageRegion& aRegion, + const mozilla::gfx::SurfaceFormat aFormat, + mozilla::gfx::SamplingFilter aSamplingFilter, + uint32_t aImageFlags = imgIContainer::FLAG_NONE, + gfxFloat aOpacity = 1.0, + bool aUseOptimalFillOp = true); + + /** + * Clip aContext to the region aRegion. + */ + static void ClipToRegion(gfxContext* aContext, const nsIntRegion& aRegion); + + /** + * Clip aTarget to the region aRegion. + */ + static void ClipToRegion(mozilla::gfx::DrawTarget* aTarget, + const nsIntRegion& aRegion); + + /* + * Convert image format to depth value + */ + static int ImageFormatToDepth(gfxImageFormat aFormat); + + /** + * Return the transform matrix that maps aFrom to the rectangle defined by + * aToTopLeft/aToTopRight/aToBottomRight. aFrom must be + * nonempty and the destination rectangle must be axis-aligned. + */ + static gfxMatrix TransformRectToRect(const gfxRect& aFrom, + const gfxPoint& aToTopLeft, + const gfxPoint& aToTopRight, + const gfxPoint& aToBottomRight); + + static Matrix TransformRectToRect(const gfxRect& aFrom, + const IntPoint& aToTopLeft, + const IntPoint& aToTopRight, + const IntPoint& aToBottomRight); + + /** + * If aIn can be represented exactly using an gfx::IntRect (i.e. + * integer-aligned edges and coordinates in the int32_t range) then we + * set aOut to that rectangle, otherwise return failure. + */ + static bool GfxRectToIntRect(const gfxRect& aIn, mozilla::gfx::IntRect* aOut); + + /* Conditions this border to Cairo's max coordinate space. + * The caller can check IsEmpty() after Condition() -- if it's TRUE, + * the caller can possibly avoid doing any extra rendering. + */ + static void ConditionRect(gfxRect& aRect); + + /* + * Transform this rectangle with aMatrix, resulting in a gfxQuad. + */ + static gfxQuad TransformToQuad(const gfxRect& aRect, + const mozilla::gfx::Matrix4x4& aMatrix); + + /** + * Return the smallest power of kScaleResolution (2) greater than or equal to + * aVal. If aRoundDown is specified, the power of 2 will rather be less than + * or equal to aVal. + */ + static float ClampToScaleFactor(float aVal, bool aRoundDown = false); + + /** + * We can snap layer transforms for two reasons: + * 1) To avoid unnecessary resampling when a transform is a translation + * by a non-integer number of pixels. + * Snapping the translation to an integer number of pixels avoids + * blurring the layer and can be faster to composite. + * 2) When a layer is used to render a rectangular object, we need to + * emulate the rendering of rectangular inactive content and snap the + * edges of the rectangle to pixel boundaries. This is both to ensure + * layer rendering is consistent with inactive content rendering, and to + * avoid seams. + * This function implements type 1 snapping. If aTransform is a 2D + * translation, and this layer's layer manager has enabled snapping + * (which is the default), return aTransform with the translation snapped + * to nearest pixels. Otherwise just return aTransform. Call this when the + * layer does not correspond to a single rectangular content object. + * This function does not try to snap if aTransform has a scale, because in + * that case resampling is inevitable and there's no point in trying to + * avoid it. In fact snapping can cause problems because pixel edges in the + * layer's content can be rendered unpredictably (jiggling) as the scale + * interacts with the snapping of the translation, especially with animated + * transforms. + * @param aResidualTransform a transform to apply before the result transform + * in order to get the results to completely match aTransform. + */ + static Matrix4x4 SnapTransformTranslation(const Matrix4x4& aTransform, + Matrix* aResidualTransform); + static Matrix SnapTransformTranslation(const Matrix& aTransform, + Matrix* aResidualTransform); + static Matrix4x4 SnapTransformTranslation3D(const Matrix4x4& aTransform, + Matrix* aResidualTransform); + /** + * See comment for SnapTransformTranslation. + * This function implements type 2 snapping. If aTransform is a translation + * and/or scale, transform aSnapRect by aTransform, snap to pixel boundaries, + * and return the transform that maps aSnapRect to that rect. Otherwise + * just return aTransform. + * @param aSnapRect a rectangle whose edges should be snapped to pixel + * boundaries in the destination surface. + * @param aResidualTransform a transform to apply before the result transform + * in order to get the results to completely match aTransform. + */ + static Matrix4x4 SnapTransform(const Matrix4x4& aTransform, + const gfxRect& aSnapRect, + Matrix* aResidualTransform); + static Matrix SnapTransform(const Matrix& aTransform, + const gfxRect& aSnapRect, + Matrix* aResidualTransform); + + /** + * Clears surface to aColor (which defaults to transparent black). + */ + static void ClearThebesSurface(gfxASurface* aSurface); + + static const float* YuvToRgbMatrix4x3RowMajor( + mozilla::gfx::YUVColorSpace aYUVColorSpace); + static const float* YuvToRgbMatrix3x3ColumnMajor( + mozilla::gfx::YUVColorSpace aYUVColorSpace); + static const float* YuvToRgbMatrix4x4ColumnMajor( + mozilla::gfx::YUVColorSpace aYUVColorSpace); + + static mozilla::Maybe<mozilla::gfx::YUVColorSpace> CicpToColorSpace( + const mozilla::gfx::CICP::MatrixCoefficients, + const mozilla::gfx::CICP::ColourPrimaries, + mozilla::LazyLogModule& aLogger); + + static mozilla::Maybe<mozilla::gfx::ColorSpace2> CicpToColorPrimaries( + const mozilla::gfx::CICP::ColourPrimaries, + mozilla::LazyLogModule& aLogger); + + static mozilla::Maybe<mozilla::gfx::TransferFunction> CicpToTransferFunction( + const mozilla::gfx::CICP::TransferCharacteristics); + + /** + * Creates a copy of aSurface, but having the SurfaceFormat aFormat. + * + * This function always creates a new surface. Do not call it if aSurface's + * format is the same as aFormat. Such a non-conversion would just be an + * unnecessary and wasteful copy (this function asserts to prevent that). + * + * This function is intended to be called by code that needs to access the + * pixel data of the surface, but doesn't want to have lots of branches + * to handle different pixel data formats (code which would become out of + * date if and when new formats are added). Callers can use this function + * to copy the surface to a specified format so that they only have to + * handle pixel data in that one format. + * + * WARNING: There are format conversions that will not be supported by this + * function. It very much depends on what the Moz2D backends support. If + * the temporary B8G8R8A8 DrawTarget that this function creates has a + * backend that supports DrawSurface() calls passing a surface with + * aSurface's format it will work. Otherwise it will not. + * + * *** IMPORTANT PERF NOTE *** + * + * This function exists partly because format conversion is fraught with + * non-obvious performance hazards, so we don't want Moz2D consumers to be + * doing their own format conversion. Do not try to do so, or at least read + * the comments in this functions implemtation. That said, the copy that + * this function carries out has a cost and, although this function tries + * to avoid perf hazards such as expensive uploads to/readbacks from the + * GPU, it can't guarantee that it always successfully does so. Perf + * critical code that can directly handle the common formats that it + * encounters in a way that is cheaper than a copy-with-format-conversion + * should consider doing so, and only use this function as a fallback to + * handle other formats. + * + * XXXjwatt it would be nice if SourceSurface::GetDataSurface took a + * SurfaceFormat argument (with a default argument meaning "use the + * existing surface's format") and returned a DataSourceSurface in that + * format. (There would then be an issue of callers maybe failing to + * realize format conversion may involve expensive copying/uploading/ + * readback.) + */ + static already_AddRefed<DataSourceSurface> + CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface, + SurfaceFormat aFormat); + + /** + * Return a color that can be used to identify a frame with a given frame + * number. The colors will cycle after sNumFrameColors. You can query colors + * 0 .. sNumFrameColors-1 to get all the colors back. + */ + static const mozilla::gfx::DeviceColor& GetColorForFrameNumber( + uint64_t aFrameNumber); + static const uint32_t sNumFrameColors; + + enum BinaryOrData { eBinaryEncode, eDataURIEncode }; + + /** + * Encodes the given surface to PNG/JPEG/BMP/etc. using imgIEncoder. + * If both aFile and aString are null, the encoded data is copied to the + * clipboard. + * + * @param aImageType The image type that the surface is to be encoded to. + * Used to create an appropriate imgIEncoder instance to do the encoding. + * + * @param aOutputOptions Passed directly to imgIEncoder::InitFromData as + * the value of the |outputOptions| parameter. Callers are responsible + * for making sure that this is a reasonable value for the passed MIME-type + * (i.e. for the type of encoder that will be created). + * + * @aBinaryOrData Flag used to determine if the surface is simply encoded + * to the requested binary image format, or if the binary image is + * further converted to base-64 and written out as a 'data:' URI. + * + * @aFile If specified, the encoded data is written out to aFile. + * + * @aString If specified, the encoded data is written out to aString. + * + * TODO: Copying to the clipboard as a binary file is not currently + * supported. + */ + static nsresult EncodeSourceSurface(SourceSurface* aSurface, + const ImageType aImageType, + const nsAString& aOutputOptions, + BinaryOrData aBinaryOrData, FILE* aFile, + nsACString* aString = nullptr); + + /** + * Encodes the given surface to PNG/JPEG/BMP/etc. using imgIEncoder + * and returns the result as an nsIInputStream. + * + * @param aSurface The source surface to encode + * + * @param aImageType The image type that the surface is to be encoded to. + * Used to create an appropriate imgIEncoder instance to do the encoding. + * + * @param aOutputOptions Passed directly to imgIEncoder::InitFromData as + * the value of the |outputOptions| parameter. Callers are responsible + * for making sure that this is a reasonable value for the passed MIME-type + * (i.e. for the type of encoder that will be created). + * + * @param aOutStream pointer to the output stream + * + */ + static nsresult EncodeSourceSurfaceAsStream(SourceSurface* aSurface, + const ImageType aImageType, + const nsAString& aOutputOptions, + nsIInputStream** aOutStream); + + /** + * Encodes the given surface to PNG/JPEG/BMP/etc. using imgIEncoder + * and returns the result as a vector of bytes + * + * @param aImageType The image type that the surface is to be encoded to. + * Used to create an appropriate imgIEncoder instance to do the encoding. + * + * @param aOutputOptions Passed directly to imgIEncoder::InitFromData as + * the value of the |outputOptions| parameter. Callers are responsible + * for making sure that this is a reasonable value for the passed MIME-type + * (i.e. for the type of encoder that will be created). + * + */ + static mozilla::Maybe<nsTArray<uint8_t>> EncodeSourceSurfaceAsBytes( + SourceSurface* aSurface, const ImageType aImageType, + const nsAString& aOutputOptions); + + /** + * Write as a PNG file to the path aFile. + */ + static void WriteAsPNG(SourceSurface* aSurface, const nsAString& aFile); + static void WriteAsPNG(SourceSurface* aSurface, const char* aFile); + static void WriteAsPNG(DrawTarget* aDT, const nsAString& aFile); + static void WriteAsPNG(DrawTarget* aDT, const char* aFile); + + /** + * Dump as a PNG encoded Data URL to a FILE stream (using stdout by + * default). + * + * Rather than giving aFile a default argument we have separate functions + * to make them easier to use from a debugger. + */ + static void DumpAsDataURI(SourceSurface* aSourceSurface, FILE* aFile); + static inline void DumpAsDataURI(SourceSurface* aSourceSurface) { + DumpAsDataURI(aSourceSurface, stdout); + } + static void DumpAsDataURI(DrawTarget* aDT, FILE* aFile); + static inline void DumpAsDataURI(DrawTarget* aDT) { + DumpAsDataURI(aDT, stdout); + } + static nsCString GetAsDataURI(SourceSurface* aSourceSurface); + static nsCString GetAsDataURI(DrawTarget* aDT); + static nsCString GetAsLZ4Base64Str(DataSourceSurface* aSourceSurface); + + static mozilla::UniquePtr<uint8_t[]> GetImageBuffer( + DataSourceSurface* aSurface, bool aIsAlphaPremultiplied, + int32_t* outFormat); + + static mozilla::UniquePtr<uint8_t[]> GetImageBufferWithRandomNoise( + DataSourceSurface* aSurface, bool aIsAlphaPremultiplied, + nsICookieJarSettings* aCookieJarSettings, int32_t* outFormat); + + static nsresult GetInputStream(DataSourceSurface* aSurface, + bool aIsAlphaPremultiplied, + const char* aMimeType, + const nsAString& aEncoderOptions, + nsIInputStream** outStream); + + static nsresult GetInputStreamWithRandomNoise( + DataSourceSurface* aSurface, bool aIsAlphaPremultiplied, + const char* aMimeType, const nsAString& aEncoderOptions, + nsICookieJarSettings* aCookieJarSettings, nsIInputStream** outStream); + + static void RemoveShaderCacheFromDiskIfNecessary(); + + /** + * Copy to the clipboard as a PNG encoded Data URL. + */ + static void CopyAsDataURI(SourceSurface* aSourceSurface); + static void CopyAsDataURI(DrawTarget* aDT); + + static bool DumpDisplayList(); + + static FILE* sDumpPaintFile; +}; + +namespace mozilla { + +// Container for either a single element of type T, or an nsTArray<T>. +// Provides a minimal subset of nsTArray's API, just enough to support use +// by ContextState for the clipsAndTransforms list, and by gfxTextRun for +// its mGlyphRuns. +// Using this instead of a simple nsTArray avoids an extra allocation in the +// common case where no more than one element is ever added to the list. +// Unlike an AutoTArray<..., 1>, this class is memmovable and therefore can +// be used in ContextState without breaking its movability. +template <typename T> +class ElementOrArray { + union { + T mElement; + nsTArray<T> mArray; + }; + enum class Tag : uint8_t { + Element, + Array, + } mTag; + + // gfxTextRun::SortGlyphRuns and SanitizeGlyphRuns directly access the array. + friend class ::gfxTextRun; + nsTArray<T>& Array() { + MOZ_DIAGNOSTIC_ASSERT(mTag == Tag::Array); + return mArray; + } + + public: + // Construct as an empty array. + ElementOrArray() : mTag(Tag::Array) { new (&mArray) nsTArray<T>(); } + + // For now, don't support copy/move. + ElementOrArray(const ElementOrArray&) = delete; + ElementOrArray(ElementOrArray&&) = delete; + + ElementOrArray& operator=(const ElementOrArray&) = delete; + ElementOrArray& operator=(ElementOrArray&&) = delete; + + // Destroy the appropriate variant. + ~ElementOrArray() { + switch (mTag) { + case Tag::Element: + mElement.~T(); + break; + case Tag::Array: + mArray.~nsTArray(); + break; + } + } + + size_t Length() const { return mTag == Tag::Element ? 1 : mArray.Length(); } + + T* AppendElement(const T& aElement) { + switch (mTag) { + case Tag::Element: { + // Move the existing element into an array, then append the new one. + T temp = std::move(mElement); + mElement.~T(); + mTag = Tag::Array; + new (&mArray) nsTArray<T>(); + mArray.AppendElement(std::move(temp)); + return mArray.AppendElement(aElement); + } + case Tag::Array: { + // If currently empty, just store the element directly. + if (mArray.IsEmpty()) { + mArray.~nsTArray(); + mTag = Tag::Element; + new (&mElement) T(aElement); + return &mElement; + } + // Otherwise, append it to the array. + return mArray.AppendElement(aElement); + } + default: + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("invalid tag"); + } + } + + const T& LastElement() const { + return mTag == Tag::Element ? mElement : mArray.LastElement(); + } + + T& LastElement() { + return mTag == Tag::Element ? mElement : mArray.LastElement(); + } + + bool IsEmpty() const { + return mTag == Tag::Element ? false : mArray.IsEmpty(); + } + + void TruncateLength(uint32_t aLength = 0) { + MOZ_DIAGNOSTIC_ASSERT(aLength <= Length()); + switch (mTag) { + case Tag::Element: + if (aLength == 0) { + // Destroy the single element, and convert to an empty array. + mElement.~T(); + mTag = Tag::Array; + new (&mArray) nsTArray<T>(); + } + break; + case Tag::Array: + mArray.TruncateLength(aLength); + break; + } + } + + void Clear() { + switch (mTag) { + case Tag::Element: + mElement.~T(); + mTag = Tag::Array; + new (&mArray) nsTArray<T>(); + break; + case Tag::Array: + mArray.Clear(); + break; + } + } + + // Convert from Array to Element storage. Only to be used when the current + // state is a single-element array! + void ConvertToElement() { + MOZ_DIAGNOSTIC_ASSERT(mTag == Tag::Array && mArray.Length() == 1); + T temp = std::move(mArray[0]); + mArray.~nsTArray(); + mTag = Tag::Element; + new (&mElement) T(std::move(temp)); + } + + const T& operator[](uint32_t aIndex) const { + MOZ_DIAGNOSTIC_ASSERT(aIndex < Length()); + return mTag == Tag::Element ? mElement : mArray[aIndex]; + } + T& operator[](uint32_t aIndex) { + MOZ_DIAGNOSTIC_ASSERT(aIndex < Length()); + return mTag == Tag::Element ? mElement : mArray[aIndex]; + } + + // Simple iterators to support range-for loops. + const T* begin() const { + return mTag == Tag::Array ? mArray.IsEmpty() ? nullptr : &*mArray.begin() + : &mElement; + } + T* begin() { + return mTag == Tag::Array ? mArray.IsEmpty() ? nullptr : &*mArray.begin() + : &mElement; + } + + const T* end() const { + return mTag == Tag::Array ? begin() + mArray.Length() : &mElement + 1; + } + T* end() { + return mTag == Tag::Array ? begin() + mArray.Length() : &mElement + 1; + } + + size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) { + return mTag == Tag::Array ? mArray.ShallowSizeOfExcludingThis(aMallocSizeOf) + : 0; + } +}; + +struct StyleAbsoluteColor; + +namespace gfx { + +/** + * If the CMS mode is CMSMode::All, these functions transform the passed + * color to a device color using the transform returned by + * gfxPlatform::GetCMSRGBTransform(). If the CMS mode is some other value, the + * color is returned unchanged (other than a type change to Moz2D Color, if + * applicable). + */ +DeviceColor ToDeviceColor(const sRGBColor&); +DeviceColor ToDeviceColor(const StyleAbsoluteColor&); +DeviceColor ToDeviceColor(nscolor); + +sRGBColor ToSRGBColor(const StyleAbsoluteColor&); + +/** + * Performs a checked multiply of the given width, height, and bytes-per-pixel + * values. + */ +static inline CheckedInt<uint32_t> SafeBytesForBitmap(uint32_t aWidth, + uint32_t aHeight, + unsigned aBytesPerPixel) { + MOZ_ASSERT(aBytesPerPixel > 0); + CheckedInt<uint32_t> width = uint32_t(aWidth); + CheckedInt<uint32_t> height = uint32_t(aHeight); + return width * height * aBytesPerPixel; +} + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/thebes/gfxWindowsNativeDrawing.cpp b/gfx/thebes/gfxWindowsNativeDrawing.cpp new file mode 100644 index 0000000000..548ec56869 --- /dev/null +++ b/gfx/thebes/gfxWindowsNativeDrawing.cpp @@ -0,0 +1,302 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <windows.h> + +#include "nsMathUtils.h" + +#include "gfxWindowsNativeDrawing.h" +#include "gfxWindowsSurface.h" +#include "gfxAlphaRecovery.h" +#include "gfxPattern.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Helpers.h" +#include "gfx2DGlue.h" + +#include "cairo.h" +#include "cairo-win32.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +enum { + RENDER_STATE_INIT, + + RENDER_STATE_NATIVE_DRAWING, + RENDER_STATE_NATIVE_DRAWING_DONE, + + RENDER_STATE_ALPHA_RECOVERY_BLACK, + RENDER_STATE_ALPHA_RECOVERY_BLACK_DONE, + RENDER_STATE_ALPHA_RECOVERY_WHITE, + RENDER_STATE_ALPHA_RECOVERY_WHITE_DONE, + + RENDER_STATE_DONE +}; + +gfxWindowsNativeDrawing::gfxWindowsNativeDrawing(gfxContext* ctx, + const gfxRect& nativeRect, + uint32_t nativeDrawFlags) + : mContext(ctx), + mNativeRect(nativeRect), + mNativeDrawFlags(nativeDrawFlags), + mRenderState(RENDER_STATE_INIT) {} + +HDC gfxWindowsNativeDrawing::BeginNativeDrawing() { + if (mRenderState == RENDER_STATE_INIT) { + RefPtr<gfxASurface> surf; + DrawTarget* drawTarget = mContext->GetDrawTarget(); + cairo_t* cairo = nullptr; + if (drawTarget->GetBackendType() == BackendType::CAIRO) { + cairo = static_cast<cairo_t*>( + drawTarget->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT)); + if (cairo) { + cairo_surface_t* s = cairo_get_group_target(cairo); + if (s) { + mDeviceOffset = mContext->GetDeviceOffset(); + double sdx, sdy; + cairo_surface_get_device_offset(s, &sdx, &sdy); + mDeviceOffset.x -= sdx; + mDeviceOffset.y -= sdy; + surf = gfxASurface::Wrap(s); + } + } + } + + if (surf && surf->CairoStatus() != 0) return nullptr; + + gfxMatrix m = mContext->CurrentMatrixDouble(); + if (!m.HasNonTranslation()) + mTransformType = TRANSLATION_ONLY; + else if (m.HasNonAxisAlignedTransform()) + mTransformType = COMPLEX; + else + mTransformType = AXIS_ALIGNED_SCALE; + + // if this is a native win32 surface, we don't have to + // redirect rendering to our own HDC; in some cases, + // we may be able to use the HDC from the surface directly. + if (surf && ((surf->GetType() == gfxSurfaceType::Win32 || + surf->GetType() == gfxSurfaceType::Win32Printing) && + (surf->GetContentType() == gfxContentType::COLOR || + (surf->GetContentType() == gfxContentType::COLOR_ALPHA && + (mNativeDrawFlags & CAN_DRAW_TO_COLOR_ALPHA))))) { + // grab the DC. This can fail if there is a complex clipping path, + // in which case we'll have to fall back. + mWinSurface = static_cast<gfxWindowsSurface*>( + static_cast<gfxASurface*>(surf.get())); + mDC = cairo_win32_get_dc_with_clip(cairo); + + if (mDC) { + if (mTransformType == TRANSLATION_ONLY) { + mRenderState = RENDER_STATE_NATIVE_DRAWING; + + mTranslation = m.GetTranslation(); + } else if (((mTransformType == AXIS_ALIGNED_SCALE) && + (mNativeDrawFlags & CAN_AXIS_ALIGNED_SCALE)) || + (mNativeDrawFlags & CAN_COMPLEX_TRANSFORM)) { + mWorldTransform.eM11 = (FLOAT)m._11; + mWorldTransform.eM12 = (FLOAT)m._12; + mWorldTransform.eM21 = (FLOAT)m._21; + mWorldTransform.eM22 = (FLOAT)m._22; + mWorldTransform.eDx = (FLOAT)m._31; + mWorldTransform.eDy = (FLOAT)m._32; + + mRenderState = RENDER_STATE_NATIVE_DRAWING; + } + } + } + + // If we couldn't do native drawing, then we have to do two-buffer drawing + // and do alpha recovery + if (mRenderState == RENDER_STATE_INIT) { + mRenderState = RENDER_STATE_ALPHA_RECOVERY_BLACK; + + // We round out our native rect here, that way the snapping will + // happen correctly. + mNativeRect.RoundOut(); + + // we only do the scale bit if we can do an axis aligned + // scale; otherwise we scale (if necessary) after + // rendering with cairo. Note that if we're doing alpha recovery, + // we cannot do a full complex transform with win32 (I mean, we could, but + // it would require more code that's not here.) + if (mTransformType == TRANSLATION_ONLY || + !(mNativeDrawFlags & CAN_AXIS_ALIGNED_SCALE)) { + mScale = MatrixScalesDouble(); + + // Add 1 to the surface size; it's guaranteed to not be incorrect, + // and it fixes bug 382458 + // There's probably a better fix, but I haven't figured out + // the root cause of the problem. + mTempSurfaceSize = IntSize((int32_t)ceil(mNativeRect.Width() + 1), + (int32_t)ceil(mNativeRect.Height() + 1)); + } else { + // figure out the scale factors + mScale = m.ScaleFactors(); + + mWorldTransform.eM11 = (FLOAT)mScale.xScale; + mWorldTransform.eM12 = 0.0f; + mWorldTransform.eM21 = 0.0f; + mWorldTransform.eM22 = (FLOAT)mScale.yScale; + mWorldTransform.eDx = 0.0f; + mWorldTransform.eDy = 0.0f; + + // See comment above about "+1" + mTempSurfaceSize = + IntSize((int32_t)ceil(mNativeRect.Width() * mScale.xScale + 1), + (int32_t)ceil(mNativeRect.Height() * mScale.yScale + 1)); + } + } + } + + if (mRenderState == RENDER_STATE_NATIVE_DRAWING) { + // we can just do native drawing directly to the context's surface + + // do we need to use SetWorldTransform? + if (mTransformType != TRANSLATION_ONLY) { + SetGraphicsMode(mDC, GM_ADVANCED); + GetWorldTransform(mDC, &mOldWorldTransform); + SetWorldTransform(mDC, &mWorldTransform); + } + GetViewportOrgEx(mDC, &mOrigViewportOrigin); + SetViewportOrgEx(mDC, mOrigViewportOrigin.x - (int)mDeviceOffset.x, + mOrigViewportOrigin.y - (int)mDeviceOffset.y, nullptr); + + return mDC; + } else if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_BLACK || + mRenderState == RENDER_STATE_ALPHA_RECOVERY_WHITE) { + // we're going to use mWinSurface to create our temporary surface here + + // get us a RGB24 DIB; DIB is important, because + // we can later call GetImageSurface on it. + mWinSurface = new gfxWindowsSurface(mTempSurfaceSize); + mDC = mWinSurface->GetDC(); + + RECT r = {0, 0, mTempSurfaceSize.width, mTempSurfaceSize.height}; + if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_BLACK) + FillRect(mDC, &r, (HBRUSH)GetStockObject(BLACK_BRUSH)); + else + FillRect(mDC, &r, (HBRUSH)GetStockObject(WHITE_BRUSH)); + + if ((mTransformType != TRANSLATION_ONLY) && + (mNativeDrawFlags & CAN_AXIS_ALIGNED_SCALE)) { + SetGraphicsMode(mDC, GM_ADVANCED); + SetWorldTransform(mDC, &mWorldTransform); + } + + return mDC; + } else { + NS_ERROR("Bogus render state!"); + return nullptr; + } +} + +bool gfxWindowsNativeDrawing::ShouldRenderAgain() { + switch (mRenderState) { + case RENDER_STATE_NATIVE_DRAWING_DONE: + return false; + + case RENDER_STATE_ALPHA_RECOVERY_BLACK_DONE: + mRenderState = RENDER_STATE_ALPHA_RECOVERY_WHITE; + return true; + + case RENDER_STATE_ALPHA_RECOVERY_WHITE_DONE: + return false; + + default: + NS_ERROR( + "Invalid RenderState in gfxWindowsNativeDrawing::ShouldRenderAgain"); + break; + } + + return false; +} + +void gfxWindowsNativeDrawing::EndNativeDrawing() { + if (mRenderState == RENDER_STATE_NATIVE_DRAWING) { + // we drew directly to the HDC in the context; undo our changes + SetViewportOrgEx(mDC, mOrigViewportOrigin.x, mOrigViewportOrigin.y, + nullptr); + + if (mTransformType != TRANSLATION_ONLY) + SetWorldTransform(mDC, &mOldWorldTransform); + + mWinSurface->MarkDirty(); + + mRenderState = RENDER_STATE_NATIVE_DRAWING_DONE; + } else if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_BLACK) { + mBlackSurface = mWinSurface; + mWinSurface = nullptr; + + mRenderState = RENDER_STATE_ALPHA_RECOVERY_BLACK_DONE; + } else if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_WHITE) { + mWhiteSurface = mWinSurface; + mWinSurface = nullptr; + + mRenderState = RENDER_STATE_ALPHA_RECOVERY_WHITE_DONE; + } else { + NS_ERROR( + "Invalid RenderState in gfxWindowsNativeDrawing::EndNativeDrawing"); + } +} + +void gfxWindowsNativeDrawing::PaintToContext() { + if (mRenderState == RENDER_STATE_NATIVE_DRAWING_DONE) { + // nothing to do, it already went to the context + mRenderState = RENDER_STATE_DONE; + } else if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_WHITE_DONE) { + RefPtr<gfxImageSurface> black = mBlackSurface->GetAsImageSurface(); + RefPtr<gfxImageSurface> white = mWhiteSurface->GetAsImageSurface(); + if (!gfxAlphaRecovery::RecoverAlpha(black, white)) { + NS_ERROR("Alpha recovery failure"); + return; + } + RefPtr<DataSourceSurface> source = Factory::CreateWrappingDataSourceSurface( + black->Data(), black->Stride(), black->GetSize(), + SurfaceFormat::B8G8R8A8); + { + DrawTarget* dt = mContext->GetDrawTarget(); + AutoRestoreTransform autoRestoreTransform(dt); + + Matrix newTransform = dt->GetTransform(); + newTransform.PreTranslate(ToPoint(mNativeRect.TopLeft())); + dt->SetTransform(newTransform); + + Rect rect(Point(0.0, 0.0), ToSize(mNativeRect.Size())); + Matrix m = Matrix::Scaling(1.0 / mScale.xScale, 1.0 / mScale.yScale); + SurfacePattern pat(source, ExtendMode::CLAMP, m); + dt->FillRect(rect, pat); + } + + mRenderState = RENDER_STATE_DONE; + } else { + NS_ERROR("Invalid RenderState in gfxWindowsNativeDrawing::PaintToContext"); + } +} + +void gfxWindowsNativeDrawing::TransformToNativeRect(const gfxRect& r, + RECT& rout) { + /* If we're doing native drawing, then we're still in the coordinate space + * of the context; otherwise, we're in our own little world, + * relative to the passed-in nativeRect. + */ + + gfxRect roundedRect(r); + + if (mRenderState == RENDER_STATE_NATIVE_DRAWING) { + if (mTransformType == TRANSLATION_ONLY) { + roundedRect.MoveBy(mTranslation); + } + } else { + roundedRect.MoveBy(-mNativeRect.TopLeft()); + } + + roundedRect.Round(); + + rout.left = LONG(roundedRect.X()); + rout.right = LONG(roundedRect.XMost()); + rout.top = LONG(roundedRect.Y()); + rout.bottom = LONG(roundedRect.YMost()); +} diff --git a/gfx/thebes/gfxWindowsNativeDrawing.h b/gfx/thebes/gfxWindowsNativeDrawing.h new file mode 100644 index 0000000000..87323966d9 --- /dev/null +++ b/gfx/thebes/gfxWindowsNativeDrawing.h @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _GFXWINDOWSNATIVEDRAWING_H_ +#define _GFXWINDOWSNATIVEDRAWING_H_ + +#include <windows.h> + +#include "gfxContext.h" +#include "gfxWindowsSurface.h" + +class MOZ_STACK_CLASS gfxWindowsNativeDrawing { + public: + /* Flags for notifying this class what kind of operations the native + * drawing supports + */ + + enum { + /* Whether the native drawing can draw to a surface of content COLOR_ALPHA + */ + CAN_DRAW_TO_COLOR_ALPHA = 1 << 0, + CANNOT_DRAW_TO_COLOR_ALPHA = 0 << 0, + + /* Whether the native drawing can be scaled using SetWorldTransform */ + CAN_AXIS_ALIGNED_SCALE = 1 << 1, + CANNOT_AXIS_ALIGNED_SCALE = 0 << 1, + + /* Whether the native drawing can be both scaled and rotated arbitrarily + using SetWorldTransform */ + CAN_COMPLEX_TRANSFORM = 1 << 2, + CANNOT_COMPLEX_TRANSFORM = 0 << 2, + }; + + /* Create native win32 drawing for a rectangle bounded by + * nativeRect. + * + * Typical usage looks like: + * + * gfxWindowsNativeDrawing nativeDraw(ctx, destGfxRect, capabilities); + * do { + * HDC dc = nativeDraw.BeginNativeDrawing(); + * if (!dc) + * return NS_ERROR_FAILURE; + * + * RECT winRect; + * nativeDraw.TransformToNativeRect(rect, winRect); + * + * ... call win32 operations on HDC to draw to winRect ... + * + * nativeDraw.EndNativeDrawing(); + * } while (nativeDraw.ShouldRenderAgain()); + * nativeDraw.PaintToContext(); + */ + gfxWindowsNativeDrawing( + gfxContext* ctx, const gfxRect& nativeRect, + uint32_t nativeDrawFlags = CANNOT_DRAW_TO_COLOR_ALPHA | + CANNOT_AXIS_ALIGNED_SCALE | + CANNOT_COMPLEX_TRANSFORM); + + /* Returns a HDC which may be used for native drawing. This HDC is valid + * until EndNativeDrawing is called; if it is used for drawing after that + * time, the result is undefined. */ + HDC BeginNativeDrawing(); + + /* Transform the native rect into something valid for rendering + * to the HDC. This may or may not change RECT, depending on + * whether SetWorldTransform is used or not. */ + void TransformToNativeRect(const gfxRect& r, RECT& rout); + + /* Marks the end of native drawing */ + void EndNativeDrawing(); + + /* Returns true if the native drawing should be executed again */ + bool ShouldRenderAgain(); + + /* Places the result to the context, if necessary */ + void PaintToContext(); + + private: + gfxContext* mContext; + gfxRect mNativeRect; + uint32_t mNativeDrawFlags; + + // what state the rendering is in + uint8_t mRenderState; + + mozilla::gfx::Point mDeviceOffset; + RefPtr<gfxPattern> mBlackPattern, mWhitePattern; + + enum TransformType { TRANSLATION_ONLY, AXIS_ALIGNED_SCALE, COMPLEX }; + + TransformType mTransformType; + gfxPoint mTranslation; + mozilla::gfx::MatrixScalesDouble mScale; + XFORM mWorldTransform; + + // saved state + RefPtr<gfxWindowsSurface> mWinSurface, mBlackSurface, mWhiteSurface; + HDC mDC; + XFORM mOldWorldTransform; + POINT mOrigViewportOrigin; + mozilla::gfx::IntSize mTempSurfaceSize; +}; + +#endif diff --git a/gfx/thebes/gfxWindowsPlatform.cpp b/gfx/thebes/gfxWindowsPlatform.cpp new file mode 100644 index 0000000000..e6994f8665 --- /dev/null +++ b/gfx/thebes/gfxWindowsPlatform.cpp @@ -0,0 +1,1839 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#define INITGUID // set before devguid.h + +#include "gfxWindowsPlatform.h" + +#include "cairo.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/layers/CompositorBridgeChild.h" + +#include "gfxBlur.h" +#include "gfxImageSurface.h" +#include "gfxWindowsSurface.h" + +#include "nsUnicharUtils.h" +#include "nsUnicodeProperties.h" + +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerThreadSleep.h" +#include "mozilla/Components.h" +#include "mozilla/Sprintf.h" +#include "mozilla/WindowsVersion.h" +#include "nsIGfxInfo.h" +#include "nsServiceManagerUtils.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" +#include "mozilla/Telemetry.h" + +#include "plbase64.h" +#include "nsIXULRuntime.h" +#include "imgLoader.h" + +#include "nsIGfxInfo.h" + +#include "gfxCrashReporterUtils.h" + +#include "gfxGDIFontList.h" +#include "gfxGDIFont.h" + +#include "mozilla/layers/CanvasChild.h" +#include "mozilla/layers/CompositorThread.h" + +#include "gfxDWriteFontList.h" +#include "gfxDWriteFonts.h" +#include "gfxDWriteCommon.h" +#include <dwrite.h> + +#include "gfxTextRun.h" +#include "gfxUserFontSet.h" +#include "nsWindowsHelpers.h" +#include "gfx2DGlue.h" + +#include <string> + +#include <d3d10_1.h> + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/gfxVars.h" + +#include <dwmapi.h> +#include <d3d11.h> +#include <d2d1_1.h> + +#include "nsIMemoryReporter.h" +#include <winternl.h> +#include "d3dkmtQueryStatistics.h" + +#include "base/thread.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layers.h" +#include "gfxConfig.h" +#include "VsyncSource.h" +#include "DriverCrashGuard.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/gfx/DeviceManagerDx.h" +#include "mozilla/gfx/DisplayConfigWindows.h" +#include "mozilla/layers/DeviceAttachmentsD3D11.h" +#include "mozilla/WindowsProcessMitigations.h" +#include "D3D11Checks.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::layers; +using namespace mozilla::widget; +using namespace mozilla::image; +using namespace mozilla::unicode; + +DCForMetrics::DCForMetrics() { + // Get the whole screen DC: + mDC = GetDC(nullptr); + SetGraphicsMode(mDC, GM_ADVANCED); +} + +class GfxD2DVramReporter final : public nsIMemoryReporter { + ~GfxD2DVramReporter() {} + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + MOZ_COLLECT_REPORT("gfx-d2d-vram-draw-target", KIND_OTHER, UNITS_BYTES, + Factory::GetD2DVRAMUsageDrawTarget(), + "Video memory used by D2D DrawTargets."); + + MOZ_COLLECT_REPORT("gfx-d2d-vram-source-surface", KIND_OTHER, UNITS_BYTES, + Factory::GetD2DVRAMUsageSourceSurface(), + "Video memory used by D2D SourceSurfaces."); + + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(GfxD2DVramReporter, nsIMemoryReporter) + +class GPUAdapterReporter final : public nsIMemoryReporter { + // Callers must Release the DXGIAdapter after use or risk mem-leak + static bool GetDXGIAdapter(IDXGIAdapter** aDXGIAdapter) { + RefPtr<ID3D11Device> d3d11Device; + RefPtr<IDXGIDevice> dxgiDevice; + bool result = false; + + if ((d3d11Device = mozilla::gfx::Factory::GetDirect3D11Device())) { + if (d3d11Device->QueryInterface(__uuidof(IDXGIDevice), + getter_AddRefs(dxgiDevice)) == S_OK) { + result = (dxgiDevice->GetAdapter(aDXGIAdapter) == S_OK); + } + } + + return result; + } + + ~GPUAdapterReporter() {} + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD + CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) override { + HANDLE ProcessHandle = GetCurrentProcess(); + + int64_t dedicatedBytesUsed = 0; + int64_t sharedBytesUsed = 0; + int64_t committedBytesUsed = 0; + IDXGIAdapter* DXGIAdapter; + + HMODULE gdi32Handle; + PFND3DKMTQS queryD3DKMTStatistics = nullptr; + + if ((gdi32Handle = LoadLibrary(TEXT("gdi32.dll")))) + queryD3DKMTStatistics = + (PFND3DKMTQS)GetProcAddress(gdi32Handle, "D3DKMTQueryStatistics"); + + if (queryD3DKMTStatistics && GetDXGIAdapter(&DXGIAdapter)) { + // Most of this block is understood thanks to wj32's work on Process + // Hacker + + DXGI_ADAPTER_DESC adapterDesc; + D3DKMTQS queryStatistics; + + DXGIAdapter->GetDesc(&adapterDesc); + DXGIAdapter->Release(); + + memset(&queryStatistics, 0, sizeof(D3DKMTQS)); + queryStatistics.Type = D3DKMTQS_PROCESS; + queryStatistics.AdapterLuid = adapterDesc.AdapterLuid; + queryStatistics.hProcess = ProcessHandle; + if (NT_SUCCESS(queryD3DKMTStatistics(&queryStatistics))) { + committedBytesUsed = + queryStatistics.QueryResult.ProcessInfo.SystemMemory.BytesAllocated; + } + + memset(&queryStatistics, 0, sizeof(D3DKMTQS)); + queryStatistics.Type = D3DKMTQS_ADAPTER; + queryStatistics.AdapterLuid = adapterDesc.AdapterLuid; + if (NT_SUCCESS(queryD3DKMTStatistics(&queryStatistics))) { + ULONG i; + ULONG segmentCount = queryStatistics.QueryResult.AdapterInfo.NbSegments; + + for (i = 0; i < segmentCount; i++) { + memset(&queryStatistics, 0, sizeof(D3DKMTQS)); + queryStatistics.Type = D3DKMTQS_SEGMENT; + queryStatistics.AdapterLuid = adapterDesc.AdapterLuid; + queryStatistics.QuerySegment.SegmentId = i; + + if (NT_SUCCESS(queryD3DKMTStatistics(&queryStatistics))) { + bool aperture = queryStatistics.QueryResult.SegmentInfo.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 = + queryStatistics.QueryResult.ProcessSegmentInfo.BytesCommitted; + if (aperture) + sharedBytesUsed += bytesCommitted; + else + dedicatedBytesUsed += bytesCommitted; + } + } + } + } + } + + FreeLibrary(gdi32Handle); + + MOZ_COLLECT_REPORT("gpu-committed", KIND_OTHER, UNITS_BYTES, + committedBytesUsed, + "Memory committed by the Windows graphics system."); + + MOZ_COLLECT_REPORT( + "gpu-dedicated", KIND_OTHER, UNITS_BYTES, dedicatedBytesUsed, + "Out-of-process memory allocated for this process in a physical " + "GPU adapter's memory."); + + MOZ_COLLECT_REPORT("gpu-shared", KIND_OTHER, UNITS_BYTES, sharedBytesUsed, + "In-process memory that is shared with the GPU."); + + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(GPUAdapterReporter, nsIMemoryReporter) + +Atomic<size_t> gfxWindowsPlatform::sD3D11SharedTextures; +Atomic<size_t> gfxWindowsPlatform::sD3D9SharedTextures; + +class D3DSharedTexturesReporter final : public nsIMemoryReporter { + ~D3DSharedTexturesReporter() {} + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + if (gfxWindowsPlatform::sD3D11SharedTextures > 0) { + MOZ_COLLECT_REPORT("d3d11-shared-textures", KIND_OTHER, UNITS_BYTES, + gfxWindowsPlatform::sD3D11SharedTextures, + "D3D11 shared textures."); + } + + if (gfxWindowsPlatform::sD3D9SharedTextures > 0) { + MOZ_COLLECT_REPORT("d3d9-shared-textures", KIND_OTHER, UNITS_BYTES, + gfxWindowsPlatform::sD3D9SharedTextures, + "D3D9 shared textures."); + } + + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(D3DSharedTexturesReporter, nsIMemoryReporter) + +gfxWindowsPlatform::gfxWindowsPlatform() + : mRenderMode(RENDER_GDI), mSupportsHDR(false) { + // If win32k is locked down then we can't use COM STA and shouldn't need it. + // Also, we won't be using any GPU memory in this process. + if (!IsWin32kLockedDown()) { + /* + * Initialize COM + */ + CoInitialize(nullptr); + + RegisterStrongMemoryReporter(new GfxD2DVramReporter()); + RegisterStrongMemoryReporter(new GPUAdapterReporter()); + RegisterStrongMemoryReporter(new D3DSharedTexturesReporter()); + } +} + +gfxWindowsPlatform::~gfxWindowsPlatform() { + mozilla::gfx::Factory::D2DCleanup(); + + DeviceManagerDx::Shutdown(); + + // We don't initialize COM when win32k is locked down. + if (!IsWin32kLockedDown()) { + /* + * Uninitialize COM + */ + CoUninitialize(); + } +} + +/* static */ +void gfxWindowsPlatform::InitMemoryReportersForGPUProcess() { + MOZ_RELEASE_ASSERT(XRE_IsGPUProcess()); + + RegisterStrongMemoryReporter(new GfxD2DVramReporter()); + RegisterStrongMemoryReporter(new GPUAdapterReporter()); + RegisterStrongMemoryReporter(new D3DSharedTexturesReporter()); +} + +/* static */ +nsresult gfxWindowsPlatform::GetGpuTimeSinceProcessStartInMs( + uint64_t* aResult) { + // If win32k is locked down then we should not have any GPU processing and + // cannot use these APIs either way. + if (IsWin32kLockedDown()) { + *aResult = 0; + return NS_OK; + } + + nsModuleHandle module(LoadLibrary(L"gdi32.dll")); + if (!module) { + return NS_ERROR_NOT_AVAILABLE; + } + + PFND3DKMTQS queryD3DKMTStatistics = + (PFND3DKMTQS)GetProcAddress(module, "D3DKMTQueryStatistics"); + if (!queryD3DKMTStatistics) { + return NS_ERROR_NOT_AVAILABLE; + } + + gfx::DeviceManagerDx* dm = DeviceManagerDx::Get(); + if (!dm) { + return NS_ERROR_NOT_AVAILABLE; + } + + D3D11DeviceStatus status; + if (!dm->ExportDeviceInfo(&status)) { + // Assume that we used 0ms of GPU time if the device manager + // doesn't know the device status. + *aResult = 0; + return NS_OK; + } + + const DxgiAdapterDesc& adapterDesc = status.adapter(); + + D3DKMTQS queryStatistics; + memset(&queryStatistics, 0, sizeof(D3DKMTQS)); + queryStatistics.Type = D3DKMTQS_ADAPTER; + queryStatistics.AdapterLuid = adapterDesc.AdapterLuid; + if (!NT_SUCCESS(queryD3DKMTStatistics(&queryStatistics))) { + return NS_ERROR_FAILURE; + } + + uint64_t result = 0; + ULONG nodeCount = queryStatistics.QueryResult.AdapterInfo.NodeCount; + for (ULONG i = 0; i < nodeCount; ++i) { + memset(&queryStatistics, 0, sizeof(D3DKMTQS)); + queryStatistics.Type = D3DKMTQS_PROCESS_NODE; + queryStatistics.AdapterLuid = adapterDesc.AdapterLuid; + queryStatistics.hProcess = GetCurrentProcess(); + queryStatistics.QueryProcessNode.NodeId = i; + if (NT_SUCCESS(queryD3DKMTStatistics(&queryStatistics))) { + result += queryStatistics.QueryResult.ProcessNodeInformation.RunningTime + .QuadPart * + 100 / PR_NSEC_PER_MSEC; + } + } + + *aResult = result; + return NS_OK; +} + +static void UpdateANGLEConfig() { + if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + gfxConfig::Disable(Feature::D3D11_HW_ANGLE, FeatureStatus::Disabled, + "D3D11 compositing is disabled", + "FEATURE_FAILURE_HW_ANGLE_D3D11_DISABLED"_ns); + } +} + +void gfxWindowsPlatform::InitAcceleration() { + gfxPlatform::InitAcceleration(); + + DeviceManagerDx::Init(); + + // Content processes should have received content device data from parent. + MOZ_ASSERT_IF(XRE_IsContentProcess(), GetInitContentDeviceData()); + + 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); + + // CanUseHardwareVideoDecoding depends on DeviceManagerDx state, + // so update the cached value now. + UpdateCanUseHardwareVideoDecoding(); + UpdateSupportsHDR(); + + RecordStartupTelemetry(); +} + +void gfxWindowsPlatform::InitWebRenderConfig() { + gfxPlatform::InitWebRenderConfig(); + UpdateBackendPrefs(); +} + +bool gfxWindowsPlatform::CanUseHardwareVideoDecoding() { + DeviceManagerDx* dm = DeviceManagerDx::Get(); + if (!dm) { + return false; + } + if (!dm->TextureSharingWorks()) { + return false; + } + return !dm->IsWARP() && gfxPlatform::CanUseHardwareVideoDecoding(); +} + +bool gfxWindowsPlatform::InitDWriteSupport() { + mozilla::ScopedGfxFeatureReporter reporter("DWrite"); + if (!gfxDWriteFont::InitDWriteSupport()) { + return false; + } + + reporter.SetSuccessful(); + return true; +} + +bool gfxWindowsPlatform::HandleDeviceReset() { + DeviceResetReason resetReason = DeviceResetReason::OK; + if (!DidRenderingDeviceReset(&resetReason)) { + return false; + } + + if (resetReason != DeviceResetReason::FORCED_RESET) { + Telemetry::Accumulate(Telemetry::DEVICE_RESET_REASON, + uint32_t(resetReason)); + } + + // Remove devices and adapters. + DeviceManagerDx::Get()->ResetDevices(); + + imgLoader::NormalLoader()->ClearCache(true); + imgLoader::NormalLoader()->ClearCache(false); + imgLoader::PrivateBrowsingLoader()->ClearCache(true); + imgLoader::PrivateBrowsingLoader()->ClearCache(false); + gfxAlphaBoxBlur::ShutdownBlurCache(); + + gfxConfig::Reset(Feature::D3D11_COMPOSITING); + gfxConfig::Reset(Feature::D3D11_HW_ANGLE); + gfxConfig::Reset(Feature::DIRECT2D); + + InitializeConfig(); + // XXX Add InitWebRenderConfig() calling. + if (mInitializedDevices) { + InitGPUProcessSupport(); + InitializeDevices(); + } + UpdateANGLEConfig(); + return true; +} + +BackendPrefsData gfxWindowsPlatform::GetBackendPrefs() const { + BackendPrefsData data; + + data.mCanvasBitmask = BackendTypeBit(BackendType::SKIA); + data.mContentBitmask = BackendTypeBit(BackendType::SKIA); + data.mCanvasDefault = BackendType::SKIA; + data.mContentDefault = BackendType::SKIA; + + if (gfxConfig::IsEnabled(Feature::DIRECT2D)) { + data.mCanvasBitmask |= BackendTypeBit(BackendType::DIRECT2D1_1); + data.mCanvasDefault = BackendType::DIRECT2D1_1; + } + return data; +} + +void gfxWindowsPlatform::UpdateBackendPrefs() { + BackendPrefsData data = GetBackendPrefs(); + // Remove DIRECT2D1 preference if D2D1Device does not exist. + if (!Factory::HasD2D1Device()) { + data.mContentBitmask &= ~BackendTypeBit(BackendType::DIRECT2D1_1); + if (data.mContentDefault == BackendType::DIRECT2D1_1) { + data.mContentDefault = BackendType::SKIA; + } + + // Don't exclude DIRECT2D1_1 if using remote canvas, because DIRECT2D1_1 and + // hence the device will be used in the GPU process. + if (!gfxPlatform::UseRemoteCanvas()) { + data.mCanvasBitmask &= ~BackendTypeBit(BackendType::DIRECT2D1_1); + if (data.mCanvasDefault == BackendType::DIRECT2D1_1) { + data.mCanvasDefault = BackendType::SKIA; + } + } + } + InitBackendPrefs(std::move(data)); +} + +bool gfxWindowsPlatform::IsDirect2DBackend() { + return GetDefaultContentBackend() == BackendType::DIRECT2D1_1; +} + +void gfxWindowsPlatform::UpdateRenderMode() { + bool didReset = HandleDeviceReset(); + + UpdateBackendPrefs(); + + if (didReset) { + mScreenReferenceDrawTarget = CreateOffscreenContentDrawTarget( + IntSize(1, 1), SurfaceFormat::B8G8R8A8); + if (!mScreenReferenceDrawTarget) { + gfxCriticalNote + << "Failed to update reference draw target after device reset" + << ", D3D11 device:" << hexa(Factory::GetDirect3D11Device().get()) + << ", D3D11 status:" + << FeatureStatusToString( + gfxConfig::GetValue(Feature::D3D11_COMPOSITING)) + << ", D2D1 device:" << hexa(Factory::GetD2D1Device().get()) + << ", D2D1 status:" + << FeatureStatusToString(gfxConfig::GetValue(Feature::DIRECT2D)) + << ", content:" << int(GetDefaultContentBackend()) + << ", compositor:" << int(GetCompositorBackend()); + MOZ_CRASH( + "GFX: Failed to update reference draw target after device reset"); + } + } +} + +void gfxWindowsPlatform::UpdateSupportsHDR() { + // TODO: This function crashes content processes, for reasons that are not + // obvious from the crash reports. For now, this function can only be executed + // by the parent process. Therefore SupportsHDR() will always return false for + // content processes, as noted in the header. + if (!XRE_IsParentProcess()) { + return; + } + + // Set mSupportsHDR to true if any of the DeviceManager outputs have both: + // 1) greater than 8-bit color + // 2) a colorspace that uses BT2020 + DeviceManagerDx* dx = DeviceManagerDx::Get(); + nsTArray<DXGI_OUTPUT_DESC1> outputs = dx->EnumerateOutputs(); + + for (auto& output : outputs) { + if (output.BitsPerColor <= 8) { + continue; + } + + switch (output.ColorSpace) { + case DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P2020: + case DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020: + case DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020: + case DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020: + case DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020: + case DXGI_COLOR_SPACE_RGB_STUDIO_G2084_NONE_P2020: + case DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_TOPLEFT_P2020: + case DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_TOPLEFT_P2020: + case DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020: + case DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020: + case DXGI_COLOR_SPACE_YCBCR_FULL_GHLG_TOPLEFT_P2020: +#ifndef __MINGW32__ + // Windows MinGW has an older dxgicommon.h that doesn't define + // these enums. We'd like to define them ourselves in that case, + // but there's no compilable way to add new enums to an existing + // enum type. So instead we just don't check for these values. + case DXGI_COLOR_SPACE_RGB_STUDIO_G24_NONE_P2020: + case DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_LEFT_P2020: + case DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_TOPLEFT_P2020: +#endif + mSupportsHDR = true; + return; + default: + break; + } + } + + mSupportsHDR = false; +} + +mozilla::gfx::BackendType gfxWindowsPlatform::GetContentBackendFor( + mozilla::layers::LayersBackend aLayers) { + mozilla::gfx::BackendType defaultBackend = + gfxPlatform::GetDefaultContentBackend(); + if (aLayers == LayersBackend::LAYERS_WR && + gfx::gfxVars::UseWebRenderANGLE()) { + return defaultBackend; + } + + if (defaultBackend == BackendType::DIRECT2D1_1) { + // We can't have D2D without D3D11 layers, so fallback to Skia. + return BackendType::SKIA; + } + + // Otherwise we have some non-accelerated backend and that's ok. + return defaultBackend; +} + +mozilla::gfx::BackendType gfxWindowsPlatform::GetPreferredCanvasBackend() { + mozilla::gfx::BackendType backend = gfxPlatform::GetPreferredCanvasBackend(); + + if (backend == BackendType::DIRECT2D1_1) { + if (!gfx::gfxVars::UseWebRenderANGLE()) { + // We can't have D2D without ANGLE when WebRender is enabled, so fallback + // to Skia. + return BackendType::SKIA; + } + + // Fall back to software when remote canvas has been deactivated. + if (CanvasChild::Deactivated()) { + return BackendType::SKIA; + } + } + return backend; +} + +bool gfxWindowsPlatform::CreatePlatformFontList() { + if (DWriteEnabled()) { + if (gfxPlatformFontList::Initialize(new gfxDWriteFontList)) { + return true; + } + + // DWrite font initialization failed! Don't know why this would happen, + // but apparently it can - see bug 594865. + // So we're going to fall back to GDI fonts & rendering. + DisableD2D(FeatureStatus::Failed, "Failed to initialize fonts", + "FEATURE_FAILURE_FONT_FAIL"_ns); + } + + // Make sure the static variable is initialized... + gfxPlatform::HasVariationFontSupport(); + // ...then force it to false, even if the Windows version was recent enough + // to permit it, as we're using GDI fonts. + sHasVariationFontSupport = false; + + return gfxPlatformFontList::Initialize(new gfxGDIFontList); +} + +// This function will permanently disable D2D for the session. It's intended to +// be used when, after initially chosing to use Direct2D, we encounter a +// scenario we can't support. +// +// This is called during gfxPlatform::Init() so at this point there should be no +// DrawTargetD2D/1 instances. +void gfxWindowsPlatform::DisableD2D(FeatureStatus aStatus, const char* aMessage, + const nsACString& aFailureId) { + gfxConfig::SetFailed(Feature::DIRECT2D, aStatus, aMessage, aFailureId); + Factory::SetDirect3D11Device(nullptr); + UpdateBackendPrefs(); +} + +already_AddRefed<gfxASurface> gfxWindowsPlatform::CreateOffscreenSurface( + const IntSize& aSize, gfxImageFormat aFormat) { + if (!Factory::AllowedSurfaceSize(aSize)) { + return nullptr; + } + + RefPtr<gfxASurface> surf = nullptr; + +#ifdef CAIRO_HAS_WIN32_SURFACE + if (!XRE_IsContentProcess()) { + if (mRenderMode == RENDER_GDI || mRenderMode == RENDER_DIRECT2D) { + surf = new gfxWindowsSurface(aSize, aFormat); + } + } +#endif + + if (!surf || surf->CairoStatus()) { + surf = new gfxImageSurface(aSize, aFormat); + } + + return surf.forget(); +} + +static const char kFontAparajita[] = "Aparajita"; +static const char kFontArabicTypesetting[] = "Arabic Typesetting"; +static const char kFontArial[] = "Arial"; +static const char kFontArialUnicodeMS[] = "Arial Unicode MS"; +static const char kFontCambria[] = "Cambria"; +static const char kFontCambriaMath[] = "Cambria Math"; +static const char kFontEbrima[] = "Ebrima"; +static const char kFontEstrangeloEdessa[] = "Estrangelo Edessa"; +static const char kFontEuphemia[] = "Euphemia"; +static const char kFontGabriola[] = "Gabriola"; +static const char kFontJavaneseText[] = "Javanese Text"; +static const char kFontKhmerUI[] = "Khmer UI"; +static const char kFontLaoUI[] = "Lao UI"; +static const char kFontLeelawadeeUI[] = "Leelawadee UI"; +static const char kFontLucidaSansUnicode[] = "Lucida Sans Unicode"; +static const char kFontMVBoli[] = "MV Boli"; +static const char kFontMalgunGothic[] = "Malgun Gothic"; +static const char kFontMicrosoftJhengHei[] = "Microsoft JhengHei"; +static const char kFontMicrosoftNewTaiLue[] = "Microsoft New Tai Lue"; +static const char kFontMicrosoftPhagsPa[] = "Microsoft PhagsPa"; +static const char kFontMicrosoftTaiLe[] = "Microsoft Tai Le"; +static const char kFontMicrosoftUighur[] = "Microsoft Uighur"; +static const char kFontMicrosoftYaHei[] = "Microsoft YaHei"; +static const char kFontMicrosoftYiBaiti[] = "Microsoft Yi Baiti"; +static const char kFontMeiryo[] = "Meiryo"; +static const char kFontMongolianBaiti[] = "Mongolian Baiti"; +static const char kFontMyanmarText[] = "Myanmar Text"; +static const char kFontNirmalaUI[] = "Nirmala UI"; +static const char kFontNyala[] = "Nyala"; +static const char kFontPlantagenetCherokee[] = "Plantagenet Cherokee"; +static const char kFontSegoeUI[] = "Segoe UI"; +static const char kFontSegoeUIEmoji[] = "Segoe UI Emoji"; +static const char kFontSegoeUISymbol[] = "Segoe UI Symbol"; +static const char kFontSylfaen[] = "Sylfaen"; +static const char kFontTraditionalArabic[] = "Traditional Arabic"; +static const char kFontTwemojiMozilla[] = "Twemoji Mozilla"; +static const char kFontUtsaah[] = "Utsaah"; +static const char kFontYuGothic[] = "Yu Gothic"; + +void gfxWindowsPlatform::GetCommonFallbackFonts( + uint32_t aCh, Script aRunScript, eFontPresentation aPresentation, + nsTArray<const char*>& aFontList) { + if (PrefersColor(aPresentation)) { + aFontList.AppendElement(kFontSegoeUIEmoji); + aFontList.AppendElement(kFontTwemojiMozilla); + } + + // Arial is used as the default fallback for system fallback + aFontList.AppendElement(kFontArial); + + if (!IS_IN_BMP(aCh)) { + uint32_t p = aCh >> 16; + if (p == 1) { // SMP plane + aFontList.AppendElement(kFontSegoeUISymbol); + aFontList.AppendElement(kFontEbrima); + aFontList.AppendElement(kFontNirmalaUI); + aFontList.AppendElement(kFontCambriaMath); + } + } else { + uint32_t b = (aCh >> 8) & 0xff; + + switch (b) { + case 0x05: + aFontList.AppendElement(kFontEstrangeloEdessa); + aFontList.AppendElement(kFontCambria); + break; + case 0x06: + aFontList.AppendElement(kFontMicrosoftUighur); + break; + case 0x07: + aFontList.AppendElement(kFontEstrangeloEdessa); + aFontList.AppendElement(kFontMVBoli); + aFontList.AppendElement(kFontEbrima); + break; + case 0x09: + aFontList.AppendElement(kFontNirmalaUI); + aFontList.AppendElement(kFontUtsaah); + aFontList.AppendElement(kFontAparajita); + break; + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + aFontList.AppendElement(kFontNirmalaUI); + break; + case 0x0e: + aFontList.AppendElement(kFontLaoUI); + aFontList.AppendElement(kFontLeelawadeeUI); + break; + case 0x10: + aFontList.AppendElement(kFontMyanmarText); + break; + case 0x11: + aFontList.AppendElement(kFontMalgunGothic); + break; + case 0x12: + case 0x13: + aFontList.AppendElement(kFontNyala); + aFontList.AppendElement(kFontPlantagenetCherokee); + break; + case 0x14: + case 0x15: + case 0x16: + aFontList.AppendElement(kFontEuphemia); + aFontList.AppendElement(kFontSegoeUISymbol); + break; + case 0x17: + aFontList.AppendElement(kFontKhmerUI); + aFontList.AppendElement(kFontLeelawadeeUI); + break; + case 0x18: // Mongolian + aFontList.AppendElement(kFontMongolianBaiti); + aFontList.AppendElement(kFontEuphemia); + break; + case 0x19: + aFontList.AppendElement(kFontMicrosoftTaiLe); + aFontList.AppendElement(kFontMicrosoftNewTaiLue); + aFontList.AppendElement(kFontKhmerUI); + aFontList.AppendElement(kFontLeelawadeeUI); + break; + case 0x1a: + aFontList.AppendElement(kFontLeelawadeeUI); + break; + case 0x1c: + aFontList.AppendElement(kFontNirmalaUI); + break; + case 0x20: // Symbol ranges + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x29: + case 0x2a: + case 0x2b: + case 0x2c: + aFontList.AppendElement(kFontSegoeUI); + aFontList.AppendElement(kFontSegoeUISymbol); + aFontList.AppendElement(kFontCambria); + aFontList.AppendElement(kFontMeiryo); + aFontList.AppendElement(kFontArial); + aFontList.AppendElement(kFontLucidaSansUnicode); + aFontList.AppendElement(kFontEbrima); + break; + case 0x2d: + case 0x2e: + case 0x2f: + aFontList.AppendElement(kFontEbrima); + aFontList.AppendElement(kFontNyala); + aFontList.AppendElement(kFontSegoeUI); + aFontList.AppendElement(kFontSegoeUISymbol); + aFontList.AppendElement(kFontMeiryo); + break; + case 0x28: // Braille + aFontList.AppendElement(kFontSegoeUISymbol); + break; + case 0x30: + case 0x31: + aFontList.AppendElement(kFontMicrosoftYaHei); + break; + case 0x32: + aFontList.AppendElement(kFontMalgunGothic); + break; + case 0x4d: + aFontList.AppendElement(kFontSegoeUISymbol); + break; + case 0x9f: + aFontList.AppendElement(kFontMicrosoftYaHei); + aFontList.AppendElement(kFontYuGothic); + break; + case 0xa0: // Yi + case 0xa1: + case 0xa2: + case 0xa3: + case 0xa4: + aFontList.AppendElement(kFontMicrosoftYiBaiti); + aFontList.AppendElement(kFontSegoeUI); + break; + case 0xa5: + case 0xa6: + case 0xa7: + aFontList.AppendElement(kFontEbrima); + aFontList.AppendElement(kFontSegoeUI); + aFontList.AppendElement(kFontCambriaMath); + break; + case 0xa8: + aFontList.AppendElement(kFontMicrosoftPhagsPa); + aFontList.AppendElement(kFontNirmalaUI); + break; + case 0xa9: + aFontList.AppendElement(kFontMalgunGothic); + aFontList.AppendElement(kFontJavaneseText); + aFontList.AppendElement(kFontLeelawadeeUI); + break; + case 0xaa: + aFontList.AppendElement(kFontMyanmarText); + break; + case 0xab: + aFontList.AppendElement(kFontEbrima); + aFontList.AppendElement(kFontNyala); + break; + case 0xd7: + aFontList.AppendElement(kFontMalgunGothic); + break; + case 0xfb: + aFontList.AppendElement(kFontMicrosoftUighur); + aFontList.AppendElement(kFontGabriola); + aFontList.AppendElement(kFontSylfaen); + break; + case 0xfc: + case 0xfd: + aFontList.AppendElement(kFontTraditionalArabic); + aFontList.AppendElement(kFontArabicTypesetting); + break; + case 0xfe: + aFontList.AppendElement(kFontTraditionalArabic); + aFontList.AppendElement(kFontMicrosoftJhengHei); + break; + case 0xff: + aFontList.AppendElement(kFontMicrosoftJhengHei); + break; + default: + break; + } + } + + // Arial Unicode MS has lots of glyphs for obscure characters, + // use it as a last resort + aFontList.AppendElement(kFontArialUnicodeMS); + + // If we didn't begin with the color-emoji fonts, include them here + // so that they'll be preferred over user-installed (and possibly + // broken) fonts in the global fallback path. + if (!PrefersColor(aPresentation)) { + aFontList.AppendElement(kFontSegoeUIEmoji); + aFontList.AppendElement(kFontTwemojiMozilla); + } +} + +bool gfxWindowsPlatform::DidRenderingDeviceReset( + DeviceResetReason* aResetReason) { + DeviceManagerDx* dm = DeviceManagerDx::Get(); + if (!dm) { + return false; + } + return dm->HasDeviceReset(aResetReason); +} + +void gfxWindowsPlatform::CompositorUpdated() { + DeviceManagerDx::Get()->ForceDeviceReset( + ForcedDeviceResetReason::COMPOSITOR_UPDATED); + UpdateRenderMode(); +} + +BOOL CALLBACK InvalidateWindowForDeviceReset(HWND aWnd, LPARAM aMsg) { + RedrawWindow(aWnd, nullptr, nullptr, + RDW_INVALIDATE | RDW_INTERNALPAINT | RDW_FRAME); + return TRUE; +} + +void gfxWindowsPlatform::SchedulePaintIfDeviceReset() { + AUTO_PROFILER_LABEL("gfxWindowsPlatform::SchedulePaintIfDeviceReset", OTHER); + + DeviceResetReason resetReason = DeviceResetReason::OK; + if (!DidRenderingDeviceReset(&resetReason)) { + return; + } + + gfxCriticalNote << "(gfxWindowsPlatform) Detected device reset: " + << (int)resetReason; + + if (XRE_IsParentProcess()) { + // Trigger an ::OnPaint for each window. + ::EnumThreadWindows(GetCurrentThreadId(), InvalidateWindowForDeviceReset, + 0); + } else { + NS_DispatchToMainThread(NS_NewRunnableFunction( + "gfx::gfxWindowsPlatform::SchedulePaintIfDeviceReset", []() -> void { + gfxWindowsPlatform::GetPlatform()->CheckForContentOnlyDeviceReset(); + })); + } + + gfxCriticalNote << "(gfxWindowsPlatform) scheduled device update."; +} + +void gfxWindowsPlatform::CheckForContentOnlyDeviceReset() { + if (!DidRenderingDeviceReset()) { + return; + } + + bool isContentOnlyTDR; + D3D11DeviceStatus status; + + DeviceManagerDx::Get()->ExportDeviceInfo(&status); + CompositorBridgeChild::Get()->SendCheckContentOnlyTDR(status.sequenceNumber(), + &isContentOnlyTDR); + + // The parent process doesn't know about the reset yet, or the reset is + // local to our device. + if (isContentOnlyTDR) { + gfxCriticalNote << "A content-only TDR is detected."; + dom::ContentChild* cc = dom::ContentChild::GetSingleton(); + cc->RecvReinitRenderingForDeviceReset(); + } +} + +nsTArray<uint8_t> gfxWindowsPlatform::GetPlatformCMSOutputProfileData() { + if (XRE_IsContentProcess()) { + auto& cmsOutputProfileData = GetCMSOutputProfileData(); + // We should have set our profile data when we received our initial + // ContentDeviceData. + MOZ_ASSERT(cmsOutputProfileData.isSome(), + "Should have created output profile data when we received " + "initial content device data."); + + // If we have data, it should not be empty. + MOZ_ASSERT_IF(cmsOutputProfileData.isSome(), + !cmsOutputProfileData->IsEmpty()); + + if (cmsOutputProfileData.isSome()) { + return cmsOutputProfileData.ref().Clone(); + } + return nsTArray<uint8_t>(); + } + + return GetPlatformCMSOutputProfileData_Impl(); +} + +nsTArray<uint8_t> gfxWindowsPlatform::GetPlatformCMSOutputProfileData_Impl() { + static nsTArray<uint8_t> sCached = [&] { + // Check override pref first: + nsTArray<uint8_t> prefProfileData = + gfxPlatform::GetPrefCMSOutputProfileData(); + if (!prefProfileData.IsEmpty()) { + return prefProfileData; + } + + // - + // Otherwise, create a dummy DC and pull from that. + + HDC dc = ::GetDC(nullptr); + if (!dc) { + return nsTArray<uint8_t>(); + } + + WCHAR profilePath[MAX_PATH]; + DWORD profilePathLen = MAX_PATH; + + bool getProfileResult = ::GetICMProfileW(dc, &profilePathLen, profilePath); + + ::ReleaseDC(nullptr, dc); + + if (!getProfileResult) { + return nsTArray<uint8_t>(); + } + + void* mem = nullptr; + size_t size = 0; + + qcms_data_from_unicode_path(profilePath, &mem, &size); + if (!mem) { + return nsTArray<uint8_t>(); + } + + nsTArray<uint8_t> result; + result.AppendElements(static_cast<uint8_t*>(mem), size); + + free(mem); + + return result; + }(); + + return sCached.Clone(); +} + +void gfxWindowsPlatform::GetDLLVersion(char16ptr_t aDLLPath, + nsAString& aVersion) { + DWORD versInfoSize, vers[4] = {0}; + // version info not available case + aVersion.AssignLiteral(u"0.0.0.0"); + versInfoSize = GetFileVersionInfoSizeW(aDLLPath, nullptr); + AutoTArray<BYTE, 512> versionInfo; + + if (versInfoSize == 0) { + return; + } + + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + versionInfo.AppendElements(uint32_t(versInfoSize)); + + if (!GetFileVersionInfoW(aDLLPath, 0, versInfoSize, + LPBYTE(versionInfo.Elements()))) { + return; + } + + UINT len = 0; + VS_FIXEDFILEINFO* fileInfo = nullptr; + if (!VerQueryValue(LPBYTE(versionInfo.Elements()), TEXT("\\"), + (LPVOID*)&fileInfo, &len) || + len == 0 || fileInfo == nullptr) { + return; + } + + DWORD fileVersMS = fileInfo->dwFileVersionMS; + DWORD fileVersLS = fileInfo->dwFileVersionLS; + + vers[0] = HIWORD(fileVersMS); + vers[1] = LOWORD(fileVersMS); + vers[2] = HIWORD(fileVersLS); + vers[3] = LOWORD(fileVersLS); + + char buf[256]; + SprintfLiteral(buf, "%lu.%lu.%lu.%lu", vers[0], vers[1], vers[2], vers[3]); + aVersion.Assign(NS_ConvertUTF8toUTF16(buf)); +} + +static BOOL CALLBACK AppendClearTypeParams(HMONITOR aMonitor, HDC, LPRECT, + LPARAM aContext) { + MONITORINFOEXW monitorInfo; + monitorInfo.cbSize = sizeof(MONITORINFOEXW); + if (!GetMonitorInfoW(aMonitor, &monitorInfo)) { + return TRUE; + } + + ClearTypeParameterInfo ctinfo; + ctinfo.displayName.Assign(monitorInfo.szDevice); + + RefPtr<IDWriteRenderingParams> renderingParams; + HRESULT hr = Factory::GetDWriteFactory()->CreateMonitorRenderingParams( + aMonitor, getter_AddRefs(renderingParams)); + if (FAILED(hr)) { + return TRUE; + } + + ctinfo.gamma = renderingParams->GetGamma() * 1000; + ctinfo.pixelStructure = renderingParams->GetPixelGeometry(); + ctinfo.clearTypeLevel = renderingParams->GetClearTypeLevel() * 100; + ctinfo.enhancedContrast = renderingParams->GetEnhancedContrast() * 100; + + auto* params = reinterpret_cast<nsTArray<ClearTypeParameterInfo>*>(aContext); + params->AppendElement(ctinfo); + return TRUE; +} + +void gfxWindowsPlatform::GetCleartypeParams( + nsTArray<ClearTypeParameterInfo>& aParams) { + aParams.Clear(); + if (!DWriteEnabled()) { + return; + } + EnumDisplayMonitors(nullptr, nullptr, AppendClearTypeParams, + reinterpret_cast<LPARAM>(&aParams)); +} + +void gfxWindowsPlatform::FontsPrefsChanged(const char* aPref) { + bool clearTextFontCaches = true; + + gfxPlatform::FontsPrefsChanged(aPref); + + if (aPref && + !strncmp(GFX_CLEARTYPE_PARAMS, aPref, strlen(GFX_CLEARTYPE_PARAMS))) { + gfxDWriteFont::UpdateClearTypeVars(); + } else { + clearTextFontCaches = false; + } + + if (clearTextFontCaches) { + gfxFontCache* fc = gfxFontCache::GetCache(); + if (fc) { + fc->Flush(); + } + } +} + +bool gfxWindowsPlatform::IsOptimus() { + static int knowIsOptimus = -1; + if (knowIsOptimus == -1) { + // other potential optimus -- nvd3d9wrapx.dll & nvdxgiwrap.dll + if (GetModuleHandleA("nvumdshim.dll") || + GetModuleHandleA("nvumdshimx.dll")) { + knowIsOptimus = 1; + } else { + knowIsOptimus = 0; + } + } + return knowIsOptimus; +} + +static 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 { + ImportCachedContentDeviceData(); + 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"); + } + + nsCString message; + nsCString failureId; + if (StaticPrefs::layers_d3d11_enable_blacklist_AtStartup() && + !gfxPlatform::IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, + &message, failureId)) { + d3d11.Disable(FeatureStatus::Blocklisted, message.get(), failureId); + } +} + +/* static */ +void gfxWindowsPlatform::RecordContentDeviceFailure( + TelemetryDeviceCode aDevice) { + // If the parent process fails to acquire a device, we record this + // normally as part of the environment. The exceptional case we're + // looking for here is when the parent process successfully acquires + // a device, but the content process fails to acquire the same device. + // This would not normally be displayed in about:support. + if (!XRE_IsContentProcess()) { + return; + } + Telemetry::Accumulate(Telemetry::GFX_CONTENT_FAILED_TO_ACQUIRE_DEVICE, + uint32_t(aDevice)); +} + +void gfxWindowsPlatform::RecordStartupTelemetry() { + if (!XRE_IsParentProcess()) { + return; + } + + DeviceManagerDx* dx = DeviceManagerDx::Get(); + nsTArray<DXGI_OUTPUT_DESC1> outputs = dx->EnumerateOutputs(); + + uint32_t allSupportedColorSpaces = 0; + for (auto& output : outputs) { + uint32_t colorSpace = 1 << output.ColorSpace; + allSupportedColorSpaces |= colorSpace; + } + + Telemetry::ScalarSet( + Telemetry::ScalarID::GFX_HDR_WINDOWS_DISPLAY_COLORSPACE_BITFIELD, + allSupportedColorSpaces); +} + +// Supports lazy device initialization on Windows, so that WebRender can avoid +// initializing GPU state and allocating swap chains for most non-GPU processes. +void gfxWindowsPlatform::EnsureDevicesInitialized() { + MOZ_DIAGNOSTIC_ASSERT(!IsWin32kLockedDown()); + + if (!mInitializedDevices) { + mInitializedDevices = true; + InitializeDevices(); + UpdateBackendPrefs(); + } +} + +bool gfxWindowsPlatform::DevicesInitialized() { return mInitializedDevices; } + +void gfxWindowsPlatform::InitializeDevices() { + MOZ_ASSERT(NS_IsMainThread()); + + if (XRE_IsParentProcess()) { + // If we're the UI process, and the GPU process is enabled, then we don't + // initialize any DirectX devices. We do leave them enabled in gfxConfig + // though. If the GPU process fails to create these devices it will send + // a message back and we'll update their status. + if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { + return; + } + + // No GPU process, continue initializing devices as normal. + } + + // If acceleration is disabled, we refuse to initialize anything. + if (!gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) { + return; + } + + // If we previously crashed initializing devices, bail out now. + D3D11LayersCrashGuard detectCrashes; + if (detectCrashes.Crashed()) { + gfxConfig::SetFailed(Feature::HW_COMPOSITING, + FeatureStatus::CrashedOnStartup, + "Crashed during startup in a previous session"); + gfxConfig::SetFailed( + Feature::D3D11_COMPOSITING, FeatureStatus::CrashedOnStartup, + "Harware acceleration crashed during startup in a previous session"); + gfxConfig::SetFailed( + Feature::DIRECT2D, FeatureStatus::CrashedOnStartup, + "Harware acceleration crashed during startup in a previous session"); + return; + } + + bool shouldUseD2D = gfxConfig::IsEnabled(Feature::DIRECT2D); + + // First, initialize D3D11. If this succeeds we attempt to use Direct2D. + InitializeD3D11(); + InitializeD2D(); + + if (!gfxConfig::IsEnabled(Feature::DIRECT2D) && XRE_IsContentProcess() && + shouldUseD2D) { + RecordContentDeviceFailure(TelemetryDeviceCode::D2D1); + } +} + +void gfxWindowsPlatform::InitializeD3D11() { + // This function attempts to initialize our D3D11 devices, if the hardware + // is not blocklisted for D3D11 layers. This first attempt will try to create + // a hardware accelerated device. If this creation fails or the hardware is + // blocklisted, then this function will abort if WARP is disabled, causing us + // to fallback to Basic layers. If WARP is not disabled it will use a WARP + // device which should always be available on Windows 7 and higher. + if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + return; + } + + DeviceManagerDx* dm = DeviceManagerDx::Get(); + if (XRE_IsParentProcess()) { + if (!dm->CreateCompositorDevices()) { + return; + } + } + + dm->CreateContentDevices(); + + // Content process failed to create the d3d11 device while parent process + // succeed. + if (XRE_IsContentProcess() && + !gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + gfxCriticalError() + << "[D3D11] Failed to create the D3D11 device in content \ + process."; + } +} + +void gfxWindowsPlatform::InitializeD2DConfig() { + FeatureState& d2d1 = gfxConfig::GetFeature(Feature::DIRECT2D); + + if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + d2d1.DisableByDefault(FeatureStatus::Unavailable, + "Direct2D requires Direct3D 11 compositing", + "FEATURE_FAILURE_D2D_D3D11_COMP"_ns); + return; + } + + d2d1.SetDefaultFromPref(StaticPrefs::GetPrefName_gfx_direct2d_disabled(), + false, + StaticPrefs::GetPrefDefault_gfx_direct2d_disabled()); + + nsCString message; + nsCString failureId; + if (!gfxPlatform::IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_DIRECT2D, &message, + failureId)) { + d2d1.Disable(FeatureStatus::Blocklisted, message.get(), failureId); + } + + if (!d2d1.IsEnabled() && + StaticPrefs::gfx_direct2d_force_enabled_AtStartup()) { + d2d1.UserForceEnable("Force-enabled via user-preference"); + } +} + +void gfxWindowsPlatform::InitializeD2D() { + ScopedGfxFeatureReporter d2d1_1("D2D1.1"); + + FeatureState& d2d1 = gfxConfig::GetFeature(Feature::DIRECT2D); + + DeviceManagerDx* dm = DeviceManagerDx::Get(); + + // We don't know this value ahead of time, but the user can force-override + // it, so we use Disable instead of SetFailed. + if (dm->IsWARP()) { + d2d1.Disable(FeatureStatus::Blocked, + "Direct2D is not compatible with Direct3D11 WARP", + "FEATURE_FAILURE_D2D_WARP_BLOCK"_ns); + } + + // If we pass all the initial checks, we can proceed to runtime decisions. + if (!d2d1.IsEnabled()) { + return; + } + + if (!Factory::SupportsD2D1()) { + d2d1.SetFailed(FeatureStatus::Unavailable, + "Failed to acquire a Direct2D 1.1 factory", + "FEATURE_FAILURE_D2D_FACTORY"_ns); + return; + } + + if (!dm->GetContentDevice()) { + d2d1.SetFailed(FeatureStatus::Failed, + "Failed to acquire a Direct3D 11 content device", + "FEATURE_FAILURE_D2D_DEVICE"_ns); + return; + } + + if (!dm->TextureSharingWorks()) { + d2d1.SetFailed(FeatureStatus::Failed, + "Direct3D11 device does not support texture sharing", + "FEATURE_FAILURE_D2D_TXT_SHARING"_ns); + return; + } + + // Using Direct2D depends on DWrite support. + if (!DWriteEnabled() && !InitDWriteSupport()) { + d2d1.SetFailed(FeatureStatus::Failed, + "Failed to initialize DirectWrite support", + "FEATURE_FAILURE_D2D_DWRITE"_ns); + return; + } + + // Verify that Direct2D device creation succeeded. + RefPtr<ID3D11Device> contentDevice = dm->GetContentDevice(); + if (!Factory::SetDirect3D11Device(contentDevice)) { + d2d1.SetFailed(FeatureStatus::Failed, "Failed to create a Direct2D device", + "FEATURE_FAILURE_D2D_CREATE_FAILED"_ns); + return; + } + + MOZ_ASSERT(d2d1.IsEnabled()); + d2d1_1.SetSuccessful(); +} + +void gfxWindowsPlatform::InitGPUProcessSupport() { + FeatureState& gpuProc = gfxConfig::GetFeature(Feature::GPU_PROCESS); + + if (!gpuProc.IsEnabled()) { + return; + } + + if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + // Don't use the GPU process if not using D3D11, unless software + // compositor is allowed + if (StaticPrefs::layers_gpu_process_allow_software_AtStartup()) { + return; + } + gpuProc.Disable(FeatureStatus::Unavailable, + "Not using GPU Process since D3D11 is unavailable", + "FEATURE_FAILURE_NO_D3D11"_ns); + } + // If we're still enabled at this point, the user set the force-enabled pref. +} + +class D3DVsyncSource final : public VsyncSource { + public: + D3DVsyncSource() + : mPrevVsync(TimeStamp::Now()), + mVsyncEnabled(false), + mWaitVBlankMonitor(NULL) { + mVsyncThread = new base::Thread("WindowsVsyncThread"); + MOZ_RELEASE_ASSERT(mVsyncThread->Start(), + "GFX: Could not start Windows vsync thread"); + SetVsyncRate(); + } + + void SetVsyncRate() { + DWM_TIMING_INFO vblankTime; + // Make sure to init the cbSize, otherwise GetCompositionTiming will fail + vblankTime.cbSize = sizeof(DWM_TIMING_INFO); + HRESULT hr = DwmGetCompositionTimingInfo(0, &vblankTime); + if (SUCCEEDED(hr)) { + UNSIGNED_RATIO refreshRate = vblankTime.rateRefresh; + // We get the rate in hertz / time, but we want the rate in ms. + float rate = + ((float)refreshRate.uiDenominator / (float)refreshRate.uiNumerator) * + 1000; + mVsyncRate = TimeDuration::FromMilliseconds(rate); + } else { + mVsyncRate = TimeDuration::FromMilliseconds(1000.0 / 60.0); + } + } + + virtual void Shutdown() override { + MOZ_ASSERT(NS_IsMainThread()); + DisableVsync(); + mVsyncThread->Stop(); + delete mVsyncThread; + } + + virtual void EnableVsync() override { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mVsyncThread->IsRunning()); + { // scope lock + if (mVsyncEnabled) { + return; + } + mVsyncEnabled = true; + } + + mVsyncThread->message_loop()->PostTask(NewRunnableMethod( + "D3DVsyncSource::VBlankLoop", this, &D3DVsyncSource::VBlankLoop)); + } + + virtual void DisableVsync() override { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mVsyncThread->IsRunning()); + if (!mVsyncEnabled) { + return; + } + mVsyncEnabled = false; + } + + virtual bool IsVsyncEnabled() override { + MOZ_ASSERT(NS_IsMainThread()); + return mVsyncEnabled; + } + + virtual TimeDuration GetVsyncRate() override { return mVsyncRate; } + + void ScheduleSoftwareVsync(TimeStamp aVsyncTimestamp) { + MOZ_ASSERT(IsInVsyncThread()); + NS_WARNING( + "DwmComposition dynamically disabled, falling back to software " + "timers"); + + TimeStamp nextVsync = aVsyncTimestamp + mVsyncRate; + TimeDuration delay = nextVsync - TimeStamp::Now(); + if (delay.ToMilliseconds() < 0) { + delay = mozilla::TimeDuration::FromMilliseconds(0); + } + + mVsyncThread->message_loop()->PostDelayedTask( + NewRunnableMethod("D3DVsyncSource::VBlankLoop", this, + &D3DVsyncSource::VBlankLoop), + delay.ToMilliseconds()); + } + + // Returns the timestamp for the just happened vsync + TimeStamp GetVBlankTime() { + TimeStamp vsync = TimeStamp::Now(); + TimeStamp now = vsync; + + DWM_TIMING_INFO vblankTime; + // Make sure to init the cbSize, otherwise + // GetCompositionTiming will fail + vblankTime.cbSize = sizeof(DWM_TIMING_INFO); + HRESULT hr = DwmGetCompositionTimingInfo(0, &vblankTime); + if (!SUCCEEDED(hr)) { + return vsync; + } + + LARGE_INTEGER frequency; + QueryPerformanceFrequency(&frequency); + + LARGE_INTEGER qpcNow; + QueryPerformanceCounter(&qpcNow); + + const int microseconds = 1000000; + int64_t adjust = qpcNow.QuadPart - vblankTime.qpcVBlank; + int64_t usAdjust = (adjust * microseconds) / frequency.QuadPart; + vsync -= TimeDuration::FromMicroseconds((double)usAdjust); + + // 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. + if ((now - vsync).ToMilliseconds() > 4.0) { + vsync = now; + } + + return vsync; + } + + void VBlankLoop() { + MOZ_ASSERT(IsInVsyncThread()); + MOZ_ASSERT(sizeof(int64_t) == sizeof(QPC_TIME)); + + TimeStamp vsync = TimeStamp::Now(); + mPrevVsync = TimeStamp(); + TimeStamp flushTime = TimeStamp::Now(); + TimeDuration longVBlank = mVsyncRate * 2; + + for (;;) { + { // scope lock + if (!mVsyncEnabled) return; + } + + // Large parts of gecko assume that the refresh driver timestamp + // must be <= Now() and cannot be in the future. + MOZ_ASSERT(vsync <= TimeStamp::Now()); + NotifyVsync(vsync, vsync + mVsyncRate); + + HRESULT hr = E_FAIL; + if (!StaticPrefs::gfx_vsync_force_disable_waitforvblank()) { + UpdateVBlankOutput(); + if (mWaitVBlankOutput) { + const TimeStamp vblank_begin_wait = TimeStamp::Now(); + { + AUTO_PROFILER_THREAD_SLEEP; + hr = mWaitVBlankOutput->WaitForVBlank(); + } + if (SUCCEEDED(hr)) { + // vblank might return instantly when running headless, + // monitor powering off, etc. Since we're on a dedicated + // thread, instant-return should not happen in the normal + // case, so catch any odd behavior with a time cutoff: + TimeDuration vblank_wait = TimeStamp::Now() - vblank_begin_wait; + if (vblank_wait.ToMilliseconds() < 1.0) { + hr = E_FAIL; // fall back on old behavior + } + } + } + } + if (!SUCCEEDED(hr)) { + hr = DwmFlush(); + } + if (!SUCCEEDED(hr)) { + // DWMFlush isn't working, fallback to software vsync. + ScheduleSoftwareVsync(TimeStamp::Now()); + return; + } + + TimeStamp now = TimeStamp::Now(); + TimeDuration flushDiff = now - flushTime; + flushTime = now; + if ((flushDiff > longVBlank) || mPrevVsync.IsNull()) { + // Our vblank took longer than 2 intervals, readjust our timestamps + vsync = GetVBlankTime(); + mPrevVsync = vsync; + } else { + // Instead of giving the actual vsync time, a constant interval + // between vblanks instead of the noise generated via hardware + // is actually what we want. Most apps just care about the diff + // between vblanks to animate, so a clean constant interval is + // smoother. + vsync = mPrevVsync + mVsyncRate; + if (vsync > now) { + // DWMFlush woke up very early, so readjust our times again + vsync = GetVBlankTime(); + } + + if (vsync <= mPrevVsync) { + vsync = TimeStamp::Now(); + } + + if ((now - vsync).ToMilliseconds() > 2.0) { + // Account for time drift here where vsync never quite catches up to + // Now and we'd fall ever so slightly further behind Now(). + vsync = GetVBlankTime(); + } + + mPrevVsync = vsync; + } + } // end for + } + virtual ~D3DVsyncSource() { MOZ_ASSERT(NS_IsMainThread()); } + + private: + bool IsInVsyncThread() { + return mVsyncThread->thread_id() == PlatformThread::CurrentId(); + } + + void UpdateVBlankOutput() { + HMONITOR primary_monitor = + MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY); + if (primary_monitor == mWaitVBlankMonitor && mWaitVBlankOutput) { + return; + } + + mWaitVBlankMonitor = primary_monitor; + + RefPtr<IDXGIOutput> output = nullptr; + if (DeviceManagerDx* dx = DeviceManagerDx::Get()) { + if (dx->GetOutputFromMonitor(mWaitVBlankMonitor, &output)) { + mWaitVBlankOutput = output; + return; + } + } + + // failed to convert a monitor to an output so keep trying + mWaitVBlankOutput = nullptr; + } + + TimeStamp mPrevVsync; + base::Thread* mVsyncThread; + TimeDuration mVsyncRate; + Atomic<bool> mVsyncEnabled; + + HMONITOR mWaitVBlankMonitor; + RefPtr<IDXGIOutput> mWaitVBlankOutput; +}; // D3DVsyncSource + +already_AddRefed<mozilla::gfx::VsyncSource> +gfxWindowsPlatform::CreateGlobalHardwareVsyncSource() { + MOZ_RELEASE_ASSERT(NS_IsMainThread(), "GFX: Not in main thread."); + + RefPtr<VsyncSource> d3dVsyncSource = new D3DVsyncSource(); + return d3dVsyncSource.forget(); +} + +void gfxWindowsPlatform::ImportGPUDeviceData( + const mozilla::gfx::GPUDeviceData& aData) { + MOZ_ASSERT(XRE_IsParentProcess()); + + gfxPlatform::ImportGPUDeviceData(aData); + + gfxConfig::ImportChange(Feature::D3D11_COMPOSITING, aData.d3d11Compositing()); + + DeviceManagerDx* dm = DeviceManagerDx::Get(); + if (gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + dm->ImportDeviceInfo(aData.gpuDevice().ref()); + } else { + // There should be no devices, so this just takes away the device status. + dm->ResetDevices(); + + // Make sure we disable D2D if content processes might use it. + FeatureState& d2d1 = gfxConfig::GetFeature(Feature::DIRECT2D); + if (d2d1.IsEnabled()) { + d2d1.SetFailed(FeatureStatus::Unavailable, + "Direct2D requires Direct3D 11 compositing", + "FEATURE_FAILURE_D2D_D3D11_COMP"_ns); + } + } + + // CanUseHardwareVideoDecoding depends on d3d11 state, so update + // the cached value now. + UpdateCanUseHardwareVideoDecoding(); + + // For completeness (and messaging in about:support). Content recomputes this + // on its own, and we won't use ANGLE in the UI process if we're using a GPU + // process. + UpdateANGLEConfig(); +} + +void gfxWindowsPlatform::ImportContentDeviceData( + const mozilla::gfx::ContentDeviceData& aData) { + MOZ_ASSERT(XRE_IsContentProcess()); + + gfxPlatform::ImportContentDeviceData(aData); + + const DevicePrefs& prefs = aData.prefs(); + gfxConfig::Inherit(Feature::D3D11_COMPOSITING, prefs.d3d11Compositing()); + gfxConfig::Inherit(Feature::DIRECT2D, prefs.useD2D1()); + + if (gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + DeviceManagerDx* dm = DeviceManagerDx::Get(); + dm->ImportDeviceInfo(aData.d3d11()); + } +} + +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..0956a384b0 --- /dev/null +++ b/gfx/thebes/gfxWindowsPlatform.h @@ -0,0 +1,250 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_WINDOWS_PLATFORM_H +#define GFX_WINDOWS_PLATFORM_H + +#include "gfxCrashReporterUtils.h" +#include "gfxFontUtils.h" +#include "gfxWindowsSurface.h" +#include "gfxFont.h" +#include "gfxDWriteFonts.h" +#include "gfxPlatform.h" +#include "gfxTelemetry.h" +#include "gfxTypes.h" +#include "mozilla/Attributes.h" +#include "mozilla/Atomics.h" +#include "nsTArray.h" + +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" + +#include <windows.h> +#include <objbase.h> + +#include <dxgi.h> + +// This header is available in the June 2010 SDK and in the Win8 SDK +#include <d3dcommon.h> +// Win 8.0 SDK types we'll need when building using older sdks. +#if !defined(D3D_FEATURE_LEVEL_11_1) // defined in the 8.0 SDK only +# define D3D_FEATURE_LEVEL_11_1 static_cast<D3D_FEATURE_LEVEL>(0xb100) +# define D3D_FL9_1_REQ_TEXTURE2D_U_OR_V_DIMENSION 2048 +# define D3D_FL9_3_REQ_TEXTURE2D_U_OR_V_DIMENSION 4096 +#endif + +namespace mozilla { +namespace gfx { +class DrawTarget; +class FeatureState; +class DeviceManagerDx; +} // namespace gfx +} // namespace mozilla +struct IDirect3DDevice9; +struct ID3D11Device; +struct IDXGIAdapter1; + +/** + * Utility to get a Windows HDC from a Moz2D DrawTarget. If the DrawTarget is + * not backed by a HDC this will get the HDC for the screen device context + * instead. + */ +class MOZ_STACK_CLASS DCForMetrics final { + public: + explicit DCForMetrics(); + + ~DCForMetrics() { ReleaseDC(nullptr, mDC); } + + operator HDC() { return mDC; } + + private: + HDC mDC; +}; + +// ClearType parameters set by running ClearType tuner +struct ClearTypeParameterInfo { + ClearTypeParameterInfo() + : gamma(-1), + pixelStructure(-1), + clearTypeLevel(-1), + enhancedContrast(-1) {} + + nsString displayName; // typically just 'DISPLAY1' + int32_t gamma; + int32_t pixelStructure; + int32_t clearTypeLevel; + int32_t enhancedContrast; +}; + +class gfxWindowsPlatform final : public gfxPlatform { + friend class mozilla::gfx::DeviceManagerDx; + + public: + enum TextRenderingMode { + TEXT_RENDERING_NO_CLEARTYPE, + TEXT_RENDERING_NORMAL, + TEXT_RENDERING_GDI_CLASSIC, + TEXT_RENDERING_COUNT + }; + + gfxWindowsPlatform(); + virtual ~gfxWindowsPlatform(); + static gfxWindowsPlatform* GetPlatform() { + return (gfxWindowsPlatform*)gfxPlatform::GetPlatform(); + } + + void EnsureDevicesInitialized() override; + bool DevicesInitialized() override; + + bool CreatePlatformFontList() override; + + virtual already_AddRefed<gfxASurface> CreateOffscreenSurface( + const IntSize& aSize, gfxImageFormat aFormat) override; + + enum RenderMode { + /* Use GDI and windows surfaces */ + RENDER_GDI = 0, + + /* Use 32bpp image surfaces and call StretchDIBits */ + RENDER_IMAGE_STRETCH32, + + /* Use 32bpp image surfaces, and do 32->24 conversion before calling + StretchDIBits */ + RENDER_IMAGE_STRETCH24, + + /* Use Direct2D rendering */ + RENDER_DIRECT2D, + + /* max */ + RENDER_MODE_MAX + }; + + bool IsDirect2DBackend(); + + /** + * Updates render mode with relation to the current preferences and + * available devices. + */ + void UpdateRenderMode(); + + /** + * Verifies a D2D device is present and working, will attempt to create one + * it is non-functional or non-existant. + * + * \param aAttemptForce Attempt to force D2D cairo device creation by using + * cairo device creation routines. + */ + void VerifyD2DDevice(bool aAttemptForce); + + void GetCommonFallbackFonts(uint32_t aCh, Script aRunScript, + eFontPresentation aPresentation, + nsTArray<const char*>& aFontList) override; + + bool CanUseHardwareVideoDecoding() override; + + void CompositorUpdated() override; + + bool DidRenderingDeviceReset( + DeviceResetReason* aResetReason = nullptr) override; + void SchedulePaintIfDeviceReset() override; + void CheckForContentOnlyDeviceReset(); + + mozilla::gfx::BackendType GetContentBackendFor( + mozilla::layers::LayersBackend aLayers) override; + + mozilla::gfx::BackendType GetPreferredCanvasBackend() override; + + static void GetDLLVersion(char16ptr_t aDLLPath, nsAString& aVersion); + + // returns ClearType tuning information for each display + static void GetCleartypeParams(nsTArray<ClearTypeParameterInfo>& aParams); + + void FontsPrefsChanged(const char* aPref) override; + + static inline bool DWriteEnabled() { + return !!mozilla::gfx::Factory::GetDWriteFactory(); + } + + public: + static nsresult GetGpuTimeSinceProcessStartInMs(uint64_t* aResult); + + static bool IsOptimus(); + + bool SupportsApzWheelInput() const override { return true; } + + // Recreate devices as needed for a device reset. Returns true if a device + // reset occurred. + bool HandleDeviceReset(); + void UpdateBackendPrefs(); + + already_AddRefed<mozilla::gfx::VsyncSource> CreateGlobalHardwareVsyncSource() + override; + static mozilla::Atomic<size_t> sD3D11SharedTextures; + static mozilla::Atomic<size_t> sD3D9SharedTextures; + + bool SupportsPluginDirectBitmapDrawing() override { return true; } + + static void RecordContentDeviceFailure( + mozilla::gfx::TelemetryDeviceCode aDevice); + + static void InitMemoryReportersForGPUProcess(); + + static bool CheckVariationFontSupport(); + + // Always false for content processes. + bool SupportsHDR() override { return mSupportsHDR; } + + protected: + bool AccelerateLayersByDefault() override { return true; } + + nsTArray<uint8_t> GetPlatformCMSOutputProfileData() override; + + public: + static nsTArray<uint8_t> GetPlatformCMSOutputProfileData_Impl(); + + protected: + void GetPlatformDisplayInfo(mozilla::widget::InfoObject& aObj) override; + + void ImportGPUDeviceData(const mozilla::gfx::GPUDeviceData& aData) override; + void ImportContentDeviceData( + const mozilla::gfx::ContentDeviceData& aData) override; + void BuildContentDeviceData(mozilla::gfx::ContentDeviceData* aOut) override; + + BackendPrefsData GetBackendPrefs() const override; + + void UpdateSupportsHDR(); + + RenderMode mRenderMode; + bool mSupportsHDR; + + private: + void Init(); + void InitAcceleration() override; + void InitWebRenderConfig() override; + + void InitializeDevices(); + void InitializeD3D11(); + void InitializeD2D(); + bool InitDWriteSupport(); + void InitGPUProcessSupport(); + + void DisableD2D(mozilla::gfx::FeatureStatus aStatus, const char* aMessage, + const nsACString& aFailureId); + + void InitializeConfig(); + void InitializeD3D9Config(); + void InitializeD3D11Config(); + void InitializeD2DConfig(); + void InitializeDirectDrawConfig(); + + void RecordStartupTelemetry(); + + bool mInitializedDevices = false; + + // Cached contents of the output color profile file + nsTArray<uint8_t> mCachedOutputColorProfile; +}; + +#endif /* GFX_WINDOWS_PLATFORM_H */ diff --git a/gfx/thebes/gfxWindowsSurface.cpp b/gfx/thebes/gfxWindowsSurface.cpp new file mode 100644 index 0000000000..419c9ee696 --- /dev/null +++ b/gfx/thebes/gfxWindowsSurface.cpp @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxWindowsSurface.h" +#include "gfxContext.h" +#include "gfxPlatform.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/HelpersCairo.h" +#include "mozilla/gfx/Logging.h" + +#include "cairo.h" +#include "cairo-win32.h" + +#include "nsString.h" + +gfxWindowsSurface::gfxWindowsSurface(HDC dc, uint32_t flags) + : mOwnsDC(false), mDC(dc), mWnd(nullptr) { + InitWithDC(flags); +} + +void gfxWindowsSurface::MakeInvalid(mozilla::gfx::IntSize& size) { + size = mozilla::gfx::IntSize(-1, -1); +} + +gfxWindowsSurface::gfxWindowsSurface(const mozilla::gfx::IntSize& realSize, + gfxImageFormat imageFormat) + : mOwnsDC(false), mWnd(nullptr) { + mozilla::gfx::IntSize size(realSize); + if (!mozilla::gfx::Factory::CheckSurfaceSize(size)) MakeInvalid(size); + + cairo_format_t cformat = GfxFormatToCairoFormat(imageFormat); + cairo_surface_t* surf = + cairo_win32_surface_create_with_dib(cformat, size.width, size.height); + + Init(surf); + + if (CairoStatus() == CAIRO_STATUS_SUCCESS) { + mDC = cairo_win32_surface_get_dc(CairoSurface()); + RecordMemoryUsed(size.width * size.height * 4 + sizeof(gfxWindowsSurface)); + } else { + mDC = nullptr; + } +} + +gfxWindowsSurface::gfxWindowsSurface(cairo_surface_t* csurf) + : mOwnsDC(false), mWnd(nullptr) { + if (cairo_surface_status(csurf) == 0) + mDC = cairo_win32_surface_get_dc(csurf); + else + mDC = nullptr; + + MOZ_ASSERT(cairo_surface_get_type(csurf) != + CAIRO_SURFACE_TYPE_WIN32_PRINTING); + + Init(csurf, true); +} + +void gfxWindowsSurface::InitWithDC(uint32_t flags) { + if (flags & FLAG_IS_TRANSPARENT) { + Init(cairo_win32_surface_create_with_format(mDC, CAIRO_FORMAT_ARGB32)); + } else { + Init(cairo_win32_surface_create(mDC)); + } +} + +gfxWindowsSurface::~gfxWindowsSurface() { + if (mOwnsDC) { + if (mWnd) + ::ReleaseDC(mWnd, mDC); + else + ::DeleteDC(mDC); + } +} + +HDC gfxWindowsSurface::GetDC() { + return cairo_win32_surface_get_dc(CairoSurface()); +} + +already_AddRefed<gfxImageSurface> gfxWindowsSurface::GetAsImageSurface() { + if (!mSurfaceValid) { + NS_WARNING( + "GetImageSurface on an invalid (null) surface; who's calling this " + "without checking for surface errors?"); + return nullptr; + } + + NS_ASSERTION( + CairoSurface() != nullptr, + "CairoSurface() shouldn't be nullptr when mSurfaceValid is TRUE!"); + + cairo_surface_t* isurf = cairo_win32_surface_get_image(CairoSurface()); + if (!isurf) return nullptr; + + RefPtr<gfxImageSurface> result = + gfxASurface::Wrap(isurf).downcast<gfxImageSurface>(); + result->SetOpaqueRect(GetOpaqueRect()); + + return result.forget(); +} + +const mozilla::gfx::IntSize gfxWindowsSurface::GetSize() const { + if (!mSurfaceValid) { + NS_WARNING( + "GetImageSurface on an invalid (null) surface; who's calling this " + "without checking for surface errors?"); + return mozilla::gfx::IntSize(-1, -1); + } + + NS_ASSERTION( + mSurface != nullptr, + "CairoSurface() shouldn't be nullptr when mSurfaceValid is TRUE!"); + + int width, height; + if (cairo_win32_surface_get_size(mSurface, &width, &height) != + CAIRO_STATUS_SUCCESS) { + return mozilla::gfx::IntSize(-1, -1); + } + + return mozilla::gfx::IntSize(width, height); +} diff --git a/gfx/thebes/gfxWindowsSurface.h b/gfx/thebes/gfxWindowsSurface.h new file mode 100644 index 0000000000..3659cab37c --- /dev/null +++ b/gfx/thebes/gfxWindowsSurface.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_WINDOWSSURFACE_H +#define GFX_WINDOWSSURFACE_H + +#include "gfxASurface.h" +#include "gfxImageSurface.h" + +/* include windows.h for the HWND and HDC definitions that we need. */ +#include <windows.h> + +struct IDirect3DSurface9; + +/* undefine LoadImage because our code uses that name */ +#undef LoadImage + +class gfxContext; + +class gfxWindowsSurface : public gfxASurface { + public: + enum { FLAG_IS_TRANSPARENT = (1 << 2) }; + + explicit gfxWindowsSurface(HDC dc, uint32_t flags = 0); + + // Create a DIB surface + explicit gfxWindowsSurface(const mozilla::gfx::IntSize& size, + gfxImageFormat imageFormat = + mozilla::gfx::SurfaceFormat::X8R8G8B8_UINT32); + + explicit gfxWindowsSurface(cairo_surface_t* csurf); + + void InitWithDC(uint32_t flags); + + virtual ~gfxWindowsSurface(); + + HDC GetDC(); + + already_AddRefed<gfxImageSurface> GetAsImageSurface(); + + const mozilla::gfx::IntSize GetSize() const; + + private: + void MakeInvalid(mozilla::gfx::IntSize& size); + + bool mOwnsDC; + + HDC mDC; + HWND mWnd; +}; + +#endif /* GFX_WINDOWSSURFACE_H */ diff --git a/gfx/thebes/gfxXlibSurface.cpp b/gfx/thebes/gfxXlibSurface.cpp new file mode 100644 index 0000000000..2d67a8e05a --- /dev/null +++ b/gfx/thebes/gfxXlibSurface.cpp @@ -0,0 +1,412 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxXlibSurface.h" + +#include "cairo.h" +#include "cairo-xlib.h" +#include <X11/Xlibint.h> /* For XESetCloseDisplay */ +#undef max // Xlibint.h defines this and it breaks std::max +#undef min // Xlibint.h defines this and it breaks std::min +#undef Data + +#include "nsTArray.h" +#include "nsAlgorithm.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/Preferences.h" +#include <algorithm> +#include "mozilla/CheckedInt.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +gfxXlibSurface::gfxXlibSurface(Display* dpy, Drawable drawable, Visual* visual) + : mPixmapTaken(false), + mDisplay(XlibDisplay::Borrow(dpy)), + mDrawable(drawable) { + const gfx::IntSize size = DoSizeQuery(); + cairo_surface_t* surf = + cairo_xlib_surface_create(dpy, drawable, visual, size.width, size.height); + Init(surf); +} + +gfxXlibSurface::gfxXlibSurface(Display* dpy, Drawable drawable, Visual* visual, + const gfx::IntSize& size) + : gfxXlibSurface(XlibDisplay::Borrow(dpy), drawable, visual, size) {} + +gfxXlibSurface::gfxXlibSurface(const std::shared_ptr<XlibDisplay>& dpy, + Drawable drawable, Visual* visual, + const gfx::IntSize& size) + : mPixmapTaken(false), mDisplay(dpy), mDrawable(drawable) { + NS_ASSERTION(Factory::CheckSurfaceSize(size, XLIB_IMAGE_SIDE_SIZE_LIMIT), + "Bad size"); + + cairo_surface_t* surf = cairo_xlib_surface_create(*dpy, drawable, visual, + size.width, size.height); + Init(surf); +} + +gfxXlibSurface::gfxXlibSurface(cairo_surface_t* csurf) : mPixmapTaken(false) { + MOZ_ASSERT(cairo_surface_status(csurf) == 0, + "Not expecting an error surface"); + + mDrawable = cairo_xlib_surface_get_drawable(csurf); + mDisplay = XlibDisplay::Borrow(cairo_xlib_surface_get_display(csurf)); + + Init(csurf, true); +} + +gfxXlibSurface::~gfxXlibSurface() { + // gfxASurface's destructor calls RecordMemoryFreed(). + if (mPixmapTaken) { + XFreePixmap(*mDisplay, mDrawable); + } +} + +static Drawable CreatePixmap(Screen* screen, const gfx::IntSize& size, + unsigned int depth, Drawable relatedDrawable) { + if (!Factory::CheckSurfaceSize(size, XLIB_IMAGE_SIDE_SIZE_LIMIT)) + return X11None; + + if (relatedDrawable == X11None) { + relatedDrawable = RootWindowOfScreen(screen); + } + Display* dpy = DisplayOfScreen(screen); + // X gives us a fatal error if we try to create a pixmap of width + // or height 0 + return XCreatePixmap(dpy, relatedDrawable, std::max(1, size.width), + std::max(1, size.height), depth); +} + +void gfxXlibSurface::TakePixmap() { + NS_ASSERTION(!mPixmapTaken, "I already own the Pixmap!"); + mPixmapTaken = true; + + // The bit depth returned from Cairo is technically int, but this is + // the last place we'd be worried about that scenario. + unsigned int bitDepth = cairo_xlib_surface_get_depth(CairoSurface()); + MOZ_ASSERT((bitDepth % 8) == 0, "Memory used not recorded correctly"); + + // Divide by 8 because surface_get_depth gives us the number of *bits* per + // pixel. + gfx::IntSize size = GetSize(); + CheckedInt32 totalBytes = + CheckedInt32(size.width) * CheckedInt32(size.height) * (bitDepth / 8); + + // Don't do anything in the "else" case. We could add INT32_MAX, but that + // would overflow the memory used counter. It would also mean we tried for + // a 2G image. For now, we'll just assert, + MOZ_ASSERT(totalBytes.isValid(), "Did not expect to exceed 2Gb image"); + if (totalBytes.isValid()) { + RecordMemoryUsed(totalBytes.value()); + } +} + +Drawable gfxXlibSurface::ReleasePixmap() { + NS_ASSERTION(mPixmapTaken, "I don't own the Pixmap!"); + mPixmapTaken = false; + RecordMemoryFreed(); + return mDrawable; +} + +static cairo_user_data_key_t gDestroyPixmapKey; + +struct DestroyPixmapClosure { + DestroyPixmapClosure(Drawable d, Screen* s) : mPixmap(d), mScreen(s) {} + Drawable mPixmap; + Screen* mScreen; +}; + +static void DestroyPixmap(void* data) { + DestroyPixmapClosure* closure = static_cast<DestroyPixmapClosure*>(data); + XFreePixmap(DisplayOfScreen(closure->mScreen), closure->mPixmap); + delete closure; +} + +/* static */ +cairo_surface_t* gfxXlibSurface::CreateCairoSurface(Screen* screen, + Visual* visual, + const gfx::IntSize& size, + Drawable relatedDrawable) { + Drawable drawable = CreatePixmap(screen, size, DepthOfVisual(screen, visual), + relatedDrawable); + if (!drawable) return nullptr; + + cairo_surface_t* surface = cairo_xlib_surface_create( + DisplayOfScreen(screen), drawable, visual, size.width, size.height); + if (cairo_surface_status(surface)) { + cairo_surface_destroy(surface); + XFreePixmap(DisplayOfScreen(screen), drawable); + return nullptr; + } + + DestroyPixmapClosure* closure = new DestroyPixmapClosure(drawable, screen); + cairo_surface_set_user_data(surface, &gDestroyPixmapKey, closure, + DestroyPixmap); + return surface; +} + +/* static */ +already_AddRefed<gfxXlibSurface> gfxXlibSurface::Create( + Screen* screen, Visual* visual, const gfx::IntSize& size, + Drawable relatedDrawable) { + return Create(XlibDisplay::Borrow(DisplayOfScreen(screen)), screen, visual, + size, relatedDrawable); +}; + +/* static */ +already_AddRefed<gfxXlibSurface> gfxXlibSurface::Create( + const std::shared_ptr<XlibDisplay>& display, Screen* screen, Visual* visual, + const gfx::IntSize& size, Drawable relatedDrawable) { + MOZ_ASSERT(*display == DisplayOfScreen(screen)); + + Drawable drawable = CreatePixmap(screen, size, DepthOfVisual(screen, visual), + relatedDrawable); + if (!drawable) return nullptr; + + RefPtr<gfxXlibSurface> result = + new gfxXlibSurface(display, drawable, visual, size); + result->TakePixmap(); + + if (result->CairoStatus() != 0) return nullptr; + + return result.forget(); +} + +void gfxXlibSurface::Finish() { gfxASurface::Finish(); } + +const gfx::IntSize gfxXlibSurface::GetSize() const { + if (!mSurfaceValid) return gfx::IntSize(0, 0); + + return gfx::IntSize(cairo_xlib_surface_get_width(mSurface), + cairo_xlib_surface_get_height(mSurface)); +} + +const gfx::IntSize gfxXlibSurface::DoSizeQuery() { + // figure out width/height/depth + Window root_ignore; + int x_ignore, y_ignore; + unsigned int bwidth_ignore, width, height, depth; + + XGetGeometry(*mDisplay, mDrawable, &root_ignore, &x_ignore, &y_ignore, &width, + &height, &bwidth_ignore, &depth); + + return gfx::IntSize(width, height); +} + +class DisplayTable { + public: + static bool GetColormapAndVisual(Screen* screen, Visual* visual, + Colormap* colormap, + Visual** visualForColormap); + + private: + struct ColormapEntry { + // The Screen is needed here because colormaps (and their visuals) may + // only be used on one Screen + Screen* mScreen; + Visual* mVisual; + Colormap mColormap; + }; + + class DisplayInfo { + public: + explicit DisplayInfo(Display* display) : mDisplay(display) {} + Display* mDisplay; + nsTArray<ColormapEntry> mColormapEntries; + }; + + // Comparator for finding the DisplayInfo + class FindDisplay { + public: + bool Equals(const DisplayInfo& info, const Display* display) const { + return info.mDisplay == display; + } + }; + + static int DisplayClosing(Display* display, XExtCodes* codes); + + nsTArray<DisplayInfo> mDisplays; + static DisplayTable* sDisplayTable; +}; + +DisplayTable* DisplayTable::sDisplayTable; + +// Pixmaps don't have a particular associated visual but the pixel values are +// interpreted according to a visual/colormap pairs. +// +// cairo is designed for surfaces with either TrueColor visuals or the +// default visual (which may not be true color). TrueColor visuals don't +// really need a colormap because the visual indicates the pixel format, +// and cairo uses the default visual with the default colormap, so cairo +// surfaces don't need an explicit colormap. +// +// However, some toolkits (e.g. GDK) need a colormap even with TrueColor +// visuals. We can create a colormap for these visuals, but it will use about +// 20kB of memory in the server, so we use the default colormap when +// suitable and share colormaps between surfaces. Another reason for +// minimizing colormap turnover is that the plugin process must leak resources +// for each new colormap id when using older GDK libraries (bug 569775). +// +// Only the format of the pixels is important for rendering to Pixmaps, so if +// the format of a visual matches that of the surface, then that visual can be +// used for rendering to the surface. Multiple visuals can match the same +// format (but have different GLX properties), so the visual returned may +// differ from the visual passed in. Colormaps are tied to a visual, so +// should only be used with their visual. + +/* static */ +bool DisplayTable::GetColormapAndVisual(Screen* aScreen, Visual* aVisual, + Colormap* aColormap, + Visual** aVisualForColormap) + +{ + Display* display = DisplayOfScreen(aScreen); + + // Use the default colormap if the default visual matches. + Visual* defaultVisual = DefaultVisualOfScreen(aScreen); + if (aVisual == defaultVisual) { + *aColormap = DefaultColormapOfScreen(aScreen); + *aVisualForColormap = defaultVisual; + return true; + } + + // Only supporting TrueColor non-default visuals + if (!aVisual || aVisual->c_class != TrueColor) return false; + + if (!sDisplayTable) { + sDisplayTable = new DisplayTable(); + } + + nsTArray<DisplayInfo>* displays = &sDisplayTable->mDisplays; + size_t d = displays->IndexOf(display, 0, FindDisplay()); + + if (d == displays->NoIndex) { + d = displays->Length(); + // Register for notification of display closing, when this info + // becomes invalid. + XExtCodes* codes = XAddExtension(display); + if (!codes) return false; + + XESetCloseDisplay(display, codes->extension, DisplayClosing); + // Add a new DisplayInfo. + displays->AppendElement(display); + } + + nsTArray<ColormapEntry>* entries = &displays->ElementAt(d).mColormapEntries; + + // Only a small number of formats are expected to be used, so just do a + // simple linear search. + for (uint32_t i = 0; i < entries->Length(); ++i) { + const ColormapEntry& entry = entries->ElementAt(i); + if (aVisual == entry.mVisual) { + *aColormap = entry.mColormap; + *aVisualForColormap = entry.mVisual; + return true; + } + } + + // No existing entry. Create a colormap and add an entry. + Colormap colormap = + XCreateColormap(display, RootWindowOfScreen(aScreen), aVisual, AllocNone); + ColormapEntry* newEntry = entries->AppendElement(); + newEntry->mScreen = aScreen; + newEntry->mVisual = aVisual; + newEntry->mColormap = colormap; + + *aColormap = colormap; + *aVisualForColormap = aVisual; + return true; +} + +/* static */ +int DisplayTable::DisplayClosing(Display* display, XExtCodes* codes) { + // No need to free the colormaps explicitly as they will be released when + // the connection is closed. + sDisplayTable->mDisplays.RemoveElement(display, FindDisplay()); + if (sDisplayTable->mDisplays.Length() == 0) { + delete sDisplayTable; + sDisplayTable = nullptr; + } + return 0; +} + +/* static */ +bool gfxXlibSurface::GetColormapAndVisual(cairo_surface_t* aXlibSurface, + Colormap* aColormap, + Visual** aVisual) { + Screen* screen = cairo_xlib_surface_get_screen(aXlibSurface); + Visual* visual = cairo_xlib_surface_get_visual(aXlibSurface); + + return DisplayTable::GetColormapAndVisual(screen, visual, aColormap, aVisual); +} + +bool gfxXlibSurface::GetColormapAndVisual(Colormap* aColormap, + Visual** aVisual) { + if (!mSurfaceValid) return false; + + return GetColormapAndVisual(CairoSurface(), aColormap, aVisual); +} + +/* static */ +int gfxXlibSurface::DepthOfVisual(const Screen* screen, const Visual* visual) { + for (int d = 0; d < screen->ndepths; d++) { + const Depth& d_info = screen->depths[d]; + if (visual >= &d_info.visuals[0] && + visual < &d_info.visuals[d_info.nvisuals]) + return d_info.depth; + } + + NS_ERROR("Visual not on Screen."); + return 0; +} + +/* static */ +Visual* gfxXlibSurface::FindVisual(Screen* screen, gfxImageFormat format) { + int depth; + unsigned long red_mask, green_mask, blue_mask; + switch (format) { + case gfx::SurfaceFormat::A8R8G8B8_UINT32: + depth = 32; + red_mask = 0xff0000; + green_mask = 0xff00; + blue_mask = 0xff; + break; + case gfx::SurfaceFormat::X8R8G8B8_UINT32: + depth = 24; + red_mask = 0xff0000; + green_mask = 0xff00; + blue_mask = 0xff; + break; + case gfx::SurfaceFormat::R5G6B5_UINT16: + depth = 16; + red_mask = 0xf800; + green_mask = 0x7e0; + blue_mask = 0x1f; + break; + case gfx::SurfaceFormat::A8: + default: + return nullptr; + } + + for (int d = 0; d < screen->ndepths; d++) { + const Depth& d_info = screen->depths[d]; + if (d_info.depth != depth) continue; + + for (int v = 0; v < d_info.nvisuals; v++) { + Visual* visual = &d_info.visuals[v]; + + if (visual->c_class == TrueColor && visual->red_mask == red_mask && + visual->green_mask == green_mask && visual->blue_mask == blue_mask) + return visual; + } + } + + return nullptr; +} + +Screen* gfxXlibSurface::XScreen() { + return cairo_xlib_surface_get_screen(CairoSurface()); +} diff --git a/gfx/thebes/gfxXlibSurface.h b/gfx/thebes/gfxXlibSurface.h new file mode 100644 index 0000000000..17f2b76782 --- /dev/null +++ b/gfx/thebes/gfxXlibSurface.h @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_XLIBSURFACE_H +#define GFX_XLIBSURFACE_H + +#include "gfxASurface.h" + +#include <X11/Xlib.h> +#include "X11UndefineNone.h" + +#include "GLXLibrary.h" +#include "mozilla/gfx/XlibDisplay.h" + +#include "nsSize.h" + +// Although the dimension parameters in the xCreatePixmapReq wire protocol are +// 16-bit unsigned integers, the server's CreatePixmap returns BadAlloc if +// either dimension cannot be represented by a 16-bit *signed* integer. +#define XLIB_IMAGE_SIDE_SIZE_LIMIT 0x7fff + +class gfxXlibSurface final : public gfxASurface { + public: + // construct a wrapper around the specified drawable with dpy/visual. + // Will use XGetGeometry to query the window/pixmap size. + gfxXlibSurface(Display* dpy, Drawable drawable, Visual* visual); + + // construct a wrapper around the specified drawable with dpy/visual, + // and known width/height. + gfxXlibSurface(Display* dpy, Drawable drawable, Visual* visual, + const mozilla::gfx::IntSize& size); + gfxXlibSurface(const std::shared_ptr<mozilla::gfx::XlibDisplay>& dpy, + Drawable drawable, Visual* visual, + const mozilla::gfx::IntSize& size); + + explicit gfxXlibSurface(cairo_surface_t* csurf); + + // create a new Pixmap and wrapper surface. + // |relatedDrawable| provides a hint to the server for determining whether + // the pixmap should be in video or system memory. It must be on + // |screen| (if specified). + static already_AddRefed<gfxXlibSurface> Create( + ::Screen* screen, Visual* visual, const mozilla::gfx::IntSize& size, + Drawable relatedDrawable = X11None); + static already_AddRefed<gfxXlibSurface> Create( + const std::shared_ptr<mozilla::gfx::XlibDisplay>& display, + ::Screen* screen, Visual* visual, const mozilla::gfx::IntSize& size, + Drawable relatedDrawable = X11None); + static cairo_surface_t* CreateCairoSurface( + ::Screen* screen, Visual* visual, const mozilla::gfx::IntSize& size, + Drawable relatedDrawable = X11None); + + virtual ~gfxXlibSurface(); + + void Finish() override; + + const mozilla::gfx::IntSize GetSize() const override; + + Display* XDisplay() { return *mDisplay; } + ::Screen* XScreen(); + Drawable XDrawable() { return mDrawable; } + + static int DepthOfVisual(const ::Screen* screen, const Visual* visual); + static Visual* FindVisual(::Screen* screen, gfxImageFormat format); + static bool GetColormapAndVisual(cairo_surface_t* aXlibSurface, + Colormap* colormap, Visual** visual); + + // take ownership of a passed-in Pixmap, calling XFreePixmap on it + // when the gfxXlibSurface is destroyed. + void TakePixmap(); + + // Release ownership of this surface's Pixmap. This is only valid + // on gfxXlibSurfaces for which the user called TakePixmap(), or + // on those created by a Create() factory method. + Drawable ReleasePixmap(); + + // Find a visual and colormap pair suitable for rendering to this surface. + bool GetColormapAndVisual(Colormap* colormap, Visual** visual); + + // Return true if cairo will take its slow path when this surface is used + // in a pattern with EXTEND_PAD. As a workaround for XRender's RepeatPad + // not being implemented correctly on old X servers, cairo avoids XRender + // and instead reads back to perform EXTEND_PAD with pixman. Cairo does + // this for servers older than xorg-server 1.7. + bool IsPadSlow() { + // The test here matches that for buggy_pad_reflect in + // _cairo_xlib_device_create. + return VendorRelease(mDisplay->get()) >= 60700000 || + VendorRelease(mDisplay->get()) < 10699000; + } + + protected: + // if TakePixmap() has been called on this + bool mPixmapTaken; + + std::shared_ptr<mozilla::gfx::XlibDisplay> mDisplay; + Drawable mDrawable; + + const mozilla::gfx::IntSize DoSizeQuery(); +}; + +#endif /* GFX_XLIBSURFACE_H */ diff --git a/gfx/thebes/moz.build b/gfx/thebes/moz.build new file mode 100644 index 0000000000..fd1fcf236d --- /dev/null +++ b/gfx/thebes/moz.build @@ -0,0 +1,300 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("*Text*"): + BUG_COMPONENT = ("Core", "Graphics: Text") + +with Files("*DWrite*"): + BUG_COMPONENT = ("Core", "Graphics: Text") + +XPIDL_SOURCES += [ + "nsIFontLoadCompleteCallback.idl", +] + +XPIDL_MODULE = "gfx" + +EXPORTS += [ + "COLRFonts.h", + "DrawMode.h", + "gfx2DGlue.h", + "gfxAlphaRecovery.h", + "gfxASurface.h", + "gfxBaseSharedMemorySurface.h", + "gfxBlur.h", + "gfxColor.h", + "gfxContext.h", + "gfxDrawable.h", + "gfxEnv.h", + "gfxFailure.h", + "gfxFont.h", + "gfxFontConstants.h", + "gfxFontEntry.h", + "gfxFontFeatures.h", + "gfxFontInfoLoader.h", + "gfxFontPrefLangList.h", + "gfxFontSrcPrincipal.h", + "gfxFontSrcURI.h", + "gfxFontUtils.h", + "gfxFontVariations.h", + "gfxGradientCache.h", + "gfxImageSurface.h", + "gfxLineSegment.h", + "gfxMathTable.h", + "gfxMatrix.h", + "gfxOTSUtils.h", + "gfxPattern.h", + "gfxPlatform.h", + "gfxPlatformFontList.h", + "gfxPlatformWorker.h", + "gfxPoint.h", + "gfxQuad.h", + "gfxQuaternion.h", + "gfxRect.h", + "gfxSharedImageSurface.h", + "gfxSkipChars.h", + "gfxSVGGlyphs.h", + "gfxTextRun.h", + "gfxTypes.h", + "gfxUserFontSet.h", + "gfxUtils.h", + "SharedFontList.h", + "SoftwareVsyncSource.h", + "ThebesRLBoxTypes.h", + "VsyncSource.h", +] + +EXPORTS.mozilla.gfx += [ + "D3D11Checks.h", + "DeviceManagerDx.h", + "DisplayConfigWindows.h", + "FontPaletteCache.h", + "PrintPromise.h", + "PrintTarget.h", + "PrintTargetThebes.h", + "ThebesRLBox.h", +] + +EXPORTS.mozilla.gfx += ["SkMemoryReporter.h"] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android": + EXPORTS += [ + "gfxAndroidPlatform.h", + "gfxFT2FontBase.h", + "gfxFT2Fonts.h", + ] + EXPORTS.mozilla.gfx += [ + "PrintTargetPDF.h", + ] + SOURCES += [ + "gfxAndroidPlatform.cpp", + "gfxFT2FontBase.cpp", + "gfxFT2FontList.cpp", + "gfxFT2Fonts.cpp", + "gfxFT2Utils.cpp", + "PrintTargetPDF.cpp", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + EXPORTS += [ + "gfxMacUtils.h", + "gfxPlatformMac.h", + "gfxQuartzNativeDrawing.h", + "gfxQuartzSurface.h", + ] + EXPORTS.mozilla.gfx += [ + "PrintTargetCG.h", + ] + SOURCES += [ + "CoreTextFontList.cpp", + "gfxCoreTextShaper.cpp", + "gfxMacFont.cpp", + "gfxMacUtils.cpp", + "gfxPlatformMac.cpp", + "gfxQuartzNativeDrawing.cpp", + "gfxQuartzSurface.cpp", + "PrintTargetCG.mm", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + EXPORTS += [ + "gfxFT2FontBase.h", + "gfxPlatformGtk.h", + ] + EXPORTS.mozilla.gfx += [ + "PrintTargetPDF.h", + ] + SOURCES += [ + "gfxFcPlatformFontList.cpp", + "gfxFT2FontBase.cpp", + "gfxFT2Utils.cpp", + "gfxPlatformGtk.cpp", + "PrintTargetPDF.cpp", + ] + + if CONFIG["MOZ_X11"]: + EXPORTS += [ + "gfxXlibSurface.h", + ] + EXPORTS.mozilla.gfx += [ + "XlibDisplay.h", + ] + SOURCES += [ + "gfxXlibSurface.cpp", + "XlibDisplay.cpp", + ] + +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": + EXPORTS += [ + "gfxDWriteCommon.h", + "gfxDWriteFonts.h", + "gfxGDIFont.h", + "gfxGDIFontList.h", + "gfxWindowsNativeDrawing.h", + "gfxWindowsPlatform.h", + "gfxWindowsSurface.h", + ] + EXPORTS.mozilla.gfx += [ + "PrintTargetPDF.h", + "PrintTargetWindows.h", + ] + SOURCES += [ + "DisplayConfigWindows.cpp", + "gfxDWriteCommon.cpp", + "gfxDWriteFonts.cpp", + "gfxGDIFont.cpp", + "gfxGDIFontList.cpp", + "gfxWindowsNativeDrawing.cpp", + "gfxWindowsPlatform.cpp", + "gfxWindowsSurface.cpp", + "PrintTargetPDF.cpp", + "PrintTargetWindows.cpp", + ] + UNIFIED_SOURCES += [ + "gfxDWriteFontList.cpp", + ] + +# Are we targeting x86 or x64? If so, build gfxAlphaRecoverySSE2.cpp. +if CONFIG["INTEL_ARCHITECTURE"]: + SOURCES += ["gfxAlphaRecoverySSE2.cpp"] + # The file uses SSE2 intrinsics, so it needs special compile flags on some + # compilers. + SOURCES["gfxAlphaRecoverySSE2.cpp"].flags += CONFIG["SSE2_FLAGS"] + +SOURCES += [ + # Includes mac system header conflicting with point/size, + # and includes glxXlibSurface.h which drags in Xrender.h + "gfxASurface.cpp", + # on X11, gfxDrawable.cpp includes X headers for an old workaround which + # we could consider removing soon (affects Ubuntus older than 10.04 LTS) + # which currently prevent it from joining UNIFIED_SOURCES. + "gfxDrawable.cpp", + # gfxFontUtils.cpp and gfxPlatform.cpp include mac system header conflicting with point/size + "gfxFontUtils.cpp", + "gfxPlatform.cpp", + "PrintTarget.cpp", + "PrintTargetThebes.cpp", +] + +UNIFIED_SOURCES += [ + "CJKCompatSVS.cpp", + "COLRFonts.cpp", + "FontPaletteCache.cpp", + "gfxAlphaRecovery.cpp", + "gfxBaseSharedMemorySurface.cpp", + "gfxBlur.cpp", + "gfxContext.cpp", + "gfxFont.cpp", + "gfxFontEntry.cpp", + "gfxFontFeatures.cpp", + "gfxFontInfoLoader.cpp", + "gfxFontMissingGlyphs.cpp", + "gfxFontSrcPrincipal.cpp", + "gfxFontSrcURI.cpp", + "gfxGlyphExtents.cpp", + "gfxGradientCache.cpp", + "gfxGraphiteShaper.cpp", + "gfxHarfBuzzShaper.cpp", + "gfxImageSurface.cpp", + "gfxMathTable.cpp", + "gfxPattern.cpp", + "gfxPlatformFontList.cpp", + "gfxPlatformWorker.cpp", + "gfxScriptItemizer.cpp", + "gfxSkipChars.cpp", + "gfxSVGGlyphs.cpp", + "gfxTextRun.cpp", + "gfxUserFontSet.cpp", + "gfxUtils.cpp", + "SharedFontList.cpp", + "SoftwareVsyncSource.cpp", + "VsyncSource.cpp", +] + +UNIFIED_SOURCES += [ + "SkMemoryReporter.cpp", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + UNIFIED_SOURCES += [ + "gfxMacPlatformFontList.mm", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": + UNIFIED_SOURCES += [ + "D3D11Checks.cpp", + "DeviceManagerDx.cpp", + ] + +if CONFIG["MOZ_ENABLE_SKIA_PDF"]: + EXPORTS.mozilla.gfx += [ + "PrintTargetSkPDF.h", + ] + SOURCES += [ + "PrintTargetSkPDF.cpp", + ] + +# We use ICU for normalization functions: +USE_LIBS += [ + "icu", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "!/security/rlbox", + "/dom/base", + "/dom/media/platforms/apple", + "/dom/xml", + "/gfx/cairo/cairo/src", + "/third_party/xsimd/include", + "/widget/gtk", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("android", "gtk"): + DEFINES["MOZ_ENABLE_FREETYPE"] = True + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": + for var in ("MOZ_ENABLE_D3D10_LAYER",): + if CONFIG[var]: + DEFINES[var] = True + +if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("android"): + CXXFLAGS += CONFIG["CAIRO_FT_CFLAGS"] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] + CFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] + CXXFLAGS += CONFIG["MOZ_PANGO_CFLAGS"] + +if CONFIG["MOZ_WAYLAND"]: + CXXFLAGS += CONFIG["MOZ_WAYLAND_CFLAGS"] + +LOCAL_INCLUDES += CONFIG["SKIA_INCLUDES"] + +DEFINES["GRAPHITE2_STATIC"] = True + +CXXFLAGS += ["-Werror=switch"] + +include("/tools/fuzzing/libfuzzer-config.mozbuild") diff --git a/gfx/thebes/nsIFontLoadCompleteCallback.idl b/gfx/thebes/nsIFontLoadCompleteCallback.idl new file mode 100644 index 0000000000..4f1df74a79 --- /dev/null +++ b/gfx/thebes/nsIFontLoadCompleteCallback.idl @@ -0,0 +1,13 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[uuid(302dbf09-079b-4648-8a06-a0486c1749c0)] +interface nsIFontLoadCompleteCallback : nsISupports +{ + void fontLoadComplete(); +}; |