| /* |
| * 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[] DECLSPEC_HIDDEN; |
| extern const unsigned short wine_scripts_table[] DECLSPEC_HIDDEN; |
| |
| /* Number of characters needed for LOCALE_SNATIVEDIGITS */ |
| #define NATIVE_DIGITS_LEN 11 |
| |
| struct dwritescript_properties { |
| DWRITE_SCRIPT_PROPERTIES props; |
| UINT32 scripttag; /* OpenType script tag */ |
| UINT32 scriptalttag; /* Version 2 tag, 0 if not defined */ |
| BOOL is_complex; |
| const struct scriptshaping_ops *ops; |
| }; |
| |
| #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'), 0, TRUE }, |
| { /* 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','e','n','g'), _OT('b','n','g','2'), TRUE }, |
| { /* 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'), 0, TRUE }, |
| { /* 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'), 0, TRUE }, |
| { /* 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'), 0, TRUE }, |
| { /* 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'), 0, TRUE }, |
| { /* Deva */ { 0x61766544, 315, 15, 0x0020, 1, 1, 0, 0, 0, 1, 0 }, _OT('d','e','v','a'), _OT('d','e','v','2'), TRUE }, |
| { /* 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'), 0, TRUE }, |
| { /* 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','u','j','r'), _OT('g','j','r','2'), TRUE }, |
| { /* Guru */ { 0x75727547, 310, 15, 0x0020, 1, 1, 0, 0, 0, 1, 0 }, _OT('g','u','r','u'), _OT('g','u','r','2'), TRUE }, |
| { /* 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'), 0, TRUE }, |
| { /* 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'), 0, TRUE }, |
| { /* 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','a'), _OT('k','n','d','2'), TRUE }, |
| { /* 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'), 0, TRUE }, |
| { /* Laoo */ { 0x6f6f614c, 356, 8, 0x0020, 1, 0, 1, 0, 1, 0, 0 }, _OT('l','a','o',' '), 0, TRUE }, |
| { /* Latn */ { 0x6e74614c, 215, 1, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, _OT('l','a','t','n'), 0, FALSE, &latn_shaping_ops }, |
| { /* 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','y','m'), _OT('m','l','m','2'), TRUE }, |
| { /* 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'), 0, TRUE }, |
| { /* Mymr */ { 0x726d794d, 350, 15, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, _OT('m','y','m','r'), 0, TRUE }, |
| { /* Talu */ { 0x756c6154, 354, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, _OT('t','a','l','u'), 0, TRUE }, |
| { /* Nkoo */ { 0x6f6f6b4e, 165, 8, 0x0020, 0, 1, 0, 0, 0, 1, 1 }, _OT('n','k','o',' '), 0, TRUE }, |
| { /* Ogam */ { 0x6d61674f, 212, 1, 0x1680, 0, 1, 0, 0, 0, 1, 0 }, _OT('o','g','a','m'), 0, TRUE }, |
| { /* 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'), 0, TRUE }, |
| { /* 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','a'), _OT('o','r','y','2'), TRUE }, |
| { /* Osma */ { 0x616d734f, 260, 8, 0x0020, 0, 1, 1, 0, 0, 0, 0 }, _OT('o','s','m','a'), 0, TRUE }, |
| { /* Phag */ { 0x67616850, 331, 8, 0x0020, 0, 1, 0, 0, 0, 1, 1 }, _OT('p','h','a','g'), 0, TRUE }, |
| { /* 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'), 0, TRUE }, |
| { /* 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'), 0, TRUE }, |
| { /* 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'), 0, TRUE }, |
| { /* 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'), 0, TRUE }, |
| { /* 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','a','m','l'), _OT('t','m','l','2'), TRUE }, |
| { /* Telu */ { 0x756c6554, 340, 15, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, _OT('t','e','l','u'), _OT('t','e','l','2'), TRUE }, |
| { /* Thaa */ { 0x61616854, 170, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, _OT('t','h','a','a'), 0, TRUE }, |
| { /* Thai */ { 0x69616854, 352, 8, 0x0020, 1, 0, 1, 0, 1, 0, 0 }, _OT('t','h','a','i'), 0, TRUE }, |
| { /* Tibt */ { 0x74626954, 330, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, _OT('t','i','b','t'), 0, TRUE }, |
| { /* Tfng */ { 0x676e6654, 120, 8, 0x0020, 1, 1, 1, 0, 0, 0, 0 }, _OT('t','f','n','g'), 0, TRUE }, |
| { /* 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',' '), 0, TRUE }, |
| { /* Yiii */ { 0x69696959, 460, 1, 0x0020, 0, 0, 1, 1, 0, 0, 0 }, _OT('y','i',' ',' '), 0, TRUE }, |
| { /* 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') }, |
| }; |
| #undef _OT |
| |
| const char *debugstr_sa_script(UINT16 script) |
| { |
| return script < Script_LastId ? debugstr_an((char*)&dwritescripts_properties[script].props.isoScriptCode, 4): |
| "undefined"; |
| } |
| |
| /* system font falback configuration */ |
| static const WCHAR meiryoW[] = {'M','e','i','r','y','o',0}; |
| |
| static const WCHAR *cjk_families[] = { meiryoW }; |
| |
| static const DWRITE_UNICODE_RANGE cjk_ranges[] = |
| { |
| { 0x3000, 0x30ff }, /* CJK Symbols and Punctuation, Hiragana, Katakana */ |
| { 0x31f0, 0x31ff }, /* Katakana Phonetic Extensions */ |
| { 0x4e00, 0x9fff }, /* CJK Unified Ideographs */ |
| }; |
| |
| struct fallback_mapping { |
| DWRITE_UNICODE_RANGE *ranges; |
| UINT32 ranges_count; |
| WCHAR **families; |
| UINT32 families_count; |
| IDWriteFontCollection *collection; |
| WCHAR *locale; |
| FLOAT scale; |
| }; |
| |
| static const struct fallback_mapping fontfallback_neutral_data[] = { |
| #define MAPPING_RANGE(ranges, families) \ |
| { (DWRITE_UNICODE_RANGE *)ranges, sizeof(ranges)/sizeof(ranges[0]), \ |
| (WCHAR **)families, sizeof(families)/sizeof(families[0]) } |
| |
| MAPPING_RANGE(cjk_ranges, cjk_families), |
| |
| #undef MAPPING_RANGE |
| }; |
| |
| struct dwrite_fontfallback { |
| IDWriteFontFallback IDWriteFontFallback_iface; |
| LONG ref; |
| IDWriteFactory5 *factory; |
| IDWriteFontCollection1 *systemcollection; |
| struct fallback_mapping *mappings; |
| UINT32 mappings_count; |
| }; |
| |
| struct dwrite_fontfallback_builder { |
| IDWriteFontFallbackBuilder IDWriteFontFallbackBuilder_iface; |
| LONG ref; |
| IDWriteFactory5 *factory; |
| struct fallback_mapping *mappings; |
| UINT32 mappings_count; |
| UINT32 mappings_capacity; |
| }; |
| |
| struct dwrite_numbersubstitution { |
| IDWriteNumberSubstitution IDWriteNumberSubstitution_iface; |
| LONG ref; |
| |
| 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_IDWriteFontFallback(IDWriteFontFallback *iface) |
| { |
| return CONTAINING_RECORD(iface, struct dwrite_fontfallback, IDWriteFontFallback_iface); |
| } |
| |
| static inline struct dwrite_fontfallback_builder *impl_from_IDWriteFontFallbackBuilder(IDWriteFontFallbackBuilder *iface) |
| { |
| return CONTAINING_RECORD(iface, struct dwrite_fontfallback_builder, IDWriteFontFallbackBuilder_iface); |
| } |
| |
| static inline UINT32 decode_surrogate_pair(const WCHAR *str, UINT32 index, UINT32 end) |
| { |
| if (index < end-1 && IS_SURROGATE_PAIR(str[index], str[index+1])) { |
| UINT32 ch = 0x10000 + ((str[index] - 0xd800) << 10) + (str[index+1] - 0xdc00); |
| TRACE("surrogate pair (%x %x) => %x\n", str[index], str[index+1], ch); |
| return ch; |
| } |
| return 0; |
| } |
| |
| static inline UINT16 get_char_script(WCHAR c) |
| { |
| UINT16 script = get_table_entry(wine_scripts_table, c); |
| return script == Script_Inherited ? Script_Unknown : script; |
| } |
| |
| static DWRITE_SCRIPT_ANALYSIS get_char_sa(WCHAR c) |
| { |
| DWRITE_SCRIPT_ANALYSIS sa; |
| |
| sa.script = get_char_script(c); |
| sa.shapes = iscntrlW(c) || c == 0x2028 /* LINE SEPARATOR */ || c == 0x2029 /* PARAGRAPH SEPARATOR */ ? |
| DWRITE_SCRIPT_SHAPES_NO_VISUAL : DWRITE_SCRIPT_SHAPES_DEFAULT; |
| return sa; |
| } |
| |
| static HRESULT analyze_script(const WCHAR *text, UINT32 position, UINT32 length, IDWriteTextAnalysisSink *sink) |
| { |
| DWRITE_SCRIPT_ANALYSIS sa; |
| UINT32 pos, i, seq_length; |
| |
| if (!length) |
| return S_OK; |
| |
| sa = get_char_sa(*text); |
| |
| pos = position; |
| seq_length = 1; |
| |
| for (i = 1; i < length; i++) |
| { |
| DWRITE_SCRIPT_ANALYSIS cur_sa = get_char_sa(text[i]); |
| |
| /* 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) |
| seq_length++; |
| else { |
| HRESULT hr; |
| |
| hr = IDWriteTextAnalysisSink_SetScriptAnalysis(sink, pos, seq_length, &sa); |
| if (FAILED(hr)) return hr; |
| pos = position + i; |
| seq_length = 1; |
| sa = cur_sa; |
| } |
| } |
| |
| /* one char length case or normal completion call */ |
| return IDWriteTextAnalysisSink_SetScriptAnalysis(sink, pos, seq_length, &sa); |
| } |
| |
| struct linebreaking_state { |
| DWRITE_LINE_BREAKPOINT *breakpoints; |
| 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, |
| }; |
| |
| 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) |
| { |
| if (location == BreakConditionBefore) { |
| if (has_strong_condition(state->breakpoints[pos].breakConditionBefore, condition)) |
| return; |
| state->breakpoints[pos].breakConditionBefore = condition; |
| if (pos > 0) |
| state->breakpoints[pos-1].breakConditionAfter = condition; |
| } |
| else { |
| if (has_strong_condition(state->breakpoints[pos].breakConditionAfter, condition)) |
| return; |
| state->breakpoints[pos].breakConditionAfter = condition; |
| if (pos + 1 < state->count) |
| state->breakpoints[pos+1].breakConditionBefore = condition; |
| } |
| } |
| |
| BOOL lb_is_newline_char(WCHAR ch) |
| { |
| short c = get_table_entry(wine_linebreak_table, ch); |
| return c == b_LF || c == b_NL || c == b_CR || c == b_BK; |
| } |
| |
| static HRESULT analyze_linebreaks(const WCHAR *text, UINT32 count, DWRITE_LINE_BREAKPOINT *breakpoints) |
| { |
| struct linebreaking_state state; |
| short *break_class; |
| int i, j; |
| |
| break_class = heap_alloc(count*sizeof(short)); |
| if (!break_class) |
| return E_OUTOFMEMORY; |
| |
| state.breakpoints = breakpoints; |
| state.count = count; |
| |
| for (i = 0; i < count; i++) |
| { |
| break_class[i] = get_table_entry(wine_linebreak_table, text[i]); |
| |
| breakpoints[i].breakConditionBefore = DWRITE_BREAK_CONDITION_NEUTRAL; |
| breakpoints[i].breakConditionAfter = DWRITE_BREAK_CONDITION_NEUTRAL; |
| breakpoints[i].isWhitespace = !!isspaceW(text[i]); |
| breakpoints[i].isSoftHyphen = text[i] == 0x00ad /* Unicode Soft Hyphen */; |
| breakpoints[i].padding = 0; |
| |
| /* LB1 - resolve some classes. TODO: use external algorithms for these classes. */ |
| switch (break_class[i]) |
| { |
| case b_AI: |
| case b_SA: |
| case b_SG: |
| case b_XX: |
| break_class[i] = b_AL; |
| break; |
| case b_CJ: |
| break_class[i] = b_NS; |
| break; |
| } |
| } |
| |
| /* 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 */ |
| 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 between ZWJ and an ideograph, emoji base or emoji modifier */ |
| case b_ZWJ: |
| if (i < count-1 && (break_class[i+1] == b_ID || break_class[i+1] == b_EB || break_class[i+1] == b_EM)) |
| 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) |
| 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_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_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: |
| if (i > 0) |
| { |
| switch (break_class[i-1]) |
| { |
| case b_AL: |
| case b_HL: |
| case b_EX: |
| case b_ID: |
| case b_EB: |
| case b_EM: |
| case b_IN: |
| case b_NU: |
| 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); |
| } |
| |
| heap_free(break_class); |
| 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; |
| } |
| |
| /* This helper tries to get 'length' chars from a source, allocating a buffer only if source failed to provide enough |
| data after a first request. */ |
| static HRESULT get_text_source_ptr(IDWriteTextAnalysisSource *source, UINT32 position, UINT32 length, const WCHAR **text, WCHAR **buff) |
| { |
| HRESULT hr; |
| UINT32 len; |
| |
| *buff = NULL; |
| *text = NULL; |
| len = 0; |
| hr = IDWriteTextAnalysisSource_GetTextAtPosition(source, position, text, &len); |
| if (FAILED(hr)) return hr; |
| |
| if (len < length) { |
| UINT32 read; |
| |
| *buff = heap_alloc(length*sizeof(WCHAR)); |
| if (!*buff) |
| return E_OUTOFMEMORY; |
| memcpy(*buff, *text, len*sizeof(WCHAR)); |
| read = len; |
| |
| while (read < length && *text) { |
| *text = NULL; |
| len = 0; |
| hr = IDWriteTextAnalysisSource_GetTextAtPosition(source, read, text, &len); |
| if (FAILED(hr)) { |
| heap_free(*buff); |
| return hr; |
| } |
| memcpy(*buff + read, *text, min(len, length-read)*sizeof(WCHAR)); |
| read += len; |
| } |
| |
| *text = *buff; |
| } |
| |
| return hr; |
| } |
| |
| static HRESULT WINAPI dwritetextanalyzer_AnalyzeScript(IDWriteTextAnalyzer2 *iface, |
| IDWriteTextAnalysisSource* source, UINT32 position, UINT32 length, IDWriteTextAnalysisSink* sink) |
| { |
| WCHAR *buff = NULL; |
| const WCHAR *text; |
| HRESULT hr; |
| |
| TRACE("(%p %u %u %p)\n", source, position, length, sink); |
| |
| if (length == 0) |
| return S_OK; |
| |
| hr = get_text_source_ptr(source, position, length, &text, &buff); |
| if (FAILED(hr)) |
| return hr; |
| |
| hr = analyze_script(text, position, length, sink); |
| heap_free(buff); |
| |
| return hr; |
| } |
| |
| static HRESULT WINAPI dwritetextanalyzer_AnalyzeBidi(IDWriteTextAnalyzer2 *iface, |
| IDWriteTextAnalysisSource* source, UINT32 position, UINT32 length, IDWriteTextAnalysisSink* sink) |
| { |
| UINT8 *levels = NULL, *explicit = NULL; |
| UINT8 baselevel, level, explicit_level; |
| UINT32 pos, i, seq_length; |
| WCHAR *buff = NULL; |
| const WCHAR *text; |
| HRESULT hr; |
| |
| TRACE("(%p %u %u %p)\n", source, position, length, sink); |
| |
| if (!length) |
| return S_OK; |
| |
| hr = get_text_source_ptr(source, position, length, &text, &buff); |
| if (FAILED(hr)) |
| return hr; |
| |
| levels = heap_alloc(length*sizeof(*levels)); |
| explicit = heap_alloc(length*sizeof(*explicit)); |
| |
| if (!levels || !explicit) { |
| hr = E_OUTOFMEMORY; |
| goto done; |
| } |
| |
| baselevel = IDWriteTextAnalysisSource_GetParagraphReadingDirection(source); |
| hr = bidi_computelevels(text, length, baselevel, explicit, levels); |
| if (FAILED(hr)) |
| goto done; |
| |
| level = levels[0]; |
| explicit_level = explicit[0]; |
| pos = position; |
| seq_length = 1; |
| |
| for (i = 1; i < length; i++) { |
| if (levels[i] == level && explicit[i] == explicit_level) |
| seq_length++; |
| else { |
| hr = IDWriteTextAnalysisSink_SetBidiLevel(sink, pos, seq_length, explicit_level, level); |
| if (FAILED(hr)) |
| goto done; |
| |
| pos += seq_length; |
| seq_length = 1; |
| level = levels[i]; |
| explicit_level = explicit[i]; |
| } |
| } |
| /* one char length case or normal completion call */ |
| hr = IDWriteTextAnalysisSink_SetBidiLevel(sink, pos, seq_length, explicit_level, level); |
| |
| done: |
| heap_free(explicit); |
| heap_free(levels); |
| heap_free(buff); |
| |
| 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 = NULL; |
| WCHAR *buff = NULL; |
| const WCHAR *text; |
| HRESULT hr; |
| UINT32 len; |
| |
| TRACE("(%p %u %u %p)\n", source, position, length, sink); |
| |
| if (length == 0) |
| return S_OK; |
| |
| /* get some, check for length */ |
| text = NULL; |
| len = 0; |
| hr = IDWriteTextAnalysisSource_GetTextAtPosition(source, position, &text, &len); |
| if (FAILED(hr)) return hr; |
| |
| if (len < length) { |
| UINT32 read; |
| |
| buff = heap_alloc(length*sizeof(WCHAR)); |
| if (!buff) |
| return E_OUTOFMEMORY; |
| memcpy(buff, text, len*sizeof(WCHAR)); |
| read = len; |
| |
| while (read < length && text) { |
| text = NULL; |
| len = 0; |
| hr = IDWriteTextAnalysisSource_GetTextAtPosition(source, read, &text, &len); |
| if (FAILED(hr)) |
| goto done; |
| memcpy(&buff[read], text, min(len, length-read)*sizeof(WCHAR)); |
| read += len; |
| } |
| |
| text = buff; |
| } |
| |
| breakpoints = heap_alloc(length*sizeof(*breakpoints)); |
| if (!breakpoints) { |
| hr = E_OUTOFMEMORY; |
| goto done; |
| } |
| |
| hr = analyze_linebreaks(text, length, breakpoints); |
| if (FAILED(hr)) |
| goto done; |
| |
| hr = IDWriteTextAnalysisSink_SetLineBreakpoints(sink, position, length, breakpoints); |
| |
| done: |
| heap_free(breakpoints); |
| heap_free(buff); |
| |
| return hr; |
| } |
| |
| static UINT32 get_opentype_language(const WCHAR *locale) |
| { |
| UINT32 language = DWRITE_FONT_FEATURE_TAG_DEFAULT; |
| |
| if (locale) { |
| WCHAR tag[5]; |
| if (GetLocaleInfoEx(locale, LOCALE_SOPENTYPELANGUAGETAG, tag, sizeof(tag)/sizeof(WCHAR))) |
| language = DWRITE_MAKE_OPENTYPE_TAG(tag[0],tag[1],tag[2],tag[3]); |
| } |
| |
| return language; |
| } |
| |
| static DWRITE_NUMBER_SUBSTITUTION_METHOD get_number_substitutes(IDWriteNumberSubstitution *substitution, WCHAR *digits) |
| { |
| struct dwrite_numbersubstitution *numbersubst = unsafe_impl_from_IDWriteNumberSubstitution(substitution); |
| DWRITE_NUMBER_SUBSTITUTION_METHOD method; |
| WCHAR isolang[9]; |
| DWORD lctype; |
| |
| if (!numbersubst) |
| return DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE; |
| |
| 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 %u, 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; |
| |
| digits[0] = 0; |
| 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, sizeof(isolang)/sizeof(isolang[0]))) { |
| static const WCHAR arW[] = {'a','r',0}; |
| 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 (!strcmpW(arW, isolang)) { |
| strcpyW(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; |
| } |
| |
| return method; |
| } |
| |
| 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; i++, 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_an((char *)&features[i]->features[j].nameTag, 4), |
| 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* glyph_indices, |
| DWRITE_SHAPING_GLYPH_PROPERTIES* glyph_props, UINT32* actual_glyph_count) |
| { |
| const struct dwritescript_properties *scriptprops; |
| DWRITE_NUMBER_SUBSTITUTION_METHOD method; |
| struct scriptshaping_context context; |
| struct scriptshaping_cache *cache = NULL; |
| BOOL update_cluster, need_vertical; |
| WCHAR digits[NATIVE_DIGITS_LEN]; |
| IDWriteFontFace1 *fontface1; |
| WCHAR *string; |
| UINT32 i, g; |
| HRESULT hr = S_OK; |
| UINT16 script; |
| |
| 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, glyph_indices, |
| glyph_props, actual_glyph_count); |
| |
| analyzer_dump_user_features(features, feature_range_lengths, feature_ranges); |
| |
| script = analysis->script > Script_LastId ? Script_Unknown : analysis->script; |
| |
| if (max_glyph_count < length) |
| return E_NOT_SUFFICIENT_BUFFER; |
| |
| string = heap_alloc(sizeof(WCHAR)*length); |
| if (!string) |
| return E_OUTOFMEMORY; |
| |
| method = get_number_substitutes(substitution, digits); |
| |
| for (i = 0; i < length; i++) { |
| /* FIXME: set to better values */ |
| glyph_props[i].justification = text[i] == ' ' ? SCRIPT_JUSTIFY_BLANK : SCRIPT_JUSTIFY_CHARACTER; |
| glyph_props[i].isClusterStart = 1; |
| glyph_props[i].isDiacritic = 0; |
| glyph_props[i].isZeroWidthSpace = 0; |
| glyph_props[i].reserved = 0; |
| |
| /* FIXME: have the shaping engine set this */ |
| text_props[i].isShapedAlone = 0; |
| text_props[i].reserved = 0; |
| |
| clustermap[i] = i; |
| |
| string[i] = text[i]; |
| switch (method) |
| { |
| case DWRITE_NUMBER_SUBSTITUTION_METHOD_CONTEXTUAL: |
| if (!is_rtl) |
| break; |
| /* fallthrough */ |
| default: |
| if (string[i] >= '0' && string[i] <= '9') |
| string[i] = digits[string[i] - '0']; |
| break; |
| case DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE: |
| ; |
| } |
| } |
| |
| for (; i < max_glyph_count; i++) { |
| glyph_props[i].justification = SCRIPT_JUSTIFY_NONE; |
| glyph_props[i].isClusterStart = 0; |
| glyph_props[i].isDiacritic = 0; |
| glyph_props[i].isZeroWidthSpace = 0; |
| glyph_props[i].reserved = 0; |
| } |
| |
| hr = IDWriteFontFace_QueryInterface(fontface, &IID_IDWriteFontFace1, (void**)&fontface1); |
| if (FAILED(hr)) |
| WARN("failed to get IDWriteFontFace1\n"); |
| |
| need_vertical = is_sideways && fontface1 && IDWriteFontFace1_HasVerticalGlyphVariants(fontface1); |
| |
| for (i = 0, g = 0, update_cluster = FALSE; i < length; i++) { |
| UINT32 codepoint; |
| |
| if (!update_cluster) { |
| codepoint = decode_surrogate_pair(string, i, length); |
| if (!codepoint) |
| codepoint = is_rtl ? bidi_get_mirrored_char(string[i]) : string[i]; |
| else |
| update_cluster = TRUE; |
| |
| hr = IDWriteFontFace_GetGlyphIndices(fontface, &codepoint, 1, &glyph_indices[g]); |
| if (FAILED(hr)) |
| goto done; |
| |
| if (need_vertical) { |
| UINT16 vertical; |
| |
| hr = IDWriteFontFace1_GetVerticalGlyphVariants(fontface1, 1, &glyph_indices[g], &vertical); |
| if (hr == S_OK) |
| glyph_indices[g] = vertical; |
| } |
| |
| g++; |
| } |
| else { |
| INT32 k; |
| |
| update_cluster = FALSE; |
| /* mark surrogate halves with same cluster */ |
| clustermap[i] = clustermap[i-1]; |
| /* update following clusters */ |
| for (k = i + 1; k >= 0 && k < length; k++) |
| clustermap[k]--; |
| } |
| } |
| *actual_glyph_count = g; |
| |
| hr = create_scriptshaping_cache(fontface, &cache); |
| if (FAILED(hr)) |
| goto done; |
| |
| context.cache = cache; |
| context.text = text; |
| context.length = length; |
| context.is_rtl = is_rtl; |
| context.max_glyph_count = max_glyph_count; |
| context.language_tag = get_opentype_language(locale); |
| |
| scriptprops = &dwritescripts_properties[script]; |
| if (scriptprops->ops && scriptprops->ops->contextual_shaping) { |
| hr = scriptprops->ops->contextual_shaping(&context, clustermap, glyph_indices, actual_glyph_count); |
| if (FAILED(hr)) |
| goto done; |
| } |
| |
| /* FIXME: apply default features */ |
| |
| if (scriptprops->ops && scriptprops->ops->set_text_glyphs_props) |
| hr = scriptprops->ops->set_text_glyphs_props(&context, clustermap, glyph_indices, *actual_glyph_count, text_props, glyph_props); |
| else |
| hr = default_shaping_ops.set_text_glyphs_props(&context, clustermap, glyph_indices, *actual_glyph_count, text_props, glyph_props); |
| |
| done: |
| if (fontface1) |
| IDWriteFontFace1_Release(fontface1); |
| release_scriptshaping_cache(cache); |
| heap_free(string); |
| |
| return hr; |
| } |
| |
| static HRESULT WINAPI dwritetextanalyzer_GetGlyphPlacements(IDWriteTextAnalyzer2 *iface, |
| WCHAR const* text, UINT16 const* clustermap, DWRITE_SHAPING_TEXT_PROPERTIES* 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) |
| { |
| DWRITE_FONT_METRICS metrics; |
| IDWriteFontFace1 *fontface1; |
| HRESULT hr; |
| UINT32 i; |
| |
| 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, 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; |
| |
| hr = IDWriteFontFace_QueryInterface(fontface, &IID_IDWriteFontFace1, (void**)&fontface1); |
| if (FAILED(hr)) { |
| WARN("failed to get IDWriteFontFace1.\n"); |
| return hr; |
| } |
| |
| IDWriteFontFace_GetMetrics(fontface, &metrics); |
| for (i = 0; i < glyph_count; i++) { |
| if (glyph_props[i].isZeroWidthSpace) |
| advances[i] = 0.0f; |
| else { |
| INT32 a; |
| |
| hr = IDWriteFontFace1_GetDesignGlyphAdvances(fontface1, 1, &glyphs[i], &a, is_sideways); |
| if (FAILED(hr)) |
| a = 0; |
| advances[i] = get_scaled_advance_width(a, emSize, &metrics); |
| } |
| offsets[i].advanceOffset = 0.0f; |
| offsets[i].ascenderOffset = 0.0f; |
| } |
| |
| /* FIXME: actually apply features */ |
| |
| IDWriteFontFace1_Release(fontface1); |
| return S_OK; |
| } |
| |
| static HRESULT WINAPI dwritetextanalyzer_GetGdiCompatibleGlyphPlacements(IDWriteTextAnalyzer2 *iface, |
| WCHAR const* text, UINT16 const* clustermap, DWRITE_SHAPING_TEXT_PROPERTIES* 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) |
| { |
| DWRITE_FONT_METRICS metrics; |
| IDWriteFontFace1 *fontface1; |
| HRESULT hr; |
| UINT32 i; |
| |
| 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, 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; |
| |
| hr = IDWriteFontFace_QueryInterface(fontface, &IID_IDWriteFontFace1, (void**)&fontface1); |
| if (FAILED(hr)) { |
| WARN("failed to get IDWriteFontFace1.\n"); |
| return hr; |
| } |
| |
| hr = IDWriteFontFace_GetGdiCompatibleMetrics(fontface, emSize, ppdip, transform, &metrics); |
| if (FAILED(hr)) { |
| IDWriteFontFace1_Release(fontface1); |
| WARN("failed to get compat metrics, 0x%08x\n", hr); |
| return hr; |
| } |
| for (i = 0; i < glyph_count; i++) { |
| INT32 a; |
| |
| hr = IDWriteFontFace1_GetGdiCompatibleGlyphAdvances(fontface1, emSize, ppdip, |
| transform, use_gdi_natural, is_sideways, 1, &glyphs[i], &a); |
| if (FAILED(hr)) |
| advances[i] = 0.0f; |
| else |
| advances[i] = floorf(a * emSize * ppdip / metrics.designUnitsPerEm + 0.5f) / ppdip; |
| offsets[i].advanceOffset = 0.0f; |
| offsets[i].ascenderOffset = 0.0f; |
| } |
| |
| /* FIXME: actually apply features */ |
| |
| IDWriteFontFace1_Release(fontface1); |
| return S_OK; |
| } |
| |
| static inline FLOAT get_cluster_advance(const FLOAT *advances, UINT32 start, UINT32 end) |
| { |
| FLOAT advance = 0.0f; |
| for (; start < end; start++) |
| advance += advances[start]; |
| return advance; |
| } |
| |
| static void apply_single_glyph_spacing(FLOAT leading_spacing, FLOAT trailing_spacing, |
| FLOAT min_advance_width, UINT32 g, FLOAT const *advances, DWRITE_GLYPH_OFFSET const *offsets, |
| DWRITE_SHAPING_GLYPH_PROPERTIES const *props, FLOAT *modified_advances, DWRITE_GLYPH_OFFSET *modified_offsets) |
| { |
| BOOL reduced = leading_spacing < 0.0f || trailing_spacing < 0.0f; |
| FLOAT advance = advances[g]; |
| FLOAT origin = 0.0f; |
| |
| if (props[g].isZeroWidthSpace) { |
| modified_advances[g] = advances[g]; |
| modified_offsets[g] = offsets[g]; |
| return; |
| } |
| |
| /* first apply negative spacing and check if we hit minimum width */ |
| if (leading_spacing < 0.0f) { |
| advance += leading_spacing; |
| origin -= leading_spacing; |
| } |
| if (trailing_spacing < 0.0f) |
| advance += trailing_spacing; |
| |
| if (advance < min_advance_width) { |
| FLOAT half = (min_advance_width - advance) / 2.0f; |
| |
| if (!reduced) |
| origin -= half; |
| else if (leading_spacing < 0.0f && trailing_spacing < 0.0f) |
| origin -= half; |
| else if (leading_spacing < 0.0f) |
| origin -= min_advance_width - advance; |
| |
| advance = min_advance_width; |
| } |
| |
| /* now apply positive spacing adjustments */ |
| if (leading_spacing > 0.0f) { |
| advance += leading_spacing; |
| origin -= leading_spacing; |
| } |
| if (trailing_spacing > 0.0f) |
| advance += trailing_spacing; |
| |
| modified_advances[g] = advance; |
| modified_offsets[g].advanceOffset = offsets[g].advanceOffset - origin; |
| /* ascender is never touched, it's orthogonal to reading direction and is not |
| affected by advance adjustments */ |
| modified_offsets[g].ascenderOffset = offsets[g].ascenderOffset; |
| } |
| |
| static void apply_cluster_spacing(FLOAT leading_spacing, FLOAT trailing_spacing, FLOAT min_advance_width, |
| UINT32 start, UINT32 end, FLOAT const *advances, DWRITE_GLYPH_OFFSET const *offsets, |
| FLOAT *modified_advances, DWRITE_GLYPH_OFFSET *modified_offsets) |
| { |
| BOOL reduced = leading_spacing < 0.0f || trailing_spacing < 0.0f; |
| FLOAT advance = get_cluster_advance(advances, start, end); |
| FLOAT origin = 0.0f; |
| UINT16 g; |
| |
| modified_advances[start] = advances[start]; |
| modified_advances[end-1] = advances[end-1]; |
| |
| /* first apply negative spacing and check if we hit minimum width */ |
| if (leading_spacing < 0.0f) { |
| advance += leading_spacing; |
| modified_advances[start] += leading_spacing; |
| origin -= leading_spacing; |
| } |
| if (trailing_spacing < 0.0f) { |
| advance += trailing_spacing; |
| modified_advances[end-1] += trailing_spacing; |
| } |
| |
| advance = min_advance_width - advance; |
| if (advance > 0.0f) { |
| /* additional spacing is only applied to leading and trailing glyph */ |
| FLOAT half = advance / 2.0f; |
| |
| if (!reduced) { |
| origin -= half; |
| modified_advances[start] += half; |
| modified_advances[end-1] += half; |
| } |
| else if (leading_spacing < 0.0f && trailing_spacing < 0.0f) { |
| origin -= half; |
| modified_advances[start] += half; |
| modified_advances[end-1] += half; |
| } |
| else if (leading_spacing < 0.0f) { |
| origin -= advance; |
| modified_advances[start] += advance; |
| } |
| else |
| modified_advances[end-1] += advance; |
| } |
| |
| /* now apply positive spacing adjustments */ |
| if (leading_spacing > 0.0f) { |
| modified_advances[start] += leading_spacing; |
| origin -= leading_spacing; |
| } |
| if (trailing_spacing > 0.0f) |
| modified_advances[end-1] += trailing_spacing; |
| |
| for (g = start; g < end; g++) { |
| if (g == start) { |
| modified_offsets[g].advanceOffset = offsets[g].advanceOffset - origin; |
| modified_offsets[g].ascenderOffset = offsets[g].ascenderOffset; |
| } |
| else if (g == end - 1) |
| /* trailing glyph offset is not adjusted */ |
| modified_offsets[g] = offsets[g]; |
| else { |
| /* for all glyphs within a cluster use original advances and offsets */ |
| modified_advances[g] = advances[g]; |
| modified_offsets[g] = offsets[g]; |
| } |
| } |
| } |
| |
| 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 && 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. |
| |
| TODO: test other properties; make isZeroWidthSpace work properly for clusters |
| with more than one glyph. |
| |
| */ |
| 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) |
| { |
| UINT16 start; |
| |
| 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; |
| } |
| |
| /* minimum advance is not applied if no adjustments were made */ |
| if (leading_spacing == 0.0f && trailing_spacing == 0.0f) { |
| memmove(modified_advances, advances, glyph_count*sizeof(*advances)); |
| memmove(modified_offsets, offsets, glyph_count*sizeof(*offsets)); |
| return S_OK; |
| } |
| |
| for (start = 0; start < len;) { |
| UINT32 length = get_cluster_length(clustermap, start, len); |
| |
| if (length == 1) { |
| UINT32 g = clustermap[start]; |
| |
| apply_single_glyph_spacing(leading_spacing, trailing_spacing, min_advance_width, |
| g, advances, offsets, props, modified_advances, modified_offsets); |
| } |
| else { |
| UINT32 g_start, g_end; |
| |
| g_start = clustermap[start]; |
| g_end = (start + length < len) ? clustermap[start + length] : glyph_count; |
| |
| apply_cluster_spacing(leading_spacing, trailing_spacing, min_advance_width, |
| g_start, g_end, advances, offsets, modified_advances, modified_offsets); |
| } |
| |
| start += length; |
| } |
| |
| return S_OK; |
| } |
| |
| static HRESULT WINAPI dwritetextanalyzer1_GetBaseline(IDWriteTextAnalyzer2 *iface, IDWriteFontFace *face, |
| DWRITE_BASELINE baseline, BOOL vertical, BOOL is_simulation_allowed, DWRITE_SCRIPT_ANALYSIS sa, |
| const WCHAR *localeName, INT32 *baseline_coord, BOOL *exists) |
| { |
| FIXME("(%p %d %d %u %s %p %p): stub\n", face, vertical, is_simulation_allowed, sa.script, debugstr_w(localeName), |
| baseline_coord, exists); |
| return E_NOTIMPL; |
| } |
| |
| 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 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; |
| else { |
| UINT16 script = get_char_script(c); |
| return !dwritescripts_properties[script].is_complex; |
| } |
| } |
| |
| 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 = heap_alloc(*len_read*sizeof(UINT32)); |
| if (!codepoints) |
| return E_OUTOFMEMORY; |
| |
| for (i = 0; i < *len_read; i++) |
| codepoints[i] = text[i]; |
| |
| hr = IDWriteFontFace_GetGlyphIndices(face, codepoints, *len_read, indices); |
| heap_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) |
| { |
| const struct dwritescript_properties *props; |
| HRESULT hr = S_OK; |
| UINT32 language; |
| |
| TRACE("(%p %u %s %u %p %p)\n", fontface, sa.script, debugstr_w(locale), max_tagcount, actual_tagcount, |
| tags); |
| |
| if (sa.script > Script_LastId) |
| return E_INVALIDARG; |
| |
| language = get_opentype_language(locale); |
| props = &dwritescripts_properties[sa.script]; |
| *actual_tagcount = 0; |
| |
| if (props->scriptalttag) |
| hr = opentype_get_typographic_features(fontface, props->scriptalttag, language, max_tagcount, actual_tagcount, tags); |
| |
| if (*actual_tagcount == 0) |
| hr = opentype_get_typographic_features(fontface, props->scripttag, language, max_tagcount, actual_tagcount, tags); |
| |
| return hr; |
| }; |
| |
| static HRESULT WINAPI dwritetextanalyzer2_CheckTypographicFeature(IDWriteTextAnalyzer2 *iface, |
| IDWriteFontFace *face, DWRITE_SCRIPT_ANALYSIS sa, const WCHAR *localeName, |
| DWRITE_FONT_FEATURE_TAG feature, UINT32 glyph_count, const UINT16 *indices, UINT8 *feature_applies) |
| { |
| FIXME("(%p %u %s %x %u %p %p): stub\n", face, sa.script, debugstr_w(localeName), feature, glyph_count, indices, |
| feature_applies); |
| return E_NOTIMPL; |
| } |
| |
| static const struct 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 }; |
| |
| IDWriteTextAnalyzer *get_text_analyzer(void) |
| { |
| return (IDWriteTextAnalyzer *)&textanalyzer; |
| } |
| |
| static HRESULT WINAPI dwritenumbersubstitution_QueryInterface(IDWriteNumberSubstitution *iface, REFIID riid, void **obj) |
| { |
| struct dwrite_numbersubstitution *This = impl_from_IDWriteNumberSubstitution(iface); |
| |
| TRACE("(%p)->(%s %p)\n", This, 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 *This = impl_from_IDWriteNumberSubstitution(iface); |
| ULONG ref = InterlockedIncrement(&This->ref); |
| TRACE("(%p)->(%d)\n", This, ref); |
| return ref; |
| } |
| |
| static ULONG WINAPI dwritenumbersubstitution_Release(IDWriteNumberSubstitution *iface) |
| { |
| struct dwrite_numbersubstitution *This = impl_from_IDWriteNumberSubstitution(iface); |
| ULONG ref = InterlockedDecrement(&This->ref); |
| |
| TRACE("(%p)->(%d)\n", This, ref); |
| |
| if (!ref) { |
| heap_free(This->locale); |
| heap_free(This); |
| } |
| |
| return ref; |
| } |
| |
| static const struct 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; |
| |
| substitution = heap_alloc(sizeof(*substitution)); |
| if (!substitution) |
| return E_OUTOFMEMORY; |
| |
| substitution->IDWriteNumberSubstitution_iface.lpVtbl = &numbersubstitutionvtbl; |
| substitution->ref = 1; |
| substitution->ignore_user_override = ignore_user_override; |
| substitution->method = method; |
| substitution->locale = heap_strdupW(locale); |
| if (locale && !substitution->locale) { |
| heap_free(substitution); |
| return E_OUTOFMEMORY; |
| } |
| |
| *ret = &substitution->IDWriteNumberSubstitution_iface; |
| return S_OK; |
| } |
| |
| /* IDWriteFontFallback */ |
| static HRESULT WINAPI fontfallback_QueryInterface(IDWriteFontFallback *iface, REFIID riid, void **obj) |
| { |
| struct dwrite_fontfallback *fallback = impl_from_IDWriteFontFallback(iface); |
| |
| TRACE("(%p)->(%s %p)\n", fallback, debugstr_guid(riid), obj); |
| |
| if (IsEqualIID(riid, &IID_IDWriteFontFallback) || IsEqualIID(riid, &IID_IUnknown)) { |
| *obj = iface; |
| IDWriteFontFallback_AddRef(iface); |
| return S_OK; |
| } |
| |
| WARN("%s not implemented.\n", debugstr_guid(riid)); |
| |
| *obj = NULL; |
| return E_NOINTERFACE; |
| } |
| |
| static ULONG WINAPI fontfallback_AddRef(IDWriteFontFallback *iface) |
| { |
| struct dwrite_fontfallback *fallback = impl_from_IDWriteFontFallback(iface); |
| TRACE("(%p)\n", fallback); |
| return IDWriteFactory5_AddRef(fallback->factory); |
| } |
| |
| static ULONG WINAPI fontfallback_Release(IDWriteFontFallback *iface) |
| { |
| struct dwrite_fontfallback *fallback = impl_from_IDWriteFontFallback(iface); |
| TRACE("(%p)\n", fallback); |
| return IDWriteFactory5_Release(fallback->factory); |
| } |
| |
| static int compare_mapping_range(const void *a, const void *b) |
| { |
| UINT32 ch = *(UINT32 *)a; |
| DWRITE_UNICODE_RANGE *range = (DWRITE_UNICODE_RANGE *)b; |
| |
| if (ch > range->last) |
| return 1; |
| else if (ch < range->first) |
| return -1; |
| else |
| return 0; |
| } |
| |
| static const struct fallback_mapping *find_fallback_mapping(struct dwrite_fontfallback *fallback, UINT32 ch) |
| { |
| UINT32 i; |
| |
| for (i = 0; i < fallback->mappings_count; i++) { |
| struct fallback_mapping *mapping = &fallback->mappings[i]; |
| |
| if (bsearch(&ch, mapping->ranges, mapping->ranges_count, sizeof(*mapping->ranges), |
| compare_mapping_range) != NULL) |
| return mapping; |
| } |
| |
| return NULL; |
| } |
| |
| HRESULT create_matching_font(IDWriteFontCollection *collection, const WCHAR *name, |
| DWRITE_FONT_WEIGHT weight, DWRITE_FONT_STYLE style, DWRITE_FONT_STRETCH stretch, IDWriteFont **font) |
| { |
| IDWriteFontFamily *family; |
| BOOL exists = FALSE; |
| HRESULT hr; |
| UINT32 i; |
| |
| *font = 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); |
| return hr; |
| } |
| |
| static HRESULT fallback_map_characters(IDWriteFont *font, const WCHAR *text, UINT32 length, UINT32 *mapped_length) |
| { |
| HRESULT hr = S_OK; |
| UINT32 i; |
| |
| for (i = 0; i < length; i++) { |
| UINT16 script = get_char_script(text[i]); |
| BOOL exists; |
| |
| if (script == Script_Unknown || script == Script_Common) { |
| ++*mapped_length; |
| continue; |
| } |
| |
| /* stop on first unsupported character */ |
| exists = FALSE; |
| hr = IDWriteFont_HasCharacter(font, text[i], &exists); |
| if (hr == S_OK && exists) |
| ++*mapped_length; |
| else |
| break; |
| } |
| |
| return hr; |
| } |
| |
| static HRESULT fallback_get_fallback_font(struct dwrite_fontfallback *fallback, const WCHAR *text, UINT32 length, |
| DWRITE_FONT_WEIGHT weight, DWRITE_FONT_STYLE style, DWRITE_FONT_STRETCH stretch, UINT32 *mapped_length, |
| IDWriteFont **mapped_font) |
| { |
| const struct fallback_mapping *mapping; |
| HRESULT hr; |
| UINT32 i; |
| |
| *mapped_font = NULL; |
| |
| mapping = find_fallback_mapping(fallback, text[0]); |
| if (!mapping) { |
| WARN("No mapping range for %#x.\n", text[0]); |
| return E_FAIL; |
| } |
| |
| /* Now let's see what fallback can handle. Pick first font that could be created. */ |
| for (i = 0; i < mapping->families_count; i++) { |
| hr = create_matching_font((IDWriteFontCollection *)fallback->systemcollection, mapping->families[i], |
| weight, style, stretch, mapped_font); |
| if (hr == S_OK) { |
| TRACE("Created fallback font using family %s.\n", debugstr_w(mapping->families[i])); |
| break; |
| } |
| } |
| |
| if (!*mapped_font) { |
| WARN("Failed to create fallback font.\n"); |
| return E_FAIL; |
| } |
| |
| hr = fallback_map_characters(*mapped_font, text, length, mapped_length); |
| if (FAILED(hr)) |
| WARN("Mapping with fallback family %s failed, hr %#x.\n", debugstr_w(mapping->families[i]), hr); |
| |
| if (!*mapped_length) { |
| IDWriteFont_Release(*mapped_font); |
| *mapped_font = NULL; |
| } |
| |
| return *mapped_length ? S_OK : E_FAIL; |
| } |
| |
| static HRESULT WINAPI fontfallback_MapCharacters(IDWriteFontFallback *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_IDWriteFontFallback(iface); |
| WCHAR *buff = NULL; |
| const WCHAR *text; |
| HRESULT hr; |
| |
| TRACE("(%p)->(%p %u %u %p, %s, %u, %u, %u, %p, %p, %p)\n", fallback, source, position, length, |
| basecollection, debugstr_w(basefamily), weight, style, stretch, mapped_length, ret_font, scale); |
| |
| *mapped_length = 0; |
| *scale = 1.0f; |
| *ret_font = NULL; |
| |
| if (!source) |
| return E_INVALIDARG; |
| |
| if (length == 0) |
| return S_OK; |
| |
| if (!basecollection) |
| basecollection = (IDWriteFontCollection*)fallback->systemcollection; |
| |
| hr = get_text_source_ptr(source, position, length, &text, &buff); |
| if (FAILED(hr)) |
| goto done; |
| |
| if (basefamily && *basefamily) { |
| hr = create_matching_font(basecollection, basefamily, weight, style, stretch, ret_font); |
| if (FAILED(hr)) |
| goto done; |
| |
| hr = fallback_map_characters(*ret_font, text, length, mapped_length); |
| if (FAILED(hr)) |
| goto done; |
| } |
| |
| if (!*mapped_length) { |
| IDWriteFont *mapped_font; |
| |
| hr = fallback_get_fallback_font(fallback, text, length, weight, style, stretch, mapped_length, &mapped_font); |
| if (FAILED(hr)) { |
| /* fallback wasn't found, keep base font if any, so we can get at least some visual output */ |
| if (*ret_font) { |
| *mapped_length = length; |
| hr = S_OK; |
| } |
| } |
| else { |
| if (*ret_font) |
| IDWriteFont_Release(*ret_font); |
| *ret_font = mapped_font; |
| } |
| } |
| |
| done: |
| heap_free(buff); |
| return hr; |
| } |
| |
| static const IDWriteFontFallbackVtbl fontfallbackvtbl = { |
| fontfallback_QueryInterface, |
| fontfallback_AddRef, |
| fontfallback_Release, |
| fontfallback_MapCharacters |
| }; |
| |
| HRESULT create_system_fontfallback(IDWriteFactory5 *factory, IDWriteFontFallback **ret) |
| { |
| struct dwrite_fontfallback *fallback; |
| |
| *ret = NULL; |
| |
| fallback = heap_alloc(sizeof(*fallback)); |
| if (!fallback) |
| return E_OUTOFMEMORY; |
| |
| fallback->IDWriteFontFallback_iface.lpVtbl = &fontfallbackvtbl; |
| fallback->factory = factory; |
| fallback->mappings = (struct fallback_mapping *)fontfallback_neutral_data; |
| fallback->mappings_count = sizeof(fontfallback_neutral_data) / sizeof(*fontfallback_neutral_data); |
| IDWriteFactory5_GetSystemFontCollection(fallback->factory, FALSE, &fallback->systemcollection, FALSE); |
| |
| *ret = &fallback->IDWriteFontFallback_iface; |
| return S_OK; |
| } |
| |
| void release_system_fontfallback(IDWriteFontFallback *iface) |
| { |
| struct dwrite_fontfallback *fallback = impl_from_IDWriteFontFallback(iface); |
| IDWriteFontCollection1_Release(fallback->systemcollection); |
| heap_free(fallback); |
| } |
| |
| static ULONG WINAPI customfontfallback_AddRef(IDWriteFontFallback *iface) |
| { |
| struct dwrite_fontfallback *fallback = impl_from_IDWriteFontFallback(iface); |
| ULONG ref = InterlockedIncrement(&fallback->ref); |
| TRACE("(%p)->(%d)\n", fallback, ref); |
| return ref; |
| } |
| |
| static ULONG WINAPI customfontfallback_Release(IDWriteFontFallback *iface) |
| { |
| struct dwrite_fontfallback *fallback = impl_from_IDWriteFontFallback(iface); |
| ULONG ref = InterlockedDecrement(&fallback->ref); |
| |
| TRACE("(%p)->(%d)\n", fallback, ref); |
| |
| if (!ref) { |
| IDWriteFactory5_Release(fallback->factory); |
| heap_free(fallback); |
| } |
| |
| return ref; |
| } |
| |
| static HRESULT WINAPI customfontfallback_MapCharacters(IDWriteFontFallback *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_IDWriteFontFallback(iface); |
| |
| FIXME("(%p)->(%p %u %u %p, %s, %u, %u, %u, %p, %p, %p)\n", fallback, source, position, length, |
| basecollection, debugstr_w(basefamily), weight, style, stretch, mapped_length, ret_font, scale); |
| |
| return E_NOTIMPL; |
| } |
| |
| static const IDWriteFontFallbackVtbl customfontfallbackvtbl = |
| { |
| fontfallback_QueryInterface, |
| customfontfallback_AddRef, |
| customfontfallback_Release, |
| customfontfallback_MapCharacters, |
| }; |
| |
| static HRESULT WINAPI fontfallbackbuilder_QueryInterface(IDWriteFontFallbackBuilder *iface, REFIID riid, void **obj) |
| { |
| struct dwrite_fontfallback_builder *fallbackbuilder = impl_from_IDWriteFontFallbackBuilder(iface); |
| |
| TRACE("(%p)->(%s %p)\n", fallbackbuilder, 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 ref = InterlockedIncrement(&fallbackbuilder->ref); |
| TRACE("(%p)->(%d)\n", fallbackbuilder, ref); |
| return ref; |
| } |
| |
| static ULONG WINAPI fontfallbackbuilder_Release(IDWriteFontFallbackBuilder *iface) |
| { |
| struct dwrite_fontfallback_builder *fallbackbuilder = impl_from_IDWriteFontFallbackBuilder(iface); |
| ULONG ref = InterlockedDecrement(&fallbackbuilder->ref); |
| |
| TRACE("(%p)->(%d)\n", fallbackbuilder, ref); |
| |
| if (!ref) { |
| UINT32 i; |
| |
| for (i = 0; i < fallbackbuilder->mappings_count; i++) { |
| struct fallback_mapping *mapping = &fallbackbuilder->mappings[i]; |
| UINT32 j; |
| |
| for (j = 0; j < mapping->families_count; j++) |
| heap_free(mapping->families[j]); |
| heap_free(mapping->families); |
| |
| if (mapping->collection) |
| IDWriteFontCollection_Release(mapping->collection); |
| heap_free(mapping->ranges); |
| heap_free(mapping->locale); |
| } |
| |
| IDWriteFactory5_Release(fallbackbuilder->factory); |
| heap_free(fallbackbuilder->mappings); |
| heap_free(fallbackbuilder); |
| } |
| |
| return ref; |
| } |
| |
| static HRESULT WINAPI fontfallbackbuilder_AddMapping(IDWriteFontFallbackBuilder *iface, |
| const DWRITE_UNICODE_RANGE *ranges, UINT32 ranges_count, WCHAR const **target_families, UINT32 families_count, |
| IDWriteFontCollection *collection, WCHAR const *locale, WCHAR const *base_family, FLOAT scale) |
| { |
| struct dwrite_fontfallback_builder *fallbackbuilder = impl_from_IDWriteFontFallbackBuilder(iface); |
| struct fallback_mapping *mapping; |
| UINT32 i; |
| |
| TRACE("(%p)->(%p, %u, %p, %u, %p, %s, %s, %f)\n", fallbackbuilder, ranges, ranges_count, target_families, |
| families_count, collection, debugstr_w(locale), debugstr_w(base_family), scale); |
| |
| if (!ranges || ranges_count == 0 || !target_families || families_count == 0 || scale < 0.0f) |
| return E_INVALIDARG; |
| |
| if (base_family) |
| FIXME("base family ignored.\n"); |
| |
| if (fallbackbuilder->mappings_count == fallbackbuilder->mappings_capacity) { |
| struct fallback_mapping *mappings; |
| |
| if (fallbackbuilder->mappings_capacity == 0) { |
| if ((mappings = heap_alloc(sizeof(*fallbackbuilder->mappings) * 16))) |
| fallbackbuilder->mappings_capacity = 16; |
| } |
| else { |
| if ((mappings = heap_realloc(fallbackbuilder->mappings, sizeof(*fallbackbuilder->mappings) * |
| fallbackbuilder->mappings_capacity * 2))) |
| fallbackbuilder->mappings_capacity *= 2; |
| } |
| if (!mappings) |
| return E_OUTOFMEMORY; |
| |
| fallbackbuilder->mappings = mappings; |
| } |
| |
| mapping = &fallbackbuilder->mappings[fallbackbuilder->mappings_count++]; |
| |
| mapping->ranges = heap_alloc(sizeof(*mapping->ranges) * ranges_count); |
| memcpy(mapping->ranges, ranges, sizeof(*mapping->ranges) * ranges_count); |
| mapping->ranges_count = ranges_count; |
| mapping->families = heap_alloc_zero(sizeof(*mapping->families) * families_count); |
| mapping->families_count = families_count; |
| for (i = 0; i < families_count; i++) |
| mapping->families[i] = heap_strdupW(target_families[i]); |
| mapping->collection = collection; |
| if (mapping->collection) |
| IDWriteFontCollection_AddRef(mapping->collection); |
| mapping->locale = heap_strdupW(locale); |
| mapping->scale = scale; |
| |
| return S_OK; |
| } |
| |
| static HRESULT WINAPI fontfallbackbuilder_AddMappings(IDWriteFontFallbackBuilder *iface, IDWriteFontFallback *fallback) |
| { |
| struct dwrite_fontfallback_builder *fallbackbuilder = impl_from_IDWriteFontFallbackBuilder(iface); |
| |
| FIXME("(%p)->(%p): stub\n", fallbackbuilder, fallback); |
| |
| return E_NOTIMPL; |
| } |
| |
| static HRESULT WINAPI fontfallbackbuilder_CreateFontFallback(IDWriteFontFallbackBuilder *iface, |
| IDWriteFontFallback **ret) |
| { |
| struct dwrite_fontfallback_builder *fallbackbuilder = impl_from_IDWriteFontFallbackBuilder(iface); |
| struct dwrite_fontfallback *fallback; |
| |
| FIXME("(%p)->(%p): stub\n", fallbackbuilder, ret); |
| |
| *ret = NULL; |
| |
| fallback = heap_alloc(sizeof(*fallback)); |
| if (!fallback) |
| return E_OUTOFMEMORY; |
| |
| fallback->IDWriteFontFallback_iface.lpVtbl = &customfontfallbackvtbl; |
| fallback->ref = 1; |
| fallback->factory = fallbackbuilder->factory; |
| IDWriteFactory5_AddRef(fallback->factory); |
| |
| *ret = &fallback->IDWriteFontFallback_iface; |
| return S_OK; |
| } |
| |
| static const IDWriteFontFallbackBuilderVtbl fontfallbackbuildervtbl = |
| { |
| fontfallbackbuilder_QueryInterface, |
| fontfallbackbuilder_AddRef, |
| fontfallbackbuilder_Release, |
| fontfallbackbuilder_AddMapping, |
| fontfallbackbuilder_AddMappings, |
| fontfallbackbuilder_CreateFontFallback, |
| }; |
| |
| HRESULT create_fontfallback_builder(IDWriteFactory5 *factory, IDWriteFontFallbackBuilder **ret) |
| { |
| struct dwrite_fontfallback_builder *builder; |
| |
| *ret = NULL; |
| |
| builder = heap_alloc_zero(sizeof(*builder)); |
| if (!builder) |
| return E_OUTOFMEMORY; |
| |
| builder->IDWriteFontFallbackBuilder_iface.lpVtbl = &fontfallbackbuildervtbl; |
| builder->ref = 1; |
| builder->factory = factory; |
| IDWriteFactory5_AddRef(builder->factory); |
| |
| *ret = &builder->IDWriteFontFallbackBuilder_iface; |
| return S_OK; |
| } |