Path: blob/master/tests/servers/test_text_server.cpp
23449 views
/**************************************************************************/1/* test_text_server.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "tests/test_macros.h"3132TEST_FORCE_LINK(test_text_server)3334#ifdef TOOLS_ENABLED3536#include "core/variant/typed_array.h"37#include "editor/themes/builtin_fonts.gen.h"38#include "servers/text/text_server.h"3940namespace TestTextServer {4142TEST_SUITE("[TextServer]") {43TEST_CASE("[TextServer] Init, font loading and shaping") {44SUBCASE("[TextServer] Loading fonts") {45for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {46Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);47CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");4849if (!ts->has_feature(TextServer::FEATURE_FONT_DYNAMIC)) {50continue;51}5253RID font = ts->create_font();54ts->font_set_data_ptr(font, _font_Inter_Regular, _font_Inter_Regular_size);55CHECK_FALSE_MESSAGE(font == RID(), "Loading font failed.");56ts->free_rid(font);57}58}5960SUBCASE("[TextServer] Text layout: Font fallback") {61for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {62Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);63CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");6465if (!ts->has_feature(TextServer::FEATURE_FONT_DYNAMIC) || !ts->has_feature(TextServer::FEATURE_SIMPLE_LAYOUT)) {66continue;67}6869RID font1 = ts->create_font();70ts->font_set_data_ptr(font1, _font_Inter_Regular, _font_Inter_Regular_size);71ts->font_set_allow_system_fallback(font1, false);72RID font2 = ts->create_font();73ts->font_set_data_ptr(font2, _font_NotoSansThai_Regular, _font_NotoSansThai_Regular_size);74ts->font_set_allow_system_fallback(font2, false);7576Array font = { font1, font2 };77String test = U"คนอ้วน khon uan ראה";78// 6^ 17^7980RID ctx = ts->create_shaped_text();81CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");82bool ok = ts->shaped_text_add_string(ctx, test, font, 16);83CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");8485const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);86int gl_size = ts->shaped_text_get_glyph_count(ctx);87CHECK_FALSE_MESSAGE(gl_size == 0, "Shaping failed");88for (int j = 0; j < gl_size; j++) {89if (glyphs[j].start < 6) {90CHECK_FALSE_MESSAGE(glyphs[j].font_rid != font[1], "Incorrect font selected.");91}92if ((glyphs[j].start > 6) && (glyphs[j].start < 16)) {93CHECK_FALSE_MESSAGE(glyphs[j].font_rid != font[0], "Incorrect font selected.");94}95if (glyphs[j].start > 16) {96CHECK_FALSE_MESSAGE(glyphs[j].font_rid != RID(), "Incorrect font selected.");97CHECK_FALSE_MESSAGE(glyphs[j].index != test[glyphs[j].start], "Incorrect glyph index.");98}99CHECK_FALSE_MESSAGE((glyphs[j].start < 0 || glyphs[j].end > test.length()), "Incorrect glyph range.");100CHECK_FALSE_MESSAGE(glyphs[j].font_size != 16, "Incorrect glyph font size.");101}102103ts->free_rid(ctx);104105for (int j = 0; j < font.size(); j++) {106ts->free_rid(font[j]);107}108font.clear();109}110}111112SUBCASE("[TextServer] Text layout: BiDi") {113for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {114Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);115CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");116117if (!ts->has_feature(TextServer::FEATURE_FONT_DYNAMIC) || !ts->has_feature(TextServer::FEATURE_BIDI_LAYOUT)) {118continue;119}120121RID font1 = ts->create_font();122ts->font_set_data_ptr(font1, _font_Inter_Regular, _font_Inter_Regular_size);123RID font2 = ts->create_font();124ts->font_set_data_ptr(font2, _font_Vazirmatn_Regular, _font_Vazirmatn_Regular_size);125126Array font = { font1, font2 };127String test = U"Arabic (اَلْعَرَبِيَّةُ, al-ʿarabiyyah)";128// 7^ 26^129130RID ctx = ts->create_shaped_text();131CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");132bool ok = ts->shaped_text_add_string(ctx, test, font, 16);133CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");134135const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);136int gl_size = ts->shaped_text_get_glyph_count(ctx);137CHECK_FALSE_MESSAGE(gl_size == 0, "Shaping failed");138for (int j = 0; j < gl_size; j++) {139if (glyphs[j].count > 0) {140if (glyphs[j].start < 7) {141CHECK_FALSE_MESSAGE(((glyphs[j].flags & TextServer::GRAPHEME_IS_RTL) == TextServer::GRAPHEME_IS_RTL), "Incorrect direction.");142}143if ((glyphs[j].start > 8) && (glyphs[j].start < 23)) {144CHECK_FALSE_MESSAGE(((glyphs[j].flags & TextServer::GRAPHEME_IS_RTL) != TextServer::GRAPHEME_IS_RTL), "Incorrect direction.");145}146if (glyphs[j].start > 26) {147CHECK_FALSE_MESSAGE(((glyphs[j].flags & TextServer::GRAPHEME_IS_RTL) == TextServer::GRAPHEME_IS_RTL), "Incorrect direction.");148}149}150}151152ts->free_rid(ctx);153154for (int j = 0; j < font.size(); j++) {155ts->free_rid(font[j]);156}157font.clear();158}159}160161SUBCASE("[TextServer] Text layout: Line break and align points") {162for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {163Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);164CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");165166if (!ts->has_feature(TextServer::FEATURE_FONT_DYNAMIC) || !ts->has_feature(TextServer::FEATURE_SIMPLE_LAYOUT)) {167continue;168}169170RID font1 = ts->create_font();171ts->font_set_data_ptr(font1, _font_Inter_Regular, _font_Inter_Regular_size);172ts->font_set_allow_system_fallback(font1, false);173RID font2 = ts->create_font();174ts->font_set_data_ptr(font2, _font_NotoSansThai_Regular, _font_NotoSansThai_Regular_size);175ts->font_set_allow_system_fallback(font2, false);176RID font3 = ts->create_font();177ts->font_set_data_ptr(font3, _font_Vazirmatn_Regular, _font_Vazirmatn_Regular_size);178ts->font_set_allow_system_fallback(font3, false);179180Array font = { font1, font2, font3 };181{182RID ctx = ts->create_shaped_text();183CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");184ts->shaped_text_add_string(ctx, U"Xtest", font, 10);185ts->shaped_text_add_string(ctx, U"xs", font, 10);186RID sctx = ts->shaped_text_substr(ctx, 1, 5);187CHECK_FALSE_MESSAGE(sctx == RID(), "Creating substring text buffer failed.");188PackedInt32Array sbrk = ts->shaped_text_get_character_breaks(sctx);189CHECK_FALSE_MESSAGE(sbrk.size() != 5, "Invalid substring char breaks number.");190if (sbrk.size() == 5) {191CHECK_FALSE_MESSAGE(sbrk[0] != 2, "Invalid substring char break position.");192CHECK_FALSE_MESSAGE(sbrk[1] != 3, "Invalid substring char break position.");193CHECK_FALSE_MESSAGE(sbrk[2] != 4, "Invalid substring char break position.");194CHECK_FALSE_MESSAGE(sbrk[3] != 5, "Invalid substring char break position.");195CHECK_FALSE_MESSAGE(sbrk[4] != 6, "Invalid substring char break position.");196}197PackedInt32Array fbrk = ts->shaped_text_get_character_breaks(ctx);198CHECK_FALSE_MESSAGE(fbrk.size() != 7, "Invalid char breaks number.");199if (fbrk.size() == 7) {200CHECK_FALSE_MESSAGE(fbrk[0] != 1, "Invalid char break position.");201CHECK_FALSE_MESSAGE(fbrk[1] != 2, "Invalid char break position.");202CHECK_FALSE_MESSAGE(fbrk[2] != 3, "Invalid char break position.");203CHECK_FALSE_MESSAGE(fbrk[3] != 4, "Invalid char break position.");204CHECK_FALSE_MESSAGE(fbrk[4] != 5, "Invalid char break position.");205CHECK_FALSE_MESSAGE(fbrk[5] != 6, "Invalid char break position.");206CHECK_FALSE_MESSAGE(fbrk[6] != 7, "Invalid char break position.");207}208PackedInt32Array rbrk = ts->string_get_character_breaks(U"Xtestxs");209CHECK_FALSE_MESSAGE(rbrk.size() != 7, "Invalid char breaks number.");210if (rbrk.size() == 7) {211CHECK_FALSE_MESSAGE(rbrk[0] != 1, "Invalid char break position.");212CHECK_FALSE_MESSAGE(rbrk[1] != 2, "Invalid char break position.");213CHECK_FALSE_MESSAGE(rbrk[2] != 3, "Invalid char break position.");214CHECK_FALSE_MESSAGE(rbrk[3] != 4, "Invalid char break position.");215CHECK_FALSE_MESSAGE(rbrk[4] != 5, "Invalid char break position.");216CHECK_FALSE_MESSAGE(rbrk[5] != 6, "Invalid char break position.");217CHECK_FALSE_MESSAGE(rbrk[6] != 7, "Invalid char break position.");218}219220ts->free_rid(sctx);221ts->free_rid(ctx);222}223224if (ts->has_feature(TextServer::FEATURE_BREAK_ITERATORS)) {225RID ctx = ts->create_shaped_text();226CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");227ts->shaped_text_add_string(ctx, U"X❤️🔥", font, 10);228ts->shaped_text_add_string(ctx, U"xs", font, 10);229RID sctx = ts->shaped_text_substr(ctx, 1, 5);230CHECK_FALSE_MESSAGE(sctx == RID(), "Creating substring text buffer failed.");231PackedInt32Array sbrk = ts->shaped_text_get_character_breaks(sctx);232CHECK_FALSE_MESSAGE(sbrk.size() != 2, "Invalid substring char breaks number.");233if (sbrk.size() == 2) {234CHECK_FALSE_MESSAGE(sbrk[0] != 5, "Invalid substring char break position.");235CHECK_FALSE_MESSAGE(sbrk[1] != 6, "Invalid substring char break position.");236}237PackedInt32Array fbrk = ts->shaped_text_get_character_breaks(ctx);238CHECK_FALSE_MESSAGE(fbrk.size() != 4, "Invalid char breaks number.");239if (fbrk.size() == 4) {240CHECK_FALSE_MESSAGE(fbrk[0] != 1, "Invalid char break position.");241CHECK_FALSE_MESSAGE(fbrk[1] != 5, "Invalid char break position.");242CHECK_FALSE_MESSAGE(fbrk[2] != 6, "Invalid char break position.");243CHECK_FALSE_MESSAGE(fbrk[3] != 7, "Invalid char break position.");244}245PackedInt32Array rbrk = ts->string_get_character_breaks(U"X❤️🔥xs");246CHECK_FALSE_MESSAGE(rbrk.size() != 4, "Invalid char breaks number.");247if (rbrk.size() == 4) {248CHECK_FALSE_MESSAGE(rbrk[0] != 1, "Invalid char break position.");249CHECK_FALSE_MESSAGE(rbrk[1] != 5, "Invalid char break position.");250CHECK_FALSE_MESSAGE(rbrk[2] != 6, "Invalid char break position.");251CHECK_FALSE_MESSAGE(rbrk[3] != 7, "Invalid char break position.");252}253254ts->free_rid(sctx);255ts->free_rid(ctx);256}257258{259String test = U"Test test long text long text\n";260RID ctx = ts->create_shaped_text();261CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");262bool ok = ts->shaped_text_add_string(ctx, test, font, 16);263CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");264ts->shaped_text_update_breaks(ctx);265ts->shaped_text_update_justification_ops(ctx);266267const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);268int gl_size = ts->shaped_text_get_glyph_count(ctx);269270CHECK_FALSE_MESSAGE(gl_size != 30, "Invalid glyph count.");271for (int j = 0; j < gl_size; j++) {272bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;273bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;274bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;275bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;276bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;277if (j == 4 || j == 9 || j == 14 || j == 19 || j == 24) {278CHECK_FALSE_MESSAGE((!soft || !space || hard || virt || elo), "Invalid glyph flags.");279} else if (j == 29) {280CHECK_FALSE_MESSAGE((soft || !space || !hard || virt || elo), "Invalid glyph flags.");281} else {282CHECK_FALSE_MESSAGE((soft || space || hard || virt || elo), "Invalid glyph flags.");283}284}285ts->free_rid(ctx);286}287288{289String test = U"الحمـد";290RID ctx = ts->create_shaped_text();291CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");292bool ok = ts->shaped_text_add_string(ctx, test, font, 16);293CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");294ts->shaped_text_update_breaks(ctx);295296const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);297int gl_size = ts->shaped_text_get_glyph_count(ctx);298CHECK_FALSE_MESSAGE(gl_size != 6, "Invalid glyph count.");299for (int j = 0; j < gl_size; j++) {300bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;301bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;302bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;303bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;304bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;305CHECK_FALSE_MESSAGE((soft || space || hard || virt || elo), "Invalid glyph flags.");306}307if (ts->has_feature(TextServer::FEATURE_KASHIDA_JUSTIFICATION)) {308ts->shaped_text_update_justification_ops(ctx);309310glyphs = ts->shaped_text_get_glyphs(ctx);311gl_size = ts->shaped_text_get_glyph_count(ctx);312313CHECK_FALSE_MESSAGE(gl_size != 6, "Invalid glyph count.");314for (int j = 0; j < gl_size; j++) {315bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;316bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;317bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;318bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;319bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;320if (j == 1) {321CHECK_FALSE_MESSAGE((soft || space || hard || virt || !elo), "Invalid glyph flags.");322} else {323CHECK_FALSE_MESSAGE((soft || space || hard || virt || elo), "Invalid glyph flags.");324}325}326}327ts->free_rid(ctx);328}329330{331String test = U"الحمد";332RID ctx = ts->create_shaped_text();333CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");334bool ok = ts->shaped_text_add_string(ctx, test, font, 16);335CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");336ts->shaped_text_update_breaks(ctx);337338const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);339int gl_size = ts->shaped_text_get_glyph_count(ctx);340CHECK_FALSE_MESSAGE(gl_size != 5, "Invalid glyph count.");341for (int j = 0; j < gl_size; j++) {342bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;343bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;344bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;345bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;346bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;347CHECK_FALSE_MESSAGE((soft || space || hard || virt || elo), "Invalid glyph flags.");348}349350if (ts->has_feature(TextServer::FEATURE_KASHIDA_JUSTIFICATION)) {351ts->shaped_text_update_justification_ops(ctx);352353glyphs = ts->shaped_text_get_glyphs(ctx);354gl_size = ts->shaped_text_get_glyph_count(ctx);355356CHECK_FALSE_MESSAGE(gl_size != 6, "Invalid glyph count.");357for (int j = 0; j < gl_size; j++) {358bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;359bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;360bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;361bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;362bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;363if (j == 1) {364CHECK_FALSE_MESSAGE((soft || space || hard || !virt || !elo), "Invalid glyph flags.");365} else {366CHECK_FALSE_MESSAGE((soft || space || hard || virt || elo), "Invalid glyph flags.");367}368}369}370ts->free_rid(ctx);371}372373{374String test = U"الحمـد الرياضي العربي";375RID ctx = ts->create_shaped_text();376CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");377bool ok = ts->shaped_text_add_string(ctx, test, font, 16);378CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");379ts->shaped_text_update_breaks(ctx);380381const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);382int gl_size = ts->shaped_text_get_glyph_count(ctx);383384CHECK_FALSE_MESSAGE(gl_size != 21, "Invalid glyph count.");385for (int j = 0; j < gl_size; j++) {386bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;387bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;388bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;389bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;390bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;391if (j == 6 || j == 14) {392CHECK_FALSE_MESSAGE((!soft || !space || hard || virt || elo), "Invalid glyph flags.");393} else {394CHECK_FALSE_MESSAGE((soft || space || hard || virt || elo), "Invalid glyph flags.");395}396}397398if (ts->has_feature(TextServer::FEATURE_KASHIDA_JUSTIFICATION)) {399ts->shaped_text_update_justification_ops(ctx);400401glyphs = ts->shaped_text_get_glyphs(ctx);402gl_size = ts->shaped_text_get_glyph_count(ctx);403404CHECK_FALSE_MESSAGE(gl_size != 23, "Invalid glyph count.");405for (int j = 0; j < gl_size; j++) {406bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;407bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;408bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;409bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;410bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;411if (j == 7 || j == 16) {412CHECK_FALSE_MESSAGE((!soft || !space || hard || virt || elo), "Invalid glyph flags.");413} else if (j == 3 || j == 9) {414CHECK_FALSE_MESSAGE((soft || space || hard || !virt || !elo), "Invalid glyph flags.");415} else if (j == 18) {416CHECK_FALSE_MESSAGE((soft || space || hard || virt || !elo), "Invalid glyph flags.");417} else {418CHECK_FALSE_MESSAGE((soft || space || hard || virt || elo), "Invalid glyph flags.");419}420}421}422423ts->free_rid(ctx);424}425426{427String test = U"เป็น ภาษา ราชการ และ ภาษา";428RID ctx = ts->create_shaped_text();429CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");430bool ok = ts->shaped_text_add_string(ctx, test, font, 16);431CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");432ts->shaped_text_update_breaks(ctx);433ts->shaped_text_update_justification_ops(ctx);434435const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);436int gl_size = ts->shaped_text_get_glyph_count(ctx);437438CHECK_FALSE_MESSAGE(gl_size != 25, "Invalid glyph count.");439for (int j = 0; j < gl_size; j++) {440bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;441bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;442bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;443bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;444bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;445if (j == 4 || j == 9 || j == 16 || j == 20) {446CHECK_FALSE_MESSAGE((!soft || !space || hard || virt || elo), "Invalid glyph flags.");447} else {448CHECK_FALSE_MESSAGE((soft || space || hard || virt || elo), "Invalid glyph flags.");449}450}451ts->free_rid(ctx);452}453454if (ts->has_feature(TextServer::FEATURE_BREAK_ITERATORS)) { // Line breaking opportunities.455String test = U"เป็นภาษาราชการและภาษา";456RID ctx = ts->create_shaped_text();457CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");458bool ok = ts->shaped_text_add_string(ctx, test, font, 16);459CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");460ts->shaped_text_update_breaks(ctx);461ts->shaped_text_update_justification_ops(ctx);462463const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);464int gl_size = ts->shaped_text_get_glyph_count(ctx);465466CHECK_FALSE_MESSAGE(gl_size != 25, "Invalid glyph count.");467for (int j = 0; j < gl_size; j++) {468bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;469bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;470bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;471bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;472bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;473if (j == 4 || j == 9 || j == 16 || j == 20) {474CHECK_FALSE_MESSAGE((!soft || !space || hard || !virt || elo), "Invalid glyph flags.");475} else {476CHECK_FALSE_MESSAGE((soft || space || hard || virt || elo), "Invalid glyph flags.");477}478}479ts->free_rid(ctx);480}481482if (ts->has_feature(TextServer::FEATURE_BREAK_ITERATORS)) { // Break line.483struct TestCase {484String text;485PackedInt32Array breaks;486};487TestCase cases[] = {488{ U" เมาส์ตัวนี้", { 0, 17, 17, 23 } },489{ U" กู้ไฟล์", { 0, 17, 17, 21 } },490{ U" ไม่มีคำ", { 0, 18, 18, 20 } },491{ U" ไม่มีคำพูด", { 0, 18, 18, 23 } },492{ U" ไม่มีคำ", { 0, 17, 17, 19 } },493{ U" มีอุปกรณ์\nนี้", { 0, 11, 11, 19, 19, 22 } },494{ U"الحمدا لحمدا لحمـــد", { 0, 13, 13, 20 } },495{ U" الحمد test", { 0, 15, 15, 19 } },496{ U"الحمـد الرياضي العربي", { 0, 7, 7, 15, 15, 21 } },497{ U"test \rtest", { 0, 6, 6, 10 } },498{ U"test\r test", { 0, 5, 5, 10 } },499{ U"test\r test \r test", { 0, 5, 5, 12, 12, 17 } },500};501for (size_t j = 0; j < std_size(cases); j++) {502RID ctx = ts->create_shaped_text();503CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");504bool ok = ts->shaped_text_add_string(ctx, cases[j].text, font, 16);505CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");506507PackedInt32Array breaks = ts->shaped_text_get_line_breaks(ctx, 90.0);508CHECK_FALSE_MESSAGE(breaks != cases[j].breaks, "Invalid break points.");509510breaks = ts->shaped_text_get_line_breaks_adv(ctx, { 90.0 }, 0, false);511CHECK_FALSE_MESSAGE(breaks != cases[j].breaks, "Invalid break points.");512513ts->free_rid(ctx);514}515}516517if (ts->has_feature(TextServer::FEATURE_BREAK_ITERATORS)) { // Break line and trim spaces.518struct TestCase {519String text;520PackedInt32Array breaks;521BitField<TextServer::LineBreakFlag> flags = TextServer::BREAK_NONE;522};523TestCase cases[] = {524{ U"test \rtest", { 0, 4, 6, 10 }, TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_TRIM_START_EDGE_SPACES | TextServer::BREAK_TRIM_END_EDGE_SPACES },525{ U"test \rtest", { 0, 6, 6, 10 }, TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_TRIM_START_EDGE_SPACES },526{ U"test\r test", { 0, 4, 6, 10 }, TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_TRIM_START_EDGE_SPACES | TextServer::BREAK_TRIM_END_EDGE_SPACES },527{ U"test\r test", { 0, 4, 5, 10 }, TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_TRIM_END_EDGE_SPACES },528{ U"test\r test \r test", { 0, 4, 6, 10, 13, 17 }, TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_TRIM_START_EDGE_SPACES | TextServer::BREAK_TRIM_END_EDGE_SPACES },529{ U"test\r test \r test", { 0, 5, 6, 12, 13, 17 }, TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_TRIM_START_EDGE_SPACES },530{ U"test\r test \r test", { 0, 4, 5, 10, 12, 17 }, TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_TRIM_END_EDGE_SPACES },531{ U"test\r test \r test", { 0, 5, 5, 12, 12, 17 }, TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND },532};533for (size_t j = 0; j < sizeof(cases) / sizeof(TestCase); j++) {534RID ctx = ts->create_shaped_text();535CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");536bool ok = ts->shaped_text_add_string(ctx, cases[j].text, font, 16);537CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");538539PackedInt32Array breaks = ts->shaped_text_get_line_breaks(ctx, 90.0, 0, cases[j].flags);540CHECK_FALSE_MESSAGE(breaks != cases[j].breaks, "Invalid break points.");541542breaks = ts->shaped_text_get_line_breaks_adv(ctx, { 90.0 }, 0, false, cases[j].flags);543CHECK_FALSE_MESSAGE(breaks != cases[j].breaks, "Invalid break points.");544545ts->free_rid(ctx);546}547}548549for (int j = 0; j < font.size(); j++) {550ts->free_rid(font[j]);551}552font.clear();553}554}555556SUBCASE("[TextServer] Text layout: Line breaking") {557for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {558Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);559CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");560561if (!ts->has_feature(TextServer::FEATURE_FONT_DYNAMIC) || !ts->has_feature(TextServer::FEATURE_SIMPLE_LAYOUT)) {562continue;563}564565String test_1 = U"test test test";566// 5^ 10^567568RID font1 = ts->create_font();569ts->font_set_data_ptr(font1, _font_Inter_Regular, _font_Inter_Regular_size);570RID font2 = ts->create_font();571ts->font_set_data_ptr(font2, _font_NotoSansThai_Regular, _font_NotoSansThai_Regular_size);572573Array font = { font1, font2 };574RID ctx = ts->create_shaped_text();575CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");576bool ok = ts->shaped_text_add_string(ctx, test_1, font, 16);577CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");578579PackedInt32Array brks = ts->shaped_text_get_line_breaks(ctx, 1);580CHECK_FALSE_MESSAGE(brks.size() != 6, "Invalid line breaks number.");581if (brks.size() == 6) {582CHECK_FALSE_MESSAGE(brks[0] != 0, "Invalid line break position.");583CHECK_FALSE_MESSAGE(brks[1] != 5, "Invalid line break position.");584585CHECK_FALSE_MESSAGE(brks[2] != 5, "Invalid line break position.");586CHECK_FALSE_MESSAGE(brks[3] != 10, "Invalid line break position.");587588CHECK_FALSE_MESSAGE(brks[4] != 10, "Invalid line break position.");589CHECK_FALSE_MESSAGE(brks[5] != 14, "Invalid line break position.");590}591592brks = ts->shaped_text_get_line_breaks(ctx, 35.0, 0, TextServer::BREAK_WORD_BOUND | TextServer::BREAK_MANDATORY | TextServer::BREAK_TRIM_START_EDGE_SPACES | TextServer::BREAK_TRIM_END_EDGE_SPACES);593CHECK_FALSE_MESSAGE(brks.size() != 6, "Invalid line breaks number.");594if (brks.size() == 6) {595CHECK_FALSE_MESSAGE(brks[0] != 0, "Invalid line break position.");596CHECK_FALSE_MESSAGE(brks[1] != 4, "Invalid line break position.");597598CHECK_FALSE_MESSAGE(brks[2] != 5, "Invalid line break position.");599CHECK_FALSE_MESSAGE(brks[3] != 9, "Invalid line break position.");600601CHECK_FALSE_MESSAGE(brks[4] != 10, "Invalid line break position.");602CHECK_FALSE_MESSAGE(brks[5] != 14, "Invalid line break position.");603}604605ts->free_rid(ctx);606607String test_2 = U"Word Wrap";608// 5^609610ctx = ts->create_shaped_text();611CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");612ok = ts->shaped_text_add_string(ctx, test_2, font, 16);613CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");614615brks = ts->shaped_text_get_line_breaks(ctx, 43);616CHECK_FALSE_MESSAGE(brks.size() != 4, "Invalid line breaks number.");617if (brks.size() == 4) {618CHECK_FALSE_MESSAGE(brks[0] != 0, "Invalid line break position.");619CHECK_FALSE_MESSAGE(brks[1] != 5, "Invalid line break position.");620621CHECK_FALSE_MESSAGE(brks[2] != 5, "Invalid line break position.");622CHECK_FALSE_MESSAGE(brks[3] != 9, "Invalid line break position.");623}624625brks = ts->shaped_text_get_line_breaks(ctx, 43.0, 0, TextServer::BREAK_WORD_BOUND | TextServer::BREAK_MANDATORY | TextServer::BREAK_TRIM_START_EDGE_SPACES | TextServer::BREAK_TRIM_END_EDGE_SPACES);626CHECK_FALSE_MESSAGE(brks.size() != 4, "Invalid line breaks number.");627if (brks.size() == 4) {628CHECK_FALSE_MESSAGE(brks[0] != 0, "Invalid line break position.");629CHECK_FALSE_MESSAGE(brks[1] != 4, "Invalid line break position.");630631CHECK_FALSE_MESSAGE(brks[2] != 5, "Invalid line break position.");632CHECK_FALSE_MESSAGE(brks[3] != 9, "Invalid line break position.");633}634635brks = ts->shaped_text_get_line_breaks(ctx, 43.0, 0, TextServer::BREAK_WORD_BOUND | TextServer::BREAK_ADAPTIVE | TextServer::BREAK_MANDATORY | TextServer::BREAK_TRIM_START_EDGE_SPACES | TextServer::BREAK_TRIM_END_EDGE_SPACES);636CHECK_FALSE_MESSAGE(brks.size() != 4, "Invalid line breaks number.");637if (brks.size() == 4) {638CHECK_FALSE_MESSAGE(brks[0] != 0, "Invalid line break position.");639CHECK_FALSE_MESSAGE(brks[1] != 4, "Invalid line break position.");640641CHECK_FALSE_MESSAGE(brks[2] != 5, "Invalid line break position.");642CHECK_FALSE_MESSAGE(brks[3] != 9, "Invalid line break position.");643}644645brks = ts->shaped_text_get_line_breaks(ctx, 43.0, 0, TextServer::BREAK_WORD_BOUND | TextServer::BREAK_ADAPTIVE | TextServer::BREAK_MANDATORY);646CHECK_FALSE_MESSAGE(brks.size() != 6, "Invalid line breaks number.");647if (brks.size() == 6) {648CHECK_FALSE_MESSAGE(brks[0] != 0, "Invalid line break position.");649CHECK_FALSE_MESSAGE(brks[1] != 4, "Invalid line break position.");650651CHECK_FALSE_MESSAGE(brks[2] != 4, "Invalid line break position.");652CHECK_FALSE_MESSAGE(brks[3] != 5, "Invalid line break position.");653654CHECK_FALSE_MESSAGE(brks[4] != 5, "Invalid line break position.");655CHECK_FALSE_MESSAGE(brks[5] != 9, "Invalid line break position.");656}657658ts->free_rid(ctx);659660for (int j = 0; j < font.size(); j++) {661ts->free_rid(font[j]);662}663font.clear();664}665}666667SUBCASE("[TextServer] Text layout: Justification") {668for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {669Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);670CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");671672if (!ts->has_feature(TextServer::FEATURE_FONT_DYNAMIC) || !ts->has_feature(TextServer::FEATURE_SIMPLE_LAYOUT)) {673continue;674}675676RID font1 = ts->create_font();677ts->font_set_data_ptr(font1, _font_Inter_Regular, _font_Inter_Regular_size);678RID font2 = ts->create_font();679ts->font_set_data_ptr(font2, _font_Vazirmatn_Regular, _font_Vazirmatn_Regular_size);680681Array font = { font1, font2 };682String test_1 = U"الحمد";683String test_2 = U"الحمد test";684String test_3 = U"test test";685// 7^ 26^686687RID ctx;688bool ok;689float width_old, width;690if (ts->has_feature(TextServer::FEATURE_KASHIDA_JUSTIFICATION)) {691ctx = ts->create_shaped_text();692CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");693ok = ts->shaped_text_add_string(ctx, test_1, font, 16);694CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");695696width_old = ts->shaped_text_get_width(ctx);697width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND);698CHECK_FALSE_MESSAGE((width != width_old), "Invalid fill width.");699width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA);700CHECK_FALSE_MESSAGE((width <= width_old || width > 100), "Invalid fill width.");701702ts->free_rid(ctx);703704ctx = ts->create_shaped_text();705CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");706ok = ts->shaped_text_add_string(ctx, test_2, font, 16);707CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");708709width_old = ts->shaped_text_get_width(ctx);710width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND);711CHECK_FALSE_MESSAGE((width <= width_old || width > 100), "Invalid fill width.");712width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA);713CHECK_FALSE_MESSAGE((width <= width_old || width > 100), "Invalid fill width.");714715ts->free_rid(ctx);716}717718ctx = ts->create_shaped_text();719CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");720ok = ts->shaped_text_add_string(ctx, test_3, font, 16);721CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");722723width_old = ts->shaped_text_get_width(ctx);724width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND);725CHECK_FALSE_MESSAGE((width <= width_old || width > 100), "Invalid fill width.");726727ts->free_rid(ctx);728729for (int j = 0; j < font.size(); j++) {730ts->free_rid(font[j]);731}732font.clear();733}734}735736SUBCASE("[TextServer] Unicode identifiers") {737for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {738Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);739CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");740741static 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"ÂÃÄÅĀĂĄÇĆĈĊ" };742static bool isid[19] = { false, false, false, false, false, false, false, false, true, true, false, false, true, false, true, true, true, true, true };743for (int j = 0; j < 19; j++) {744String s = String(data[j]);745CHECK(ts->is_valid_identifier(s) == isid[j]);746}747748if (ts->has_feature(TextServer::FEATURE_UNICODE_IDENTIFIERS)) {749// Test UAX 3.2 ZW(N)J usage.750CHECK(ts->is_valid_identifier(U"\u0646\u0627\u0645\u0647\u200C\u0627\u06CC"));751CHECK(ts->is_valid_identifier(U"\u0D26\u0D43\u0D15\u0D4D\u200C\u0D38\u0D3E\u0D15\u0D4D\u0D37\u0D3F"));752CHECK(ts->is_valid_identifier(U"\u0DC1\u0DCA\u200D\u0DBB\u0DD3"));753}754}755}756757SUBCASE("[TextServer] Unicode letters") {758for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {759Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);760CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");761762struct ul_testcase {763int fail_index = -1; // Expecting failure at given index.764char32_t text[10]; // Using 0 as the terminator.765};766ul_testcase cases[14] = {767{7680,769{ 0x2D, 0x33, 0x30, 0, 0, 0, 0, 0, 0, 0 }, // "-30"770},771{7721,773{ 0x61, 0x2E, 0x31, 0, 0, 0, 0, 0, 0, 0 }, // "a.1"774},775{7761,777{ 0x61, 0x2C, 0x31, 0, 0, 0, 0, 0, 0, 0 }, // "a,1"778},779{7800,781{ 0x31, 0x65, 0x2D, 0x32, 0, 0, 0, 0, 0, 0 }, // "1e-2"782},783{7840,785{ 0xAB, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // "Left-Pointing Double Angle Quotation Mark"786},787{788-1,789{ 0x41, 0x42, 0, 0, 0, 0, 0, 0, 0, 0 }, // "AB"790},791{7924,793{ 0x54, 0x65, 0x73, 0x74, 0x31, 0, 0, 0, 0, 0 }, // "Test1"794},795{7962,797{ 0x54, 0x65, 0x2A, 0x73, 0x74, 0, 0, 0, 0, 0 }, // "Te*st"798},799{8004,801{ 0x74, 0x65, 0x73, 0x74, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x65 }, // "test_teste"802},803{8044,805{ 0x74, 0x65, 0x73, 0x74, 0x20, 0x74, 0x65, 0x73, 0x74, 0 }, // "test test"806},807{808-1,809{ 0x643, 0x402, 0x716, 0xB05, 0, 0, 0, 0, 0, 0 }, // "كЂܖଅ" (arabic letters),810},811{812-1,813{ 0x643, 0x402, 0x716, 0xB05, 0x54, 0x65, 0x73, 0x74, 0x30AA, 0x4E21 }, // 0-3 arabic letters, 4-7 latin letters, 8-9 CJK letters814},815{816-1,817{ 0x4D2, 0x4D6, 0x4DA, 0x4DC, 0, 0, 0, 0, 0, 0 }, // "ӒӖӚӜ" cyrillic letters818},819{820-1,821{ 0xC2, 0xC3, 0xC4, 0xC5, 0x100, 0x102, 0x104, 0xC7, 0x106, 0x108 }, // "ÂÃÄÅĀĂĄÇĆĈ" rarer latin letters822},823};824825for (int j = 0; j < 14; j++) {826ul_testcase test = cases[j];827int failed_on_index = -1;828for (int k = 0; k < 10; k++) {829char32_t character = test.text[k];830if (character == 0) {831break;832}833if (!ts->is_valid_letter(character)) {834failed_on_index = k;835break;836}837}838839if (test.fail_index == -1) {840CHECK_MESSAGE(test.fail_index == failed_on_index, "In interface ", ts->get_name() + ": In test case ", j, ", the character at index ", failed_on_index, " should have been a letter.");841} else {842CHECK_MESSAGE(test.fail_index == failed_on_index, "In interface ", ts->get_name() + ": In test case ", j, ", expected first non-letter at index ", test.fail_index, ", but found at index ", failed_on_index);843}844}845}846}847848SUBCASE("[TextServer] Strip Diacritics") {849for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {850Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);851CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");852853if (ts->has_feature(TextServer::FEATURE_SHAPING)) {854CHECK(ts->strip_diacritics(U"ٱلسَّلَامُ عَلَيْكُمْ") == U"ٱلسلام عليكم");855}856857CHECK(ts->strip_diacritics(U"pêches épinards tomates fraises") == U"peches epinards tomates fraises");858CHECK(ts->strip_diacritics(U"ΆΈΉΊΌΎΏΪΫϓϔ") == U"ΑΕΗΙΟΥΩΙΥΥΥ");859CHECK(ts->strip_diacritics(U"άέήίΐϊΰϋόύώ") == U"αεηιιιυυουω");860CHECK(ts->strip_diacritics(U"ЀЁЃ ЇЌЍӢӤЙ ЎӮӰӲ ӐӒӖӚӜӞ ӦӪ Ӭ Ӵ Ӹ") == U"ЕЕГ ІКИИИИ УУУУ ААЕӘЖЗ ОӨ Э Ч Ы");861CHECK(ts->strip_diacritics(U"ѐёѓ їќѝӣӥй ўӯӱӳ ӑӓӗӛӝӟ ӧӫ ӭ ӵ ӹ") == U"еег ікииии уууу ааеәжз оө э ч ы");862CHECK(ts->strip_diacritics(U"ÀÁÂÃÄÅĀĂĄÇĆĈĊČĎÈÉÊËĒĔĖĘĚĜĞĠĢĤÌÍÎÏĨĪĬĮİĴĶĹĻĽÑŃŅŇŊÒÓÔÕÖØŌŎŐƠŔŖŘŚŜŞŠŢŤÙÚÛÜŨŪŬŮŰŲƯŴÝŶŹŻŽ") == U"AAAAAAAAACCCCCDEEEEEEEEEGGGGHIIIIIIIIIJKLLLNNNNŊOOOOOØOOOORRRSSSSTTUUUUUUUUUUUWYYZZZ");863CHECK(ts->strip_diacritics(U"àáâãäåāăąçćĉċčďèéêëēĕėęěĝğġģĥìíîïĩīĭįĵķĺļľñńņňŋòóôõöøōŏőơŕŗřśŝşšţťùúûüũūŭůűųưŵýÿŷźżž") == U"aaaaaaaaacccccdeeeeeeeeegggghiiiiiiiijklllnnnnŋoooooøoooorrrssssttuuuuuuuuuuuwyyyzzz");864CHECK(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");865CHECK(ts->strip_diacritics(U"ǎǐȉȋǒǫǭȍȏȫȭȯȱǔǖǘǚǜȕȗǟǡǻȁȃȧǣǽǧǵǩǹȇȑȓșțȟȩ ḁ ḃḅḇ ḉ ḋḍḏḑḓ ḟ ḡ ḭḯ ḱḳḵ ḷḹḻḽ ḿṁṃ ṅṇṉṋ ṍṏṑṓ ṗṕ ṙṛṝṟ ṡṣṥṧṩ ṫṭṯṱ ṳṵṷṹṻ") == U"aiiiooooooooouuuuuuuaaaaaaææggknerrsthe a bbb c ddddd f g ii kkk llll mmm nnnn oooo pp rrrr sssss tttt uuuuu");866CHECK(ts->strip_diacritics(U"ṼṾ ẀẂẄẆẈ ẊẌ Ẏ ẐẒẔ") == U"VV WWWWW XX Y ZZZ");867CHECK(ts->strip_diacritics(U"ṽṿ ẁẃẅẇẉ ẋẍ ẏ ẑẓẕ ẖ ẗẘẙẛ") == U"vv wwwww xx y zzz h twys");868}869}870871SUBCASE("[TextServer] Word break") {872for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {873Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);874CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");875876if (!ts->has_feature(TextServer::FEATURE_SIMPLE_LAYOUT)) {877continue;878}879880{881String text1 = U"linguistically similar and effectively form";882// 14^ 22^ 26^ 38^883PackedInt32Array breaks = ts->string_get_word_breaks(text1, "en");884CHECK(breaks.size() == 10);885if (breaks.size() == 10) {886CHECK(breaks[0] == 0);887CHECK(breaks[1] == 14);888CHECK(breaks[2] == 15);889CHECK(breaks[3] == 22);890CHECK(breaks[4] == 23);891CHECK(breaks[5] == 26);892CHECK(breaks[6] == 27);893CHECK(breaks[7] == 38);894CHECK(breaks[8] == 39);895CHECK(breaks[9] == 43);896}897}898899if (ts->has_feature(TextServer::FEATURE_BREAK_ITERATORS)) {900String text2 = U"เป็นภาษาราชการและภาษาประจำชาติของประเทศไทย";901// เป็น ภาษา ราชการ และ ภาษา ประจำ ชาติ ของ ประเทศไทย902// 3^ 7^ 13^ 16^ 20^ 25^ 29^ 32^903904PackedInt32Array breaks = ts->string_get_word_breaks(text2, "th");905CHECK(breaks.size() == 18);906if (breaks.size() == 18) {907CHECK(breaks[0] == 0);908CHECK(breaks[1] == 4);909CHECK(breaks[2] == 4);910CHECK(breaks[3] == 8);911CHECK(breaks[4] == 8);912CHECK(breaks[5] == 14);913CHECK(breaks[6] == 14);914CHECK(breaks[7] == 17);915CHECK(breaks[8] == 17);916CHECK(breaks[9] == 21);917CHECK(breaks[10] == 21);918CHECK(breaks[11] == 26);919CHECK(breaks[12] == 26);920CHECK(breaks[13] == 30);921CHECK(breaks[14] == 30);922CHECK(breaks[15] == 33);923CHECK(breaks[16] == 33);924CHECK(breaks[17] == 42);925}926}927928if (ts->has_feature(TextServer::FEATURE_BREAK_ITERATORS)) {929String text2 = U"U+2764 U+FE0F U+200D U+1F525 ; 13.1 # ❤️🔥";930931PackedInt32Array breaks = ts->string_get_character_breaks(text2, "en");932CHECK(breaks.size() == 39);933if (breaks.size() == 39) {934CHECK(breaks[0] == 1);935CHECK(breaks[1] == 2);936CHECK(breaks[2] == 3);937CHECK(breaks[3] == 4);938CHECK(breaks[4] == 5);939CHECK(breaks[5] == 6);940CHECK(breaks[6] == 7);941CHECK(breaks[7] == 8);942CHECK(breaks[8] == 9);943CHECK(breaks[9] == 10);944CHECK(breaks[10] == 11);945CHECK(breaks[11] == 12);946CHECK(breaks[12] == 13);947CHECK(breaks[13] == 14);948CHECK(breaks[14] == 15);949CHECK(breaks[15] == 16);950CHECK(breaks[16] == 17);951CHECK(breaks[17] == 18);952CHECK(breaks[18] == 19);953CHECK(breaks[19] == 20);954CHECK(breaks[20] == 21);955CHECK(breaks[21] == 22);956CHECK(breaks[22] == 23);957CHECK(breaks[23] == 24);958CHECK(breaks[24] == 25);959CHECK(breaks[25] == 26);960CHECK(breaks[26] == 27);961CHECK(breaks[27] == 28);962CHECK(breaks[28] == 29);963CHECK(breaks[29] == 30);964CHECK(breaks[30] == 31);965CHECK(breaks[31] == 32);966CHECK(breaks[32] == 33);967CHECK(breaks[33] == 34);968CHECK(breaks[34] == 35);969CHECK(breaks[35] == 36);970CHECK(breaks[36] == 37);971CHECK(breaks[37] == 38);972CHECK(breaks[38] == 42);973}974}975}976}977978SUBCASE("[TextServer] Buffer invalidation") {979for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {980Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);981CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");982983if (!ts->has_feature(TextServer::FEATURE_SIMPLE_LAYOUT)) {984continue;985}986987RID font1 = ts->create_font();988ts->font_set_data_ptr(font1, _font_Inter_Regular, _font_Inter_Regular_size);989990Array font = { font1 };991RID ctx = ts->create_shaped_text();992CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");993bool ok = ts->shaped_text_add_string(ctx, "T", font, 16);994CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");995int gl_size = ts->shaped_text_get_glyph_count(ctx);996CHECK_MESSAGE(gl_size == 1, "Shaping failed, invalid glyph count");997998ok = ts->shaped_text_add_object(ctx, "key", Size2(20, 20), INLINE_ALIGNMENT_CENTER, 1, 0.0);999CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");1000gl_size = ts->shaped_text_get_glyph_count(ctx);1001CHECK_MESSAGE(gl_size == 2, "Shaping failed, invalid glyph count");10021003ok = ts->shaped_text_add_string(ctx, "B", font, 16);1004CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");1005gl_size = ts->shaped_text_get_glyph_count(ctx);1006CHECK_MESSAGE(gl_size == 3, "Shaping failed, invalid glyph count");10071008ts->free_rid(ctx);10091010for (int j = 0; j < font.size(); j++) {1011ts->free_rid(font[j]);1012}1013font.clear();1014}1015}1016}1017}10181019} // namespace TestTextServer10201021#endif // TOOLS_ENABLED102210231024