| /* | |
| * Text analyzer | |
| * | |
| * Copyright 2011 Aric Stewart for CodeWeavers | |
| * Copyright 2012, 2014 Nikolay Sivov for CodeWeavers | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 of the License, or (at your option) any later version. | |
| * | |
| * This library is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | |
| */ | |
| #define COBJMACROS | |
| #include <math.h> | |
| #include "dwrite_private.h" | |
| #include "scripts.h" | |
| WINE_DEFAULT_DEBUG_CHANNEL(dwrite); | |
| extern const unsigned short wine_linebreak_table[]; | |
| extern const unsigned short wine_scripts_table[]; | |
| extern const unsigned short bidi_direction_table[]; | |
| /* Number of characters needed for LOCALE_SNATIVEDIGITS */ | |
| #define NATIVE_DIGITS_LEN 11 | |
| struct dwritescript_properties | |
| { | |
| DWRITE_SCRIPT_PROPERTIES props; | |
| UINT32 scripttags[3]; /* Maximum 2 script tags, 0-terminated. */ | |
| }; | |
| #define _OT(a,b,c,d) DWRITE_MAKE_OPENTYPE_TAG(a,b,c,d) | |
| /* NOTE: keep this array synced with script ids from scripts.h */ | |
| static const struct dwritescript_properties dwritescripts_properties[Script_LastId+1] = { | |
| { /* Zzzz */ { 0x7a7a7a5a, 999, 15, 0x0020, 0, 0, 0, 0, 0, 0, 0 } }, | |
| { /* Zyyy */ { 0x7979795a, 998, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 } }, | |
| { /* Zinh */ { 0x686e695a, 994, 15, 0x0020, 1, 0, 0, 0, 0, 0, 0 } }, | |
| { /* Arab */ { 0x62617241, 160, 8, 0x0640, 0, 1, 0, 0, 0, 1, 1 }, { _OT('a','r','a','b') } }, | |
| { /* Armn */ { 0x6e6d7241, 230, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('a','r','m','n') } }, | |
| { /* Avst */ { 0x74737641, 134, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('a','v','s','t') } }, | |
| { /* Bali */ { 0x696c6142, 360, 15, 0x0020, 1, 0, 1, 0, 0, 0, 0 }, { _OT('b','a','l','i') } }, | |
| { /* Bamu */ { 0x756d6142, 435, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('b','a','m','u') } }, | |
| { /* Batk */ { 0x6b746142, 365, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('b','a','t','k') } }, | |
| { /* Beng */ { 0x676e6542, 325, 15, 0x0020, 1, 1, 0, 0, 0, 1, 0 }, { _OT('b','n','g','2'), _OT('b','e','n','g') } }, | |
| { /* Bopo */ { 0x6f706f42, 285, 1, 0x0020, 0, 0, 1, 1, 0, 0, 0 }, { _OT('b','o','p','o') } }, | |
| { /* Brah */ { 0x68617242, 300, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('b','r','a','h') } }, | |
| { /* Brai */ { 0x69617242, 570, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('b','r','a','i') } }, | |
| { /* Bugi */ { 0x69677542, 367, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('b','u','g','i') } }, | |
| { /* Buhd */ { 0x64687542, 372, 8, 0x0020, 0, 0, 1, 0, 0, 0, 0 }, { _OT('b','u','h','d') } }, | |
| { /* Cans */ { 0x736e6143, 440, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('c','a','n','s') } }, | |
| { /* Cari */ { 0x69726143, 201, 1, 0x0020, 0, 0, 1, 0, 0, 0, 0 }, { _OT('c','a','r','i') } }, | |
| { /* Cham */ { 0x6d616843, 358, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('c','h','a','m') } }, | |
| { /* Cher */ { 0x72656843, 445, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('c','h','e','r') } }, | |
| { /* Copt */ { 0x74706f43, 204, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('c','o','p','t') } }, | |
| { /* Xsux */ { 0x78757358, 20, 1, 0x0020, 0, 0, 1, 1, 0, 0, 0 }, { _OT('x','s','u','x') } }, | |
| { /* Cprt */ { 0x74727043, 403, 1, 0x0020, 0, 0, 1, 0, 0, 0, 0 }, { _OT('c','p','r','t') } }, | |
| { /* Cyrl */ { 0x6c727943, 220, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('c','y','r','l') } }, | |
| { /* Dsrt */ { 0x74727344, 250, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('d','s','r','t') } }, | |
| { /* Deva */ { 0x61766544, 315, 15, 0x0020, 1, 1, 0, 0, 0, 1, 0 }, { _OT('d','e','v','2'), _OT('d','e','v','a') } }, | |
| { /* Egyp */ { 0x70796745, 50, 1, 0x0020, 0, 0, 1, 1, 0, 0, 0 }, { _OT('e','g','y','p') } }, | |
| { /* Ethi */ { 0x69687445, 430, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('e','t','h','i') } }, | |
| { /* Geor */ { 0x726f6547, 240, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('g','e','o','r') } }, | |
| { /* Glag */ { 0x67616c47, 225, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('g','l','a','g') } }, | |
| { /* Goth */ { 0x68746f47, 206, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('g','o','t','h') } }, | |
| { /* Grek */ { 0x6b657247, 200, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('g','r','e','k') } }, | |
| { /* Gujr */ { 0x726a7547, 320, 15, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('g','j','r','2'), _OT('g','u','j','r') } }, | |
| { /* Guru */ { 0x75727547, 310, 15, 0x0020, 1, 1, 0, 0, 0, 1, 0 }, { _OT('g','u','r','2'), _OT('g','u','r','u') } }, | |
| { /* Hani */ { 0x696e6148, 500, 8, 0x0020, 0, 0, 1, 1, 0, 0, 0 }, { _OT('h','a','n','i') } }, | |
| { /* Hang */ { 0x676e6148, 286, 8, 0x0020, 1, 1, 1, 1, 0, 0, 0 }, { _OT('h','a','n','g') } }, | |
| { /* Hano */ { 0x6f6e6148, 371, 8, 0x0020, 0, 0, 1, 0, 0, 0, 0 }, { _OT('h','a','n','o') } }, | |
| { /* Hebr */ { 0x72626548, 125, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('h','e','b','r') } }, | |
| { /* Hira */ { 0x61726948, 410, 8, 0x0020, 0, 0, 1, 1, 0, 0, 0 }, { _OT('k','a','n','a') } }, | |
| { /* Armi */ { 0x696d7241, 124, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('a','r','m','i') } }, | |
| { /* Phli */ { 0x696c6850, 131, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('p','h','l','i') } }, | |
| { /* Prti */ { 0x69747250, 130, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('p','r','t','i') } }, | |
| { /* Java */ { 0x6176614a, 361, 15, 0x0020, 1, 0, 1, 0, 0, 0, 0 }, { _OT('j','a','v','a') } }, | |
| { /* Kthi */ { 0x6968744b, 317, 15, 0x0020, 1, 1, 0, 0, 0, 1, 0 }, { _OT('k','t','h','i') } }, | |
| { /* Knda */ { 0x61646e4b, 345, 15, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('k','n','d','2'), _OT('k','n','d','a') } }, | |
| { /* Kana */ { 0x616e614b, 411, 8, 0x0020, 0, 0, 1, 1, 0, 0, 0 }, { _OT('k','a','n','a') } }, | |
| { /* Kali */ { 0x696c614b, 357, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('k','a','l','i') } }, | |
| { /* Khar */ { 0x7261684b, 305, 15, 0x0020, 1, 0, 1, 0, 0, 0, 0 }, { _OT('k','h','a','r') } }, | |
| { /* Khmr */ { 0x726d684b, 355, 8, 0x0020, 1, 0, 1, 0, 1, 0, 0 }, { _OT('k','h','m','r') } }, | |
| { /* Laoo */ { 0x6f6f614c, 356, 8, 0x0020, 1, 0, 1, 0, 1, 0, 0 }, { _OT('l','a','o',' ') } }, | |
| { /* Latn */ { 0x6e74614c, 215, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('l','a','t','n') } }, | |
| { /* Lepc */ { 0x6370654c, 335, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('l','e','p','c') } }, | |
| { /* Limb */ { 0x626d694c, 336, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('l','i','m','b') } }, | |
| { /* Linb */ { 0x626e694c, 401, 1, 0x0020, 0, 0, 1, 1, 0, 0, 0 }, { _OT('l','i','n','b') } }, | |
| { /* Lisu */ { 0x7573694c, 399, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('l','i','s','u') } }, | |
| { /* Lyci */ { 0x6963794c, 202, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('l','y','c','i') } }, | |
| { /* Lydi */ { 0x6964794c, 116, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('l','y','d','i') } }, | |
| { /* Mlym */ { 0x6d796c4d, 347, 15, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('m','l','m','2'), _OT('m','l','y','m') } }, | |
| { /* Mand */ { 0x646e614d, 140, 8, 0x0640, 0, 1, 0, 0, 0, 1, 1 }, { _OT('m','a','n','d') } }, | |
| { /* Mtei */ { 0x6965744d, 337, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('m','t','e','i') } }, | |
| { /* Mong */ { 0x676e6f4d, 145, 8, 0x0020, 0, 1, 0, 0, 0, 1, 1 }, { _OT('m','o','n','g') } }, | |
| { /* Mymr */ { 0x726d794d, 350, 15, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('m','y','m','r') } }, | |
| { /* Talu */ { 0x756c6154, 354, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('t','a','l','u') } }, | |
| { /* Nkoo */ { 0x6f6f6b4e, 165, 8, 0x0020, 0, 1, 0, 0, 0, 1, 1 }, { _OT('n','k','o',' ') } }, | |
| { /* Ogam */ { 0x6d61674f, 212, 1, 0x1680, 0, 1, 0, 0, 0, 1, 0 }, { _OT('o','g','a','m') } }, | |
| { /* Olck */ { 0x6b636c4f, 261, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('o','l','c','k') } }, | |
| { /* Ital */ { 0x6c617449, 210, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('i','t','a','l') } }, | |
| { /* Xpeo */ { 0x6f657058, 30, 1, 0x0020, 0, 1, 1, 1, 0, 0, 0 }, { _OT('x','p','e','o') } }, | |
| { /* Sarb */ { 0x62726153, 105, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('s','a','r','b') } }, | |
| { /* Orkh */ { 0x686b724f, 175, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('o','r','k','h') } }, | |
| { /* Orya */ { 0x6179724f, 327, 15, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('o','r','y','2'), _OT('o','r','y','a') } }, | |
| { /* Osma */ { 0x616d734f, 260, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('o','s','m','a') } }, | |
| { /* Phag */ { 0x67616850, 331, 8, 0x0020, 0, 1, 0, 0, 0, 1, 1 }, { _OT('p','h','a','g') } }, | |
| { /* Phnx */ { 0x786e6850, 115, 1, 0x0020, 0, 0, 1, 0, 0, 0, 0 }, { _OT('p','h','n','x') } }, | |
| { /* Rjng */ { 0x676e6a52, 363, 8, 0x0020, 1, 0, 1, 0, 0, 0, 0 }, { _OT('r','j','n','g') } }, | |
| { /* Runr */ { 0x726e7552, 211, 1, 0x0020, 0, 0, 1, 0, 0, 0, 0 }, { _OT('r','u','n','r') } }, | |
| { /* Samr */ { 0x726d6153, 123, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('s','a','m','r') } }, | |
| { /* Saur */ { 0x72756153, 344, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('s','a','u','r') } }, | |
| { /* Shaw */ { 0x77616853, 281, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('s','h','a','w') } }, | |
| { /* Sinh */ { 0x686e6953, 348, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('s','i','n','h') } }, | |
| { /* Sund */ { 0x646e7553, 362, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('s','u','n','d') } }, | |
| { /* Sylo */ { 0x6f6c7953, 316, 8, 0x0020, 1, 1, 0, 0, 0, 1, 0 }, { _OT('s','y','l','o') } }, | |
| { /* Syrc */ { 0x63727953, 135, 8, 0x0640, 0, 1, 0, 0, 0, 1, 1 }, { _OT('s','y','r','c') } }, | |
| { /* Tglg */ { 0x676c6754, 370, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('t','g','l','g') } }, | |
| { /* Tagb */ { 0x62676154, 373, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('t','a','g','b') } }, | |
| { /* Tale */ { 0x656c6154, 353, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('t','a','l','e') } }, | |
| { /* Lana */ { 0x616e614c, 351, 8, 0x0020, 1, 0, 1, 0, 0, 0, 0 }, { _OT('l','a','n','a') } }, | |
| { /* Tavt */ { 0x74766154, 359, 8, 0x0020, 1, 0, 1, 0, 1, 0, 0 }, { _OT('t','a','v','t') } }, | |
| { /* Taml */ { 0x6c6d6154, 346, 15, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('t','m','l','2'), _OT('t','a','m','l') } }, | |
| { /* Telu */ { 0x756c6554, 340, 15, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('t','e','l','2'), _OT('t','e','l','u') } }, | |
| { /* Thaa */ { 0x61616854, 170, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('t','h','a','a') } }, | |
| { /* Thai */ { 0x69616854, 352, 8, 0x0020, 1, 0, 1, 0, 1, 0, 0 }, { _OT('t','h','a','i') } }, | |
| { /* Tibt */ { 0x74626954, 330, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('t','i','b','t') } }, | |
| { /* Tfng */ { 0x676e6654, 120, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('t','f','n','g') } }, | |
| { /* Ugar */ { 0x72616755, 40, 1, 0x0020, 0, 0, 1, 1, 0, 0, 0 }, { _OT('u','g','a','r') } }, | |
| { /* Vaii */ { 0x69696156, 470, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('v','a','i',' ') } }, | |
| { /* Yiii */ { 0x69696959, 460, 1, 0x0020, 0, 0, 1, 1, 0, 0, 0 }, { _OT('y','i',' ',' ') } }, | |
| { /* Cakm */ { 0x6d6b6143, 349, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('c','a','k','m') } }, | |
| { /* Merc */ { 0x6372654d, 101, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('m','e','r','c') } }, | |
| { /* Mero */ { 0x6f72654d, 100, 1, 0x0020, 0, 1, 1, 1, 0, 0, 0 }, { _OT('m','e','r','o') } }, | |
| { /* Plrd */ { 0x64726c50, 282, 8, 0x0020, 1, 0, 1, 0, 0, 0, 0 }, { _OT('p','l','r','d') } }, | |
| { /* Shrd */ { 0x64726853, 319, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('s','h','r','d') } }, | |
| { /* Sora */ { 0x61726f53, 398, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('s','o','r','a') } }, | |
| { /* Takr */ { 0x726b6154, 321, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('t','a','k','r') } }, | |
| { /* Bass */ { 0x73736142, 259, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('b','a','s','s') } }, | |
| { /* Aghb */ { 0x62686741, 239, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('a','g','h','b') } }, | |
| { /* Dupl */ { 0x6c707544, 755, 8, 0x0020, 0, 1, 0, 0, 0, 1, 1 }, { _OT('d','u','p','l') } }, | |
| { /* Elba */ { 0x61626c45, 226, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('e','l','b','a') } }, | |
| { /* Gran */ { 0x6e617247, 343, 15, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('g','r','a','n') } }, | |
| { /* Khoj */ { 0x6a6f684b, 322, 15, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('k','h','o','j') } }, | |
| { /* Sind */ { 0x646e6953, 318, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('s','i','n','d') } }, | |
| { /* Lina */ { 0x616e694c, 400, 1, 0x0020, 0, 0, 1, 1, 0, 0, 0 }, { _OT('l','i','n','a') } }, | |
| { /* Mahj */ { 0x6a68614d, 314, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('m','a','h','j') } }, | |
| { /* Mani */ { 0x696e614d, 139, 8, 0x0640, 0, 1, 0, 0, 0, 1, 1 }, { _OT('m','a','n','i') } }, | |
| { /* Mend */ { 0x646e654d, 438, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('m','e','n','d') } }, | |
| { /* Modi */ { 0x69646f4d, 324, 15, 0x0020, 1, 1, 0, 0, 0, 1, 0 }, { _OT('m','o','d','i') } }, | |
| { /* Mroo */ { 0x6f6f724d, 199, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('m','r','o','o') } }, | |
| { /* Nbat */ { 0x7461624e, 159, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('n','b','a','t') } }, | |
| { /* Narb */ { 0x6272614e, 106, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('n','a','r','b') } }, | |
| { /* Perm */ { 0x6d726550, 227, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('p','e','r','m') } }, | |
| { /* Hmng */ { 0x676e6d48, 450, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('h','m','n','g') } }, | |
| { /* Palm */ { 0x6d6c6150, 126, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('p','a','l','m') } }, | |
| { /* Pauc */ { 0x63756150, 263, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('p','a','u','c') } }, | |
| { /* Phlp */ { 0x706c6850, 132, 8, 0x0640, 0, 1, 0, 0, 0, 1, 1 }, { _OT('p','h','l','p') } }, | |
| { /* Sidd */ { 0x64646953, 302, 8, 0x0020, 1, 0, 1, 1, 0, 0, 0 }, { _OT('s','i','d','d') } }, | |
| { /* Tirh */ { 0x68726954, 326, 15, 0x0020, 1, 1, 0, 0, 0, 1, 0 }, { _OT('t','i','r','h') } }, | |
| { /* Wara */ { 0x61726157, 262, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('w','a','r','a') } }, | |
| { /* Adlm */ { 0x6d6c6441, 166, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('a','d','l','m') } }, | |
| { /* Ahom */ { 0x6d6f6841, 338, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('a','h','o','m') } }, | |
| { /* Hluw */ { 0x77756c48, 80, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('h','l','u','w') } }, | |
| { /* Bhks */ { 0x736b6842, 334, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('b','h','k','s') } }, | |
| { /* Hatr */ { 0x72746148, 127, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('h','a','t','r') } }, | |
| { /* Marc */ { 0x6372614d, 332, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('m','a','r','c') } }, | |
| { /* Mult */ { 0x746c754d, 323, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('m','u','l','t') } }, | |
| { /* Newa */ { 0x6177654e, 333, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('n','e','w','a') } }, | |
| { /* Hung */ { 0x676e7548, 176, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('h','u','n','g') } }, | |
| { /* Osge */ { 0x6567734f, 219, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('o','s','g','e') } }, | |
| { /* Sgnw */ { 0x776e6753, 95, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('s','g','n','w') } }, | |
| { /* Tang */ { 0x676e6154, 520, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('t','a','n','g') } }, | |
| { /* Gonm */ { 0x6d6e6f47, 313, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('g','o','n','m') } }, | |
| { /* Nshu */ { 0x7568734e, 499, 1, 0x0020, 0, 0, 1, 1, 0, 0, 0 }, { _OT('n','s','h','u') } }, | |
| { /* Soyo */ { 0x6f796f53, 329, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('s','o','y','o') } }, | |
| { /* Zanb */ { 0x626e615a, 339, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('z','a','n','b') } }, | |
| { /* Dogr */ { 0x72676f44, 328, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('d','o','g','r') } }, | |
| { /* Gong */ { 0x676e6f47, 312, 8, 0x0020, 1, 1, 0, 0, 0, 1, 0 }, { _OT('g','o','n','g') } }, | |
| { /* Rohg */ { 0x67686f52, 167, 8, 0x0020, 0, 1, 0, 0, 0, 1, 1 }, { _OT('r','o','h','g') } }, | |
| { /* Maka */ { 0x616b614d, 366, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('m','a','k','a') } }, | |
| { /* Medf */ { 0x6664654d, 265, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('m','e','d','f') } }, | |
| { /* Sogo */ { 0x6f676f53, 142, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('s','o','g','o') } }, | |
| { /* Sogd */ { 0x64676f53, 141, 8, 0x0020, 1, 1, 0, 0, 0, 1, 1 }, { _OT('s','o','g','d') } }, | |
| { /* Elym */ { 0x6d796c45, 128, 1, 0x0020, 0, 0, 1, 0, 0, 0, 0 }, { _OT('e','l','y','m') } }, | |
| { /* Hmnp */ { 0x706e6d48, 451, 8, 0x0020, 1, 1, 0, 0, 0, 0, 0 }, { _OT('h','m','n','p') } }, | |
| { /* Nand */ { 0x646e614e, 311, 8, 0x0020, 1, 1, 0, 0, 0, 1, 0 }, { _OT('n','a','n','d') } }, | |
| { /* Wcho */ { 0x6f686357, 283, 8, 0x0020, 1, 1, 0, 0, 0, 0, 0 }, { _OT('w','c','h','o') } }, | |
| { /* Chrs */ { 0x73726843, 109, 8, 0x0020, 0, 1, 0, 0, 0, 1, 1 }, { _OT('c','h','r','s') } }, | |
| { /* Diak */ { 0x6b616944, 342, 8, 0x0020, 1, 1, 0, 0, 0, 0, 0 }, { _OT('d','i','a','k') } }, | |
| { /* Kits */ { 0x7374694b, 288, 8, 0x0020, 1, 0, 1, 1, 0, 0, 0 }, { _OT('k','i','t','s') } }, | |
| { /* Yezi */ { 0x697a6559, 192, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('y','e','z','i') } }, | |
| { /* Cpmn */ { 0x6e6d7043, 402, 8, 0x0020, 0, 0, 1, 1, 0, 0, 0 }, { _OT('c','p','m','n') } }, | |
| { /* Kawi */ { 0x6977614b, 368, 15, 0x0020, 1, 0, 1, 0, 0, 0, 0 }, { _OT('k','a','w','i') } }, | |
| { /* Nagm */ { 0x6d67614e, 295, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('n','a','g','m') } }, | |
| { /* Ougr */ { 0x7267754f, 143, 8, 0x0020, 0, 1, 0, 0, 0, 1, 1 }, { _OT('o','u','g','r') } }, | |
| { /* Tnsa */ { 0x61736e54, 275, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('t','n','s','a') } }, | |
| { /* Toto */ { 0x6f746f54, 294, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('t','o','t','o') } }, | |
| { /* Vith */ { 0x68746956, 228, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('v','i','t','h') } }, | |
| { /* Gara */ { 0x61726147, 164, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('g','a','r','a') } }, | |
| { /* Gukh */ { 0x686b7547, 397, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('g','u','k','h') } }, | |
| { /* Krai */ { 0x6961724b, 396, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('k','r','a','i') } }, | |
| { /* Onao */ { 0x6f616e4f, 296, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('o','n','a','o') } }, | |
| { /* Sunu */ { 0x756e7553, 274, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('s','u','n','u') } }, | |
| { /* Todr */ { 0x72646f54, 229, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, { _OT('t','o','d','r') } }, | |
| { /* Tutg */ { 0x67747554, 341, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, { _OT('t','u','t','g') } }, | |
| }; | |
| #undef _OT | |
| const char *debugstr_sa_script(UINT16 script) | |
| { | |
| return script < Script_LastId ? debugstr_fourcc(dwritescripts_properties[script].props.isoScriptCode) : "undefined"; | |
| } | |
| static const struct fallback_description | |
| { | |
| const char *ranges; | |
| const WCHAR *families; | |
| const WCHAR *locale; | |
| } | |
| system_fallback_config[] = | |
| { | |
| /* Latin, Combining Diacritical Marks */ | |
| { "0000-007F, 0080-00FF, 0100-017F, 0180-024F, " | |
| "0250-02AF, 02B0-02FF, 0300-036F", L"Tahoma" }, | |
| /* Cyrillic, Cyrillic Supplement */ | |
| { "0400-052F", L"Tahoma" }, | |
| { "0530-058F, FB10-FB1C", L"Noto Sans Armenian" }, | |
| { "0590-05FF, FB1D-FB4F", L"Noto Sans Hebrew" }, | |
| { "0600-06FF, 0750-077F, " | |
| "08A0-08FF, FB50-FDCF, " | |
| "FDF0-FDFF, FE70-FEFE", L"Noto Sans Arabic" }, | |
| { "0700-074F", L"Noto Sans Syriac" }, | |
| { "0780-07BF", L"Noto Sans Thaana" }, | |
| { "07C0-07FF", L"Noto Sans NKo" }, | |
| { "0800-083F", L"Noto Sans Samaritan" }, | |
| { "0840-085F", L"Noto Sans Mandaic" }, | |
| { "0900-097F", L"Noto Sans Devanagari" }, | |
| { "0980-09FF", L"Noto Sans Bengali" }, | |
| { "0A00-0A7F", L"Noto Sans Gurmukhi" }, | |
| { "0A80-0AFF", L"Noto Sans Gujarati" }, | |
| { "0B00-0B7F", L"Noto Sans Oriya" }, | |
| { "0B80-0BFF", L"Noto Sans Tamil" }, | |
| { "0C00-0C7F", L"Noto Sans Telugu" }, | |
| { "0C80-0CFF", L"Noto Sans Kannada" }, | |
| { "0D00-0D7F", L"Noto Sans Malayalam" }, | |
| { "0D80-0DFF", L"Noto Sans Sinhala" }, | |
| { "0E00-0E7F", L"Noto Sans Thai" }, | |
| { "0E80-0EFF", L"Noto Sans Lao" }, | |
| { "0F00-0FFF", L"Noto Serif Tibetan" }, | |
| { "1000-109F, A9E0-A9FF, AA60-AA7F", L"Noto Sans Myanmar" }, | |
| { "10A0-10FF, 2D00-2D2F", L"Noto Sans Georgian" }, | |
| /* Hangul Jamo - 1100-11FF | |
| Hangul Compatibility Jamo - 3130-318F | |
| Enc. CJK (Paren Hangul) - 3200-321F | |
| Enc. CJK (Circled Hangul) - 3260-327F | |
| Hangul Jamo Extended-A - A960-A97F | |
| Hangul Syllables - AC00-D7AF | |
| Hangul Jamo Extended-B - D7B0-D7FF */ | |
| { "1100-11FF, 3130-318F, " | |
| "3200-321F, 3260-327F, " | |
| "A960-A97F, AC00-D7FF, " | |
| "D7B0-D7FF", L"Noto Sans CJK KR" }, | |
| { "1680-169F", L"Noto Sans Ogham" }, | |
| { "16A0-16FF", L"Noto Sans Runic" }, | |
| { "1700-171F", L"Noto Sans Tagalog" }, | |
| { "1720-173F", L"Noto Sans Hanunoo" }, | |
| { "1740-175F", L"Noto Sans Buhid" }, | |
| { "1760-177F", L"Noto Sans Tagbanwa" }, | |
| { "1800-18AF, 202F, 11660-1167F", L"Noto Sans Mongolian" }, | |
| { "1900-194F", L"Noto Sans Limbu" }, | |
| { "1950-197F", L"Noto Sans Tai Le" }, | |
| { "1980-19DF", L"Noto Sans New Tai Lue" }, | |
| { "1A00-1A1F", L"Noto Sans Buginese" }, | |
| { "1A20-1AAF", L"Noto Sans Tai Tham" }, | |
| { "1B00-1B7F", L"Noto Sans Balinese" }, | |
| { "1B80-1BBF, 1CC0-1CCF", L"Noto Sans Sundanese" }, | |
| { "1BC0-1BFF", L"Noto Sans Batak" }, | |
| { "1C00-1C4F", L"Noto Sans Lepcha" }, | |
| { "1C50-1C7F", L"Noto Sans Ol Chiki" }, | |
| /* Dingbats - 2700-27BF */ | |
| { "2700-27BF", L"Noto Sans Symbols2, Noto Sans Symbols 2" }, | |
| { "2C80-2CFF", L"Noto Sans Coptic" }, | |
| { "2D30-2D7F", L"Noto Sans Tifinagh" }, | |
| /* CJK Radicals Supplement - 2E80-2EFF */ | |
| { "2E80-2EFF", L"Noto Sans CJK SC", L"zh-Hans" }, | |
| { "2E80-2EFF", L"Noto Sans CJK TC", L"zh-Hant" }, | |
| { "2E80-2EFF", L"Noto Sans CJK KR", L"ko" }, | |
| /* CJK Symbols and Punctuation - 3000-303F | |
| Hiragana - 3040-309F | |
| Katakana - 30A0-30FF | |
| Katakana Phonetic Ext. - 31F0-31FF */ | |
| { "3000-30FF, 31F0-31FF", L"Noto Sans CJK SC", L"zh-Hans" }, | |
| { "3000-30FF, 31F0-31FF", L"Noto Sans CJK TC", L"zh-Hant" }, | |
| { "3000-30FF, 31F0-31FF", L"Noto Sans CJK KR", L"ko" }, | |
| { "3000-30FF, 31F0-31FF", L"Noto Sans CJK JP" }, | |
| /* CJK Unified Ext A - 3400-4DBF | |
| CJK Unified - 4E00-9FFF */ | |
| { "3400-4DBF, 4E00-9FFF", L"Noto Sans CJK SC", L"zh-Hans" }, | |
| { "3400-4DBF, 4E00-9FFF", L"Noto Sans CJK TC", L"zh-Hant" }, | |
| { "3400-4DBF, 4E00-9FFF", L"Noto Sans CJK KR", L"ko" }, | |
| { "3400-4DBF, 4E00-9FFF", L"Noto Sans CJK JP" }, | |
| { "A000-A4CF", L"Noto Sans Yi" }, | |
| { "A4D0-A4FF", L"Noto Sans Lisu" }, | |
| { "A500-A63F", L"Noto Sans Vai" }, | |
| { "A6A0-A6FF, 16800-16A38", L"Noto Sans Bamum" }, | |
| { "A800-A82F", L"Noto Sans Syloti Nagri" }, | |
| { "A840-A87F", L"Noto Sans PhagsPa" }, | |
| { "A880-A8DF", L"Noto Sans Saurashtra" }, | |
| { "A900-A92F", L"Noto Sans Kayah Li" }, | |
| { "A930-A95F", L"Noto Sans Rejang" }, | |
| { "A980-A9DF", L"Noto Sans Javanese" }, | |
| { "AA00-AA5F", L"Noto Sans Cham" }, | |
| /* CJK Compatibility Ideographs - F900-FAFF */ | |
| { "F900-FAFF", L"Noto Sans CJK SC", L"zh-Hans" }, | |
| { "F900-FAFF", L"Noto Sans CJK TC", L"zh-Hant" }, | |
| { "F900-FAFF", L"Noto Sans CJK KR", L"ko" }, | |
| { "F900-FAFF", L"Noto Sans CJK JP" }, | |
| /* Vertical Forms - FE10-FE1F */ | |
| { "FE10-FE1F", L"Noto Sans CJK SC", L"zh-Hans" }, | |
| { "FE10-FE1F", L"Noto Sans CJK KR", L"ko" }, | |
| { "FE10-FE1F", L"Noto Sans CJK TC" }, | |
| /* CJK Compatibility Forms - FE30-FE4F | |
| Small Form Variants - FE50-FE6F */ | |
| { "FE30-FE6F", L"Noto Sans CJK SC", L"zh-Hans" }, | |
| { "FE30-FE6F", L"Noto Sans CJK KR", L"ko" }, | |
| { "FE30-FE6F", L"Noto Sans CJK JP", L"ja" }, | |
| { "FE30-FE6F", L"Noto Sans CJK TC" }, | |
| /* Halfwidth and Fullwidth Forms */ | |
| { "FF00-FFEF", L"Noto Sans CJK SC", L"zh-Hans" }, | |
| { "FF00-FFEF", L"Noto Sans CJK TC", L"zh-Hant" }, | |
| { "FF00-FFEF", L"Noto Sans CJK KR", L"ko" }, | |
| { "FF00-FFEF", L"Noto Sans CJK JP" }, | |
| { "1F800-1F8FF", L"Noto Sans Symbols2, Noto Sans Symbols 2" }, | |
| }; | |
| struct text_source_context | |
| { | |
| IDWriteTextAnalysisSource *source; | |
| UINT32 position; | |
| UINT32 length; | |
| UINT32 consumed; | |
| UINT32 chunk_length; | |
| const WCHAR *text; | |
| UINT32 cursor; | |
| HRESULT status; | |
| BOOL end; | |
| UINT32 ch; | |
| }; | |
| static inline unsigned int text_source_get_char_length(const struct text_source_context *context) | |
| { | |
| return context->ch > 0xffff ? 2 : 1; | |
| } | |
| static void text_source_read_more(struct text_source_context *context) | |
| { | |
| if ((context->chunk_length - context->cursor) > 1) return; | |
| context->position += context->cursor; | |
| context->cursor = 0; | |
| if (FAILED(context->status = IDWriteTextAnalysisSource_GetTextAtPosition(context->source, context->position, | |
| &context->text, &context->chunk_length))) | |
| { | |
| context->end = TRUE; | |
| } | |
| } | |
| static void text_source_get_u32_char(struct text_source_context *context) | |
| { | |
| const WCHAR *text; | |
| UINT32 available; | |
| /* Make sure to have full pair of surrogates */ | |
| text_source_read_more(context); | |
| available = context->chunk_length - context->cursor; | |
| text = context->text + context->cursor; | |
| if (available > 1 && IS_HIGH_SURROGATE(*text) && IS_LOW_SURROGATE(*(text + 1))) | |
| { | |
| context->cursor += 2; | |
| context->consumed += 2; | |
| context->ch = 0x10000 + ((*text - 0xd800) << 10) + *(text + 1) - 0xdc00; | |
| return; | |
| } | |
| context->cursor++; | |
| context->consumed++; | |
| context->ch = *text; | |
| } | |
| static HRESULT text_source_context_init(struct text_source_context *context, IDWriteTextAnalysisSource *source, | |
| UINT32 position, UINT32 length) | |
| { | |
| memset(context, 0, sizeof(*context)); | |
| context->source = source; | |
| context->position = position; | |
| context->length = length; | |
| return IDWriteTextAnalysisSource_GetTextAtPosition(source, position, &context->text, &context->chunk_length); | |
| } | |
| static BOOL text_source_get_next_u32_char(struct text_source_context *context) | |
| { | |
| if (context->consumed == context->length) | |
| { | |
| context->ch = 0; | |
| context->end = TRUE; | |
| } | |
| else if (context->cursor < context->chunk_length) | |
| { | |
| text_source_get_u32_char(context); | |
| } | |
| else | |
| { | |
| text_source_read_more(context); | |
| /* Normal end-of-text condition. */ | |
| if (!context->text || !context->chunk_length) | |
| context->end = TRUE; | |
| else | |
| text_source_get_u32_char(context); | |
| } | |
| return context->end; | |
| } | |
| struct fallback_mapping | |
| { | |
| DWRITE_UNICODE_RANGE *ranges; | |
| UINT32 ranges_count; | |
| WCHAR **families; | |
| UINT32 families_count; | |
| IDWriteFontCollection *collection; | |
| float scale; | |
| }; | |
| struct fallback_locale | |
| { | |
| struct list entry; | |
| WCHAR name[LOCALE_NAME_MAX_LENGTH]; | |
| struct | |
| { | |
| size_t *data; | |
| size_t count; | |
| size_t size; | |
| } ranges; | |
| }; | |
| static void fallback_locale_list_destroy(struct list *locales) | |
| { | |
| struct fallback_locale *cur, *cur2; | |
| LIST_FOR_EACH_ENTRY_SAFE(cur, cur2, locales, struct fallback_locale, entry) | |
| { | |
| list_remove(&cur->entry); | |
| free(cur->ranges.data); | |
| free(cur); | |
| } | |
| } | |
| static HRESULT fallback_locale_add_mapping(struct fallback_locale *locale, size_t index) | |
| { | |
| size_t count = locale->ranges.count; | |
| /* Append to last range, or start a new one. */ | |
| if (count && locale->ranges.data[count - 1] == (index - 1)) | |
| { | |
| locale->ranges.data[count - 1] = index; | |
| return S_OK; | |
| } | |
| if (!dwrite_array_reserve((void **)&locale->ranges.data, &locale->ranges.size, count + 2, | |
| sizeof(*locale->ranges.data))) | |
| { | |
| return E_OUTOFMEMORY; | |
| } | |
| locale->ranges.data[count] = locale->ranges.data[count + 1] = index; | |
| locale->ranges.count += 2; | |
| return S_OK; | |
| } | |
| /* TODO: potentially needs improvement to consider partially matching locale names. */ | |
| static struct fallback_locale * font_fallback_get_locale(const struct list *locales, | |
| const WCHAR *locale_name, bool use_neutral) | |
| { | |
| struct fallback_locale *locale, *neutral = NULL; | |
| LIST_FOR_EACH_ENTRY(locale, locales, struct fallback_locale, entry) | |
| { | |
| if (!wcsicmp(locale->name, locale_name)) return locale; | |
| if (use_neutral && !*locale->name) neutral = locale; | |
| } | |
| return neutral; | |
| } | |
| struct fallback_data | |
| { | |
| struct fallback_mapping *mappings; | |
| size_t count; | |
| struct list locales; | |
| }; | |
| struct dwrite_fontfallback | |
| { | |
| IDWriteFontFallback1 IDWriteFontFallback1_iface; | |
| LONG refcount; | |
| IDWriteFactory7 *factory; | |
| IDWriteFontCollection *systemcollection; | |
| struct fallback_data data; | |
| size_t mappings_size; | |
| }; | |
| struct dwrite_fontfallback_builder | |
| { | |
| IDWriteFontFallbackBuilder IDWriteFontFallbackBuilder_iface; | |
| LONG refcount; | |
| IDWriteFactory7 *factory; | |
| struct fallback_data data; | |
| size_t mappings_size; | |
| }; | |
| static struct fallback_data system_fallback = | |
| { | |
| .locales = LIST_INIT(system_fallback.locales), | |
| }; | |
| static void release_fallback_mapping(struct fallback_mapping *mapping) | |
| { | |
| unsigned int i; | |
| free(mapping->ranges); | |
| for (i = 0; i < mapping->families_count; ++i) | |
| free(mapping->families[i]); | |
| free(mapping->families); | |
| if (mapping->collection) | |
| IDWriteFontCollection_Release(mapping->collection); | |
| } | |
| static void release_fallback_data(struct fallback_data *data) | |
| { | |
| size_t i; | |
| for (i = 0; i < data->count; ++i) | |
| release_fallback_mapping(&data->mappings[i]); | |
| free(data->mappings); | |
| fallback_locale_list_destroy(&data->locales); | |
| } | |
| static BOOL fallback_mapping_contains_character(const struct fallback_mapping *mapping, UINT32 ch) | |
| { | |
| size_t i; | |
| for (i = 0; i < mapping->ranges_count; ++i) | |
| { | |
| const DWRITE_UNICODE_RANGE *range = &mapping->ranges[i]; | |
| if (range->first <= ch && range->last >= ch) return TRUE; | |
| } | |
| return FALSE; | |
| } | |
| static const struct fallback_mapping * find_fallback_mapping(const struct fallback_data *fallback, | |
| const struct fallback_locale *locale, UINT32 ch) | |
| { | |
| const struct fallback_mapping *mapping; | |
| size_t i, j; | |
| for (i = 0; i < locale->ranges.count; i += 2) | |
| { | |
| size_t start = locale->ranges.data[i], end = locale->ranges.data[i + 1]; | |
| for (j = start; j <= end; ++j) | |
| { | |
| mapping = &fallback->mappings[j]; | |
| if (fallback_mapping_contains_character(mapping, ch)) return mapping; | |
| } | |
| } | |
| mapping = NULL; | |
| /* Mapping wasn't found for specific locale, try with neutral one. This will only recurse once. */ | |
| if (*locale->name) | |
| { | |
| locale = font_fallback_get_locale(&fallback->locales, L"", true); | |
| mapping = find_fallback_mapping(fallback, locale, ch); | |
| } | |
| return mapping; | |
| } | |
| struct dwrite_numbersubstitution | |
| { | |
| IDWriteNumberSubstitution IDWriteNumberSubstitution_iface; | |
| LONG refcount; | |
| DWRITE_NUMBER_SUBSTITUTION_METHOD method; | |
| WCHAR *locale; | |
| BOOL ignore_user_override; | |
| }; | |
| static inline struct dwrite_numbersubstitution *impl_from_IDWriteNumberSubstitution(IDWriteNumberSubstitution *iface) | |
| { | |
| return CONTAINING_RECORD(iface, struct dwrite_numbersubstitution, IDWriteNumberSubstitution_iface); | |
| } | |
| static struct dwrite_numbersubstitution *unsafe_impl_from_IDWriteNumberSubstitution(IDWriteNumberSubstitution *iface); | |
| static inline struct dwrite_fontfallback *impl_from_IDWriteFontFallback1(IDWriteFontFallback1 *iface) | |
| { | |
| return CONTAINING_RECORD(iface, struct dwrite_fontfallback, IDWriteFontFallback1_iface); | |
| } | |
| static inline struct dwrite_fontfallback_builder *impl_from_IDWriteFontFallbackBuilder(IDWriteFontFallbackBuilder *iface) | |
| { | |
| return CONTAINING_RECORD(iface, struct dwrite_fontfallback_builder, IDWriteFontFallbackBuilder_iface); | |
| } | |
| static inline UINT16 get_char_script(UINT32 c) | |
| { | |
| UINT16 script = get_table_entry_32(wine_scripts_table, c); | |
| return script == Script_Inherited ? Script_Unknown : script; | |
| } | |
| static DWRITE_SCRIPT_ANALYSIS get_char_sa(UINT32 c) | |
| { | |
| DWRITE_SCRIPT_ANALYSIS sa; | |
| sa.script = get_char_script(c); | |
| sa.shapes = DWRITE_SCRIPT_SHAPES_DEFAULT; | |
| if ((c <= 0x001f) /* C0 controls */ | |
| || (c >= 0x007f && c <= 0x009f) /* DELETE, C1 controls */ | |
| || (c == 0x00ad) /* SOFT HYPHEN */ | |
| || (c >= 0x200b && c <= 0x200f) /* ZWSP, ZWNJ, ZWJ, LRM, RLM */ | |
| || (c >= 0x2028 && c <= 0x202e) /* Line/paragraph separators, LRE, RLE, PDF, LRO, RLO */ | |
| || (c >= 0x2060 && c <= 0x2064) /* WJ, invisible operators */ | |
| || (c >= 0x2066 && c <= 0x2069) /* LRI, RLI, FSI, PDI */ | |
| || (c >= 0x206a && c <= 0x206f) /* Deprecated control characters */ | |
| || (c == 0xfeff) /* ZWBNSP */ | |
| || (c == 0xfff9) /* Interlinear annotation */ | |
| || (c == 0xfffa) | |
| || (c == 0xfffb) | |
| || (c >= 0x1bca0 && c <= 0x1bca3) /* Shorthand format controls */ | |
| || (c >= 0x1d173 && c <= 0x1d17a) /* Musical symbols: beams and slurs */ | |
| || (c == 0xe0001) /* Language tag, deprecated */ | |
| || (c >= 0xe0020 && c <= 0xe007f)) /* Tag components */ | |
| { | |
| sa.shapes = DWRITE_SCRIPT_SHAPES_NO_VISUAL; | |
| } | |
| else | |
| { | |
| sa.shapes = DWRITE_SCRIPT_SHAPES_DEFAULT; | |
| } | |
| return sa; | |
| } | |
| static HRESULT analyze_script(struct text_source_context *context, IDWriteTextAnalysisSink *sink) | |
| { | |
| DWRITE_SCRIPT_ANALYSIS sa; | |
| UINT32 pos, length; | |
| HRESULT hr; | |
| text_source_get_next_u32_char(context); | |
| sa = get_char_sa(context->ch); | |
| pos = context->position; | |
| length = text_source_get_char_length(context); | |
| while (!text_source_get_next_u32_char(context)) | |
| { | |
| DWRITE_SCRIPT_ANALYSIS cur_sa = get_char_sa(context->ch); | |
| /* Unknown type is ignored when preceded or followed by another script */ | |
| switch (sa.script) { | |
| case Script_Unknown: | |
| sa.script = cur_sa.script; | |
| break; | |
| case Script_Common: | |
| if (cur_sa.script == Script_Unknown) | |
| cur_sa.script = sa.script; | |
| else if ((cur_sa.script != Script_Common) && sa.shapes == DWRITE_SCRIPT_SHAPES_DEFAULT) | |
| sa.script = cur_sa.script; | |
| break; | |
| default: | |
| if ((cur_sa.script == Script_Common && cur_sa.shapes == DWRITE_SCRIPT_SHAPES_DEFAULT) || cur_sa.script == Script_Unknown) | |
| cur_sa.script = sa.script; | |
| } | |
| /* this is a length of a sequence to be reported next */ | |
| if (sa.script == cur_sa.script && sa.shapes == cur_sa.shapes) | |
| length += text_source_get_char_length(context); | |
| else | |
| { | |
| hr = IDWriteTextAnalysisSink_SetScriptAnalysis(sink, pos, length, &sa); | |
| if (FAILED(hr)) return hr; | |
| pos += length; | |
| length = text_source_get_char_length(context); | |
| sa = cur_sa; | |
| } | |
| } | |
| /* one char length case or normal completion call */ | |
| return IDWriteTextAnalysisSink_SetScriptAnalysis(sink, pos, length, &sa); | |
| } | |
| struct break_index | |
| { | |
| unsigned int index; | |
| UINT8 length; | |
| }; | |
| struct linebreaking_state | |
| { | |
| DWRITE_LINE_BREAKPOINT *breakpoints; | |
| struct break_index *breaks; | |
| UINT32 count; | |
| }; | |
| enum BreakConditionLocation { | |
| BreakConditionBefore, | |
| BreakConditionAfter | |
| }; | |
| enum linebreaking_classes { | |
| b_BK = 1, | |
| b_CR, | |
| b_LF, | |
| b_CM, | |
| b_SG, | |
| b_GL, | |
| b_CB, | |
| b_SP, | |
| b_ZW, | |
| b_NL, | |
| b_WJ, | |
| b_JL, | |
| b_JV, | |
| b_JT, | |
| b_H2, | |
| b_H3, | |
| b_XX, | |
| b_OP, | |
| b_CL, | |
| b_CP, | |
| b_QU, | |
| b_NS, | |
| b_EX, | |
| b_SY, | |
| b_IS, | |
| b_PR, | |
| b_PO, | |
| b_NU, | |
| b_AL, | |
| b_ID, | |
| b_IN, | |
| b_HY, | |
| b_BB, | |
| b_BA, | |
| b_SA, | |
| b_AI, | |
| b_B2, | |
| b_HL, | |
| b_CJ, | |
| b_RI, | |
| b_EB, | |
| b_EM, | |
| b_ZWJ, | |
| b_AK, | |
| b_AP, | |
| b_AS, | |
| b_VF, | |
| b_VI, | |
| b_HH, | |
| }; | |
| static BOOL has_strong_condition(DWRITE_BREAK_CONDITION old_condition, DWRITE_BREAK_CONDITION new_condition) | |
| { | |
| if (old_condition == DWRITE_BREAK_CONDITION_MAY_NOT_BREAK || old_condition == DWRITE_BREAK_CONDITION_MUST_BREAK) | |
| return TRUE; | |
| if (old_condition == DWRITE_BREAK_CONDITION_CAN_BREAK && new_condition != DWRITE_BREAK_CONDITION_MUST_BREAK) | |
| return TRUE; | |
| return FALSE; | |
| } | |
| /* "Can break" is a weak condition, stronger "may not break" and "must break" override it. Initially all conditions are | |
| set to "can break" and could only be changed once. */ | |
| static inline void set_break_condition(UINT32 pos, enum BreakConditionLocation location, DWRITE_BREAK_CONDITION condition, | |
| struct linebreaking_state *state) | |
| { | |
| unsigned int index = state->breaks[pos].index; | |
| if (location == BreakConditionBefore) | |
| { | |
| if (has_strong_condition(state->breakpoints[index].breakConditionBefore, condition)) | |
| return; | |
| state->breakpoints[index].breakConditionBefore = condition; | |
| if (pos) | |
| { | |
| --pos; | |
| index = state->breaks[pos].index; | |
| if (state->breaks[pos].length > 1) index++; | |
| state->breakpoints[index].breakConditionAfter = condition; | |
| } | |
| } | |
| else | |
| { | |
| if (state->breaks[pos].length > 1) index++; | |
| if (has_strong_condition(state->breakpoints[index].breakConditionAfter, condition)) | |
| return; | |
| state->breakpoints[index].breakConditionAfter = condition; | |
| if (pos + 1 < state->count) | |
| { | |
| index = state->breaks[pos + 1].index; | |
| state->breakpoints[index].breakConditionBefore = condition; | |
| } | |
| } | |
| } | |
| BOOL lb_is_newline_char(WCHAR ch) | |
| { | |
| short c = get_table_entry_32(wine_linebreak_table, ch); | |
| return c == b_LF || c == b_NL || c == b_CR || c == b_BK; | |
| } | |
| static HRESULT analyze_linebreaks(IDWriteTextAnalysisSource *source, UINT32 position, | |
| UINT32 length, DWRITE_LINE_BREAKPOINT *breakpoints) | |
| { | |
| struct text_source_context context; | |
| struct linebreaking_state state; | |
| struct break_index *breaks; | |
| unsigned int count, index; | |
| short *break_class; | |
| int i = 0, j; | |
| HRESULT hr; | |
| if (FAILED(hr = text_source_context_init(&context, source, position, length))) return hr; | |
| if (!(breaks = calloc(length, sizeof(*breaks)))) | |
| return E_OUTOFMEMORY; | |
| if (!(break_class = calloc(length, sizeof(*break_class)))) | |
| { | |
| free(breaks); | |
| return E_OUTOFMEMORY; | |
| } | |
| count = index = 0; | |
| while (!text_source_get_next_u32_char(&context)) | |
| { | |
| break_class[count] = get_table_entry_32(wine_linebreak_table, context.ch); | |
| breaks[count].length = text_source_get_char_length(&context); | |
| breaks[count].index = index; | |
| /* LB1 - resolve some classes. TODO: use external algorithms for these classes. */ | |
| switch (break_class[count]) | |
| { | |
| case b_AI: | |
| case b_SA: | |
| case b_SG: | |
| case b_XX: | |
| break_class[count] = b_AL; | |
| break; | |
| case b_CJ: | |
| break_class[count] = b_NS; | |
| break; | |
| } | |
| breakpoints[index].breakConditionBefore = DWRITE_BREAK_CONDITION_NEUTRAL; | |
| breakpoints[index].breakConditionAfter = DWRITE_BREAK_CONDITION_NEUTRAL; | |
| breakpoints[index].isWhitespace = opentype_is_whitespace(context.ch); | |
| breakpoints[index].isSoftHyphen = context.ch == 0x00ad /* Unicode Soft Hyphen */; | |
| breakpoints[index].padding = 0; | |
| ++index; | |
| if (breaks[count].length > 1) | |
| { | |
| breakpoints[index] = breakpoints[index - 1]; | |
| /* Never break in surrogate pairs. */ | |
| breakpoints[index - 1].breakConditionAfter = DWRITE_BREAK_CONDITION_MAY_NOT_BREAK; | |
| breakpoints[index].breakConditionBefore = DWRITE_BREAK_CONDITION_MAY_NOT_BREAK; | |
| ++index; | |
| } | |
| ++count; | |
| } | |
| state.breakpoints = breakpoints; | |
| state.breaks = breaks; | |
| state.count = count; | |
| /* LB2 - never break at the start */ | |
| set_break_condition(0, BreakConditionBefore, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| /* LB3 - always break at the end. */ | |
| set_break_condition(count - 1, BreakConditionAfter, DWRITE_BREAK_CONDITION_CAN_BREAK, &state); | |
| /* LB4 - LB6 - mandatory breaks. */ | |
| for (i = 0; i < count; i++) | |
| { | |
| switch (break_class[i]) | |
| { | |
| /* LB4 - LB6 */ | |
| case b_CR: | |
| /* LB5 - don't break CR x LF */ | |
| if (i < count-1 && break_class[i+1] == b_LF) | |
| { | |
| set_break_condition(i, BreakConditionBefore, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| break; | |
| } | |
| case b_LF: | |
| case b_NL: | |
| case b_BK: | |
| /* LB4 - LB5 - always break after hard breaks */ | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_MUST_BREAK, &state); | |
| /* LB6 - do not break before hard breaks */ | |
| set_break_condition(i, BreakConditionBefore, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| break; | |
| } | |
| } | |
| /* LB7 - LB8 - explicit breaks and non-breaks */ | |
| for (i = 0; i < count; i++) | |
| { | |
| switch (break_class[i]) | |
| { | |
| /* LB7 - do not break before spaces or zero-width space */ | |
| case b_SP: | |
| set_break_condition(i, BreakConditionBefore, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| break; | |
| case b_ZW: | |
| set_break_condition(i, BreakConditionBefore, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| /* LB8 - break before character after zero-width space, skip spaces in-between */ | |
| j = i; | |
| while (j < count-1 && break_class[j+1] == b_SP) | |
| j++; | |
| if (j < count-1 && break_class[j+1] != b_ZW) | |
| set_break_condition(j, BreakConditionAfter, DWRITE_BREAK_CONDITION_CAN_BREAK, &state); | |
| break; | |
| /* LB8a - do not break after ZWJ */ | |
| case b_ZWJ: | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| break; | |
| } | |
| } | |
| /* LB9 - LB10 - combining marks */ | |
| for (i = 0; i < count; i++) | |
| { | |
| if (break_class[i] == b_CM || break_class[i] == b_ZWJ) | |
| { | |
| if (i > 0) | |
| { | |
| switch (break_class[i-1]) | |
| { | |
| case b_SP: | |
| case b_BK: | |
| case b_CR: | |
| case b_LF: | |
| case b_NL: | |
| case b_ZW: | |
| break_class[i] = b_AL; | |
| break; | |
| default: | |
| { | |
| break_class[i] = break_class[i-1]; | |
| set_break_condition(i, BreakConditionBefore, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| } | |
| } | |
| } | |
| else break_class[i] = b_AL; | |
| } | |
| } | |
| for (i = 0; i < count; i++) | |
| { | |
| switch (break_class[i]) | |
| { | |
| /* LB11 - don't break before and after word joiner */ | |
| case b_WJ: | |
| set_break_condition(i, BreakConditionBefore, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| break; | |
| /* LB12 - don't break after glue */ | |
| case b_GL: | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| /* LB12a */ | |
| if (i > 0) | |
| { | |
| if (break_class[i-1] != b_SP && break_class[i-1] != b_BA && break_class[i-1] != b_HY && break_class[i-1] != b_HH) | |
| set_break_condition(i, BreakConditionBefore, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| } | |
| break; | |
| /* LB13 */ | |
| case b_CL: | |
| case b_CP: | |
| case b_EX: | |
| case b_IS: | |
| case b_SY: | |
| set_break_condition(i, BreakConditionBefore, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| break; | |
| /* LB14 - do not break after OP, even after spaces */ | |
| case b_OP: | |
| j = i; | |
| while (j < count-1 && break_class[j+1] == b_SP) | |
| j++; | |
| set_break_condition(j, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| break; | |
| /* LB15 - do not break within QU-OP, even with intervening spaces */ | |
| case b_QU: | |
| j = i; | |
| while (j < count-1 && break_class[j+1] == b_SP) | |
| j++; | |
| if (j < count - 1 && break_class[j+1] == b_OP) | |
| set_break_condition(j, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| break; | |
| /* LB16 */ | |
| case b_NS: | |
| j = i-1; | |
| while(j > 0 && break_class[j] == b_SP) | |
| j--; | |
| if (break_class[j] == b_CL || break_class[j] == b_CP) | |
| for (j++; j <= i; j++) | |
| set_break_condition(j, BreakConditionBefore, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| break; | |
| /* LB17 - do not break within B2, even with intervening spaces */ | |
| case b_B2: | |
| j = i; | |
| while (j < count && break_class[j+1] == b_SP) | |
| j++; | |
| if (j < count - 1 && break_class[j+1] == b_B2) | |
| set_break_condition(j, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| break; | |
| } | |
| } | |
| for (i = 0; i < count; i++) | |
| { | |
| switch(break_class[i]) | |
| { | |
| /* LB18 - break is allowed after space */ | |
| case b_SP: | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_CAN_BREAK, &state); | |
| break; | |
| /* LB19 - don't break before or after quotation mark */ | |
| case b_QU: | |
| set_break_condition(i, BreakConditionBefore, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| break; | |
| /* LB20 */ | |
| case b_CB: | |
| set_break_condition(i, BreakConditionBefore, DWRITE_BREAK_CONDITION_CAN_BREAK, &state); | |
| if (i < count - 1 && break_class[i+1] != b_QU) | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_CAN_BREAK, &state); | |
| break; | |
| /* LB21 */ | |
| case b_BA: | |
| case b_HY: | |
| case b_HH: | |
| case b_NS: | |
| set_break_condition(i, BreakConditionBefore, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| break; | |
| case b_BB: | |
| if (i < count - 1 && break_class[i+1] != b_CB) | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| break; | |
| /* LB21a, LB21b */ | |
| case b_HL: | |
| /* LB21a */ | |
| if (i < count-1) | |
| switch (break_class[i+1]) | |
| { | |
| case b_HY: | |
| case b_HH: | |
| case b_BA: | |
| set_break_condition(i+1, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| } | |
| /* LB21b */ | |
| if (i > 0 && break_class[i-1] == b_SY) | |
| set_break_condition(i, BreakConditionBefore, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| break; | |
| /* LB22 */ | |
| case b_IN: | |
| set_break_condition(i, BreakConditionBefore, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| break; | |
| } | |
| if (i < count-1) | |
| { | |
| /* LB23 - do not break between digits and letters */ | |
| if ((break_class[i] == b_AL && break_class[i+1] == b_NU) || | |
| (break_class[i] == b_HL && break_class[i+1] == b_NU) || | |
| (break_class[i] == b_NU && break_class[i+1] == b_AL) || | |
| (break_class[i] == b_NU && break_class[i+1] == b_HL)) | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| /* LB23a - do not break between numeric prefixes and ideographs, or between ideographs and numeric postfixes */ | |
| if ((break_class[i] == b_PR && break_class[i+1] == b_ID) || | |
| (break_class[i] == b_PR && break_class[i+1] == b_EB) || | |
| (break_class[i] == b_PR && break_class[i+1] == b_EM) || | |
| (break_class[i] == b_ID && break_class[i+1] == b_PO) || | |
| (break_class[i] == b_EM && break_class[i+1] == b_PO) || | |
| (break_class[i] == b_EB && break_class[i+1] == b_PO)) | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| /* LB24 - do not break between numeric prefix/postfix and letters, or letters and prefix/postfix */ | |
| if ((break_class[i] == b_PR && break_class[i+1] == b_AL) || | |
| (break_class[i] == b_PR && break_class[i+1] == b_HL) || | |
| (break_class[i] == b_PO && break_class[i+1] == b_AL) || | |
| (break_class[i] == b_PO && break_class[i+1] == b_HL) || | |
| (break_class[i] == b_AL && break_class[i+1] == b_PR) || | |
| (break_class[i] == b_HL && break_class[i+1] == b_PR) || | |
| (break_class[i] == b_AL && break_class[i+1] == b_PO) || | |
| (break_class[i] == b_HL && break_class[i+1] == b_PO)) | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| /* LB25 */ | |
| if ((break_class[i] == b_CL && break_class[i+1] == b_PO) || | |
| (break_class[i] == b_CP && break_class[i+1] == b_PO) || | |
| (break_class[i] == b_CL && break_class[i+1] == b_PR) || | |
| (break_class[i] == b_CP && break_class[i+1] == b_PR) || | |
| (break_class[i] == b_NU && break_class[i+1] == b_PO) || | |
| (break_class[i] == b_NU && break_class[i+1] == b_PR) || | |
| (break_class[i] == b_PO && break_class[i+1] == b_OP) || | |
| (break_class[i] == b_PO && break_class[i+1] == b_NU) || | |
| (break_class[i] == b_PR && break_class[i+1] == b_OP) || | |
| (break_class[i] == b_PR && break_class[i+1] == b_NU) || | |
| (break_class[i] == b_HY && break_class[i+1] == b_NU) || | |
| (break_class[i] == b_IS && break_class[i+1] == b_NU) || | |
| (break_class[i] == b_NU && break_class[i+1] == b_NU) || | |
| (break_class[i] == b_SY && break_class[i+1] == b_NU)) | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| /* LB26 */ | |
| if (break_class[i] == b_JL) | |
| { | |
| switch (break_class[i+1]) | |
| { | |
| case b_JL: | |
| case b_JV: | |
| case b_H2: | |
| case b_H3: | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| } | |
| } | |
| if ((break_class[i] == b_JV || break_class[i] == b_H2) && | |
| (break_class[i+1] == b_JV || break_class[i+1] == b_JT)) | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| if ((break_class[i] == b_JT || break_class[i] == b_H3) && | |
| break_class[i+1] == b_JT) | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| /* LB27 */ | |
| switch (break_class[i]) | |
| { | |
| case b_JL: | |
| case b_JV: | |
| case b_JT: | |
| case b_H2: | |
| case b_H3: | |
| if (break_class[i+1] == b_IN || break_class[i+1] == b_PO) | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| } | |
| if (break_class[i] == b_PR) | |
| { | |
| switch (break_class[i+1]) | |
| { | |
| case b_JL: | |
| case b_JV: | |
| case b_JT: | |
| case b_H2: | |
| case b_H3: | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| } | |
| } | |
| /* LB28 */ | |
| if ((break_class[i] == b_AL && break_class[i+1] == b_AL) || | |
| (break_class[i] == b_AL && break_class[i+1] == b_HL) || | |
| (break_class[i] == b_HL && break_class[i+1] == b_AL) || | |
| (break_class[i] == b_HL && break_class[i+1] == b_HL)) | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| /* LB29 */ | |
| if ((break_class[i] == b_IS && break_class[i+1] == b_AL) || | |
| (break_class[i] == b_IS && break_class[i+1] == b_HL)) | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| /* LB30 */ | |
| if ((break_class[i] == b_AL || break_class[i] == b_HL || break_class[i] == b_NU) && | |
| break_class[i+1] == b_OP) | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| if (break_class[i] == b_CP && | |
| (break_class[i+1] == b_AL || break_class[i+1] == b_HL || break_class[i+1] == b_NU)) | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| /* LB30a - break between two RIs if and only if there are an even number of RIs preceding position of the break */ | |
| if (break_class[i] == b_RI && break_class[i+1] == b_RI) { | |
| unsigned int c = 0; | |
| j = i + 1; | |
| while (j > 0 && break_class[--j] == b_RI) | |
| c++; | |
| if ((c & 1) == 0) | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| } | |
| /* LB30b - do not break between an emoji base and an emoji modifier */ | |
| if (break_class[i] == b_EB && break_class[i+1] == b_EM) | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, &state); | |
| } | |
| } | |
| /* LB31 - allow breaks everywhere else. */ | |
| for (i = 0; i < count; i++) | |
| { | |
| set_break_condition(i, BreakConditionBefore, DWRITE_BREAK_CONDITION_CAN_BREAK, &state); | |
| set_break_condition(i, BreakConditionAfter, DWRITE_BREAK_CONDITION_CAN_BREAK, &state); | |
| } | |
| free(break_class); | |
| free(breaks); | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI dwritetextanalyzer_QueryInterface(IDWriteTextAnalyzer2 *iface, REFIID riid, void **obj) | |
| { | |
| TRACE("%s, %p.\n", debugstr_guid(riid), obj); | |
| if (IsEqualIID(riid, &IID_IDWriteTextAnalyzer2) || | |
| IsEqualIID(riid, &IID_IDWriteTextAnalyzer1) || | |
| IsEqualIID(riid, &IID_IDWriteTextAnalyzer) || | |
| IsEqualIID(riid, &IID_IUnknown)) | |
| { | |
| *obj = iface; | |
| return S_OK; | |
| } | |
| WARN("%s not implemented.\n", debugstr_guid(riid)); | |
| *obj = NULL; | |
| return E_NOINTERFACE; | |
| } | |
| static ULONG WINAPI dwritetextanalyzer_AddRef(IDWriteTextAnalyzer2 *iface) | |
| { | |
| return 2; | |
| } | |
| static ULONG WINAPI dwritetextanalyzer_Release(IDWriteTextAnalyzer2 *iface) | |
| { | |
| return 1; | |
| } | |
| static HRESULT WINAPI dwritetextanalyzer_AnalyzeScript(IDWriteTextAnalyzer2 *iface, | |
| IDWriteTextAnalysisSource* source, UINT32 position, UINT32 length, IDWriteTextAnalysisSink* sink) | |
| { | |
| struct text_source_context context; | |
| HRESULT hr; | |
| TRACE("%p, %u, %u, %p.\n", source, position, length, sink); | |
| if (!length) | |
| return S_OK; | |
| if (FAILED(hr = text_source_context_init(&context, source, position, length))) return hr; | |
| return analyze_script(&context, sink); | |
| } | |
| static inline unsigned int get_bidi_char_length(const struct bidi_char *c) | |
| { | |
| return c->ch > 0xffff ? 2 : 1; | |
| } | |
| static inline UINT8 get_char_bidi_class(UINT32 ch) | |
| { | |
| return get_table_entry_32(bidi_direction_table, ch); | |
| } | |
| static HRESULT WINAPI dwritetextanalyzer_AnalyzeBidi(IDWriteTextAnalyzer2 *iface, | |
| IDWriteTextAnalysisSource* source, UINT32 position, UINT32 length, IDWriteTextAnalysisSink* sink) | |
| { | |
| struct text_source_context context; | |
| UINT8 baselevel, resolved, explicit; | |
| unsigned int i, chars_count = 0; | |
| struct bidi_char *chars, *ptr; | |
| UINT32 pos, seq_length; | |
| HRESULT hr; | |
| TRACE("%p, %u, %u, %p.\n", source, position, length, sink); | |
| if (!length) | |
| return S_OK; | |
| if (!(chars = calloc(length, sizeof(*chars)))) | |
| return E_OUTOFMEMORY; | |
| ptr = chars; | |
| text_source_context_init(&context, source, position, length); | |
| while (!text_source_get_next_u32_char(&context)) | |
| { | |
| ptr->ch = context.ch; | |
| ptr->nominal_bidi_class = ptr->bidi_class = get_char_bidi_class(context.ch); | |
| ptr++; | |
| ++chars_count; | |
| } | |
| /* Resolve levels using utf-32 codepoints, size differences are accounted for | |
| when levels are reported with SetBidiLevel(). */ | |
| baselevel = IDWriteTextAnalysisSource_GetParagraphReadingDirection(source); | |
| hr = bidi_computelevels(chars, chars_count, baselevel); | |
| if (FAILED(hr)) | |
| goto done; | |
| pos = position; | |
| resolved = chars->resolved; | |
| explicit = chars->explicit; | |
| seq_length = get_bidi_char_length(chars); | |
| for (i = 1, ptr = chars + 1; i < chars_count; ++i, ++ptr) | |
| { | |
| if (ptr->resolved == resolved && ptr->explicit == explicit) | |
| { | |
| seq_length += get_bidi_char_length(ptr); | |
| } | |
| else | |
| { | |
| hr = IDWriteTextAnalysisSink_SetBidiLevel(sink, pos, seq_length, explicit, resolved); | |
| if (FAILED(hr)) | |
| goto done; | |
| pos += seq_length; | |
| seq_length = get_bidi_char_length(ptr); | |
| resolved = ptr->resolved; | |
| explicit = ptr->explicit; | |
| } | |
| } | |
| /* one char length case or normal completion call */ | |
| hr = IDWriteTextAnalysisSink_SetBidiLevel(sink, pos, seq_length, explicit, resolved); | |
| done: | |
| free(chars); | |
| return hr; | |
| } | |
| static HRESULT WINAPI dwritetextanalyzer_AnalyzeNumberSubstitution(IDWriteTextAnalyzer2 *iface, | |
| IDWriteTextAnalysisSource* source, UINT32 position, UINT32 length, IDWriteTextAnalysisSink* sink) | |
| { | |
| static int once; | |
| if (!once++) | |
| FIXME("(%p %u %u %p): stub\n", source, position, length, sink); | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI dwritetextanalyzer_AnalyzeLineBreakpoints(IDWriteTextAnalyzer2 *iface, | |
| IDWriteTextAnalysisSource *source, UINT32 position, UINT32 length, IDWriteTextAnalysisSink *sink) | |
| { | |
| DWRITE_LINE_BREAKPOINT *breakpoints; | |
| HRESULT hr; | |
| TRACE("%p, %u, %u, %p.\n", source, position, length, sink); | |
| if (!length) | |
| return S_OK; | |
| if (!(breakpoints = calloc(length, sizeof(*breakpoints)))) | |
| return E_OUTOFMEMORY; | |
| if (SUCCEEDED(hr = analyze_linebreaks(source, position, length, breakpoints))) | |
| hr = IDWriteTextAnalysisSink_SetLineBreakpoints(sink, position, length, breakpoints); | |
| free(breakpoints); | |
| return hr; | |
| } | |
| static UINT32 get_opentype_language(const WCHAR *locale) | |
| { | |
| UINT32 language = DWRITE_MAKE_OPENTYPE_TAG('d','f','l','t'); | |
| if (locale) { | |
| WCHAR tag[5]; | |
| if (GetLocaleInfoEx(locale, LOCALE_SOPENTYPELANGUAGETAG, tag, ARRAY_SIZE(tag))) | |
| language = DWRITE_MAKE_OPENTYPE_TAG(tag[0],tag[1],tag[2],tag[3]); | |
| } | |
| return language; | |
| } | |
| static void get_number_substitutes(IDWriteNumberSubstitution *substitution, BOOL is_rtl, WCHAR *digits) | |
| { | |
| struct dwrite_numbersubstitution *numbersubst = unsafe_impl_from_IDWriteNumberSubstitution(substitution); | |
| DWRITE_NUMBER_SUBSTITUTION_METHOD method; | |
| WCHAR isolang[9]; | |
| DWORD lctype; | |
| digits[0] = 0; | |
| if (!numbersubst) | |
| return; | |
| lctype = numbersubst->ignore_user_override ? LOCALE_NOUSEROVERRIDE : 0; | |
| if (numbersubst->method == DWRITE_NUMBER_SUBSTITUTION_METHOD_FROM_CULTURE) { | |
| DWORD value; | |
| method = DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE; | |
| if (GetLocaleInfoEx(numbersubst->locale, lctype | LOCALE_IDIGITSUBSTITUTION | LOCALE_RETURN_NUMBER, (WCHAR *)&value, 2)) { | |
| switch (value) | |
| { | |
| case 0: | |
| method = DWRITE_NUMBER_SUBSTITUTION_METHOD_CONTEXTUAL; | |
| break; | |
| case 2: | |
| method = DWRITE_NUMBER_SUBSTITUTION_METHOD_NATIONAL; | |
| break; | |
| case 1: | |
| default: | |
| if (value != 1) | |
| WARN("Unknown IDIGITSUBSTITUTION value %lu, locale %s.\n", value, debugstr_w(numbersubst->locale)); | |
| } | |
| } | |
| else | |
| WARN("Failed to get IDIGITSUBSTITUTION for locale %s\n", debugstr_w(numbersubst->locale)); | |
| } | |
| else | |
| method = numbersubst->method; | |
| switch (method) | |
| { | |
| case DWRITE_NUMBER_SUBSTITUTION_METHOD_NATIONAL: | |
| GetLocaleInfoEx(numbersubst->locale, lctype | LOCALE_SNATIVEDIGITS, digits, NATIVE_DIGITS_LEN); | |
| break; | |
| case DWRITE_NUMBER_SUBSTITUTION_METHOD_CONTEXTUAL: | |
| case DWRITE_NUMBER_SUBSTITUTION_METHOD_TRADITIONAL: | |
| if (GetLocaleInfoEx(numbersubst->locale, LOCALE_SISO639LANGNAME, isolang, ARRAY_SIZE(isolang))) | |
| { | |
| static const WCHAR arabicW[] = {0x640,0x641,0x642,0x643,0x644,0x645,0x646,0x647,0x648,0x649,0}; | |
| /* For some Arabic locales Latin digits are returned for SNATIVEDIGITS */ | |
| if (!wcscmp(L"ar", isolang)) | |
| { | |
| wcscpy(digits, arabicW); | |
| break; | |
| } | |
| } | |
| GetLocaleInfoEx(numbersubst->locale, lctype | LOCALE_SNATIVEDIGITS, digits, NATIVE_DIGITS_LEN); | |
| break; | |
| default: | |
| ; | |
| } | |
| if (method != DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE && !*digits) { | |
| WARN("Failed to get number substitutes for locale %s, method %d\n", debugstr_w(numbersubst->locale), method); | |
| method = DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE; | |
| } | |
| if ((method == DWRITE_NUMBER_SUBSTITUTION_METHOD_CONTEXTUAL && !is_rtl) || method == DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE) | |
| digits[0] = 0; | |
| } | |
| static void analyzer_dump_user_features(DWRITE_TYPOGRAPHIC_FEATURES const **features, | |
| UINT32 const *feature_range_lengths, UINT32 feature_ranges) | |
| { | |
| UINT32 i, j, start; | |
| if (!TRACE_ON(dwrite) || !features) | |
| return; | |
| for (i = 0, start = 0; i < feature_ranges; start += feature_range_lengths[i++]) { | |
| TRACE("feature range [%u,%u)\n", start, start + feature_range_lengths[i]); | |
| for (j = 0; j < features[i]->featureCount; j++) | |
| TRACE("feature %s, parameter %u\n", debugstr_fourcc(features[i]->features[j].nameTag), | |
| features[i]->features[j].parameter); | |
| } | |
| } | |
| static HRESULT WINAPI dwritetextanalyzer_GetGlyphs(IDWriteTextAnalyzer2 *iface, | |
| WCHAR const* text, UINT32 length, IDWriteFontFace* fontface, BOOL is_sideways, | |
| BOOL is_rtl, DWRITE_SCRIPT_ANALYSIS const* analysis, WCHAR const* locale, | |
| IDWriteNumberSubstitution* substitution, DWRITE_TYPOGRAPHIC_FEATURES const** features, | |
| UINT32 const* feature_range_lengths, UINT32 feature_ranges, UINT32 max_glyph_count, | |
| UINT16* clustermap, DWRITE_SHAPING_TEXT_PROPERTIES* text_props, UINT16 *glyphs, | |
| DWRITE_SHAPING_GLYPH_PROPERTIES* glyph_props, UINT32* actual_glyph_count) | |
| { | |
| const struct dwritescript_properties *scriptprops; | |
| struct scriptshaping_context context = { 0 }; | |
| struct dwrite_fontface *font_obj; | |
| WCHAR digits[NATIVE_DIGITS_LEN]; | |
| unsigned int glyph_count; | |
| HRESULT hr; | |
| TRACE("%s:%u, %p, %d, %d, %s, %s, %p, %p, %p, %u, %u, %p, %p, %p, %p, %p.\n", debugstr_wn(text, length), | |
| length, fontface, is_sideways, is_rtl, debugstr_sa_script(analysis->script), debugstr_w(locale), substitution, | |
| features, feature_range_lengths, feature_ranges, max_glyph_count, clustermap, text_props, glyphs, | |
| glyph_props, actual_glyph_count); | |
| analyzer_dump_user_features(features, feature_range_lengths, feature_ranges); | |
| get_number_substitutes(substitution, is_rtl, digits); | |
| font_obj = unsafe_impl_from_IDWriteFontFace(fontface); | |
| glyph_count = max(max_glyph_count, length); | |
| context.cache = fontface_get_shaping_cache(font_obj); | |
| context.script = analysis->script > Script_LastId ? Script_Unknown : analysis->script; | |
| context.text = text; | |
| context.length = length; | |
| context.is_rtl = is_rtl; | |
| context.is_sideways = is_sideways; | |
| context.u.subst.glyphs = calloc(glyph_count, sizeof(*glyphs)); | |
| context.u.subst.glyph_props = calloc(glyph_count, sizeof(*glyph_props)); | |
| context.u.subst.text_props = text_props; | |
| context.u.subst.clustermap = clustermap; | |
| context.u.subst.max_glyph_count = max_glyph_count; | |
| context.u.subst.capacity = glyph_count; | |
| context.u.subst.digits = digits; | |
| context.language_tag = get_opentype_language(locale); | |
| context.user_features.features = features; | |
| context.user_features.range_lengths = feature_range_lengths; | |
| context.user_features.range_count = feature_ranges; | |
| context.glyph_infos = calloc(glyph_count, sizeof(*context.glyph_infos)); | |
| context.table = &context.cache->gsub; | |
| *actual_glyph_count = 0; | |
| if (!context.u.subst.glyphs || !context.u.subst.glyph_props || !context.glyph_infos) | |
| { | |
| hr = E_OUTOFMEMORY; | |
| goto failed; | |
| } | |
| scriptprops = &dwritescripts_properties[context.script]; | |
| hr = shape_get_glyphs(&context, scriptprops->scripttags); | |
| if (SUCCEEDED(hr)) | |
| { | |
| *actual_glyph_count = context.glyph_count; | |
| memcpy(glyphs, context.u.subst.glyphs, context.glyph_count * sizeof(*glyphs)); | |
| memcpy(glyph_props, context.u.subst.glyph_props, context.glyph_count * sizeof(*glyph_props)); | |
| } | |
| failed: | |
| free(context.u.subst.glyph_props); | |
| free(context.u.subst.glyphs); | |
| free(context.glyph_infos); | |
| return hr; | |
| } | |
| static HRESULT WINAPI dwritetextanalyzer_GetGlyphPlacements(IDWriteTextAnalyzer2 *iface, | |
| WCHAR const* text, UINT16 const* clustermap, DWRITE_SHAPING_TEXT_PROPERTIES *text_props, | |
| UINT32 text_len, UINT16 const* glyphs, DWRITE_SHAPING_GLYPH_PROPERTIES const* glyph_props, | |
| UINT32 glyph_count, IDWriteFontFace *fontface, float emSize, BOOL is_sideways, BOOL is_rtl, | |
| DWRITE_SCRIPT_ANALYSIS const* analysis, WCHAR const* locale, DWRITE_TYPOGRAPHIC_FEATURES const** features, | |
| UINT32 const* feature_range_lengths, UINT32 feature_ranges, float *advances, DWRITE_GLYPH_OFFSET *offsets) | |
| { | |
| const struct dwritescript_properties *scriptprops; | |
| struct scriptshaping_context context = { 0 }; | |
| struct dwrite_fontface *font_obj; | |
| unsigned int i; | |
| HRESULT hr; | |
| TRACE("%s, %p, %p, %u, %p, %p, %u, %p, %.2f, %d, %d, %s, %s, %p, %p, %u, %p, %p.\n", debugstr_wn(text, text_len), | |
| clustermap, text_props, text_len, glyphs, glyph_props, glyph_count, fontface, emSize, is_sideways, | |
| is_rtl, debugstr_sa_script(analysis->script), debugstr_w(locale), features, feature_range_lengths, | |
| feature_ranges, advances, offsets); | |
| analyzer_dump_user_features(features, feature_range_lengths, feature_ranges); | |
| if (glyph_count == 0) | |
| return S_OK; | |
| font_obj = unsafe_impl_from_IDWriteFontFace(fontface); | |
| for (i = 0; i < glyph_count; ++i) | |
| { | |
| if (glyph_props[i].isZeroWidthSpace) | |
| advances[i] = 0.0f; | |
| else | |
| advances[i] = fontface_get_scaled_design_advance(font_obj, DWRITE_MEASURING_MODE_NATURAL, emSize, 1.0f, | |
| NULL, glyphs[i], is_sideways); | |
| offsets[i].advanceOffset = 0.0f; | |
| offsets[i].ascenderOffset = 0.0f; | |
| } | |
| context.cache = fontface_get_shaping_cache(font_obj); | |
| context.script = analysis->script > Script_LastId ? Script_Unknown : analysis->script; | |
| context.text = text; | |
| context.length = text_len; | |
| context.is_rtl = is_rtl; | |
| context.is_sideways = is_sideways; | |
| context.u.pos.glyphs = glyphs; | |
| context.u.pos.glyph_props = glyph_props; | |
| context.u.pos.text_props = text_props; | |
| context.u.pos.clustermap = clustermap; | |
| context.glyph_count = glyph_count; | |
| context.emsize = emSize; | |
| context.measuring_mode = DWRITE_MEASURING_MODE_NATURAL; | |
| context.advances = advances; | |
| context.offsets = offsets; | |
| context.language_tag = get_opentype_language(locale); | |
| context.user_features.features = features; | |
| context.user_features.range_lengths = feature_range_lengths; | |
| context.user_features.range_count = feature_ranges; | |
| context.glyph_infos = calloc(glyph_count, sizeof(*context.glyph_infos)); | |
| context.table = &context.cache->gpos; | |
| if (!context.glyph_infos) | |
| { | |
| hr = E_OUTOFMEMORY; | |
| goto failed; | |
| } | |
| scriptprops = &dwritescripts_properties[context.script]; | |
| hr = shape_get_positions(&context, scriptprops->scripttags); | |
| failed: | |
| free(context.glyph_infos); | |
| return hr; | |
| } | |
| static HRESULT WINAPI dwritetextanalyzer_GetGdiCompatibleGlyphPlacements(IDWriteTextAnalyzer2 *iface, | |
| WCHAR const* text, UINT16 const* clustermap, DWRITE_SHAPING_TEXT_PROPERTIES *text_props, | |
| UINT32 text_len, UINT16 const* glyphs, DWRITE_SHAPING_GLYPH_PROPERTIES const* glyph_props, | |
| UINT32 glyph_count, IDWriteFontFace *fontface, float emSize, float ppdip, | |
| DWRITE_MATRIX const* transform, BOOL use_gdi_natural, BOOL is_sideways, BOOL is_rtl, | |
| DWRITE_SCRIPT_ANALYSIS const* analysis, WCHAR const* locale, DWRITE_TYPOGRAPHIC_FEATURES const** features, | |
| UINT32 const* feature_range_lengths, UINT32 feature_ranges, float *advances, DWRITE_GLYPH_OFFSET *offsets) | |
| { | |
| const struct dwritescript_properties *scriptprops; | |
| struct scriptshaping_context context = { 0 }; | |
| DWRITE_MEASURING_MODE measuring_mode; | |
| struct dwrite_fontface *font_obj; | |
| unsigned int i; | |
| HRESULT hr; | |
| TRACE("%s, %p, %p, %u, %p, %p, %u, %p, %.2f, %.2f, %p, %d, %d, %d, %s, %s, %p, %p, %u, %p, %p.\n", | |
| debugstr_wn(text, text_len), clustermap, text_props, text_len, glyphs, glyph_props, glyph_count, fontface, | |
| emSize, ppdip, transform, use_gdi_natural, is_sideways, is_rtl, debugstr_sa_script(analysis->script), | |
| debugstr_w(locale), features, feature_range_lengths, feature_ranges, advances, offsets); | |
| analyzer_dump_user_features(features, feature_range_lengths, feature_ranges); | |
| if (glyph_count == 0) | |
| return S_OK; | |
| font_obj = unsafe_impl_from_IDWriteFontFace(fontface); | |
| measuring_mode = use_gdi_natural ? DWRITE_MEASURING_MODE_GDI_NATURAL : DWRITE_MEASURING_MODE_GDI_CLASSIC; | |
| for (i = 0; i < glyph_count; ++i) | |
| { | |
| if (glyph_props[i].isZeroWidthSpace) | |
| advances[i] = 0.0f; | |
| else | |
| advances[i] = fontface_get_scaled_design_advance(font_obj, measuring_mode, emSize, ppdip, | |
| transform, glyphs[i], is_sideways); | |
| offsets[i].advanceOffset = 0.0f; | |
| offsets[i].ascenderOffset = 0.0f; | |
| } | |
| context.cache = fontface_get_shaping_cache(font_obj); | |
| context.script = analysis->script > Script_LastId ? Script_Unknown : analysis->script; | |
| context.text = text; | |
| context.length = text_len; | |
| context.is_rtl = is_rtl; | |
| context.is_sideways = is_sideways; | |
| context.u.pos.glyphs = glyphs; | |
| context.u.pos.glyph_props = glyph_props; | |
| context.u.pos.text_props = text_props; | |
| context.u.pos.clustermap = clustermap; | |
| context.glyph_count = glyph_count; | |
| context.emsize = emSize * ppdip; | |
| context.measuring_mode = measuring_mode; | |
| context.advances = advances; | |
| context.offsets = offsets; | |
| context.language_tag = get_opentype_language(locale); | |
| context.user_features.features = features; | |
| context.user_features.range_lengths = feature_range_lengths; | |
| context.user_features.range_count = feature_ranges; | |
| context.glyph_infos = calloc(glyph_count, sizeof(*context.glyph_infos)); | |
| context.table = &context.cache->gpos; | |
| if (!context.glyph_infos) | |
| { | |
| hr = E_OUTOFMEMORY; | |
| goto failed; | |
| } | |
| scriptprops = &dwritescripts_properties[context.script]; | |
| hr = shape_get_positions(&context, scriptprops->scripttags); | |
| failed: | |
| free(context.glyph_infos); | |
| return hr; | |
| } | |
| static HRESULT apply_cluster_spacing(float leading_spacing, float trailing_spacing, float min_advance_width, | |
| unsigned int start, unsigned int end, float const *advances, DWRITE_GLYPH_OFFSET const *offsets, | |
| DWRITE_SHAPING_GLYPH_PROPERTIES const *glyph_props, float *modified_advances, | |
| DWRITE_GLYPH_OFFSET *modified_offsets) | |
| { | |
| BOOL reduced = leading_spacing < 0.0f || trailing_spacing < 0.0f; | |
| unsigned int first_spacing, last_spacing, i; | |
| float advance, origin = 0.0f, *deltas; | |
| BOOL is_spacing_cluster = FALSE; | |
| if (modified_advances != advances) | |
| memcpy(&modified_advances[start], &advances[start], (end - start + 1) * sizeof(*advances)); | |
| if (modified_offsets != offsets) | |
| memcpy(&modified_offsets[start], &offsets[start], (end - start + 1) * sizeof(*offsets)); | |
| for (first_spacing = start; first_spacing <= end; ++first_spacing) | |
| { | |
| if ((is_spacing_cluster = !glyph_props[first_spacing].isZeroWidthSpace)) | |
| break; | |
| } | |
| /* Nothing to adjust if there is no spacing glyphs. */ | |
| if (!is_spacing_cluster) | |
| return S_OK; | |
| for (last_spacing = end; last_spacing >= start; --last_spacing) | |
| { | |
| if (!glyph_props[last_spacing].isZeroWidthSpace) | |
| break; | |
| } | |
| if (!(deltas = calloc(end - start + 1, sizeof(*deltas)))) | |
| return E_OUTOFMEMORY; | |
| /* Cluster advance, note that properties are ignored. */ | |
| origin = offsets[start].advanceOffset; | |
| for (i = start, advance = 0.0f; i <= end; ++i) | |
| { | |
| float cur = advance + offsets[i].advanceOffset; | |
| deltas[i - start] = cur - origin; | |
| advance += advances[i]; | |
| origin = cur; | |
| } | |
| /* Negative spacing. */ | |
| if (leading_spacing < 0.0f) | |
| { | |
| advance += leading_spacing; | |
| modified_advances[first_spacing] += leading_spacing; | |
| modified_offsets[first_spacing].advanceOffset += leading_spacing; | |
| } | |
| if (trailing_spacing < 0.0f) | |
| { | |
| advance += trailing_spacing; | |
| modified_advances[last_spacing] += trailing_spacing; | |
| } | |
| /* Minimal advance. */ | |
| advance = min_advance_width - advance; | |
| if (advance > 0.0f) { | |
| /* Additional spacing is only applied to leading and trailing spacing glyphs. */ | |
| float half = advance / 2.0f; | |
| if (!reduced) | |
| { | |
| modified_advances[first_spacing] += half; | |
| modified_advances[last_spacing] += half; | |
| modified_offsets[first_spacing].advanceOffset += half; | |
| } | |
| else if (leading_spacing < 0.0f && trailing_spacing < 0.0f) | |
| { | |
| modified_advances[first_spacing] += half; | |
| modified_advances[last_spacing] += half; | |
| modified_offsets[first_spacing].advanceOffset += half; | |
| } | |
| else if (leading_spacing < 0.0f) | |
| { | |
| modified_advances[first_spacing] += advance; | |
| modified_offsets[first_spacing].advanceOffset += advance; | |
| } | |
| else | |
| modified_advances[last_spacing] += advance; | |
| } | |
| /* Positive spacing. */ | |
| if (leading_spacing > 0.0f) | |
| { | |
| modified_advances[first_spacing] += leading_spacing; | |
| modified_offsets[first_spacing].advanceOffset += leading_spacing; | |
| } | |
| if (trailing_spacing > 0.0f) | |
| modified_advances[last_spacing] += trailing_spacing; | |
| /* Update offsets to preserve original relative positions within cluster. */ | |
| for (i = first_spacing; i > start; --i) | |
| { | |
| unsigned int cur = i - 1; | |
| modified_offsets[cur].advanceOffset = modified_advances[cur] + modified_offsets[i].advanceOffset - | |
| deltas[i - start]; | |
| } | |
| for (i = first_spacing + 1; i <= end; ++i) | |
| { | |
| modified_offsets[i].advanceOffset = deltas[i - start] + modified_offsets[i - 1].advanceOffset - | |
| modified_advances[i - 1]; | |
| } | |
| free(deltas); | |
| return S_OK; | |
| } | |
| static inline UINT32 get_cluster_length(UINT16 const *clustermap, UINT32 start, UINT32 text_len) | |
| { | |
| UINT16 g = clustermap[start]; | |
| UINT32 length = 1; | |
| while (start < (text_len - 1) && clustermap[++start] == g) | |
| length++; | |
| return length; | |
| } | |
| /* Applies spacing adjustments to clusters. | |
| Adjustments are applied in the following order: | |
| 1. Negative adjustments | |
| Leading and trailing spacing could be negative, at this step | |
| only negative ones are actually applied. Leading spacing is only | |
| applied to leading glyph, trailing - to trailing glyph. | |
| 2. Minimum advance width | |
| Advances could only be reduced at this point or unchanged. In any | |
| case it's checked if cluster advance width is less than minimum width. | |
| If it's the case advance width is incremented up to minimum value. | |
| Important part is the direction in which this increment is applied; | |
| it depends on direction from which total cluster advance was trimmed | |
| at step 1. So it could be incremented from leading, trailing, or both | |
| sides. When applied to both sides, each side gets half of difference | |
| that brings advance to minimum width. | |
| 3. Positive adjustments | |
| After minimum width rule was applied, positive spacing is applied in the same | |
| way as negative one on step 1. | |
| Glyph offset for leading glyph is adjusted too in a way that glyph origin | |
| keeps its position in coordinate system where initial advance width is counted | |
| from 0. | |
| Glyph properties | |
| It's known that isZeroWidthSpace property keeps initial advance from changing. | |
| */ | |
| static HRESULT WINAPI dwritetextanalyzer1_ApplyCharacterSpacing(IDWriteTextAnalyzer2 *iface, | |
| FLOAT leading_spacing, FLOAT trailing_spacing, FLOAT min_advance_width, UINT32 len, | |
| UINT32 glyph_count, UINT16 const *clustermap, FLOAT const *advances, DWRITE_GLYPH_OFFSET const *offsets, | |
| DWRITE_SHAPING_GLYPH_PROPERTIES const *props, FLOAT *modified_advances, DWRITE_GLYPH_OFFSET *modified_offsets) | |
| { | |
| unsigned int i; | |
| TRACE("%.2f, %.2f, %.2f, %u, %u, %p, %p, %p, %p, %p, %p.\n", leading_spacing, trailing_spacing, min_advance_width, | |
| len, glyph_count, clustermap, advances, offsets, props, modified_advances, modified_offsets); | |
| if (min_advance_width < 0.0f) { | |
| if (modified_advances != advances) | |
| memset(modified_advances, 0, glyph_count*sizeof(*modified_advances)); | |
| return E_INVALIDARG; | |
| } | |
| for (i = 0; i < len;) | |
| { | |
| unsigned int length = get_cluster_length(clustermap, i, len); | |
| unsigned int start, end; | |
| start = clustermap[i]; | |
| end = i + length < len ? clustermap[i + length] : glyph_count; | |
| apply_cluster_spacing(leading_spacing, trailing_spacing, min_advance_width, start, end - 1, advances, | |
| offsets, props, modified_advances, modified_offsets); | |
| i += length; | |
| } | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI dwritetextanalyzer1_GetBaseline(IDWriteTextAnalyzer2 *iface, IDWriteFontFace *fontface, | |
| DWRITE_BASELINE baseline, BOOL vertical, BOOL is_simulation_allowed, DWRITE_SCRIPT_ANALYSIS sa, | |
| const WCHAR *localeName, INT32 *baseline_coord, BOOL *exists) | |
| { | |
| struct dwrite_fontface *font_obj; | |
| const DWRITE_FONT_METRICS1 *metrics; | |
| TRACE("%p, %d, %d, %u, %s, %p, %p.\n", fontface, vertical, is_simulation_allowed, sa.script, debugstr_w(localeName), | |
| baseline_coord, exists); | |
| *exists = FALSE; | |
| *baseline_coord = 0; | |
| if (baseline == DWRITE_BASELINE_DEFAULT) | |
| baseline = vertical ? DWRITE_BASELINE_CENTRAL : DWRITE_BASELINE_ROMAN; | |
| if ((unsigned int)baseline > DWRITE_BASELINE_MAXIMUM) | |
| return E_INVALIDARG; | |
| /* TODO: fetch BASE table data if available. */ | |
| if (!*exists && is_simulation_allowed) | |
| { | |
| font_obj = unsafe_impl_from_IDWriteFontFace(fontface); | |
| metrics = &font_obj->metrics; | |
| switch (baseline) | |
| { | |
| case DWRITE_BASELINE_ROMAN: | |
| *baseline_coord = vertical ? metrics->descent : 0; | |
| break; | |
| case DWRITE_BASELINE_CENTRAL: | |
| *baseline_coord = vertical ? (metrics->ascent + metrics->descent) / 2 : | |
| -(metrics->ascent - metrics->descent) / 2; | |
| break; | |
| case DWRITE_BASELINE_MATH: | |
| *baseline_coord = vertical ? (metrics->ascent + metrics->descent) / 2 : | |
| -(metrics->ascent + metrics->descent) / 2; | |
| break; | |
| case DWRITE_BASELINE_HANGING: | |
| /* FIXME: this one isn't accurate, but close. */ | |
| *baseline_coord = vertical ? metrics->capHeight * 6 / 7 + metrics->descent : metrics->capHeight * 6 / 7; | |
| break; | |
| case DWRITE_BASELINE_IDEOGRAPHIC_BOTTOM: | |
| case DWRITE_BASELINE_MINIMUM: | |
| *baseline_coord = vertical ? 0 : metrics->descent; | |
| break; | |
| case DWRITE_BASELINE_IDEOGRAPHIC_TOP: | |
| case DWRITE_BASELINE_MAXIMUM: | |
| *baseline_coord = vertical ? metrics->ascent + metrics->descent : -metrics->ascent; | |
| break; | |
| default: | |
| ; | |
| } | |
| } | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI dwritetextanalyzer1_AnalyzeVerticalGlyphOrientation(IDWriteTextAnalyzer2 *iface, | |
| IDWriteTextAnalysisSource1* source, UINT32 text_pos, UINT32 len, IDWriteTextAnalysisSink1 *sink) | |
| { | |
| FIXME("(%p %u %u %p): stub\n", source, text_pos, len, sink); | |
| return E_NOTIMPL; | |
| } | |
| static HRESULT WINAPI dwritetextanalyzer1_GetGlyphOrientationTransform(IDWriteTextAnalyzer2 *iface, | |
| DWRITE_GLYPH_ORIENTATION_ANGLE angle, BOOL is_sideways, DWRITE_MATRIX *transform) | |
| { | |
| TRACE("%d, %d, %p.\n", angle, is_sideways, transform); | |
| return IDWriteTextAnalyzer2_GetGlyphOrientationTransform(iface, angle, is_sideways, 0.0, 0.0, transform); | |
| } | |
| static HRESULT WINAPI dwritetextanalyzer1_GetScriptProperties(IDWriteTextAnalyzer2 *iface, DWRITE_SCRIPT_ANALYSIS sa, | |
| DWRITE_SCRIPT_PROPERTIES *props) | |
| { | |
| TRACE("%u, %p.\n", sa.script, props); | |
| if (sa.script > Script_LastId) | |
| return E_INVALIDARG; | |
| *props = dwritescripts_properties[sa.script].props; | |
| return S_OK; | |
| } | |
| static BOOL is_simple_script(UINT16 script) | |
| { | |
| switch (script) | |
| { | |
| case Script_Unknown: | |
| case Script_Common: | |
| case Script_Inherited: | |
| case Script_Armenian: | |
| case Script_Avestan: | |
| case Script_Balinese: | |
| case Script_Bamum: | |
| case Script_Batak: | |
| case Script_Bopomofo: | |
| case Script_Brahmi: | |
| case Script_Buginese: | |
| case Script_Buhid: | |
| case Script_Carian: | |
| case Script_Cham: | |
| case Script_Coptic: | |
| case Script_Cuneiform: | |
| case Script_Cypriot: | |
| case Script_Cyrillic: | |
| case Script_Egyptian_Hieroglyphs: | |
| case Script_Georgian: | |
| case Script_Glagolitic: | |
| case Script_Gothic: | |
| case Script_Greek: | |
| case Script_Han: | |
| case Script_Hanunoo: | |
| case Script_Hiragana: | |
| case Script_Imperial_Aramaic: | |
| case Script_Inscriptional_Pahlavi: | |
| case Script_Inscriptional_Parthian: | |
| case Script_Javanese: | |
| case Script_Kaithi: | |
| case Script_Katakana: | |
| case Script_Kayah_Li: | |
| case Script_Kharoshthi: | |
| case Script_Latin: | |
| case Script_Lepcha: | |
| case Script_Limbu: | |
| case Script_Linear_B: | |
| case Script_Lisu: | |
| case Script_Lycian: | |
| case Script_Lydian: | |
| case Script_Mandaic: | |
| case Script_Meetei_Mayek: | |
| case Script_Ol_Chiki: | |
| case Script_Old_Italic: | |
| case Script_Old_South_Arabian: | |
| case Script_Old_Turkic: | |
| case Script_Phoenician: | |
| case Script_Rejang: | |
| case Script_Samaritan: | |
| case Script_Saurashtra: | |
| case Script_Shavian: | |
| case Script_Sundanese: | |
| case Script_Syloti_Nagri: | |
| case Script_Tagalog: | |
| case Script_Tagbanwa: | |
| case Script_Tai_Tham: | |
| case Script_Tai_Viet: | |
| case Script_Ugaritic: | |
| return TRUE; | |
| default: | |
| return FALSE; | |
| } | |
| } | |
| static inline BOOL is_char_from_simple_script(WCHAR c) | |
| { | |
| if (IS_HIGH_SURROGATE(c) || IS_LOW_SURROGATE(c) || | |
| /* LRM, RLM, LRE, RLE, PDF, LRO, RLO */ | |
| c == 0x200e || c == 0x200f || (c >= 0x202a && c <= 0x202e)) | |
| { | |
| return FALSE; | |
| } | |
| return is_simple_script(get_char_script(c)); | |
| } | |
| static HRESULT WINAPI dwritetextanalyzer1_GetTextComplexity(IDWriteTextAnalyzer2 *iface, const WCHAR *text, | |
| UINT32 len, IDWriteFontFace *face, BOOL *is_simple, UINT32 *len_read, UINT16 *indices) | |
| { | |
| HRESULT hr = S_OK; | |
| int i; | |
| TRACE("%s:%u, %p, %p, %p, %p.\n", debugstr_wn(text, len), len, face, is_simple, len_read, indices); | |
| *is_simple = FALSE; | |
| *len_read = 0; | |
| if (!face) | |
| return E_INVALIDARG; | |
| if (len == 0) { | |
| *is_simple = TRUE; | |
| return S_OK; | |
| } | |
| *is_simple = text[0] && is_char_from_simple_script(text[0]); | |
| for (i = 1; i < len && text[i]; i++) { | |
| if (is_char_from_simple_script(text[i])) { | |
| if (!*is_simple) | |
| break; | |
| } | |
| else | |
| *is_simple = FALSE; | |
| } | |
| *len_read = i; | |
| /* fetch indices */ | |
| if (*is_simple && indices) | |
| { | |
| UINT32 *codepoints; | |
| if (!(codepoints = calloc(*len_read, sizeof(*codepoints)))) | |
| return E_OUTOFMEMORY; | |
| for (i = 0; i < *len_read; i++) | |
| codepoints[i] = text[i]; | |
| hr = IDWriteFontFace_GetGlyphIndices(face, codepoints, *len_read, indices); | |
| free(codepoints); | |
| } | |
| return hr; | |
| } | |
| static HRESULT WINAPI dwritetextanalyzer1_GetJustificationOpportunities(IDWriteTextAnalyzer2 *iface, | |
| IDWriteFontFace *face, FLOAT font_em_size, DWRITE_SCRIPT_ANALYSIS sa, UINT32 length, UINT32 glyph_count, | |
| const WCHAR *text, const UINT16 *clustermap, const DWRITE_SHAPING_GLYPH_PROPERTIES *prop, DWRITE_JUSTIFICATION_OPPORTUNITY *jo) | |
| { | |
| FIXME("(%p %.2f %u %u %u %s %p %p %p): stub\n", face, font_em_size, sa.script, length, glyph_count, | |
| debugstr_wn(text, length), clustermap, prop, jo); | |
| return E_NOTIMPL; | |
| } | |
| static HRESULT WINAPI dwritetextanalyzer1_JustifyGlyphAdvances(IDWriteTextAnalyzer2 *iface, | |
| FLOAT width, UINT32 glyph_count, const DWRITE_JUSTIFICATION_OPPORTUNITY *jo, const FLOAT *advances, | |
| const DWRITE_GLYPH_OFFSET *offsets, FLOAT *justifiedadvances, DWRITE_GLYPH_OFFSET *justifiedoffsets) | |
| { | |
| FIXME("(%.2f %u %p %p %p %p %p): stub\n", width, glyph_count, jo, advances, offsets, justifiedadvances, | |
| justifiedoffsets); | |
| return E_NOTIMPL; | |
| } | |
| static HRESULT WINAPI dwritetextanalyzer1_GetJustifiedGlyphs(IDWriteTextAnalyzer2 *iface, | |
| IDWriteFontFace *face, FLOAT font_em_size, DWRITE_SCRIPT_ANALYSIS sa, UINT32 length, | |
| UINT32 glyph_count, UINT32 max_glyphcount, const UINT16 *clustermap, const UINT16 *indices, | |
| const FLOAT *advances, const FLOAT *justifiedadvances, const DWRITE_GLYPH_OFFSET *justifiedoffsets, | |
| const DWRITE_SHAPING_GLYPH_PROPERTIES *prop, UINT32 *actual_count, UINT16 *modified_clustermap, | |
| UINT16 *modified_indices, FLOAT *modified_advances, DWRITE_GLYPH_OFFSET *modified_offsets) | |
| { | |
| FIXME("(%p %.2f %u %u %u %u %p %p %p %p %p %p %p %p %p %p %p): stub\n", face, font_em_size, sa.script, | |
| length, glyph_count, max_glyphcount, clustermap, indices, advances, justifiedadvances, justifiedoffsets, | |
| prop, actual_count, modified_clustermap, modified_indices, modified_advances, modified_offsets); | |
| return E_NOTIMPL; | |
| } | |
| static HRESULT WINAPI dwritetextanalyzer2_GetGlyphOrientationTransform(IDWriteTextAnalyzer2 *iface, | |
| DWRITE_GLYPH_ORIENTATION_ANGLE angle, BOOL is_sideways, FLOAT originX, FLOAT originY, DWRITE_MATRIX *m) | |
| { | |
| static const DWRITE_MATRIX transforms[] = { | |
| { 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f }, | |
| { 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f }, | |
| { -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f }, | |
| { 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f } | |
| }; | |
| TRACE("%d, %d, %.2f, %.2f, %p.\n", angle, is_sideways, originX, originY, m); | |
| if ((UINT32)angle > DWRITE_GLYPH_ORIENTATION_ANGLE_270_DEGREES) { | |
| memset(m, 0, sizeof(*m)); | |
| return E_INVALIDARG; | |
| } | |
| /* for sideways case simply rotate 90 degrees more */ | |
| if (is_sideways) { | |
| switch (angle) { | |
| case DWRITE_GLYPH_ORIENTATION_ANGLE_0_DEGREES: | |
| angle = DWRITE_GLYPH_ORIENTATION_ANGLE_90_DEGREES; | |
| break; | |
| case DWRITE_GLYPH_ORIENTATION_ANGLE_90_DEGREES: | |
| angle = DWRITE_GLYPH_ORIENTATION_ANGLE_180_DEGREES; | |
| break; | |
| case DWRITE_GLYPH_ORIENTATION_ANGLE_180_DEGREES: | |
| angle = DWRITE_GLYPH_ORIENTATION_ANGLE_270_DEGREES; | |
| break; | |
| case DWRITE_GLYPH_ORIENTATION_ANGLE_270_DEGREES: | |
| angle = DWRITE_GLYPH_ORIENTATION_ANGLE_0_DEGREES; | |
| break; | |
| default: | |
| ; | |
| } | |
| } | |
| *m = transforms[angle]; | |
| /* shift components represent transform necessary to get from original point to | |
| rotated one in new coordinate system */ | |
| if ((originX != 0.0f || originY != 0.0f) && angle != DWRITE_GLYPH_ORIENTATION_ANGLE_0_DEGREES) { | |
| m->dx = originX - (m->m11 * originX + m->m21 * originY); | |
| m->dy = originY - (m->m12 * originX + m->m22 * originY); | |
| } | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI dwritetextanalyzer2_GetTypographicFeatures(IDWriteTextAnalyzer2 *iface, | |
| IDWriteFontFace *fontface, DWRITE_SCRIPT_ANALYSIS sa, const WCHAR *locale, | |
| UINT32 max_tagcount, UINT32 *actual_tagcount, DWRITE_FONT_FEATURE_TAG *tags) | |
| { | |
| struct scriptshaping_context context = { 0 }; | |
| const struct dwritescript_properties *props; | |
| struct dwrite_fontface *font_obj; | |
| TRACE("%p, %p, %u, %s, %u, %p, %p.\n", iface, fontface, sa.script, debugstr_w(locale), max_tagcount, | |
| actual_tagcount, tags); | |
| if (sa.script > Script_LastId) | |
| return E_INVALIDARG; | |
| font_obj = unsafe_impl_from_IDWriteFontFace(fontface); | |
| context.cache = fontface_get_shaping_cache(font_obj); | |
| context.language_tag = get_opentype_language(locale); | |
| props = &dwritescripts_properties[sa.script]; | |
| return shape_get_typographic_features(&context, props->scripttags, max_tagcount, actual_tagcount, tags); | |
| }; | |
| static HRESULT WINAPI dwritetextanalyzer2_CheckTypographicFeature(IDWriteTextAnalyzer2 *iface, | |
| IDWriteFontFace *fontface, DWRITE_SCRIPT_ANALYSIS sa, const WCHAR *locale, DWRITE_FONT_FEATURE_TAG feature, | |
| UINT32 glyph_count, const UINT16 *glyphs, UINT8 *feature_applies) | |
| { | |
| struct scriptshaping_context context = { 0 }; | |
| const struct dwritescript_properties *props; | |
| struct dwrite_fontface *font_obj; | |
| HRESULT hr; | |
| TRACE("%p, %p, %u, %s, %s, %u, %p, %p.\n", iface, fontface, sa.script, debugstr_w(locale), debugstr_fourcc(feature), | |
| glyph_count, glyphs, feature_applies); | |
| if (sa.script > Script_LastId) | |
| return E_INVALIDARG; | |
| font_obj = unsafe_impl_from_IDWriteFontFace(fontface); | |
| context.cache = fontface_get_shaping_cache(font_obj); | |
| context.language_tag = get_opentype_language(locale); | |
| if (!(context.glyph_infos = calloc(glyph_count, sizeof(*context.glyph_infos)))) | |
| return E_OUTOFMEMORY; | |
| props = &dwritescripts_properties[sa.script]; | |
| hr = shape_check_typographic_feature(&context, props->scripttags, feature, glyph_count, glyphs, feature_applies); | |
| free(context.glyph_infos); | |
| return hr; | |
| } | |
| static const IDWriteTextAnalyzer2Vtbl textanalyzervtbl = | |
| { | |
| dwritetextanalyzer_QueryInterface, | |
| dwritetextanalyzer_AddRef, | |
| dwritetextanalyzer_Release, | |
| dwritetextanalyzer_AnalyzeScript, | |
| dwritetextanalyzer_AnalyzeBidi, | |
| dwritetextanalyzer_AnalyzeNumberSubstitution, | |
| dwritetextanalyzer_AnalyzeLineBreakpoints, | |
| dwritetextanalyzer_GetGlyphs, | |
| dwritetextanalyzer_GetGlyphPlacements, | |
| dwritetextanalyzer_GetGdiCompatibleGlyphPlacements, | |
| dwritetextanalyzer1_ApplyCharacterSpacing, | |
| dwritetextanalyzer1_GetBaseline, | |
| dwritetextanalyzer1_AnalyzeVerticalGlyphOrientation, | |
| dwritetextanalyzer1_GetGlyphOrientationTransform, | |
| dwritetextanalyzer1_GetScriptProperties, | |
| dwritetextanalyzer1_GetTextComplexity, | |
| dwritetextanalyzer1_GetJustificationOpportunities, | |
| dwritetextanalyzer1_JustifyGlyphAdvances, | |
| dwritetextanalyzer1_GetJustifiedGlyphs, | |
| dwritetextanalyzer2_GetGlyphOrientationTransform, | |
| dwritetextanalyzer2_GetTypographicFeatures, | |
| dwritetextanalyzer2_CheckTypographicFeature | |
| }; | |
| static IDWriteTextAnalyzer2 textanalyzer = { &textanalyzervtbl }; | |
| IDWriteTextAnalyzer2 *get_text_analyzer(void) | |
| { | |
| return &textanalyzer; | |
| } | |
| static HRESULT WINAPI dwritenumbersubstitution_QueryInterface(IDWriteNumberSubstitution *iface, REFIID riid, void **obj) | |
| { | |
| TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj); | |
| if (IsEqualIID(riid, &IID_IDWriteNumberSubstitution) || | |
| IsEqualIID(riid, &IID_IUnknown)) | |
| { | |
| *obj = iface; | |
| IDWriteNumberSubstitution_AddRef(iface); | |
| return S_OK; | |
| } | |
| WARN("%s not implemented.\n", debugstr_guid(riid)); | |
| *obj = NULL; | |
| return E_NOINTERFACE; | |
| } | |
| static ULONG WINAPI dwritenumbersubstitution_AddRef(IDWriteNumberSubstitution *iface) | |
| { | |
| struct dwrite_numbersubstitution *object = impl_from_IDWriteNumberSubstitution(iface); | |
| ULONG refcount = InterlockedIncrement(&object->refcount); | |
| TRACE("%p, refcount %ld.\n", iface, refcount); | |
| return refcount; | |
| } | |
| static ULONG WINAPI dwritenumbersubstitution_Release(IDWriteNumberSubstitution *iface) | |
| { | |
| struct dwrite_numbersubstitution *object = impl_from_IDWriteNumberSubstitution(iface); | |
| ULONG refcount = InterlockedDecrement(&object->refcount); | |
| TRACE("%p, refcount %ld.\n", iface, refcount); | |
| if (!refcount) | |
| { | |
| free(object->locale); | |
| free(object); | |
| } | |
| return refcount; | |
| } | |
| static const IDWriteNumberSubstitutionVtbl numbersubstitutionvtbl = | |
| { | |
| dwritenumbersubstitution_QueryInterface, | |
| dwritenumbersubstitution_AddRef, | |
| dwritenumbersubstitution_Release | |
| }; | |
| struct dwrite_numbersubstitution *unsafe_impl_from_IDWriteNumberSubstitution(IDWriteNumberSubstitution *iface) | |
| { | |
| if (!iface || iface->lpVtbl != &numbersubstitutionvtbl) | |
| return NULL; | |
| return CONTAINING_RECORD(iface, struct dwrite_numbersubstitution, IDWriteNumberSubstitution_iface); | |
| } | |
| HRESULT create_numbersubstitution(DWRITE_NUMBER_SUBSTITUTION_METHOD method, const WCHAR *locale, | |
| BOOL ignore_user_override, IDWriteNumberSubstitution **ret) | |
| { | |
| struct dwrite_numbersubstitution *substitution; | |
| *ret = NULL; | |
| if ((UINT32)method > DWRITE_NUMBER_SUBSTITUTION_METHOD_TRADITIONAL) | |
| return E_INVALIDARG; | |
| if (method != DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE && !IsValidLocaleName(locale)) | |
| return E_INVALIDARG; | |
| if (!(substitution = calloc(1, sizeof(*substitution)))) | |
| return E_OUTOFMEMORY; | |
| substitution->IDWriteNumberSubstitution_iface.lpVtbl = &numbersubstitutionvtbl; | |
| substitution->refcount = 1; | |
| substitution->ignore_user_override = ignore_user_override; | |
| substitution->method = method; | |
| substitution->locale = wcsdup(locale); | |
| if (locale && !substitution->locale) | |
| { | |
| free(substitution); | |
| return E_OUTOFMEMORY; | |
| } | |
| *ret = &substitution->IDWriteNumberSubstitution_iface; | |
| return S_OK; | |
| } | |
| /* IDWriteFontFallback */ | |
| static HRESULT WINAPI fontfallback_QueryInterface(IDWriteFontFallback1 *iface, REFIID riid, void **obj) | |
| { | |
| TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj); | |
| if (IsEqualIID(riid, &IID_IDWriteFontFallback1) || | |
| IsEqualIID(riid, &IID_IDWriteFontFallback) || | |
| IsEqualIID(riid, &IID_IUnknown)) | |
| { | |
| *obj = iface; | |
| IDWriteFontFallback1_AddRef(iface); | |
| return S_OK; | |
| } | |
| WARN("%s not implemented.\n", debugstr_guid(riid)); | |
| *obj = NULL; | |
| return E_NOINTERFACE; | |
| } | |
| static ULONG WINAPI fontfallback_AddRef(IDWriteFontFallback1 *iface) | |
| { | |
| struct dwrite_fontfallback *fallback = impl_from_IDWriteFontFallback1(iface); | |
| TRACE("%p.\n", iface); | |
| return IDWriteFactory7_AddRef(fallback->factory); | |
| } | |
| static ULONG WINAPI fontfallback_Release(IDWriteFontFallback1 *iface) | |
| { | |
| struct dwrite_fontfallback *fallback = impl_from_IDWriteFontFallback1(iface); | |
| TRACE("%p.\n", fallback); | |
| return IDWriteFactory7_Release(fallback->factory); | |
| } | |
| static inline BOOL fallback_is_uvs(const struct text_source_context *context) | |
| { | |
| /* MONGOLIAN FREE VARIATION SELECTOR ONE..THREE */ | |
| if (context->ch >= 0x180b && context->ch <= 0x180d) return TRUE; | |
| /* VARIATION SELECTOR-1..16 */ | |
| if (context->ch >= 0xfe00 && context->ch <= 0xfe0f) return TRUE; | |
| /* VARIATION SELECTOR-17..256 */ | |
| if (context->ch >= 0xe0100 && context->ch <= 0xe01ef) return TRUE; | |
| return FALSE; | |
| } | |
| static UINT32 fallback_font_get_supported_length(IDWriteFont3 *font, IDWriteTextAnalysisSource *source, | |
| UINT32 position, UINT32 length) | |
| { | |
| struct text_source_context context; | |
| UINT32 mapped = 0; | |
| text_source_context_init(&context, source, position, length); | |
| while (!text_source_get_next_u32_char(&context)) | |
| { | |
| /* Ignore selectors that are not leading. */ | |
| if (!mapped || !fallback_is_uvs(&context)) | |
| { | |
| if (!IDWriteFont3_HasCharacter(font, context.ch)) break; | |
| } | |
| mapped += text_source_get_char_length(&context); | |
| } | |
| return mapped; | |
| } | |
| static HRESULT fallback_map_characters(const struct dwrite_fontfallback *fallback, IDWriteTextAnalysisSource *source, | |
| UINT32 position, UINT32 text_length, DWRITE_FONT_WEIGHT weight, DWRITE_FONT_STYLE style, | |
| DWRITE_FONT_STRETCH stretch, IDWriteFont **ret_font, UINT32 *ret_length, float *scale) | |
| { | |
| const struct fallback_mapping *mapping = NULL; | |
| struct text_source_context context; | |
| const struct fallback_data *data; | |
| const WCHAR *locale_name = NULL; | |
| struct fallback_locale *locale; | |
| UINT32 i, length = 0, mapped; | |
| IDWriteFont3 *font; | |
| HRESULT hr; | |
| /* ~0u is a marker for system fallback data */ | |
| data = fallback->data.count == ~0u ? &system_fallback : &fallback->data; | |
| /* We will try to map as much of given input as GetLocaleName() says. It's assumed that returned length covers | |
| whole span of characters set with that locale, so callback is only used once. */ | |
| if (FAILED(hr = IDWriteTextAnalysisSource_GetLocaleName(source, position, &length, &locale_name))) | |
| return hr; | |
| length = length ? min(length, text_length) : text_length; | |
| if (!locale_name) locale_name = L""; | |
| /* Lookup locale entry once, if specific locale is missing neutral one will be returned. */ | |
| locale = font_fallback_get_locale(&data->locales, locale_name, true); | |
| if (FAILED(hr = text_source_context_init(&context, source, position, length))) return hr; | |
| /* Find a mapping for given locale. */ | |
| text_source_get_next_u32_char(&context); | |
| mapping = find_fallback_mapping(data, locale, context.ch); | |
| mapped = text_source_get_char_length(&context); | |
| while (!text_source_get_next_u32_char(&context)) | |
| { | |
| if (find_fallback_mapping(data, locale, context.ch) != mapping) break; | |
| mapped += text_source_get_char_length(&context); | |
| } | |
| if (!mapping) | |
| { | |
| *ret_font = NULL; | |
| *ret_length = mapped; | |
| return S_OK; | |
| } | |
| /* Go through families in the mapping, use first family that supports some of the input. */ | |
| for (i = 0; i < mapping->families_count; ++i) | |
| { | |
| if (SUCCEEDED(create_matching_font(mapping->collection ? mapping->collection : fallback->systemcollection, | |
| mapping->families[i], weight, style, stretch, &IID_IDWriteFont3, (void **)&font))) | |
| { | |
| if (!(*ret_length = fallback_font_get_supported_length(font, source, position, mapped))) | |
| { | |
| IDWriteFont3_Release(font); | |
| continue; | |
| } | |
| *ret_font = (IDWriteFont *)font; | |
| *scale = mapping->scale; | |
| return S_OK; | |
| } | |
| } | |
| /* Mapping was found, but either font couldn't be created or there's no font that supports given input. */ | |
| *ret_font = NULL; | |
| *ret_length = length; | |
| return S_OK; | |
| } | |
| HRESULT create_matching_font(IDWriteFontCollection *collection, const WCHAR *name, DWRITE_FONT_WEIGHT weight, | |
| DWRITE_FONT_STYLE style, DWRITE_FONT_STRETCH stretch, REFIID riid, void **obj) | |
| { | |
| IDWriteFontFamily *family; | |
| BOOL exists = FALSE; | |
| IDWriteFont *font; | |
| HRESULT hr; | |
| UINT32 i; | |
| *obj = NULL; | |
| hr = IDWriteFontCollection_FindFamilyName(collection, name, &i, &exists); | |
| if (FAILED(hr)) | |
| return hr; | |
| if (!exists) | |
| return E_FAIL; | |
| hr = IDWriteFontCollection_GetFontFamily(collection, i, &family); | |
| if (FAILED(hr)) | |
| return hr; | |
| hr = IDWriteFontFamily_GetFirstMatchingFont(family, weight, stretch, style, &font); | |
| IDWriteFontFamily_Release(family); | |
| if (SUCCEEDED(hr)) | |
| { | |
| hr = IDWriteFont_QueryInterface(font, riid, obj); | |
| IDWriteFont_Release(font); | |
| } | |
| return hr; | |
| } | |
| static HRESULT WINAPI fontfallback_MapCharacters(IDWriteFontFallback1 *iface, IDWriteTextAnalysisSource *source, | |
| UINT32 position, UINT32 length, IDWriteFontCollection *basecollection, const WCHAR *basefamily, | |
| DWRITE_FONT_WEIGHT weight, DWRITE_FONT_STYLE style, DWRITE_FONT_STRETCH stretch, UINT32 *mapped_length, | |
| IDWriteFont **ret_font, float *scale) | |
| { | |
| struct dwrite_fontfallback *fallback = impl_from_IDWriteFontFallback1(iface); | |
| IDWriteFont3 *font; | |
| TRACE("%p, %p, %u, %u, %p, %s, %u, %u, %u, %p, %p, %p.\n", iface, source, position, length, | |
| basecollection, debugstr_w(basefamily), weight, style, stretch, mapped_length, ret_font, scale); | |
| *mapped_length = 0; | |
| *ret_font = NULL; | |
| *scale = 1.0f; | |
| if (!source) | |
| return E_INVALIDARG; | |
| if (!length) | |
| return S_OK; | |
| if (!basecollection) | |
| basecollection = fallback->systemcollection; | |
| if (basefamily && *basefamily) | |
| { | |
| if (SUCCEEDED(create_matching_font(basecollection, basefamily, weight, style, stretch, | |
| &IID_IDWriteFont, (void **)&font))) | |
| { | |
| if ((*mapped_length = fallback_font_get_supported_length(font, source, position, length))) | |
| { | |
| *ret_font = (IDWriteFont *)font; | |
| *scale = 1.0f; | |
| return S_OK; | |
| } | |
| IDWriteFont3_Release(font); | |
| } | |
| } | |
| return fallback_map_characters(fallback, source, position, length, weight, style, stretch, ret_font, mapped_length, scale); | |
| } | |
| static HRESULT WINAPI fontfallback1_MapCharacters(IDWriteFontFallback1 *iface, IDWriteTextAnalysisSource *source, | |
| UINT32 position, UINT32 length, IDWriteFontCollection *basecollection, const WCHAR *basefamily, | |
| DWRITE_FONT_AXIS_VALUE const *axis_values, UINT32 values_count, UINT32 *mapped_length, FLOAT *scale, | |
| IDWriteFontFace5 **ret_fontface) | |
| { | |
| FIXME("%p, %p, %u, %u, %p, %s, %p, %u, %p, %p, %p.\n", iface, source, position, length, basecollection, | |
| debugstr_w(basefamily), axis_values, values_count, mapped_length, scale, ret_fontface); | |
| return E_NOTIMPL; | |
| } | |
| static const IDWriteFontFallback1Vtbl fontfallbackvtbl = | |
| { | |
| fontfallback_QueryInterface, | |
| fontfallback_AddRef, | |
| fontfallback_Release, | |
| fontfallback_MapCharacters, | |
| fontfallback1_MapCharacters, | |
| }; | |
| void release_system_fontfallback(IDWriteFontFallback1 *iface) | |
| { | |
| struct dwrite_fontfallback *fallback = impl_from_IDWriteFontFallback1(iface); | |
| IDWriteFontCollection_Release(fallback->systemcollection); | |
| free(fallback); | |
| } | |
| static ULONG WINAPI customfontfallback_AddRef(IDWriteFontFallback1 *iface) | |
| { | |
| struct dwrite_fontfallback *fallback = impl_from_IDWriteFontFallback1(iface); | |
| ULONG refcount = InterlockedIncrement(&fallback->refcount); | |
| TRACE("%p, refcount %lu.\n", iface, refcount); | |
| return refcount; | |
| } | |
| static ULONG WINAPI customfontfallback_Release(IDWriteFontFallback1 *iface) | |
| { | |
| struct dwrite_fontfallback *fallback = impl_from_IDWriteFontFallback1(iface); | |
| ULONG refcount = InterlockedDecrement(&fallback->refcount); | |
| TRACE("%p, refcount %lu.\n", iface, refcount); | |
| if (!refcount) | |
| { | |
| IDWriteFactory7_Release(fallback->factory); | |
| if (fallback->systemcollection) | |
| IDWriteFontCollection_Release(fallback->systemcollection); | |
| release_fallback_data(&fallback->data); | |
| free(fallback); | |
| } | |
| return refcount; | |
| } | |
| static const IDWriteFontFallback1Vtbl customfontfallbackvtbl = | |
| { | |
| fontfallback_QueryInterface, | |
| customfontfallback_AddRef, | |
| customfontfallback_Release, | |
| fontfallback_MapCharacters, | |
| fontfallback1_MapCharacters, | |
| }; | |
| static HRESULT WINAPI fontfallbackbuilder_QueryInterface(IDWriteFontFallbackBuilder *iface, REFIID riid, void **obj) | |
| { | |
| TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj); | |
| if (IsEqualIID(riid, &IID_IDWriteFontFallbackBuilder) || IsEqualIID(riid, &IID_IUnknown)) { | |
| *obj = iface; | |
| IDWriteFontFallbackBuilder_AddRef(iface); | |
| return S_OK; | |
| } | |
| WARN("%s not implemented.\n", debugstr_guid(riid)); | |
| *obj = NULL; | |
| return E_NOINTERFACE; | |
| } | |
| static ULONG WINAPI fontfallbackbuilder_AddRef(IDWriteFontFallbackBuilder *iface) | |
| { | |
| struct dwrite_fontfallback_builder *fallbackbuilder = impl_from_IDWriteFontFallbackBuilder(iface); | |
| ULONG refcount = InterlockedIncrement(&fallbackbuilder->refcount); | |
| TRACE("%p, refcount %ld.\n", iface, refcount); | |
| return refcount; | |
| } | |
| static ULONG WINAPI fontfallbackbuilder_Release(IDWriteFontFallbackBuilder *iface) | |
| { | |
| struct dwrite_fontfallback_builder *builder = impl_from_IDWriteFontFallbackBuilder(iface); | |
| ULONG refcount = InterlockedDecrement(&builder->refcount); | |
| TRACE("%p, refcount %ld.\n", iface, refcount); | |
| if (!refcount) | |
| { | |
| IDWriteFactory7_Release(builder->factory); | |
| release_fallback_data(&builder->data); | |
| free(builder); | |
| } | |
| return refcount; | |
| } | |
| static struct fallback_locale * fallback_builder_add_locale(struct dwrite_fontfallback_builder *builder, | |
| const WCHAR *locale_name) | |
| { | |
| struct fallback_locale *locale; | |
| if (!locale_name) locale_name = L""; | |
| if ((locale = font_fallback_get_locale(&builder->data.locales, locale_name, false))) return locale; | |
| if (!(locale = calloc(1, sizeof(*locale)))) return NULL; | |
| lstrcpynW(locale->name, locale_name, ARRAY_SIZE(locale->name)); | |
| list_add_tail(&builder->data.locales, &locale->entry); | |
| return locale; | |
| } | |
| static HRESULT WINAPI fontfallbackbuilder_AddMapping(IDWriteFontFallbackBuilder *iface, | |
| const DWRITE_UNICODE_RANGE *ranges, UINT32 ranges_count, WCHAR const **families, UINT32 families_count, | |
| IDWriteFontCollection *collection, WCHAR const *locale_name, WCHAR const *base_family, float scale) | |
| { | |
| struct dwrite_fontfallback_builder *builder = impl_from_IDWriteFontFallbackBuilder(iface); | |
| struct fallback_mapping *mapping; | |
| struct fallback_locale *locale; | |
| unsigned int i, j, m, count; | |
| TRACE("%p, %p, %u, %p, %u, %p, %s, %s, %f.\n", iface, ranges, ranges_count, families, families_count, | |
| collection, debugstr_w(locale_name), debugstr_w(base_family), scale); | |
| if (!ranges || !ranges_count || !families || !families_count || scale < 0.0f) | |
| return E_INVALIDARG; | |
| if (base_family) | |
| FIXME("base family ignored.\n"); | |
| if (!dwrite_array_reserve((void **)&builder->data.mappings, &builder->mappings_size, | |
| builder->data.count + 1, sizeof(*builder->data.mappings))) | |
| { | |
| return E_OUTOFMEMORY; | |
| } | |
| mapping = &builder->data.mappings[builder->data.count]; | |
| memset(mapping, 0, sizeof(*mapping)); | |
| /* Append new mapping, link to its locale node. */ | |
| if (!(locale = fallback_builder_add_locale(builder, locale_name))) | |
| return E_FAIL; | |
| if (!(mapping->ranges = calloc(ranges_count, sizeof(*mapping->ranges)))) | |
| goto failed; | |
| /* Filter ranges that won't be usable. */ | |
| for (i = 0, count = 0; i < ranges_count; ++i) | |
| { | |
| if (ranges[i].first > ranges[i].last) continue; | |
| if (ranges[i].first > 0x10ffff) continue; | |
| mapping->ranges[count].first = ranges[i].first; | |
| mapping->ranges[count].last = min(ranges[i].last, 0x10ffff); | |
| count++; | |
| } | |
| if (!count) | |
| { | |
| release_fallback_mapping(mapping); | |
| return S_OK; | |
| } | |
| mapping->ranges_count = count; | |
| if (!(mapping->families = calloc(families_count, sizeof(*mapping->families)))) | |
| goto failed; | |
| mapping->families_count = families_count; | |
| for (i = 0; i < families_count; i++) | |
| if (!(mapping->families[i] = wcsdup(families[i]))) goto failed; | |
| mapping->scale = scale; | |
| if (FAILED(fallback_locale_add_mapping(locale, builder->data.count))) goto failed; | |
| /* Mappings with explicit collections take priority, for that reduce existing mappings ranges | |
| by newly added ranges. */ | |
| mapping->collection = collection; | |
| if (mapping->collection) | |
| { | |
| IDWriteFontCollection_AddRef(mapping->collection); | |
| for (m = 0; m < builder->data.count; ++m) | |
| { | |
| struct fallback_mapping *c = &builder->data.mappings[m]; | |
| if (c->collection) continue; | |
| for (i = 0; i < count; ++i) | |
| { | |
| const DWRITE_UNICODE_RANGE *new_range = &mapping->ranges[i]; | |
| for (j = 0; j < c->ranges_count; ++j) | |
| { | |
| DWRITE_UNICODE_RANGE *range = &c->ranges[j]; | |
| /* In case existing ranges intersect, disable or reduce them */ | |
| if (range->first >= new_range->first && range->last <= new_range->last) | |
| { | |
| range->first = range->last = ~0u; | |
| } | |
| else if (range->first >= new_range->first && range->first <= new_range->last) | |
| { | |
| range->first = new_range->last; | |
| } | |
| else if (range->last >= new_range->first && range->last <= new_range->last) | |
| { | |
| range->last = new_range->first; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| builder->data.count++; | |
| return S_OK; | |
| failed: | |
| release_fallback_mapping(mapping); | |
| return E_OUTOFMEMORY; | |
| } | |
| static HRESULT WINAPI fontfallbackbuilder_AddMappings(IDWriteFontFallbackBuilder *iface, IDWriteFontFallback *fallback) | |
| { | |
| FIXME("%p, %p stub.\n", iface, fallback); | |
| return E_NOTIMPL; | |
| } | |
| static HRESULT fallbackbuilder_init_fallback_data(const struct dwrite_fontfallback_builder *builder, | |
| struct fallback_data *data) | |
| { | |
| struct fallback_locale *iter, *locale; | |
| size_t i, j; | |
| /* Duplicate locales list. */ | |
| list_init(&data->locales); | |
| LIST_FOR_EACH_ENTRY(iter, &builder->data.locales, struct fallback_locale, entry) | |
| { | |
| if (!(locale = calloc(1, sizeof(*locale)))) goto failed; | |
| wcscpy(locale->name, iter->name); | |
| locale->ranges.count = iter->ranges.count; | |
| locale->ranges.size = iter->ranges.count; | |
| if (!(locale->ranges.data = malloc(iter->ranges.count * sizeof(*iter->ranges.data)))) | |
| { | |
| free(locale); | |
| goto failed; | |
| } | |
| memcpy(locale->ranges.data, iter->ranges.data, iter->ranges.count * sizeof(*iter->ranges.data)); | |
| list_add_tail(&data->locales, &locale->entry); | |
| } | |
| /* Duplicate mappings. */ | |
| if (!(data->mappings = calloc(builder->data.count, sizeof(*data->mappings)))) | |
| goto failed; | |
| data->count = builder->data.count; | |
| for (i = 0; i < data->count; ++i) | |
| { | |
| struct fallback_mapping *src = &builder->data.mappings[i]; | |
| struct fallback_mapping *dst = &data->mappings[i]; | |
| if (!(dst->ranges = calloc(src->ranges_count, sizeof(*src->ranges)))) goto failed; | |
| memcpy(dst->ranges, src->ranges, src->ranges_count * sizeof(*src->ranges)); | |
| dst->ranges_count = src->ranges_count; | |
| if (!(dst->families = calloc(src->families_count, sizeof(*src->families)))) goto failed; | |
| dst->families_count = src->families_count; | |
| for (j = 0; j < src->families_count; ++j) | |
| { | |
| if (!(dst->families[j] = wcsdup(src->families[j]))) goto failed; | |
| } | |
| dst->collection = src->collection; | |
| if (dst->collection) | |
| IDWriteFontCollection_AddRef(dst->collection); | |
| dst->scale = src->scale; | |
| } | |
| return S_OK; | |
| failed: | |
| return E_OUTOFMEMORY; | |
| } | |
| static HRESULT fallbackbuilder_create_fallback(struct dwrite_fontfallback_builder *builder, struct dwrite_fontfallback **ret) | |
| { | |
| struct dwrite_fontfallback *fallback; | |
| HRESULT hr; | |
| if (!(fallback = calloc(1, sizeof(*fallback)))) | |
| return E_OUTOFMEMORY; | |
| fallback->IDWriteFontFallback1_iface.lpVtbl = &customfontfallbackvtbl; | |
| fallback->refcount = 1; | |
| fallback->factory = builder->factory; | |
| IDWriteFactory7_AddRef(fallback->factory); | |
| if (FAILED(hr = IDWriteFactory_GetSystemFontCollection((IDWriteFactory *)fallback->factory, | |
| &fallback->systemcollection, FALSE))) | |
| { | |
| goto done; | |
| } | |
| if (FAILED(hr = fallbackbuilder_init_fallback_data(builder, &fallback->data))) | |
| { | |
| goto done; | |
| } | |
| *ret = fallback; | |
| return S_OK; | |
| done: | |
| IDWriteFontFallback1_Release(&fallback->IDWriteFontFallback1_iface); | |
| return hr; | |
| } | |
| static HRESULT WINAPI fontfallbackbuilder_CreateFontFallback(IDWriteFontFallbackBuilder *iface, | |
| IDWriteFontFallback **ret) | |
| { | |
| struct dwrite_fontfallback_builder *builder = impl_from_IDWriteFontFallbackBuilder(iface); | |
| struct dwrite_fontfallback *fallback; | |
| HRESULT hr; | |
| TRACE("%p, %p.\n", iface, ret); | |
| *ret = NULL; | |
| if (SUCCEEDED(hr = fallbackbuilder_create_fallback(builder, &fallback))) | |
| { | |
| *ret = (IDWriteFontFallback *)&fallback->IDWriteFontFallback1_iface; | |
| } | |
| return hr; | |
| } | |
| static const IDWriteFontFallbackBuilderVtbl fontfallbackbuildervtbl = | |
| { | |
| fontfallbackbuilder_QueryInterface, | |
| fontfallbackbuilder_AddRef, | |
| fontfallbackbuilder_Release, | |
| fontfallbackbuilder_AddMapping, | |
| fontfallbackbuilder_AddMappings, | |
| fontfallbackbuilder_CreateFontFallback, | |
| }; | |
| static HRESULT create_fontfallback_builder_internal(IDWriteFactory7 *factory, struct dwrite_fontfallback_builder **ret) | |
| { | |
| struct dwrite_fontfallback_builder *builder; | |
| *ret = NULL; | |
| if (!(builder = calloc(1, sizeof(*builder)))) | |
| return E_OUTOFMEMORY; | |
| builder->IDWriteFontFallbackBuilder_iface.lpVtbl = &fontfallbackbuildervtbl; | |
| builder->refcount = 1; | |
| builder->factory = factory; | |
| IDWriteFactory7_AddRef(builder->factory); | |
| list_init(&builder->data.locales); | |
| *ret = builder; | |
| return S_OK; | |
| } | |
| HRESULT create_fontfallback_builder(IDWriteFactory7 *factory, IDWriteFontFallbackBuilder **ret) | |
| { | |
| struct dwrite_fontfallback_builder *builder; | |
| HRESULT hr; | |
| *ret = NULL; | |
| if (SUCCEEDED(hr = create_fontfallback_builder_internal(factory, &builder))) | |
| *ret = &builder->IDWriteFontFallbackBuilder_iface; | |
| return hr; | |
| } | |
| static void system_fallback_parse_ranges(const char *str, DWRITE_UNICODE_RANGE *ranges, | |
| unsigned int max_count, unsigned int *ret) | |
| { | |
| unsigned int count = 0; | |
| char *end; | |
| while (*str && count < max_count) | |
| { | |
| ranges[count].first = ranges[count].last = strtoul(str, &end, 16); | |
| if (*end == '-') | |
| { | |
| str = end + 1; | |
| ranges[count].last = strtoul(str, &end, 16); | |
| } | |
| str = end; | |
| if (*str == ',') str++; | |
| count++; | |
| } | |
| *ret = count; | |
| } | |
| static void system_fallback_parse_families(WCHAR *str, WCHAR **families, unsigned int max_count, | |
| unsigned int *ret) | |
| { | |
| unsigned int count = 0; | |
| WCHAR *family, *ctx; | |
| family = wcstok_s(str, L",", &ctx); | |
| while (family && count < max_count) | |
| { | |
| while (*family == ' ') family++; | |
| families[count++] = family; | |
| family = wcstok_s(NULL, L",", &ctx); | |
| } | |
| *ret = count; | |
| } | |
| static INIT_ONCE init_system_fallback_once = INIT_ONCE_STATIC_INIT; | |
| /* Particular factory instance used for initialization is not important, it won't be referenced by | |
| created fallback data. */ | |
| static BOOL WINAPI dwrite_system_fallback_initonce(INIT_ONCE *once, void *param, void **context) | |
| { | |
| struct dwrite_fontfallback_builder *builder; | |
| IDWriteFontFallbackBuilder *builder_iface; | |
| unsigned int range_count, families_count; | |
| IDWriteFactory7 *factory = param; | |
| DWRITE_UNICODE_RANGE ranges[16]; | |
| WCHAR *families[4], *str; | |
| HRESULT hr; | |
| size_t i; | |
| if (FAILED(create_fontfallback_builder_internal(factory, &builder))) return FALSE; | |
| builder_iface = &builder->IDWriteFontFallbackBuilder_iface; | |
| for (i = 0; i < ARRAY_SIZE(system_fallback_config); ++i) | |
| { | |
| const struct fallback_description *entry = &system_fallback_config[i]; | |
| system_fallback_parse_ranges(entry->ranges, ranges, ARRAY_SIZE(ranges), &range_count); | |
| /* TODO: reuse the buffer */ | |
| str = wcsdup(entry->families); | |
| system_fallback_parse_families(str, families, ARRAY_SIZE(families), &families_count); | |
| if (FAILED(hr = IDWriteFontFallbackBuilder_AddMapping(builder_iface, ranges, range_count, | |
| (const WCHAR **)families, families_count, NULL, entry->locale, NULL, 1.0f))) | |
| { | |
| WARN("Failed to add mapping, hr %#lx.\n", hr); | |
| } | |
| free(str); | |
| } | |
| hr = fallbackbuilder_init_fallback_data(builder, &system_fallback); | |
| IDWriteFontFallbackBuilder_Release(builder_iface); | |
| return hr == S_OK; | |
| } | |
| void release_system_fallback_data(void) | |
| { | |
| release_fallback_data(&system_fallback); | |
| } | |
| HRESULT create_system_fontfallback(IDWriteFactory7 *factory, IDWriteFontFallback1 **ret) | |
| { | |
| struct dwrite_fontfallback *fallback; | |
| *ret = NULL; | |
| if (!InitOnceExecuteOnce(&init_system_fallback_once, dwrite_system_fallback_initonce, factory, NULL)) | |
| { | |
| WARN("Failed to initialize system fallback data.\n"); | |
| return E_FAIL; | |
| } | |
| if (!(fallback = calloc(1, sizeof(*fallback)))) | |
| return E_OUTOFMEMORY; | |
| fallback->IDWriteFontFallback1_iface.lpVtbl = &fontfallbackvtbl; | |
| fallback->factory = factory; | |
| fallback->data.count = ~0u; | |
| IDWriteFactory_GetSystemFontCollection((IDWriteFactory *)fallback->factory, &fallback->systemcollection, FALSE); | |
| *ret = &fallback->IDWriteFontFallback1_iface; | |
| return S_OK; | |
| } |