test_text_server.h 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. /*************************************************************************/
  2. /* test_text_server.h */
  3. /*************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /*************************************************************************/
  8. /* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
  9. /* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /*************************************************************************/
  30. #ifndef TEST_TEXT_SERVER_H
  31. #define TEST_TEXT_SERVER_H
  32. #ifdef TOOLS_ENABLED
  33. #include "editor/builtin_fonts.gen.h"
  34. #include "servers/text_server.h"
  35. #include "tests/test_macros.h"
  36. namespace TestTextServer {
  37. TEST_SUITE("[TextServer]") {
  38. TEST_CASE("[TextServer] Init, font loading and shaping") {
  39. SUBCASE("[TextServer] Loading fonts") {
  40. for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
  41. Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
  42. CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");
  43. if (!ts->has_feature(TextServer::FEATURE_FONT_DYNAMIC)) {
  44. continue;
  45. }
  46. RID font = ts->create_font();
  47. ts->font_set_data_ptr(font, _font_NotoSans_Regular, _font_NotoSans_Regular_size);
  48. CHECK_FALSE_MESSAGE(font == RID(), "Loading font failed.");
  49. ts->free_rid(font);
  50. }
  51. }
  52. SUBCASE("[TextServer] Text layout: Font fallback") {
  53. for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
  54. Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
  55. CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");
  56. if (!ts->has_feature(TextServer::FEATURE_FONT_DYNAMIC) || !ts->has_feature(TextServer::FEATURE_SIMPLE_LAYOUT)) {
  57. continue;
  58. }
  59. RID font1 = ts->create_font();
  60. ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size);
  61. RID font2 = ts->create_font();
  62. ts->font_set_data_ptr(font2, _font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size);
  63. Array font;
  64. font.push_back(font1);
  65. font.push_back(font2);
  66. String test = U"คนอ้วน khon uan ראה";
  67. // 6^ 17^
  68. RID ctx = ts->create_shaped_text();
  69. CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");
  70. bool ok = ts->shaped_text_add_string(ctx, test, font, 16);
  71. CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");
  72. const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);
  73. int gl_size = ts->shaped_text_get_glyph_count(ctx);
  74. CHECK_FALSE_MESSAGE(gl_size == 0, "Shaping failed");
  75. for (int j = 0; j < gl_size; j++) {
  76. if (glyphs[j].start < 6) {
  77. CHECK_FALSE_MESSAGE(glyphs[j].font_rid != font[1], "Incorrect font selected.");
  78. }
  79. if ((glyphs[j].start > 6) && (glyphs[j].start < 16)) {
  80. CHECK_FALSE_MESSAGE(glyphs[j].font_rid != font[0], "Incorrect font selected.");
  81. }
  82. if (glyphs[j].start > 16) {
  83. CHECK_FALSE_MESSAGE(glyphs[j].font_rid != RID(), "Incorrect font selected.");
  84. CHECK_FALSE_MESSAGE(glyphs[j].index != test[glyphs[j].start], "Incorrect glyph index.");
  85. }
  86. CHECK_FALSE_MESSAGE((glyphs[j].start < 0 || glyphs[j].end > test.length()), "Incorrect glyph range.");
  87. CHECK_FALSE_MESSAGE(glyphs[j].font_size != 16, "Incorrect glyph font size.");
  88. }
  89. ts->free_rid(ctx);
  90. for (int j = 0; j < font.size(); j++) {
  91. ts->free_rid(font[j]);
  92. }
  93. font.clear();
  94. }
  95. }
  96. SUBCASE("[TextServer] Text layout: BiDi") {
  97. for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
  98. Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
  99. CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");
  100. if (!ts->has_feature(TextServer::FEATURE_FONT_DYNAMIC) || !ts->has_feature(TextServer::FEATURE_BIDI_LAYOUT)) {
  101. continue;
  102. }
  103. RID font1 = ts->create_font();
  104. ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size);
  105. RID font2 = ts->create_font();
  106. ts->font_set_data_ptr(font2, _font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size);
  107. Array font;
  108. font.push_back(font1);
  109. font.push_back(font2);
  110. String test = U"Arabic (اَلْعَرَبِيَّةُ, al-ʿarabiyyah)";
  111. // 7^ 26^
  112. RID ctx = ts->create_shaped_text();
  113. CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");
  114. bool ok = ts->shaped_text_add_string(ctx, test, font, 16);
  115. CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");
  116. const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);
  117. int gl_size = ts->shaped_text_get_glyph_count(ctx);
  118. CHECK_FALSE_MESSAGE(gl_size == 0, "Shaping failed");
  119. for (int j = 0; j < gl_size; j++) {
  120. if (glyphs[j].count > 0) {
  121. if (glyphs[j].start < 7) {
  122. CHECK_FALSE_MESSAGE(((glyphs[j].flags & TextServer::GRAPHEME_IS_RTL) == TextServer::GRAPHEME_IS_RTL), "Incorrect direction.");
  123. }
  124. if ((glyphs[j].start > 8) && (glyphs[j].start < 23)) {
  125. CHECK_FALSE_MESSAGE(((glyphs[j].flags & TextServer::GRAPHEME_IS_RTL) != TextServer::GRAPHEME_IS_RTL), "Incorrect direction.");
  126. }
  127. if (glyphs[j].start > 26) {
  128. CHECK_FALSE_MESSAGE(((glyphs[j].flags & TextServer::GRAPHEME_IS_RTL) == TextServer::GRAPHEME_IS_RTL), "Incorrect direction.");
  129. }
  130. }
  131. }
  132. ts->free_rid(ctx);
  133. for (int j = 0; j < font.size(); j++) {
  134. ts->free_rid(font[j]);
  135. }
  136. font.clear();
  137. }
  138. }
  139. SUBCASE("[TextServer] Text layout: Line break and align points") {
  140. for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
  141. Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
  142. CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");
  143. if (!ts->has_feature(TextServer::FEATURE_FONT_DYNAMIC) || !ts->has_feature(TextServer::FEATURE_SIMPLE_LAYOUT)) {
  144. continue;
  145. }
  146. RID font1 = ts->create_font();
  147. ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size);
  148. RID font2 = ts->create_font();
  149. ts->font_set_data_ptr(font2, _font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size);
  150. RID font3 = ts->create_font();
  151. ts->font_set_data_ptr(font3, _font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size);
  152. Array font;
  153. font.push_back(font1);
  154. font.push_back(font2);
  155. font.push_back(font3);
  156. {
  157. String test = U"Test test long text long text\n";
  158. RID ctx = ts->create_shaped_text();
  159. CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");
  160. bool ok = ts->shaped_text_add_string(ctx, test, font, 16);
  161. CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");
  162. ts->shaped_text_update_breaks(ctx);
  163. ts->shaped_text_update_justification_ops(ctx);
  164. const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);
  165. int gl_size = ts->shaped_text_get_glyph_count(ctx);
  166. CHECK_FALSE_MESSAGE(gl_size != 30, "Invalid glyph count.");
  167. for (int j = 0; j < gl_size; j++) {
  168. bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;
  169. bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;
  170. bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;
  171. bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;
  172. bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;
  173. if (j == 4 || j == 9 || j == 14 || j == 19 || j == 24) {
  174. CHECK_FALSE_MESSAGE((!soft || !space || hard || virt || elo), "Invalid glyph flags.");
  175. } else if (j == 29) {
  176. CHECK_FALSE_MESSAGE((soft || !space || !hard || virt || elo), "Invalid glyph flags.");
  177. } else {
  178. CHECK_FALSE_MESSAGE((soft || space || hard || virt || elo), "Invalid glyph flags.");
  179. }
  180. }
  181. ts->free_rid(ctx);
  182. }
  183. {
  184. String test = U"الحمـد";
  185. RID ctx = ts->create_shaped_text();
  186. CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");
  187. bool ok = ts->shaped_text_add_string(ctx, test, font, 16);
  188. CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");
  189. ts->shaped_text_update_breaks(ctx);
  190. const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);
  191. int gl_size = ts->shaped_text_get_glyph_count(ctx);
  192. CHECK_FALSE_MESSAGE(gl_size != 6, "Invalid glyph count.");
  193. for (int j = 0; j < gl_size; j++) {
  194. bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;
  195. bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;
  196. bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;
  197. bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;
  198. bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;
  199. CHECK_FALSE_MESSAGE((soft || space || hard || virt || elo), "Invalid glyph flags.");
  200. }
  201. if (ts->has_feature(TextServer::FEATURE_KASHIDA_JUSTIFICATION)) {
  202. ts->shaped_text_update_justification_ops(ctx);
  203. glyphs = ts->shaped_text_get_glyphs(ctx);
  204. gl_size = ts->shaped_text_get_glyph_count(ctx);
  205. CHECK_FALSE_MESSAGE(gl_size != 6, "Invalid glyph count.");
  206. for (int j = 0; j < gl_size; j++) {
  207. bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;
  208. bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;
  209. bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;
  210. bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;
  211. bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;
  212. if (j == 1) {
  213. CHECK_FALSE_MESSAGE((soft || space || hard || virt || !elo), "Invalid glyph flags.");
  214. } else {
  215. CHECK_FALSE_MESSAGE((soft || space || hard || virt || elo), "Invalid glyph flags.");
  216. }
  217. }
  218. }
  219. ts->free_rid(ctx);
  220. }
  221. {
  222. String test = U"الحمد";
  223. RID ctx = ts->create_shaped_text();
  224. CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");
  225. bool ok = ts->shaped_text_add_string(ctx, test, font, 16);
  226. CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");
  227. ts->shaped_text_update_breaks(ctx);
  228. const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);
  229. int gl_size = ts->shaped_text_get_glyph_count(ctx);
  230. CHECK_FALSE_MESSAGE(gl_size != 5, "Invalid glyph count.");
  231. for (int j = 0; j < gl_size; j++) {
  232. bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;
  233. bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;
  234. bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;
  235. bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;
  236. bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;
  237. CHECK_FALSE_MESSAGE((soft || space || hard || virt || elo), "Invalid glyph flags.");
  238. }
  239. if (ts->has_feature(TextServer::FEATURE_KASHIDA_JUSTIFICATION)) {
  240. ts->shaped_text_update_justification_ops(ctx);
  241. glyphs = ts->shaped_text_get_glyphs(ctx);
  242. gl_size = ts->shaped_text_get_glyph_count(ctx);
  243. CHECK_FALSE_MESSAGE(gl_size != 6, "Invalid glyph count.");
  244. for (int j = 0; j < gl_size; j++) {
  245. bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;
  246. bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;
  247. bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;
  248. bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;
  249. bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;
  250. if (j == 1) {
  251. CHECK_FALSE_MESSAGE((soft || space || hard || !virt || !elo), "Invalid glyph flags.");
  252. } else {
  253. CHECK_FALSE_MESSAGE((soft || space || hard || virt || elo), "Invalid glyph flags.");
  254. }
  255. }
  256. }
  257. ts->free_rid(ctx);
  258. }
  259. {
  260. String test = U"الحمـد الرياضي العربي";
  261. RID ctx = ts->create_shaped_text();
  262. CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");
  263. bool ok = ts->shaped_text_add_string(ctx, test, font, 16);
  264. CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");
  265. ts->shaped_text_update_breaks(ctx);
  266. const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);
  267. int gl_size = ts->shaped_text_get_glyph_count(ctx);
  268. CHECK_FALSE_MESSAGE(gl_size != 21, "Invalid glyph count.");
  269. for (int j = 0; j < gl_size; j++) {
  270. bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;
  271. bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;
  272. bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;
  273. bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;
  274. bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;
  275. if (j == 6 || j == 14) {
  276. CHECK_FALSE_MESSAGE((!soft || !space || hard || virt || elo), "Invalid glyph flags.");
  277. } else {
  278. CHECK_FALSE_MESSAGE((soft || space || hard || virt || elo), "Invalid glyph flags.");
  279. }
  280. }
  281. if (ts->has_feature(TextServer::FEATURE_KASHIDA_JUSTIFICATION)) {
  282. ts->shaped_text_update_justification_ops(ctx);
  283. glyphs = ts->shaped_text_get_glyphs(ctx);
  284. gl_size = ts->shaped_text_get_glyph_count(ctx);
  285. CHECK_FALSE_MESSAGE(gl_size != 23, "Invalid glyph count.");
  286. for (int j = 0; j < gl_size; j++) {
  287. bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;
  288. bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;
  289. bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;
  290. bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;
  291. bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;
  292. if (j == 7 || j == 16) {
  293. CHECK_FALSE_MESSAGE((!soft || !space || hard || virt || elo), "Invalid glyph flags.");
  294. } else if (j == 3 || j == 9) {
  295. CHECK_FALSE_MESSAGE((soft || space || hard || !virt || !elo), "Invalid glyph flags.");
  296. } else if (j == 18) {
  297. CHECK_FALSE_MESSAGE((soft || space || hard || virt || !elo), "Invalid glyph flags.");
  298. } else {
  299. CHECK_FALSE_MESSAGE((soft || space || hard || virt || elo), "Invalid glyph flags.");
  300. }
  301. }
  302. }
  303. ts->free_rid(ctx);
  304. }
  305. {
  306. String test = U"เป็น ภาษา ราชการ และ ภาษา";
  307. RID ctx = ts->create_shaped_text();
  308. CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");
  309. bool ok = ts->shaped_text_add_string(ctx, test, font, 16);
  310. CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");
  311. ts->shaped_text_update_breaks(ctx);
  312. ts->shaped_text_update_justification_ops(ctx);
  313. const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);
  314. int gl_size = ts->shaped_text_get_glyph_count(ctx);
  315. CHECK_FALSE_MESSAGE(gl_size != 25, "Invalid glyph count.");
  316. for (int j = 0; j < gl_size; j++) {
  317. bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;
  318. bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;
  319. bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;
  320. bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;
  321. bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;
  322. if (j == 4 || j == 9 || j == 16 || j == 20) {
  323. CHECK_FALSE_MESSAGE((!soft || !space || hard || virt || elo), "Invalid glyph flags.");
  324. } else {
  325. CHECK_FALSE_MESSAGE((soft || space || hard || virt || elo), "Invalid glyph flags.");
  326. }
  327. }
  328. ts->free_rid(ctx);
  329. }
  330. if (ts->has_feature(TextServer::FEATURE_BREAK_ITERATORS)) {
  331. String test = U"เป็นภาษาราชการและภาษา";
  332. RID ctx = ts->create_shaped_text();
  333. CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");
  334. bool ok = ts->shaped_text_add_string(ctx, test, font, 16);
  335. CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");
  336. ts->shaped_text_update_breaks(ctx);
  337. ts->shaped_text_update_justification_ops(ctx);
  338. const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);
  339. int gl_size = ts->shaped_text_get_glyph_count(ctx);
  340. CHECK_FALSE_MESSAGE(gl_size != 25, "Invalid glyph count.");
  341. for (int j = 0; j < gl_size; j++) {
  342. bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;
  343. bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;
  344. bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;
  345. bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;
  346. bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;
  347. if (j == 4 || j == 9 || j == 16 || j == 20) {
  348. CHECK_FALSE_MESSAGE((!soft || !space || hard || !virt || elo), "Invalid glyph flags.");
  349. } else {
  350. CHECK_FALSE_MESSAGE((soft || space || hard || virt || elo), "Invalid glyph flags.");
  351. }
  352. }
  353. ts->free_rid(ctx);
  354. }
  355. for (int j = 0; j < font.size(); j++) {
  356. ts->free_rid(font[j]);
  357. }
  358. font.clear();
  359. }
  360. }
  361. SUBCASE("[TextServer] Text layout: Line breaking") {
  362. for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
  363. Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
  364. CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");
  365. if (!ts->has_feature(TextServer::FEATURE_FONT_DYNAMIC) || !ts->has_feature(TextServer::FEATURE_SIMPLE_LAYOUT)) {
  366. continue;
  367. }
  368. String test_1 = U"test test test";
  369. // 5^ 10^
  370. RID font1 = ts->create_font();
  371. ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size);
  372. RID font2 = ts->create_font();
  373. ts->font_set_data_ptr(font2, _font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size);
  374. Array font;
  375. font.push_back(font1);
  376. font.push_back(font2);
  377. RID ctx = ts->create_shaped_text();
  378. CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");
  379. bool ok = ts->shaped_text_add_string(ctx, test_1, font, 16);
  380. CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");
  381. PackedInt32Array brks = ts->shaped_text_get_line_breaks(ctx, 1);
  382. CHECK_FALSE_MESSAGE(brks.size() != 6, "Invalid line breaks number.");
  383. if (brks.size() == 6) {
  384. CHECK_FALSE_MESSAGE(brks[0] != 0, "Invalid line break position.");
  385. CHECK_FALSE_MESSAGE(brks[1] != 5, "Invalid line break position.");
  386. CHECK_FALSE_MESSAGE(brks[2] != 5, "Invalid line break position.");
  387. CHECK_FALSE_MESSAGE(brks[3] != 10, "Invalid line break position.");
  388. CHECK_FALSE_MESSAGE(brks[4] != 10, "Invalid line break position.");
  389. CHECK_FALSE_MESSAGE(brks[5] != 14, "Invalid line break position.");
  390. }
  391. ts->free_rid(ctx);
  392. for (int j = 0; j < font.size(); j++) {
  393. ts->free_rid(font[j]);
  394. }
  395. font.clear();
  396. }
  397. }
  398. SUBCASE("[TextServer] Text layout: Justification") {
  399. for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
  400. Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
  401. CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");
  402. if (!ts->has_feature(TextServer::FEATURE_FONT_DYNAMIC) || !ts->has_feature(TextServer::FEATURE_SIMPLE_LAYOUT)) {
  403. continue;
  404. }
  405. RID font1 = ts->create_font();
  406. ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size);
  407. RID font2 = ts->create_font();
  408. ts->font_set_data_ptr(font2, _font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size);
  409. Array font;
  410. font.push_back(font1);
  411. font.push_back(font2);
  412. String test_1 = U"الحمد";
  413. String test_2 = U"الحمد test";
  414. String test_3 = U"test test";
  415. // 7^ 26^
  416. RID ctx;
  417. bool ok;
  418. float width_old, width;
  419. if (ts->has_feature(TextServer::FEATURE_KASHIDA_JUSTIFICATION)) {
  420. ctx = ts->create_shaped_text();
  421. CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");
  422. ok = ts->shaped_text_add_string(ctx, test_1, font, 16);
  423. CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");
  424. width_old = ts->shaped_text_get_width(ctx);
  425. width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND);
  426. CHECK_FALSE_MESSAGE((width != width_old), "Invalid fill width.");
  427. width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA);
  428. CHECK_FALSE_MESSAGE((width <= width_old || width > 100), "Invalid fill width.");
  429. ts->free_rid(ctx);
  430. ctx = ts->create_shaped_text();
  431. CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");
  432. ok = ts->shaped_text_add_string(ctx, test_2, font, 16);
  433. CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");
  434. width_old = ts->shaped_text_get_width(ctx);
  435. width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND);
  436. CHECK_FALSE_MESSAGE((width <= width_old || width > 100), "Invalid fill width.");
  437. width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA);
  438. CHECK_FALSE_MESSAGE((width <= width_old || width > 100), "Invalid fill width.");
  439. ts->free_rid(ctx);
  440. }
  441. ctx = ts->create_shaped_text();
  442. CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");
  443. ok = ts->shaped_text_add_string(ctx, test_3, font, 16);
  444. CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");
  445. width_old = ts->shaped_text_get_width(ctx);
  446. width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND);
  447. CHECK_FALSE_MESSAGE((width <= width_old || width > 100), "Invalid fill width.");
  448. ts->free_rid(ctx);
  449. for (int j = 0; j < font.size(); j++) {
  450. ts->free_rid(font[j]);
  451. }
  452. font.clear();
  453. }
  454. }
  455. SUBCASE("[TextServer] Unicode identifiers") {
  456. for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
  457. Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
  458. CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");
  459. static const char32_t *data[19] = { U"-30", U"100", U"10.1", U"10,1", U"1e2", U"1e-2", U"1e2e3", U"0xAB", U"AB", U"Test1", U"1Test", U"Test*1", U"test_testeT", U"test_tes teT", U"عَلَيْكُمْ", U"عَلَيْكُمْTest", U"ӒӖӚӜ", U"_test", U"ÂÃÄÅĀĂĄÇĆĈĊ" };
  460. static bool isid[19] = { false, false, false, false, false, false, false, false, true, true, false, false, true, false, true, true, true, true, true };
  461. for (int j = 0; j < 19; j++) {
  462. String s = String(data[j]);
  463. CHECK(ts->is_valid_identifier(s) == isid[j]);
  464. }
  465. if (ts->has_feature(TextServer::FEATURE_UNICODE_IDENTIFIERS)) {
  466. // Test UAX 3.2 ZW(N)J usage.
  467. CHECK(ts->is_valid_identifier(U"\u0646\u0627\u0645\u0647\u200C\u0627\u06CC"));
  468. CHECK(ts->is_valid_identifier(U"\u0D26\u0D43\u0D15\u0D4D\u200C\u0D38\u0D3E\u0D15\u0D4D\u0D37\u0D3F"));
  469. CHECK(ts->is_valid_identifier(U"\u0DC1\u0DCA\u200D\u0DBB\u0DD3"));
  470. }
  471. }
  472. }
  473. SUBCASE("[TextServer] Strip Diacritics") {
  474. for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
  475. Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
  476. CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");
  477. if (ts->has_feature(TextServer::FEATURE_SHAPING)) {
  478. CHECK(ts->strip_diacritics(U"ٱلسَّلَامُ عَلَيْكُمْ") == U"ٱلسلام عليكم");
  479. }
  480. CHECK(ts->strip_diacritics(U"pêches épinards tomates fraises") == U"peches epinards tomates fraises");
  481. CHECK(ts->strip_diacritics(U"ΆΈΉΊΌΎΏΪΫϓϔ") == U"ΑΕΗΙΟΥΩΙΥΥΥ");
  482. CHECK(ts->strip_diacritics(U"άέήίΐϊΰϋόύώ") == U"αεηιιιυυουω");
  483. CHECK(ts->strip_diacritics(U"ЀЁЃ ЇЌЍӢӤЙ ЎӮӰӲ ӐӒӖӚӜӞ ӦӪ Ӭ Ӵ Ӹ") == U"ЕЕГ ІКИИИИ УУУУ ААЕӘЖЗ ОӨ Э Ч Ы");
  484. CHECK(ts->strip_diacritics(U"ѐёѓ їќѝӣӥй ўӯӱӳ ӑӓӗӛӝӟ ӧӫ ӭ ӵ ӹ") == U"еег ікииии уууу ааеәжз оө э ч ы");
  485. CHECK(ts->strip_diacritics(U"ÀÁÂÃÄÅĀĂĄÇĆĈĊČĎÈÉÊËĒĔĖĘĚĜĞĠĢĤÌÍÎÏĨĪĬĮİĴĶĹĻĽÑŃŅŇŊÒÓÔÕÖØŌŎŐƠŔŖŘŚŜŞŠŢŤÙÚÛÜŨŪŬŮŰŲƯŴÝŶŹŻŽ") == U"AAAAAAAAACCCCCDEEEEEEEEEGGGGHIIIIIIIIIJKLLLNNNNŊOOOOOØOOOORRRSSSSTTUUUUUUUUUUUWYYZZZ");
  486. CHECK(ts->strip_diacritics(U"àáâãäåāăąçćĉċčďèéêëēĕėęěĝğġģĥìíîïĩīĭįĵķĺļľñńņňŋòóôõöøōŏőơŕŗřśŝşšţťùúûüũūŭůűųưŵýÿŷźżž") == U"aaaaaaaaacccccdeeeeeeeeegggghiiiiiiiijklllnnnnŋoooooøoooorrrssssttuuuuuuuuuuuwyyyzzz");
  487. CHECK(ts->strip_diacritics(U"ǍǏȈǑǪǬȌȎȪȬȮȰǓǕǗǙǛȔȖǞǠǺȀȂȦǢǼǦǴǨǸȆȐȒȘȚȞȨ Ḁ ḂḄḆ Ḉ ḊḌḎḐḒ ḔḖḘḚḜ Ḟ Ḡ ḢḤḦḨḪ ḬḮ ḰḲḴ ḶḸḺḼ ḾṀṂ ṄṆṈṊ ṌṎṐṒ ṔṖ ṘṚṜṞ ṠṢṤṦṨ ṪṬṮṰ ṲṴṶṸṺ") == U"AIIOOOOOOOOOUUUUUUUAAAAAAÆÆGGKNERRSTHE A BBB C DDDDD EEEEE F G HHHHH II KKK LLLL MMM NNNN OOOO PP RRRR SSSSS TTTT UUUUU");
  488. CHECK(ts->strip_diacritics(U"ǎǐȉȋǒǫǭȍȏȫȭȯȱǔǖǘǚǜȕȗǟǡǻȁȃȧǣǽǧǵǩǹȇȑȓșțȟȩ ḁ ḃḅḇ ḉ ḋḍḏḑḓ ḟ ḡ ḭḯ ḱḳḵ ḷḹḻḽ ḿṁṃ ṅṇṉṋ ṍṏṑṓ ṗṕ ṙṛṝṟ ṡṣṥṧṩ ṫṭṯṱ ṳṵṷṹṻ") == U"aiiiooooooooouuuuuuuaaaaaaææggknerrsthe a bbb c ddddd f g ii kkk llll mmm nnnn oooo pp rrrr sssss tttt uuuuu");
  489. CHECK(ts->strip_diacritics(U"ṼṾ ẀẂẄẆẈ ẊẌ Ẏ ẐẒẔ") == U"VV WWWWW XX Y ZZZ");
  490. CHECK(ts->strip_diacritics(U"ṽṿ ẁẃẅẇẉ ẋẍ ẏ ẑẓẕ ẖ ẗẘẙẛ") == U"vv wwwww xx y zzz h twys");
  491. }
  492. }
  493. SUBCASE("[TextServer] Word break") {
  494. for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
  495. Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
  496. if (!ts->has_feature(TextServer::FEATURE_SIMPLE_LAYOUT)) {
  497. continue;
  498. }
  499. CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");
  500. {
  501. String text1 = U"linguistically similar and effectively form";
  502. // 14^ 22^ 26^ 38^
  503. PackedInt32Array breaks = ts->string_get_word_breaks(text1, "en");
  504. CHECK(breaks.size() == 4);
  505. if (breaks.size() == 4) {
  506. CHECK(breaks[0] == 14);
  507. CHECK(breaks[1] == 22);
  508. CHECK(breaks[2] == 26);
  509. CHECK(breaks[3] == 38);
  510. }
  511. }
  512. if (ts->has_feature(TextServer::FEATURE_BREAK_ITERATORS)) {
  513. String text2 = U"เป็นภาษาราชการและภาษาประจำชาติของประเทศไทย";
  514. // เป็น ภาษา ราชการ และ ภาษา ประจำ ชาติ ของ ประเทศไทย
  515. // 3^ 7^ 13^ 16^ 20^ 25^ 29^ 32^
  516. PackedInt32Array breaks = ts->string_get_word_breaks(text2, "th");
  517. CHECK(breaks.size() == 8);
  518. if (breaks.size() == 8) {
  519. CHECK(breaks[0] == 3);
  520. CHECK(breaks[1] == 7);
  521. CHECK(breaks[2] == 13);
  522. CHECK(breaks[3] == 16);
  523. CHECK(breaks[4] == 20);
  524. CHECK(breaks[5] == 25);
  525. CHECK(breaks[6] == 29);
  526. CHECK(breaks[7] == 32);
  527. }
  528. }
  529. }
  530. }
  531. }
  532. }
  533. }; // namespace TestTextServer
  534. #endif // TOOLS_ENABLED
  535. #endif // TEST_TEXT_SERVER_H